diff --git a/packages/shorebird_cli/lib/src/commands/patch/patch_ios_command.dart b/packages/shorebird_cli/lib/src/commands/patch/patch_ios_command.dart index ed46ec051..9dc61d63b 100644 --- a/packages/shorebird_cli/lib/src/commands/patch/patch_ios_command.dart +++ b/packages/shorebird_cli/lib/src/commands/patch/patch_ios_command.dart @@ -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.', ); } @@ -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.'); @@ -221,56 +228,11 @@ 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.') @@ -278,7 +240,7 @@ Current Flutter Revision: $originalFlutterRevision return ExitCode.success.code; } - final patchFile = File(_vmcodeOutputPath); + final patchFile = File(useLinker ? _vmcodeOutputPath : _aotOutputPath); final patchFileSize = patchFile.statSync().size; final summary = [ @@ -375,4 +337,61 @@ ${summary.join('\n')} buildProgress.complete(); } + + Future _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; + } } diff --git a/packages/shorebird_cli/test/src/commands/patch/patch_ios_command_test.dart b/packages/shorebird_cli/test/src/commands/patch/patch_ios_command_test.dart index 304644985..685227f75 100644 --- a/packages/shorebird_cli/test/src/commands/patch/patch_ios_command_test.dart +++ b/packages/shorebird_cli/test/src/commands/patch/patch_ios_command_test.dart @@ -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( @@ -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 {