From 3efa2b05350e55efc196a192eeeb06095082b605 Mon Sep 17 00:00:00 2001 From: Felix Angelov Date: Thu, 3 Aug 2023 16:53:52 -0500 Subject: [PATCH] feat(shorebird_cli): `shorebird patch ios-framework-alpha` supports dynamic Flutter revision (#1019) --- .../patch/patch_ios_framework_command.dart | 57 ++-- .../patch_ios_framework_command_test.dart | 253 ++++++++++++------ 2 files changed, 198 insertions(+), 112 deletions(-) diff --git a/packages/shorebird_cli/lib/src/commands/patch/patch_ios_framework_command.dart b/packages/shorebird_cli/lib/src/commands/patch/patch_ios_framework_command.dart index a5bfd391..38506a71 100644 --- a/packages/shorebird_cli/lib/src/commands/patch/patch_ios_framework_command.dart +++ b/packages/shorebird_cli/lib/src/commands/patch/patch_ios_framework_command.dart @@ -3,6 +3,7 @@ import 'dart:io' hide Platform; import 'package:crypto/crypto.dart'; import 'package:mason_logger/mason_logger.dart'; import 'package:platform/platform.dart'; +import 'package:scoped/scoped.dart'; import 'package:shorebird_cli/src/code_push_client_wrapper.dart'; import 'package:shorebird_cli/src/command.dart'; import 'package:shorebird_cli/src/config/shorebird_yaml.dart'; @@ -13,6 +14,7 @@ import 'package:shorebird_cli/src/logger.dart'; import 'package:shorebird_cli/src/shorebird_artifact_mixin.dart'; import 'package:shorebird_cli/src/shorebird_build_mixin.dart'; import 'package:shorebird_cli/src/shorebird_env.dart'; +import 'package:shorebird_cli/src/shorebird_flutter_manager.dart'; import 'package:shorebird_cli/src/shorebird_validator.dart'; import 'package:shorebird_code_push_client/shorebird_code_push_client.dart'; @@ -75,7 +77,6 @@ of the iOS app that is using this module.''', final shorebirdYaml = shorebirdEnv.getShorebirdYaml()!; final appId = shorebirdYaml.getAppId(); final app = await codePushClientWrapper.getApp(appId: appId); - final release = await codePushClientWrapper.getRelease( appId: appId, releaseVersion: releaseVersion, @@ -89,9 +90,34 @@ Please re-run the release command for this version or create a new release.'''); return ExitCode.software.code; } + final shorebirdFlutterRevision = shorebirdEnv.flutterRevision; + if (release.flutterRevision != shorebirdFlutterRevision) { + final installFlutterRevisionProgress = logger.progress( + 'Switching to Flutter revision ${release.flutterRevision}', + ); + try { + await shorebirdFlutterManager.installRevision( + revision: release.flutterRevision, + ); + installFlutterRevisionProgress.complete(); + } catch (error) { + installFlutterRevisionProgress.fail('$error'); + return ExitCode.software.code; + } + } + final buildProgress = logger.progress('Building patch'); try { - await buildIosFramework(); + await runScoped( + buildIosFramework, + values: { + shorebirdEnvRef.overrideWith( + () => ShorebirdEnv( + flutterRevisionOverride: release.flutterRevision, + ), + ), + }, + ); buildProgress.complete(); } on ProcessException catch (error) { buildProgress.fail('Failed to build: ${error.message}'); @@ -109,33 +135,6 @@ Please re-run the release command for this version or create a new release.'''); buildProgress.complete(); - final shorebirdFlutterRevision = shorebirdEnv.flutterRevision; - if (release.flutterRevision != shorebirdFlutterRevision) { - logger - ..err(''' -Flutter revision mismatch. - -The release you are trying to patch was built with a different version of Flutter. - -Release Flutter Revision: ${release.flutterRevision} -Current Flutter Revision: $shorebirdFlutterRevision -''') - ..info( - ''' -Either create a new release using: - ${lightCyan.wrap('shorebird release')} - -Or downgrade your Flutter version and try again using: - ${lightCyan.wrap('cd ${shorebirdEnv.flutterDirectory.path}')} - ${lightCyan.wrap('git checkout ${release.flutterRevision}')} - -Shorebird plans to support this automatically, let us know if it's important to you: -https://github.com/shorebirdtech/shorebird/issues/472 -''', - ); - return ExitCode.software.code; - } - if (dryRun) { logger ..info('No issues detected.') diff --git a/packages/shorebird_cli/test/src/commands/patch/patch_ios_framework_command_test.dart b/packages/shorebird_cli/test/src/commands/patch/patch_ios_framework_command_test.dart index 9f4e024d..6bbc5165 100644 --- a/packages/shorebird_cli/test/src/commands/patch/patch_ios_framework_command_test.dart +++ b/packages/shorebird_cli/test/src/commands/patch/patch_ios_framework_command_test.dart @@ -15,6 +15,7 @@ import 'package:shorebird_cli/src/logger.dart'; import 'package:shorebird_cli/src/platform.dart'; import 'package:shorebird_cli/src/process.dart'; import 'package:shorebird_cli/src/shorebird_env.dart'; +import 'package:shorebird_cli/src/shorebird_flutter_manager.dart'; import 'package:shorebird_cli/src/shorebird_validator.dart'; import 'package:shorebird_cli/src/validators/validators.dart'; import 'package:shorebird_code_push_client/shorebird_code_push_client.dart'; @@ -37,11 +38,16 @@ class _MockProgress extends Mock implements Progress {} class _MockProcessResult extends Mock implements ShorebirdProcessResult {} +class _MockProcessWrapper extends Mock implements ProcessWrapper {} + class _MockShorebirdEnv extends Mock implements ShorebirdEnv {} class _MockShorebirdFlutterValidator extends Mock implements ShorebirdFlutterValidator {} +class _MockShorebirdFlutterManager extends Mock + implements ShorebirdFlutterManager {} + class _MockShorebirdProcess extends Mock implements ShorebirdProcess {} class _MockShorebirdValidator extends Mock implements ShorebirdValidator {} @@ -88,6 +94,7 @@ flutter: late ShorebirdProcessResult aotBuildProcessResult; late ShorebirdProcessResult flutterBuildProcessResult; late ShorebirdEnv shorebirdEnv; + late ShorebirdFlutterManager shorebirdFlutterManager; late ShorebirdFlutterValidator flutterValidator; late ShorebirdProcess shorebirdProcess; late ShorebirdValidator shorebirdValidator; @@ -104,6 +111,8 @@ flutter: platformRef.overrideWith(() => platform), processRef.overrideWith(() => shorebirdProcess), shorebirdEnvRef.overrideWith(() => shorebirdEnv), + shorebirdFlutterManagerRef + .overrideWith(() => shorebirdFlutterManager), shorebirdValidatorRef.overrideWith(() => shorebirdValidator), }, ); @@ -169,6 +178,7 @@ flutter: aotBuildProcessResult = _MockProcessResult(); flutterBuildProcessResult = _MockProcessResult(); shorebirdEnv = _MockShorebirdEnv(); + shorebirdFlutterManager = _MockShorebirdFlutterManager(); flutterValidator = _MockShorebirdFlutterValidator(); shorebirdProcess = _MockShorebirdProcess(); shorebirdValidator = _MockShorebirdValidator(); @@ -225,6 +235,11 @@ flutter: patchArtifactBundles: any(named: 'patchArtifactBundles'), ), ).thenAnswer((_) async {}); + when( + () => shorebirdFlutterManager.installRevision( + revision: any(named: 'revision'), + ), + ).thenAnswer((_) async {}); when( () => shorebirdValidator.validatePreconditions( checkUserIsAuthenticated: any(named: 'checkUserIsAuthenticated'), @@ -279,66 +294,167 @@ flutter: }); test( - '''exits with code 70 if release is in draft state for the ios platform''', - () async { - when( - () => codePushClientWrapper.getRelease( - appId: any(named: 'appId'), - releaseVersion: any(named: 'releaseVersion'), - ), - ).thenAnswer( - (_) async => const Release( - id: 0, - appId: appId, - version: version, - flutterRevision: flutterRevision, - displayName: '1.2.3+1', - platformStatuses: {ReleasePlatform.ios: ReleaseStatus.draft}, - ), - ); - final tempDir = setUpTempDir(); - setUpTempArtifacts(tempDir); - final exitCode = await IOOverrides.runZoned( - () => runWithOverrides(command.run), - getCurrentDirectory: () => tempDir, - ); - expect(exitCode, ExitCode.software.code); - verify( - () => logger.err(''' + '''exits with code 70 if release is in draft state for the ios platform''', + () async { + when( + () => codePushClientWrapper.getRelease( + appId: any(named: 'appId'), + releaseVersion: any(named: 'releaseVersion'), + ), + ).thenAnswer( + (_) async => const Release( + id: 0, + appId: appId, + version: version, + flutterRevision: flutterRevision, + displayName: '1.2.3+1', + platformStatuses: {ReleasePlatform.ios: ReleaseStatus.draft}, + ), + ); + final tempDir = setUpTempDir(); + setUpTempArtifacts(tempDir); + final exitCode = await IOOverrides.runZoned( + () => runWithOverrides(command.run), + getCurrentDirectory: () => tempDir, + ); + expect(exitCode, ExitCode.software.code); + verify( + () => logger.err(''' Release 1.2.3+1 is in an incomplete state. It's possible that the original release was terminated or failed to complete. Please re-run the release command for this version or create a new release.'''), - ).called(1); - }, - ); + ).called(1); + }); + + test('proceeds if release is in draft state for a non-ios platform', + () async { + when( + () => codePushClientWrapper.getRelease( + appId: any(named: 'appId'), + releaseVersion: any(named: 'releaseVersion'), + ), + ).thenAnswer( + (_) async => const Release( + id: 0, + appId: appId, + version: version, + flutterRevision: flutterRevision, + displayName: '1.2.3+1', + platformStatuses: {ReleasePlatform.android: ReleaseStatus.draft}, + ), + ); + final tempDir = setUpTempDir(); + setUpTempArtifacts(tempDir); + final exitCode = await IOOverrides.runZoned( + () => runWithOverrides(command.run), + getCurrentDirectory: () => tempDir, + ); + expect(exitCode, ExitCode.success.code); + }); test( - 'proceeds if release is in draft state for a non-ios platform', - () async { - when( - () => codePushClientWrapper.getRelease( - appId: any(named: 'appId'), - releaseVersion: any(named: 'releaseVersion'), + 'installs correct flutter revision ' + 'when release flutter revision differs', () async { + const otherRevision = 'other-revision'; + when(() => shorebirdEnv.flutterRevision).thenReturn(otherRevision); + final tempDir = setUpTempDir(); + setUpTempArtifacts(tempDir); + + final exitCode = await IOOverrides.runZoned( + () => runWithOverrides(command.run), + getCurrentDirectory: () => tempDir, + ); + expect(exitCode, equals(ExitCode.success.code)); + verify( + () => logger.progress( + 'Switching to Flutter revision ${release.flutterRevision}', + ), + ).called(1); + verify( + () => shorebirdFlutterManager.installRevision( + revision: release.flutterRevision, + ), + ).called(1); + }); + + test( + 'builds using correct flutter revision ' + 'when release flutter revision differs', () async { + when( + () => platform.script, + ).thenReturn(Uri.parse('file:///bin/cache/shorebird.snapshot')); + const otherRevision = 'other-revision'; + when(() => shorebirdEnv.flutterRevision).thenReturn(otherRevision); + final processWrapper = _MockProcessWrapper(); + when( + () => processWrapper.run( + any(), + any(), + runInShell: any(named: 'runInShell'), + workingDirectory: any(named: 'workingDirectory'), + environment: any(named: 'environment'), + ), + ).thenAnswer((_) async => flutterBuildProcessResult); + final tempDir = setUpTempDir(); + setUpTempArtifacts(tempDir); + await IOOverrides.runZoned( + () => runWithOverrides( + () => runScoped( + () => command.run(), + values: { + processRef.overrideWith( + () => ShorebirdProcess( + logger: logger, + processWrapper: processWrapper, + ), + ), + }, ), - ).thenAnswer( - (_) async => const Release( - id: 0, - appId: appId, - version: version, - flutterRevision: flutterRevision, - displayName: '1.2.3+1', - platformStatuses: {ReleasePlatform.android: ReleaseStatus.draft}, - ), - ); - final tempDir = setUpTempDir(); - setUpTempArtifacts(tempDir); - final exitCode = await IOOverrides.runZoned( - () => runWithOverrides(command.run), - getCurrentDirectory: () => tempDir, - ); - expect(exitCode, ExitCode.success.code); - }, - ); + ), + getCurrentDirectory: () => tempDir, + ); + verify( + () => processWrapper.run( + '/bin/cache/flutter/${release.flutterRevision}/bin/flutter', + any(), + runInShell: true, + workingDirectory: any(named: 'workingDirectory'), + environment: any(named: 'environment'), + ), + ).called(1); + }); + + test( + 'exits with code 70 when ' + 'unable to install correct flutter revision', () async { + final exception = Exception('oops'); + const otherRevision = 'other-revision'; + when(() => shorebirdEnv.flutterRevision).thenReturn(otherRevision); + when( + () => shorebirdFlutterManager.installRevision( + revision: any(named: 'revision'), + ), + ).thenThrow(exception); + final tempDir = setUpTempDir(); + setUpTempArtifacts(tempDir); + + final exitCode = await IOOverrides.runZoned( + () => runWithOverrides(command.run), + getCurrentDirectory: () => tempDir, + ); + expect(exitCode, equals(ExitCode.software.code)); + verify( + () => logger.progress( + 'Switching to Flutter revision ${release.flutterRevision}', + ), + ).called(1); + verify( + () => shorebirdFlutterManager.installRevision( + revision: release.flutterRevision, + ), + ).called(1); + verify(() => progress.fail('$exception')).called(1); + }); test('exits with code 70 when build fails', () async { when(() => flutterBuildProcessResult.exitCode).thenReturn(1); @@ -354,35 +470,6 @@ Please re-run the release command for this version or create a new release.'''), verify(() => progress.fail('Failed to build: oh no')).called(1); }); - test( - 'errors when shorebird flutter revision ' - 'does not match release revision', () async { - const otherRevision = 'other-revision'; - when(() => shorebirdEnv.flutterRevision).thenReturn(otherRevision); - final tempDir = setUpTempDir(); - setUpTempArtifacts(tempDir); - - final exitCode = await IOOverrides.runZoned( - () => runWithOverrides(command.run), - getCurrentDirectory: () => tempDir, - ); - - expect(exitCode, ExitCode.software.code); - verify( - () => logger.err(''' -Either create a new release using: - ${lightCyan.wrap('shorebird release aar')} - -Or downgrade your Flutter version and try again using: - ${lightCyan.wrap('cd ${shorebirdEnv.flutterDirectory.path}')} - ${lightCyan.wrap('git checkout ${release.flutterRevision}')} - -Shorebird plans to support this automatically, let us know if it's important to you: -https://github.com/shorebirdtech/shorebird/issues/472 -'''), - ).called(1); - }); - test('throws error when creating aot snapshot fails', () async { const error = 'oops something went wrong'; when(() => aotBuildProcessResult.exitCode).thenReturn(1);