From a1ef672b5149e283f80d14292f2132a29dbc71ac Mon Sep 17 00:00:00 2001 From: Bryan Oltman Date: Fri, 15 Nov 2024 15:46:23 -0500 Subject: [PATCH] feat: add more updates to "shorebird release ios" progress (#2623) --- .../lib/src/artifact_builder.dart | 109 +++++++- .../src/commands/release/ios_releaser.dart | 6 +- .../extensions/shorebird_process_result.dart | 27 -- .../test/src/artifact_builder_test.dart | 249 +++++++++++++++--- .../commands/release/ios_releaser_test.dart | 49 ++-- .../shorebird_process_result_test.dart | 83 ------ 6 files changed, 334 insertions(+), 189 deletions(-) delete mode 100644 packages/shorebird_cli/lib/src/extensions/shorebird_process_result.dart delete mode 100644 packages/shorebird_cli/test/src/extensions/shorebird_process_result_test.dart diff --git a/packages/shorebird_cli/lib/src/artifact_builder.dart b/packages/shorebird_cli/lib/src/artifact_builder.dart index 69aee107..297d43c9 100644 --- a/packages/shorebird_cli/lib/src/artifact_builder.dart +++ b/packages/shorebird_cli/lib/src/artifact_builder.dart @@ -3,9 +3,10 @@ import 'dart:convert'; import 'dart:io'; +import 'package:collection/collection.dart'; import 'package:mason_logger/mason_logger.dart'; +import 'package:meta/meta.dart'; import 'package:scoped_deps/scoped_deps.dart'; -import 'package:shorebird_cli/src/extensions/shorebird_process_result.dart'; import 'package:shorebird_cli/src/logging/logging.dart'; import 'package:shorebird_cli/src/os/operating_system_interface.dart'; import 'package:shorebird_cli/src/platform/platform.dart'; @@ -263,6 +264,7 @@ class ArtifactBuilder { String? target, List args = const [], String? base64PublicKey, + DetailProgress? buildProgress, }) async { String? appDillPath; await _runShorebirdBuildCommand(() async { @@ -277,30 +279,54 @@ class ArtifactBuilder { ...args, ]; - final result = await process.run( + final buildProcess = await process.start( executable, arguments, runInShell: true, environment: base64PublicKey?.toPublicKeyEnv(), ); - if (result.exitCode != ExitCode.success.code) { - throw ArtifactBuildException('Failed to build: ${result.stderr}'); + final stdoutLines = []; + buildProcess.stdout + .transform(utf8.decoder) + .transform(const LineSplitter()) + .listen((line) { + stdoutLines.add(line); + if (buildProgress == null) { + return; + } + + final update = _progressUpdateFromIpaBuildLog(line); + if (update != null) { + buildProgress.updateDetailMessage(update); + } + }); + + final stderrLines = await buildProcess.stderr + .transform(utf8.decoder) + .transform(const LineSplitter()) + .toList(); + final stderr = stderrLines.join('\n'); + final stdout = stdoutLines.join('\n'); + final exitCode = await buildProcess.exitCode; + + // If we've been updating the progress, reset it to the original base + // message so as not to leave the user with a confusing message. + buildProgress?.updateDetailMessage(null); + + if (exitCode != ExitCode.success.code) { + throw ArtifactBuildException('Failed to build: $stderr'); } - if (result.stderr - .toString() - .contains('Encountered error while creating the IPA')) { - final errorMessage = _failedToCreateIpaErrorMessage( - stderr: result.stderr.toString(), - ); + if (stderr.contains('Encountered error while creating the IPA')) { + final errorMessage = _failedToCreateIpaErrorMessage(stderr: stderr); throw ArtifactBuildException(''' Failed to build: $errorMessage'''); } - appDillPath = result.findAppDill(); + appDillPath = findAppDill(stdout: stdout); }); if (appDillPath == null) { @@ -338,7 +364,7 @@ Please file a bug at https://github.com/shorebirdtech/shorebird/issues/new with throw ArtifactBuildException('Failed to build: ${result.stderr}'); } - appDillPath = result.findAppDill(); + appDillPath = findAppDill(stdout: result.stdout.toString()); }); if (appDillPath == null) { @@ -454,4 +480,63 @@ Either run `flutter pub get` manually, or follow the steps in ${cannotRunInVSCod return File(outFilePath); } + + /// Given a log of verbose output from `flutter build ipa`, returns a + /// progress update message to display to the user if the line contains + /// a known progress update step. Returns null (no update) otherwise. + String? _progressUpdateFromIpaBuildLog(String line) { + // xcodebuild -list is a command run early in `flutter build ipa` to read + // build settings and schemes. Most users aren't familiar with this command, + // so we translate it to "Collecting schemes" below. + final collectingSchemesRegex = + RegExp(r'\[.*\] executing:.*xcrun xcodebuild -list$'); + final archivingRegex = RegExp(r'^\[.*\] (Archiving .+$)'); + final runningXcodeBuildRegex = RegExp(r'^\[.*\] (Running Xcode build).*$'); + final compilingLinkingSigningRegex = + RegExp(r'^\[.*\]\s+└─(Compiling, linking and signing).*$'); + final buildingAppStoreIpaRegex = + RegExp(r'^\[.*\] (Building App Store IPA).*$'); + final builtAppStoreIpaRegex = RegExp(r'^\[.*\] ✓ (Built IPA to \S+).*$'); + + final regexes = [ + archivingRegex, + collectingSchemesRegex, + runningXcodeBuildRegex, + compilingLinkingSigningRegex, + buildingAppStoreIpaRegex, + builtAppStoreIpaRegex, + ]; + + for (final regex in regexes) { + final match = regex.firstMatch(line); + if (match == null) continue; + + // See the note above about the collectingSchemesRegex. + if (regex == collectingSchemesRegex) { + return 'Collecting schemes'; + } + + return match.group(1); + } + + return null; + } + + /// Given the full stdout from a `flutter build ipa` command, finds the path + /// to the app.dill file that was built. + @visibleForTesting + String? findAppDill({required String stdout}) { + final appDillLine = stdout.split('\n').firstWhereOrNull( + (l) => l.contains('gen_snapshot') && l.endsWith('app.dill'), + ); + + if (appDillLine == null) return null; + + // The last argument in the line is the path to app.dill. Because + // 1) paths can contain spaces and + // 2) the path to the app.dill is absolute (i.e., it starts with a '/') + // we can grab the last space-separated part of the line that starts with + // a '/' and assume everything after it is the path to app.dill. + return '/${appDillLine.split(' /').last}'; + } } diff --git a/packages/shorebird_cli/lib/src/commands/release/ios_releaser.dart b/packages/shorebird_cli/lib/src/commands/release/ios_releaser.dart index 81fffb2c..a70de6b9 100644 --- a/packages/shorebird_cli/lib/src/commands/release/ios_releaser.dart +++ b/packages/shorebird_cli/lib/src/commands/release/ios_releaser.dart @@ -107,8 +107,9 @@ For more information see: ${supportedFlutterVersionsUrl.toLink()}''', } final flutterVersionString = await shorebirdFlutter.getVersionAndRevision(); - final buildProgress = - logger.progress('Building ipa with Flutter $flutterVersionString'); + final buildProgress = logger.detailProgress( + 'Building app bundle with Flutter $flutterVersionString', + ); try { await artifactBuilder.buildIpa( @@ -117,6 +118,7 @@ For more information see: ${supportedFlutterVersionsUrl.toLink()}''', target: target, args: argResults.forwardedArgs, base64PublicKey: argResults.encodedPublicKey, + buildProgress: buildProgress, ); buildProgress.complete(); } on ArtifactBuildException catch (error) { diff --git a/packages/shorebird_cli/lib/src/extensions/shorebird_process_result.dart b/packages/shorebird_cli/lib/src/extensions/shorebird_process_result.dart deleted file mode 100644 index a7fddb6a..00000000 --- a/packages/shorebird_cli/lib/src/extensions/shorebird_process_result.dart +++ /dev/null @@ -1,27 +0,0 @@ -import 'package:collection/collection.dart'; -import 'package:shorebird_cli/src/shorebird_process.dart'; - -/// Extensions for finding `app.dill` in a [ShorebirdProcessResult]. -extension FindAppDill on ShorebirdProcessResult { - /// Finds a line in stdout that invokes gen_snapshot with app.dill as an - /// argument. The path to the app.dill file is the last argument in the line. - /// - /// Example matching line from `flutter build ipa`: - /// [ ] executing: /Users/bryanoltman/shorebirdtech/_shorebird/shorebird/bin/cache/flutter/985ec84cb99d3c60341e2c78be9826e0a88cc697/bin/cache/artifacts/engine/ios-release/gen_snapshot_arm64 --deterministic --snapshot_kind=app-aot-assembly --assembly=/Users/bryanoltman/Documents/sandbox/ios_signing/.dart_tool/flutter_build/804399dd5f8e05d7b9ec7e0bb4ceb22c/arm64/snapshot_assembly.S /Users/bryanoltman/Documents/sandbox/ios_signing/.dart_tool/flutter_build/804399dd5f8e05d7b9ec7e0bb4ceb22c/app.dill - /// - /// Returns null if no matching line is found. - String? findAppDill() { - final appDillLine = stdout.toString().split('\n').firstWhereOrNull( - (l) => l.contains('gen_snapshot') && l.endsWith('app.dill'), - ); - - if (appDillLine == null) return null; - - // The last argument in the line is the path to app.dill. Because - // 1) paths can contain spaces and - // 2) the path to the app.dill is absolute (i.e., it starts with a '/') - // we can grab the last space-separated part of the line that starts with - // a '/' and assume everything after it is the path to app.dill. - return '/${appDillLine.split(' /').last}'; - } -} diff --git a/packages/shorebird_cli/test/src/artifact_builder_test.dart b/packages/shorebird_cli/test/src/artifact_builder_test.dart index 242906ea..484b3bad 100644 --- a/packages/shorebird_cli/test/src/artifact_builder_test.dart +++ b/packages/shorebird_cli/test/src/artifact_builder_test.dart @@ -91,21 +91,32 @@ void main() { runInShell: any(named: 'runInShell'), ), ).thenAnswer((_) async => buildProcessResult); + when(() => buildProcessResult.exitCode).thenReturn(ExitCode.success.code); + when(() => buildProcessResult.stdout).thenReturn('some stdout'); when( () => shorebirdProcess.start( any(), any(), runInShell: any(named: 'runInShell'), + environment: any(named: 'environment'), ), ).thenAnswer((_) async => buildProcess); - when(() => buildProcessResult.exitCode).thenReturn(ExitCode.success.code); - when(() => buildProcessResult.stdout).thenReturn( - ''' - [ ] Will strip AOT snapshot manually after build and dSYM generation. - [ ] executing: /bin/cache/artifacts/engine/ios-release/gen_snapshot_arm64 --deterministic --snapshot_kind=app-aot-assembly --assembly=snapshot_assembly.S /path/to/app.dill - [+3688 ms] executing: sysctl hw.optional.arm64 -''', + when(() => buildProcess.stdout).thenAnswer( + (_) => Stream.fromIterable( + [ + 'Some build output', + ].map(utf8.encode), + ), ); + when(() => buildProcess.stderr).thenAnswer( + (_) => Stream.fromIterable( + [ + 'Some build output', + ].map(utf8.encode), + ), + ); + when(() => buildProcess.exitCode).thenAnswer((_) async => 0); + when(() => logger.progress(any())).thenReturn(MockProgress()); when(() => logger.info(any())).thenReturn(null); when(() => operatingSystemInterface.which('flutter')) @@ -184,21 +195,6 @@ Either run `flutter pub get` manually, or follow the steps in ${cannotRunInVSCod flavor: any(named: 'flavor'), ), ).thenReturn(File('app-release.aab')); - when(() => buildProcess.stdout).thenAnswer( - (_) => Stream.fromIterable( - [ - 'Some build output', - ].map(utf8.encode), - ), - ); - when(() => buildProcess.stderr).thenAnswer( - (_) => Stream.fromIterable( - [ - 'Some build output', - ].map(utf8.encode), - ), - ); - when(() => buildProcess.exitCode).thenAnswer((_) async => 0); }); test('invokes the correct flutter build command', () async { @@ -709,12 +705,26 @@ Either run `flutter pub get` manually, or follow the steps in ${cannotRunInVSCod group( 'buildIpa', () { + setUp(() { + when(() => buildProcess.stdout).thenAnswer( + (_) => Stream.fromIterable( + [ + ''' + [ ] Will strip AOT snapshot manually after build and dSYM generation. + [ ] executing: /bin/cache/artifacts/engine/ios-release/gen_snapshot_arm64 --deterministic --snapshot_kind=app-aot-assembly --assembly=snapshot_assembly.S /path/to/app.dill + [+3688 ms] executing: sysctl hw.optional.arm64 +''', + ].map(utf8.encode), + ), + ); + }); + group('with default arguments', () { test('invokes flutter build with an export options plist', () async { final result = await runWithOverrides(builder.buildIpa); verify( - () => shorebirdProcess.run( + () => shorebirdProcess.start( 'flutter', [ 'build', @@ -734,7 +744,7 @@ Either run `flutter pub get` manually, or follow the steps in ${cannotRunInVSCod setUp(() { when( - () => shorebirdProcess.run( + () => shorebirdProcess.start( 'flutter', [ 'build', @@ -746,7 +756,7 @@ Either run `flutter pub get` manually, or follow the steps in ${cannotRunInVSCod 'SHOREBIRD_PUBLIC_KEY': base64PublicKey, }, ), - ).thenAnswer((_) async => buildProcessResult); + ).thenAnswer((_) async => buildProcess); }); test('adds the SHOREBIRD_PUBLIC_KEY to the environment', () async { @@ -757,7 +767,7 @@ Either run `flutter pub get` manually, or follow the steps in ${cannotRunInVSCod ); verify( - () => shorebirdProcess.run( + () => shorebirdProcess.start( 'flutter', [ 'build', @@ -784,7 +794,7 @@ Either run `flutter pub get` manually, or follow the steps in ${cannotRunInVSCod ); verify( - () => shorebirdProcess.run( + () => shorebirdProcess.start( 'flutter', [ 'build', @@ -801,11 +811,77 @@ Either run `flutter pub get` manually, or follow the steps in ${cannotRunInVSCod ).called(1); }); + group('when progress contains known build steps', () { + late DetailProgress progress; + + setUp(() { + progress = MockDetailProgress(); + + when(() => buildProcess.stdout).thenAnswer( + (_) => Stream.fromIterable( + [ + // cSpell:disable + ''' + [ ] Will strip AOT snapshot manually after build and dSYM generation. + [ ] executing: /bin/cache/artifacts/engine/ios-release/gen_snapshot_arm64 --deterministic --snapshot_kind=app-aot-assembly --assembly=snapshot_assembly.S /path/to/app.dill + [+3688 ms] executing: sysctl hw.optional.arm64''', + '[ +10 ms] Generating /Users/bryanoltman/Documents/sandbox/notification_extension/android/app/src/main/java/io/flutter/plugins/GeneratedPluginRegistrant.java', + '[ +50 ms] executing: [/Users/bryanoltman/Documents/sandbox/notification_extension/ios/] /usr/bin/arch -arm64e xcrun xcodebuild -list', + '[+32333 ms] Command line invocation:', + '[ +6 ms] Exit code 0 from: mkfifo /var/folders/64/dj6krpq1093dmx08dy4r1cwh0000gn/T/flutter_tools.WDvaE9/flutter_ios_build_temp_dirUAyStV/pipe_to_stdout', + '[ +1 ms] Running Xcode build...', + '[ ] executing: [/Users/bryanoltman/Documents/sandbox/notification_extension/ios/] /usr/bin/arch -arm64e xcrun xcodebuild -configuration Release VERBOSE_SCRIPT_LOGGING=YES -workspace Runner.xcworkspace -scheme Runner -sdk iphoneos -destination generic/platform=iOS SCRIPT_OUTPUT_STREAM_FILE=/var/folders/64/dj6krpq1093dmx08dy4r1cwh0000gn/T/flutter_tools.WDvaE9/flutter_ios_build_temp_dirUAyStV/pipe_to_stdout -resultBundlePath /var/folders/64/dj6krpq1093dmx08dy4r1cwh0000gn/T/flutter_tools.WDvaE9/flutter_ios_build_temp_dirUAyStV/temporary_xcresult_bundle -resultBundleVersion 3 FLUTTER_SUPPRESS_ANALYTICS=true COMPILER_INDEX_STORE_ENABLE=NO -archivePath /Users/bryanoltman/Documents/sandbox/notification_extension/build/ios/archive/Runner archive', + '[+62601 ms] Running Xcode build... (completed in 62.6s)', + '[ ] └─Compiling, linking and signing...', + '[+5925 ms] Command line invocation:', + '/Applications/Xcode.app/Contents/Developer/usr/bin/xcodebuild -configuration Release VERBOSE_SCRIPT_LOGGING=YES -workspace Runner.xcworkspace -scheme Runner -sdk iphoneos -destination generic/platform=iOS SCRIPT_OUTPUT_STREAM_FILE=/var/folders/64/dj6krpq1093dmx08dy4r1cwh0000gn/T/flutter_tools.WDvaE9/flutter_ios_build_temp_dirUAyStV/pipe_to_stdout -resultBundlePath /var/folders/64/dj6krpq1093dmx08dy4r1cwh0000gn/T/flutter_tools.WDvaE9/flutter_ios_build_temp_dirUAyStV/temporary_xcresult_bundle -resultBundleVersion 3 FLUTTER_SUPPRESS_ANALYTICS=true COMPILER_INDEX_STORE_ENABLE=NO -archivePath /Users/bryanoltman/Documents/sandbox/notification_extension/build/ios/archive/Runner archive', + // cSpell:enable + ] + .map((line) => '$line${Platform.lineTerminator}') + .map(utf8.encode), + ), + ); + when(() => buildProcess.stderr).thenAnswer( + (_) => Stream.fromIterable( + ['Some build output'].map(utf8.encode), + ), + ); + }); + + test('updates progress with known build steps', () async { + await expectLater( + runWithOverrides( + () => builder.buildIpa( + buildProgress: progress, + ), + ), + completes, + ); + + // Required to trigger stdout stream events + await pumpEventQueue(); + + // Ensure we update the progress in the correct order and with the + // correct messages, and reset to the base message after the build + // completes. + verifyInOrder( + [ + () => progress.updateDetailMessage('Collecting schemes'), + () => progress.updateDetailMessage('Running Xcode build'), + () => progress.updateDetailMessage('Running Xcode build'), + () => progress.updateDetailMessage( + 'Compiling, linking and signing', + ), + ], + ); + }); + }); + group('when the build fails', () { group('with non-zero exit code', () { setUp(() { - when(() => buildProcessResult.exitCode) - .thenReturn(ExitCode.software.code); + when(() => buildProcess.exitCode) + .thenAnswer((_) async => ExitCode.software.code); }); test('throws ArtifactBuildException', () { @@ -818,10 +894,12 @@ Either run `flutter pub get` manually, or follow the steps in ${cannotRunInVSCod group('with error message in stderr (Xcode <= 15.x)', () { setUp(() { - when(() => buildProcessResult.exitCode) - .thenReturn(ExitCode.success.code); - when(() => buildProcessResult.stderr).thenReturn( - ''' + when(() => buildProcess.exitCode) + .thenAnswer((_) async => ExitCode.success.code); + when(() => buildProcess.stderr).thenAnswer( + (_) => Stream.fromIterable( + [ + ''' Encountered error while creating the IPA: error: exportArchive: Communication with Apple failed error: exportArchive: No signing certificate "iOS Distribution" found @@ -835,6 +913,8 @@ error: exportArchive: Communication with Apple failed error: exportArchive: No signing certificate "iOS Distribution" found error: exportArchive: Communication with Apple failed error: exportArchive: No signing certificate "iOS Distribution" found''', + ].map(utf8.encode), + ), ); }); @@ -859,10 +939,12 @@ Failed to build: group('with error message in stderr (Xcode >= 16.x)', () { setUp(() { - when(() => buildProcessResult.exitCode) - .thenReturn(ExitCode.success.code); - when(() => buildProcessResult.stderr).thenReturn( - ''' + when(() => buildProcess.exitCode) + .thenAnswer((_) async => ExitCode.success.code); + when(() => buildProcess.stderr).thenAnswer( + (_) => Stream.fromIterable( + [ + ''' Encountered error while creating the IPA: error: exportArchive Communication with Apple failed error: exportArchive No signing certificate "iOS Distribution" found @@ -876,6 +958,8 @@ error: exportArchive Communication with Apple failed error: exportArchive No signing certificate "iOS Distribution" found error: exportArchive Communication with Apple failed error: exportArchive No signing certificate "iOS Distribution" found''', + ].map(utf8.encode), + ), ); }); @@ -901,7 +985,13 @@ Failed to build: group('when an app.dill file is not found in build stdout', () { setUp(() { - when(() => buildProcessResult.stdout).thenReturn('no app.dill'); + when(() => buildProcess.stdout).thenAnswer( + (_) => Stream.fromIterable( + [ + 'no app.dill', + ].map(utf8.encode), + ), + ); }); test('throws ArtifactBuildException', () { @@ -924,8 +1014,9 @@ Please file a bug at https://github.com/shorebirdtech/shorebird/issues/new with group('after a build', () { group('when the build is successful', () { setUp(() { - when(() => buildProcessResult.exitCode) - .thenReturn(ExitCode.success.code); + when( + () => buildProcess.exitCode, + ).thenAnswer((_) async => ExitCode.success.code); }); verifyCorrectFlutterPubGet( @@ -936,8 +1027,9 @@ Please file a bug at https://github.com/shorebirdtech/shorebird/issues/new with group('when the build fails', () { setUp(() { - when(() => buildProcessResult.exitCode) - .thenReturn(ExitCode.software.code); + when( + () => buildProcess.exitCode, + ).thenAnswer((_) async => ExitCode.software.code); }); verifyCorrectFlutterPubGet( @@ -958,6 +1050,16 @@ Please file a bug at https://github.com/shorebirdtech/shorebird/issues/new with group( 'buildIosFramework', () { + setUp(() { + when(() => buildProcessResult.stdout).thenReturn( + ''' + [ ] Will strip AOT snapshot manually after build and dSYM generation. + [ ] executing: /bin/cache/artifacts/engine/ios-release/gen_snapshot_arm64 --deterministic --snapshot_kind=app-aot-assembly --assembly=snapshot_assembly.S /path/to/app.dill + [+3688 ms] executing: sysctl hw.optional.arm64 +''', + ); + }); + test('invokes the correct flutter build command', () async { final result = await runWithOverrides(builder.buildIosFramework); @@ -1116,5 +1218,68 @@ Please file a bug at https://github.com/shorebirdtech/shorebird/issues/new with }, testOn: 'mac-os', ); + + group('findAppDill', () { + group('when gen_snapshot is invoked with app.dill', () { + test('returns the path to app.dill', () { + const result = ''' + [ ] Will strip AOT snapshot manually after build and dSYM generation. + [ ] executing: /Users/bryanoltman/shorebirdtech/_shorebird/shorebird/bin/cache/flutter/985ec84cb99d3c60341e2c78be9826e0a88cc697/bin/cache/artifacts/engine/ios-release/gen_snapshot_arm64 --deterministic --snapshot_kind=app-aot-assembly --assembly=/Users/bryanoltman/Documents/sandbox/ios_signing/.dart_tool/flutter_build/804399dd5f8e05d7b9ec7e0bb4ceb22c/arm64/snapshot_assembly.S /Users/bryanoltman/Documents/sandbox/ios_signing/.dart_tool/flutter_build/804399dd5f8e05d7b9ec7e0bb4ceb22c/app.dill + [+3688 ms] executing: sysctl hw.optional.arm64 +'''; + + expect( + builder.findAppDill(stdout: result), + equals( + '/Users/bryanoltman/Documents/sandbox/ios_signing/.dart_tool/flutter_build/804399dd5f8e05d7b9ec7e0bb4ceb22c/app.dill', + ), + ); + }); + + test('returns the path to app.dill (local engine)', () { + const result = ''' + [ ] Will strip AOT snapshot manually after build and dSYM generation. + [ ] executing: /Users/felix/Development/github.com/shorebirdtech/engine/src/out/ios_release/clang_x64/gen_snapshot_arm64 --deterministic --snapshot_kind=app-aot-assembly --assembly=/Users/felix/Development/github.com/felangel/flutter_and_friends/.dart_tool/flutter_build/ae2d368b5940aefb0c55ff62186de056/arm64/snapshot_assembly.S /Users/felix/Development/github.com/felangel/flutter_and_friends/.dart_tool/flutter_build/ae2d368b5940aefb0c55ff62186de056/app.dill + [+5435 ms] executing: sysctl hw.optional.arm64 +'''; + + expect( + builder.findAppDill(stdout: result), + equals( + '/Users/felix/Development/github.com/felangel/flutter_and_friends/.dart_tool/flutter_build/ae2d368b5940aefb0c55ff62186de056/app.dill', + ), + ); + }); + + group('when path to app.dill contains a space', () { + test('returns full path to app.dill, including the space(s)', () { + const result = ''' + [ +3 ms] targetingApplePlatform = true + [ ] extractAppleDebugSymbols = true + [ ] Will strip AOT snapshot manually after build and dSYM generation. + [ ] executing: /Users/bryanoltman/shorebirdtech/_shorebird/shorebird/bin/cache/flutter/9015e1b42a1ba41d97176e22b502b0e0e8ad28af/bin/cache/artifacts/engine/ios-release/gen_snapshot_arm64 --deterministic --snapshot_kind=app-aot-assembly --assembly=/Users/bryanoltman/Documents/sandbox/folder with space/ios_patcher/.dart_tool/flutter_build/cd4f4aa272817365910648606e3e4164/arm64/snapshot_assembly.S /Users/bryanoltman/Documents/sandbox/folder with space/ios_patcher/.dart_tool/flutter_build/cd4f4aa272817365910648606e3e4164/app.dill + [+3395 ms] executing: sysctl hw.optional.arm64 + [ +3 ms] Exit code 0 from: sysctl hw.optional.arm64 +'''; + + expect( + builder.findAppDill(stdout: result), + equals( + '/Users/bryanoltman/Documents/sandbox/folder with space/ios_patcher/.dart_tool/flutter_build/cd4f4aa272817365910648606e3e4164/app.dill', + ), + ); + }); + }); + }); + + group('when gen_snapshot is not invoked with app.dill', () { + test('returns null', () { + const result = + 'executing: .../gen_snapshot_arm64 .../snapshot_assembly.S'; + + expect(builder.findAppDill(stdout: result), isNull); + }); + }); + }); }); } diff --git a/packages/shorebird_cli/test/src/commands/release/ios_releaser_test.dart b/packages/shorebird_cli/test/src/commands/release/ios_releaser_test.dart index 4c8580c1..3f68357b 100644 --- a/packages/shorebird_cli/test/src/commands/release/ios_releaser_test.dart +++ b/packages/shorebird_cli/test/src/commands/release/ios_releaser_test.dart @@ -335,6 +335,7 @@ To change the version of this release, change your app's version in your pubspec flavor: any(named: 'flavor'), target: any(named: 'target'), args: any(named: 'args'), + buildProgress: any(named: 'buildProgress'), ), ).thenAnswer( (_) async => IpaBuildResult( @@ -378,6 +379,7 @@ To change the version of this release, change your app's version in your pubspec target: any(named: 'target'), args: any(named: 'args'), base64PublicKey: any(named: 'base64PublicKey'), + buildProgress: any(named: 'buildProgress'), ), ).thenAnswer( (_) async => IpaBuildResult( @@ -400,6 +402,7 @@ To change the version of this release, change your app's version in your pubspec target: any(named: 'target'), args: any(named: 'args'), base64PublicKey: base64PublicKey, + buildProgress: any(named: 'buildProgress'), ), ).called(1); }, @@ -435,6 +438,7 @@ To change the version of this release, change your app's version in your pubspec flavor: any(named: 'flavor'), target: any(named: 'target'), args: any(named: 'args'), + buildProgress: any(named: 'buildProgress'), ), ).thenThrow(ArtifactBuildException('Failed to build')); }); @@ -452,31 +456,30 @@ To change the version of this release, change your app's version in your pubspec }); group('when build succeeds', () { - group('when build succeeds', () { - group('when platform was specified via arg results rest', () { - setUp(() { - when(() => argResults.rest).thenReturn(['ios', '--verbose']); - }); + group('when platform was specified via arg results rest', () { + setUp(() { + when(() => argResults.rest).thenReturn(['ios', '--verbose']); + }); - test('verifies artifacts exist and returns xcarchive path', - () async { - expect( - await runWithOverrides(iosReleaser.buildReleaseArtifacts), - equals(xcarchiveDirectory), - ); + test('verifies artifacts exist and returns xcarchive path', + () async { + expect( + await runWithOverrides(iosReleaser.buildReleaseArtifacts), + equals(xcarchiveDirectory), + ); - verify(() => artifactManager.getXcarchiveDirectory()).called(1); - verify( - () => artifactManager.getIosAppDirectory( - xcarchiveDirectory: xcarchiveDirectory, - ), - ).called(1); - verify( - () => artifactBuilder.buildIpa( - args: ['--verbose'], - ), - ).called(1); - }); + verify(() => artifactManager.getXcarchiveDirectory()).called(1); + verify( + () => artifactManager.getIosAppDirectory( + xcarchiveDirectory: xcarchiveDirectory, + ), + ).called(1); + verify( + () => artifactBuilder.buildIpa( + args: ['--verbose'], + buildProgress: any(named: 'buildProgress'), + ), + ).called(1); }); }); diff --git a/packages/shorebird_cli/test/src/extensions/shorebird_process_result_test.dart b/packages/shorebird_cli/test/src/extensions/shorebird_process_result_test.dart deleted file mode 100644 index 48586937..00000000 --- a/packages/shorebird_cli/test/src/extensions/shorebird_process_result_test.dart +++ /dev/null @@ -1,83 +0,0 @@ -import 'package:shorebird_cli/src/extensions/shorebird_process_result.dart'; -import 'package:shorebird_cli/src/shorebird_process.dart'; -import 'package:test/test.dart'; - -void main() { - group('FindAppDill', () { - group('when gen_snapshot is invoked with app.dill', () { - test('returns the path to app.dill', () { - const result = ShorebirdProcessResult( - stdout: ''' - [ ] Will strip AOT snapshot manually after build and dSYM generation. - [ ] executing: /Users/bryanoltman/shorebirdtech/_shorebird/shorebird/bin/cache/flutter/985ec84cb99d3c60341e2c78be9826e0a88cc697/bin/cache/artifacts/engine/ios-release/gen_snapshot_arm64 --deterministic --snapshot_kind=app-aot-assembly --assembly=/Users/bryanoltman/Documents/sandbox/ios_signing/.dart_tool/flutter_build/804399dd5f8e05d7b9ec7e0bb4ceb22c/arm64/snapshot_assembly.S /Users/bryanoltman/Documents/sandbox/ios_signing/.dart_tool/flutter_build/804399dd5f8e05d7b9ec7e0bb4ceb22c/app.dill - [+3688 ms] executing: sysctl hw.optional.arm64 -''', - stderr: '', - exitCode: 0, - ); - - expect( - result.findAppDill(), - equals( - '/Users/bryanoltman/Documents/sandbox/ios_signing/.dart_tool/flutter_build/804399dd5f8e05d7b9ec7e0bb4ceb22c/app.dill', - ), - ); - }); - - test('returns the path to app.dill (local engine)', () { - const result = ShorebirdProcessResult( - stdout: ''' - [ ] Will strip AOT snapshot manually after build and dSYM generation. - [ ] executing: /Users/felix/Development/github.com/shorebirdtech/engine/src/out/ios_release/clang_x64/gen_snapshot_arm64 --deterministic --snapshot_kind=app-aot-assembly --assembly=/Users/felix/Development/github.com/felangel/flutter_and_friends/.dart_tool/flutter_build/ae2d368b5940aefb0c55ff62186de056/arm64/snapshot_assembly.S /Users/felix/Development/github.com/felangel/flutter_and_friends/.dart_tool/flutter_build/ae2d368b5940aefb0c55ff62186de056/app.dill - [+5435 ms] executing: sysctl hw.optional.arm64 -''', - stderr: '', - exitCode: 0, - ); - - expect( - result.findAppDill(), - equals( - '/Users/felix/Development/github.com/felangel/flutter_and_friends/.dart_tool/flutter_build/ae2d368b5940aefb0c55ff62186de056/app.dill', - ), - ); - }); - - group('when path to app.dill contains a space', () { - test('returns full path to app.dill, including the space(s)', () { - const result = ShorebirdProcessResult( - stdout: ''' - [ +3 ms] targetingApplePlatform = true - [ ] extractAppleDebugSymbols = true - [ ] Will strip AOT snapshot manually after build and dSYM generation. - [ ] executing: /Users/bryanoltman/shorebirdtech/_shorebird/shorebird/bin/cache/flutter/9015e1b42a1ba41d97176e22b502b0e0e8ad28af/bin/cache/artifacts/engine/ios-release/gen_snapshot_arm64 --deterministic --snapshot_kind=app-aot-assembly --assembly=/Users/bryanoltman/Documents/sandbox/folder with space/ios_patcher/.dart_tool/flutter_build/cd4f4aa272817365910648606e3e4164/arm64/snapshot_assembly.S /Users/bryanoltman/Documents/sandbox/folder with space/ios_patcher/.dart_tool/flutter_build/cd4f4aa272817365910648606e3e4164/app.dill - [+3395 ms] executing: sysctl hw.optional.arm64 - [ +3 ms] Exit code 0 from: sysctl hw.optional.arm64 -''', - stderr: '', - exitCode: 0, - ); - - expect( - result.findAppDill(), - equals( - '/Users/bryanoltman/Documents/sandbox/folder with space/ios_patcher/.dart_tool/flutter_build/cd4f4aa272817365910648606e3e4164/app.dill', - ), - ); - }); - }); - }); - - group('when gen_snapshot is not invoked with app.dill', () { - test('returns null', () { - const result = ShorebirdProcessResult( - stdout: 'executing: .../gen_snapshot_arm64 .../snapshot_assembly.S', - stderr: '', - exitCode: 0, - ); - - expect(result.findAppDill(), isNull); - }); - }); - }); -}