fix(shorebird_cli): switch to release flutter revision in patch (#1233)

Co-authored-by: Felix Angelov <felix@shorebird.dev>
This commit is contained in:
Bryan Oltman
2023-09-11 11:28:47 -04:00
committed by GitHub
parent c563f40728
commit 1ef7d60846
6 changed files with 180 additions and 75 deletions
@@ -17,6 +17,7 @@ import 'package:shorebird_cli/src/logger.dart';
import 'package:shorebird_cli/src/patch_diff_checker.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.dart';
import 'package:shorebird_cli/src/shorebird_release_version_mixin.dart';
import 'package:shorebird_cli/src/shorebird_validator.dart';
import 'package:shorebird_code_push_client/shorebird_code_push_client.dart';
@@ -103,6 +104,7 @@ class PatchAndroidCommand extends ShorebirdCommand
final appId = shorebirdYaml.getAppId(flavor: flavor);
final app = await codePushClientWrapper.getApp(appId: appId);
final originalFlutterRevision = shorebirdEnv.flutterRevision;
final buildProgress = logger.progress('Building patch');
try {
await buildAppBundle(flavor: flavor, target: target);
@@ -144,26 +146,33 @@ 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) {
logger
..err('''
Flutter revision mismatch.
if (release.flutterRevision != originalFlutterRevision) {
logger.info('''
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 android')}
Current Flutter Revision: $originalFlutterRevision''');
Or change your Flutter version and try again using:
${lightCyan.wrap('shorebird flutter versions use ${release.flutterRevision}')}''',
var flutterVersionProgress = logger.progress(
'Switching to Flutter revision ${release.flutterRevision}',
);
await shorebirdFlutter.useRevision(revision: release.flutterRevision);
flutterVersionProgress.complete();
final buildProgress = logger.progress('Building patch');
try {
await buildAppBundle(flavor: flavor, target: target);
buildProgress.complete();
} on ProcessException catch (error) {
buildProgress.fail('Failed to build: ${error.message}');
return ExitCode.software.code;
} finally {
flutterVersionProgress = logger.progress(
'Reverting to Flutter revision $originalFlutterRevision',
);
return ExitCode.software.code;
await shorebirdFlutter.useRevision(revision: originalFlutterRevision);
flutterVersionProgress.complete();
}
}
final releaseArtifacts = await codePushClientWrapper.getReleaseArtifacts(
@@ -3,6 +3,7 @@ import 'dart:io' hide Platform;
import 'package:crypto/crypto.dart';
import 'package:mason_logger/mason_logger.dart';
import 'package:path/path.dart' as p;
import 'package:platform/platform.dart';
import 'package:shorebird_cli/src/archive_analysis/archive_analysis.dart';
import 'package:shorebird_cli/src/code_push_client_wrapper.dart';
@@ -16,6 +17,7 @@ import 'package:shorebird_cli/src/patch_diff_checker.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.dart';
import 'package:shorebird_cli/src/shorebird_validator.dart';
import 'package:shorebird_code_push_client/shorebird_code_push_client.dart';
@@ -93,36 +95,18 @@ class PatchIosCommand extends ShorebirdCommand
const arch = 'aarch64';
const channelName = 'stable';
const releasePlatform = ReleasePlatform.ios;
final target = results['target'] as String?;
final flavor = results['flavor'] as String?;
final shorebirdYaml = shorebirdEnv.getShorebirdYaml()!;
final appId = shorebirdYaml.getAppId(flavor: flavor);
final app = await codePushClientWrapper.getApp(appId: appId);
final buildProgress = logger.progress('Building release');
try {
await buildIpa(flavor: flavor, target: target);
} on ProcessException catch (error) {
buildProgress.fail('Failed to build: ${error.message}');
return ExitCode.software.code;
} on BuildException catch (error) {
buildProgress.fail('Failed to build IPA');
logger.err(error.message);
await _buildPatch();
} catch (_) {
return ExitCode.software.code;
}
final File aotFile;
try {
final newestDillFile = newestAppDill();
aotFile = await buildElfAotSnapshot(appDillPath: newestDillFile.path);
} catch (error) {
buildProgress.fail('$error');
return ExitCode.software.code;
}
buildProgress.complete();
final detectReleaseVersionProgress = logger.progress(
'Detecting release version',
);
@@ -159,26 +143,31 @@ 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) {
logger
..err('''
Flutter revision mismatch.
final originalFlutterRevision = shorebirdEnv.flutterRevision;
if (release.flutterRevision != originalFlutterRevision) {
logger.info('''
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 ios-alpha')}
Current Flutter Revision: $originalFlutterRevision''');
Or change your Flutter version and try again using:
${lightCyan.wrap('shorebird flutter versions use ${release.flutterRevision}')}''',
var flutterVersionProgress = logger.progress(
'Switching to Flutter revision ${release.flutterRevision}',
);
await shorebirdFlutter.useRevision(revision: release.flutterRevision);
flutterVersionProgress.complete();
try {
await _buildPatch();
} catch (_) {
return ExitCode.software.code;
} finally {
flutterVersionProgress = logger.progress(
'Switching back to original Flutter revision $originalFlutterRevision',
);
return ExitCode.software.code;
await shorebirdFlutter.useRevision(revision: originalFlutterRevision);
flutterVersionProgress.complete();
}
}
final releaseArtifact = await codePushClientWrapper.getReleaseArtifact(
@@ -209,6 +198,7 @@ Or change your Flutter version and try again using:
return ExitCode.success.code;
}
final aotFile = File(_aotOutputPath);
final aotFileSize = aotFile.statSync().size;
final summary = [
@@ -257,4 +247,36 @@ ${summary.join('\n')}
return ExitCode.success.code;
}
String get _aotOutputPath =>
p.join(Directory.current.path, 'build', 'out.aot');
Future<void> _buildPatch() async {
final target = results['target'] as String?;
final flavor = results['flavor'] as String?;
final buildProgress = logger.progress('Building patch');
try {
await buildIpa(flavor: flavor, target: target);
} on ProcessException catch (error) {
buildProgress.fail('Failed to build: ${error.message}');
rethrow;
} on BuildException catch (error) {
buildProgress.fail('Failed to build IPA');
logger.err(error.message);
rethrow;
}
try {
final newestDillFile = newestAppDill();
await buildElfAotSnapshot(
appDillPath: newestDillFile.path,
outFilePath: _aotOutputPath,
);
} catch (error) {
buildProgress.fail('$error');
rethrow;
}
buildProgress.complete();
}
}
@@ -157,7 +157,10 @@ Please re-run the release command for this version or create a new release.''');
final File aotFile;
try {
final newestDillFile = newestAppDill();
aotFile = await buildElfAotSnapshot(appDillPath: newestDillFile.path);
aotFile = await buildElfAotSnapshot(
appDillPath: newestDillFile.path,
outFilePath: p.join(Directory.current.path, 'build', 'out.aot'),
);
} catch (error) {
buildProgress.fail('$error');
return ExitCode.software.code;
@@ -374,11 +374,13 @@ Either run `flutter pub get` manually, or follow the steps in ${link(uri: Uri.pa
return diffPath;
}
/// Creates an AOT snapshot of the given [appDillPath] and returns the
/// resulting snapshot file.
/// Creates an AOT snapshot of the given [appDillPath] at [outFilePath] and
/// returns the resulting file.
// TODO(bryanoltman): make this work with the --local-engine flag.
Future<File> buildElfAotSnapshot({required String appDillPath}) async {
final outFilePath = p.join(Directory.current.path, 'build', 'out.aot');
Future<File> buildElfAotSnapshot({
required String appDillPath,
required String outFilePath,
}) async {
final arguments = [
'--deterministic',
'--snapshot-kind=app-aot-elf',
@@ -61,11 +61,11 @@ class _MockHttpClient extends Mock implements http.Client {}
class _MockShorebirdEnv extends Mock implements ShorebirdEnv {}
class _MockShorebirdFlutter extends Mock implements ShorebirdFlutter {}
class _MockShorebirdFlutterValidator extends Mock
implements ShorebirdFlutterValidator {}
class _MockShorebirdFlutter extends Mock implements ShorebirdFlutter {}
class _MockShorebirdProcess extends Mock implements ShorebirdProcess {}
class _MockShorebirdValidator extends Mock implements ShorebirdValidator {}
@@ -244,6 +244,8 @@ flutter:
when(() => shorebirdEnv.shorebirdRoot).thenReturn(shorebirdRoot);
when(() => shorebirdEnv.flutterDirectory).thenReturn(flutterDirectory);
when(() => shorebirdEnv.flutterRevision).thenReturn(flutterRevision);
when(() => shorebirdFlutter.useRevision(revision: any(named: 'revision')))
.thenAnswer((_) async {});
when(
() => shorebirdProcess.run(
'flutter',
@@ -499,8 +501,8 @@ Please re-run the release command for this version or create a new release.'''),
});
test(
'errors when shorebird flutter revision '
'does not match release revision', () async {
'''switches to release flutter revision when shorebird flutter revision does not match''',
() async {
const otherRevision = 'other-revision';
when(() => shorebirdEnv.flutterRevision).thenReturn(otherRevision);
final tempDir = setUpTempDir();
@@ -511,17 +513,49 @@ Please re-run the release command for this version or create a new release.'''),
getCurrentDirectory: () => tempDir,
);
expect(exitCode, ExitCode.software.code);
expect(exitCode, ExitCode.success.code);
// Verify that we switch back to the original revision once we're done.
verifyInOrder([
() => shorebirdFlutter.useRevision(revision: release.flutterRevision),
() => shorebirdFlutter.useRevision(revision: otherRevision),
]);
verify(
() => logger.info('''
Either create a new release using:
${lightCyan.wrap('shorebird release android')}
Or change your Flutter version and try again using:
${lightCyan.wrap('shorebird flutter versions use ${release.flutterRevision}')}'''),
() => logger.info(
any(
that: stringContainsInOrder([
'''The release you are trying to patch was built with a different version of Flutter.''',
'Release Flutter Revision: ${release.flutterRevision}',
'Current Flutter Revision: $otherRevision',
]),
),
),
).called(1);
});
test(
'exits with code 70 if build fails after switching flutter versions',
() async {
const otherRevision = 'other-revision';
when(() => shorebirdEnv.flutterRevision).thenReturn(otherRevision);
when(
() => shorebirdFlutter.useRevision(revision: any(named: 'revision')),
).thenAnswer((invocation) async {
// Cause builds to fail after switching flutter versions.
when(() => flutterBuildProcessResult.exitCode).thenReturn(1);
when(() => flutterBuildProcessResult.stderr).thenReturn('oops');
});
final tempDir = setUpTempDir();
setUpTempArtifacts(tempDir);
final exitCode = await IOOverrides.runZoned(
() => runWithOverrides(command.run),
getCurrentDirectory: () => tempDir,
);
expect(exitCode, equals(ExitCode.software.code));
},
);
test('errors when detecting release version name fails', () async {
final exception = Exception(
'Failed to extract version name from app bundle: oops',
@@ -292,6 +292,8 @@ flutter:
when(() => shorebirdEnv.genSnapshotFile).thenReturn(genSnapshotFile);
when(() => shorebirdEnv.flutterRevision).thenReturn(flutterRevision);
when(() => shorebirdEnv.isRunningOnCI).thenReturn(false);
when(() => shorebirdFlutter.useRevision(revision: any(named: 'revision')))
.thenAnswer((_) async {});
when(
() => aotBuildProcessResult.exitCode,
).thenReturn(ExitCode.success.code);
@@ -568,8 +570,8 @@ Please re-run the release command for this version or create a new release.'''),
});
test(
'errors when shorebird flutter revision '
'does not match release revision', () async {
'''switches to release flutter revision when shorebird flutter revision does not match''',
() async {
const otherRevision = 'other-revision';
when(() => shorebirdEnv.flutterRevision).thenReturn(otherRevision);
final tempDir = setUpTempDir();
@@ -580,17 +582,50 @@ Please re-run the release command for this version or create a new release.'''),
getCurrentDirectory: () => tempDir,
);
expect(exitCode, ExitCode.software.code);
verify(
() => logger.info('''
Either create a new release using:
${lightCyan.wrap('shorebird release ios-alpha')}
expect(exitCode, ExitCode.success.code);
// Verify that we switch back to the original revision once we're done.
verifyInOrder([
() => shorebirdFlutter.useRevision(revision: release.flutterRevision),
() => shorebirdFlutter.useRevision(revision: otherRevision),
]);
Or change your Flutter version and try again using:
${lightCyan.wrap('shorebird flutter versions use ${release.flutterRevision}')}'''),
verify(
() => logger.info(
any(
that: stringContainsInOrder([
'''The release you are trying to patch was built with a different version of Flutter.''',
'Release Flutter Revision: ${release.flutterRevision}',
'Current Flutter Revision: $otherRevision',
]),
),
),
).called(1);
});
test(
'exits with code 70 if build fails after switching flutter versions',
() async {
const otherRevision = 'other-revision';
when(() => shorebirdEnv.flutterRevision).thenReturn(otherRevision);
when(
() => shorebirdFlutter.useRevision(revision: any(named: 'revision')),
).thenAnswer((invocation) async {
// Cause builds to fail after switching flutter versions.
when(() => flutterBuildProcessResult.exitCode).thenReturn(1);
when(() => flutterBuildProcessResult.stderr).thenReturn('oops');
});
final tempDir = setUpTempDir();
setUpTempArtifacts(tempDir);
final exitCode = await IOOverrides.runZoned(
() => runWithOverrides(command.run),
getCurrentDirectory: () => tempDir,
);
expect(exitCode, equals(ExitCode.software.code));
},
);
test('exits with code 70 when release version cannot be determiend',
() async {
when(() => ipa.versionNumber).thenThrow(Exception('oops'));