Skip to content

Commit

Permalink
feat(shorebird_cli): add --use-linker flag to `shorebird patch ios-…
Browse files Browse the repository at this point in the history
…alpha` (#1520)
  • Loading branch information
felangel authored Nov 21, 2023
1 parent 5958115 commit 3a728bf
Show file tree
Hide file tree
Showing 2 changed files with 172 additions and 135 deletions.
117 changes: 68 additions & 49 deletions packages/shorebird_cli/lib/src/commands/patch/patch_ios_command.dart
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,12 @@ If this option is not provided, the version number will be determined from the p
'staging',
negatable: false,
help: 'Whether to publish the patch to the staging environment.',
)
..addFlag(
'use-linker',
negatable: false,
hide: true,
help: 'Whether to use the new linker when building the patch.',
);
}

Expand Down Expand Up @@ -103,6 +109,7 @@ If this option is not provided, the version number will be determined from the p
final force = results['force'] == true;
final dryRun = results['dry-run'] == true;
final isStaging = results['staging'] == true;
final useLinker = results['use-linker'] == true;

if (force && dryRun) {
logger.err('Cannot use both --force and --dry-run.');
Expand Down Expand Up @@ -221,64 +228,19 @@ Current Flutter Revision: $originalFlutterRevision
return ExitCode.software.code;
}

final appDirectory = getAppDirectory();

if (appDirectory == null) {
logger.err('Unable to find .app directory within .xcarchive.');
return ExitCode.software.code;
if (useLinker) {
final exitCode = await _runLinker();
if (exitCode != ExitCode.success.code) return exitCode;
}

final base = File(
p.join(
appDirectory.path,
'Frameworks',
'App.framework',
'App',
),
);

if (!base.existsSync()) {
logger.err('Unable to find base AOT file at ${base.path}');
return ExitCode.software.code;
}

final patch = File(_aotOutputPath);

if (!patch.existsSync()) {
logger.err('Unable to find patch AOT file at ${patch.path}');
return ExitCode.software.code;
}

final analyzeSnapshot = shorebirdEnv.analyzeSnapshotFile;

if (!analyzeSnapshot.existsSync()) {
logger.err('Unable to find analyze_snapshot at ${analyzeSnapshot.path}');
return ExitCode.software.code;
}

final linkProgress = logger.progress('Linking AOT files');
try {
await aotTools.link(
base: base.path,
patch: patch.path,
analyzeSnapshot: analyzeSnapshot.path,
workingDirectory: _buildDirectory,
);
} catch (error) {
linkProgress.fail('Failed to link AOT files: $error');
return ExitCode.software.code;
}

linkProgress.complete();

if (dryRun) {
logger
..info('No issues detected.')
..info('The server may enforce additional checks.');
return ExitCode.success.code;
}

final patchFile = File(_vmcodeOutputPath);
final patchFile = File(useLinker ? _vmcodeOutputPath : _aotOutputPath);
final patchFileSize = patchFile.statSync().size;

final summary = [
Expand Down Expand Up @@ -375,4 +337,61 @@ ${summary.join('\n')}

buildProgress.complete();
}

Future<int> _runLinker() async {
logger.warn(
'--use-linker is an experimental feature and may not work as expected.',
);

final appDirectory = getAppDirectory();

if (appDirectory == null) {
logger.err('Unable to find .app directory within .xcarchive.');
return ExitCode.software.code;
}

final base = File(
p.join(
appDirectory.path,
'Frameworks',
'App.framework',
'App',
),
);

if (!base.existsSync()) {
logger.err('Unable to find base AOT file at ${base.path}');
return ExitCode.software.code;
}

final patch = File(_aotOutputPath);

if (!patch.existsSync()) {
logger.err('Unable to find patch AOT file at ${patch.path}');
return ExitCode.software.code;
}

final analyzeSnapshot = shorebirdEnv.analyzeSnapshotFile;

if (!analyzeSnapshot.existsSync()) {
logger.err('Unable to find analyze_snapshot at ${analyzeSnapshot.path}');
return ExitCode.software.code;
}

final linkProgress = logger.progress('Linking AOT files');
try {
await aotTools.link(
base: base.path,
patch: patch.path,
analyzeSnapshot: analyzeSnapshot.path,
workingDirectory: _buildDirectory,
);
} catch (error) {
linkProgress.fail('Failed to link AOT files: $error');
return ExitCode.software.code;
}

linkProgress.complete();
return ExitCode.success.code;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -295,6 +295,7 @@ flutter:
when(() => argResults['force']).thenReturn(false);
when(() => argResults['codesign']).thenReturn(true);
when(() => argResults['staging']).thenReturn(false);
when(() => argResults['use-linker']).thenReturn(false);
when(() => argResults.rest).thenReturn([]);
when(
() => aotTools.link(
Expand Down Expand Up @@ -867,111 +868,128 @@ Please re-run the release command for this version or create a new release.'''),
);
});

test('exits with code 70 if appDirectory is not found', () async {
setUpProjectRoot();
setUpProjectRootArtifacts();
group('when --use-linker', () {
setUp(() {
when(() => argResults['use-linker']).thenReturn(true);
});

File(
p.join(
projectRoot.path,
'build',
'ios',
'archive',
'Runner.xcarchive',
'Products',
'Applications',
'Runner.app',
),
).deleteSync(recursive: true);
test('logs warning', () async {
setUpProjectRoot();
setUpProjectRootArtifacts();
await runWithOverrides(command.run);
verify(
() => logger.warn(
'''--use-linker is an experimental feature and may not work as expected.''',
),
).called(1);
});

final exitCode = await runWithOverrides(command.run);
test('exits with code 70 if appDirectory is not found', () async {
setUpProjectRoot();
setUpProjectRootArtifacts();

expect(exitCode, equals(ExitCode.software.code));
verify(
() => logger.err('Unable to find .app directory within .xcarchive.'),
).called(1);
});
File(
p.join(
projectRoot.path,
'build',
'ios',
'archive',
'Runner.xcarchive',
'Products',
'Applications',
'Runner.app',
),
).deleteSync(recursive: true);

test('exits with code 70 if base app is not found', () async {
setUpProjectRoot();
setUpProjectRootArtifacts();
final exitCode = await runWithOverrides(command.run);

final base = File(
p.join(
projectRoot.path,
'build',
'ios',
'archive',
'Runner.xcarchive',
'Products',
'Applications',
'Runner.app',
'Frameworks',
'App.framework',
'App',
),
)..deleteSync(recursive: true);
expect(exitCode, equals(ExitCode.software.code));
verify(
() => logger.err('Unable to find .app directory within .xcarchive.'),
).called(1);
});

final exitCode = await runWithOverrides(command.run);
test('exits with code 70 if base app is not found', () async {
setUpProjectRoot();
setUpProjectRootArtifacts();

expect(exitCode, equals(ExitCode.software.code));
verify(
() => logger.err('Unable to find base AOT file at ${base.path}'),
).called(1);
});
final base = File(
p.join(
projectRoot.path,
'build',
'ios',
'archive',
'Runner.xcarchive',
'Products',
'Applications',
'Runner.app',
'Frameworks',
'App.framework',
'App',
),
)..deleteSync(recursive: true);

test('exits with code 70 if patch AOT file is not found', () async {
setUpProjectRoot();
setUpProjectRootArtifacts();
final exitCode = await runWithOverrides(command.run);

final patch = File(
p.join(projectRoot.path, 'build', elfAotSnapshotFileName),
)..deleteSync(recursive: true);
expect(exitCode, equals(ExitCode.software.code));
verify(
() => logger.err('Unable to find base AOT file at ${base.path}'),
).called(1);
});

final exitCode = await runWithOverrides(command.run);
test('exits with code 70 if patch AOT file is not found', () async {
setUpProjectRoot();
setUpProjectRootArtifacts();

expect(exitCode, equals(ExitCode.software.code));
verify(
() => logger.err('Unable to find patch AOT file at ${patch.path}'),
).called(1);
});
final patch = File(
p.join(projectRoot.path, 'build', elfAotSnapshotFileName),
)..deleteSync(recursive: true);

test('exits with code 70 if analyze snapshot is not found', () async {
setUpProjectRoot();
setUpProjectRootArtifacts();
final exitCode = await runWithOverrides(command.run);

analyzeSnapshotFile.deleteSync(recursive: true);
expect(exitCode, equals(ExitCode.software.code));
verify(
() => logger.err('Unable to find patch AOT file at ${patch.path}'),
).called(1);
});

final exitCode = await runWithOverrides(command.run);
test('exits with code 70 if analyze snapshot is not found', () async {
setUpProjectRoot();
setUpProjectRootArtifacts();

expect(exitCode, equals(ExitCode.software.code));
verify(
() => logger.err(
'Unable to find analyze_snapshot at ${analyzeSnapshotFile.path}',
),
).called(1);
});
analyzeSnapshotFile.deleteSync(recursive: true);

test('exits with code 70 if linking fails', () async {
final exception = Exception('oops');
when(
() => aotTools.link(
base: any(named: 'base'),
patch: any(named: 'patch'),
analyzeSnapshot: any(named: 'analyzeSnapshot'),
workingDirectory: any(named: 'workingDirectory'),
),
).thenThrow(exception);
final exitCode = await runWithOverrides(command.run);

setUpProjectRoot();
setUpProjectRootArtifacts();
expect(exitCode, equals(ExitCode.software.code));
verify(
() => logger.err(
'Unable to find analyze_snapshot at ${analyzeSnapshotFile.path}',
),
).called(1);
});

final exitCode = await runWithOverrides(command.run);
test('exits with code 70 if linking fails', () async {
final exception = Exception('oops');
when(
() => aotTools.link(
base: any(named: 'base'),
patch: any(named: 'patch'),
analyzeSnapshot: any(named: 'analyzeSnapshot'),
workingDirectory: any(named: 'workingDirectory'),
),
).thenThrow(exception);

expect(exitCode, equals(ExitCode.software.code));
verify(
() => progress.fail('Failed to link AOT files: $exception'),
).called(1);
setUpProjectRoot();
setUpProjectRootArtifacts();

final exitCode = await runWithOverrides(command.run);

expect(exitCode, equals(ExitCode.software.code));
verify(
() => progress.fail('Failed to link AOT files: $exception'),
).called(1);
});
});

test('does not create patch on --dry-run', () async {
Expand Down

0 comments on commit 3a728bf

Please sign in to comment.