From 0391b31c38fba8a11c687f6df988494ebc0be01a Mon Sep 17 00:00:00 2001 From: Bryan Oltman Date: Tue, 11 Feb 2025 11:24:49 -0500 Subject: [PATCH] feat: improve error messages for recognized issues (#2875) --- packages/shorebird_cli/bin/shorebird.dart | 2 +- .../artifact_build_exception.dart | 110 ++++++++++++++++ .../artifact_builder.dart | 112 ++++++++++------- .../lib/src/commands/patch/aar_patcher.dart | 2 +- .../src/commands/patch/android_patcher.dart | 2 +- .../commands/patch/ios_framework_patcher.dart | 2 +- .../lib/src/commands/patch/ios_patcher.dart | 2 +- .../lib/src/commands/patch/linux_patcher.dart | 2 +- .../lib/src/commands/patch/macos_patcher.dart | 2 +- .../src/commands/patch/windows_patcher.dart | 2 +- .../src/commands/release/aar_releaser.dart | 2 +- .../commands/release/android_releaser.dart | 2 +- .../release/ios_framework_releaser.dart | 2 +- .../src/commands/release/ios_releaser.dart | 2 +- .../src/commands/release/linux_releaser.dart | 2 +- .../src/commands/release/macos_releaser.dart | 2 +- .../src/commands/release/release_command.dart | 25 +++- .../commands/release/windows_releaser.dart | 2 +- .../artifact_build_exception_test.dart | 117 ++++++++++++++++++ .../artifact_builder_test.dart | 92 +++++++------- .../src/commands/patch/aar_patcher_test.dart | 2 +- .../commands/patch/android_patcher_test.dart | 2 +- .../patch/ios_framework_patcher_test.dart | 2 +- .../src/commands/patch/ios_patcher_test.dart | 2 +- .../commands/patch/linux_patcher_test.dart | 2 +- .../commands/patch/macos_patcher_test.dart | 2 +- .../commands/patch/patch_command_test.dart | 2 +- .../commands/patch/windows_patcher_test.dart | 2 +- .../commands/release/aar_releaser_test.dart | 2 +- .../release/android_releaser_test.dart | 2 +- .../release/ios_framework_releaser_test.dart | 2 +- .../commands/release/ios_releaser_test.dart | 2 +- .../commands/release/linux_releaser_test.dart | 2 +- .../commands/release/macos_releaser_test.dart | 2 +- .../release/release_command_test.dart | 78 +++++++++++- .../release/windows_releaser_test.dart | 2 +- packages/shorebird_cli/test/src/mocks.dart | 5 +- 37 files changed, 474 insertions(+), 125 deletions(-) create mode 100644 packages/shorebird_cli/lib/src/artifact_builder/artifact_build_exception.dart rename packages/shorebird_cli/lib/src/{ => artifact_builder}/artifact_builder.dart (90%) create mode 100644 packages/shorebird_cli/test/src/artifact_builder/artifact_build_exception_test.dart rename packages/shorebird_cli/test/src/{ => artifact_builder}/artifact_builder_test.dart (96%) diff --git a/packages/shorebird_cli/bin/shorebird.dart b/packages/shorebird_cli/bin/shorebird.dart index 442e334a..a29a4710 100644 --- a/packages/shorebird_cli/bin/shorebird.dart +++ b/packages/shorebird_cli/bin/shorebird.dart @@ -3,7 +3,7 @@ import 'dart:io'; import 'package:scoped_deps/scoped_deps.dart'; import 'package:shorebird_cli/src/android_sdk.dart'; import 'package:shorebird_cli/src/android_studio.dart'; -import 'package:shorebird_cli/src/artifact_builder.dart'; +import 'package:shorebird_cli/src/artifact_builder/artifact_builder.dart'; import 'package:shorebird_cli/src/artifact_manager.dart'; import 'package:shorebird_cli/src/auth/auth.dart'; import 'package:shorebird_cli/src/cache.dart'; diff --git a/packages/shorebird_cli/lib/src/artifact_builder/artifact_build_exception.dart b/packages/shorebird_cli/lib/src/artifact_builder/artifact_build_exception.dart new file mode 100644 index 00000000..5af4bb20 --- /dev/null +++ b/packages/shorebird_cli/lib/src/artifact_builder/artifact_build_exception.dart @@ -0,0 +1,110 @@ +import 'dart:convert'; + +import 'package:shorebird_cli/src/shorebird_process.dart'; + +const String _missingKeystoreFixSuggestion = + 'This error is likely due to a missing keystore file. You can read about Android app signing here: https://developer.android.com/studio/publish/app-signing.'; + +/// {@template artifact_build_exception} +/// Thrown when a build fails. +/// {@endtemplate} +class ArtifactBuildException implements Exception { + /// {@macro artifact_build_exception} + ArtifactBuildException( + this.message, { + List? stdout, + List? stderr, + String? fixRecommendation, + }) : stdout = stdout ?? [], + stderr = stderr ?? [] { + flutterError = + _errorMessageFromOutput(this.stdout + this.stderr).join('\n'); + this.fixRecommendation = fixRecommendation ?? + _recommendationFromOutput(this.stdout + this.stderr); + } + + /// {@macro artifact_build_exception} + factory ArtifactBuildException.fromProcessResult( + String message, { + required ShorebirdProcessResult buildProcessResult, + String? fixRecommendation, + }) { + return ArtifactBuildException( + message, + stdout: const LineSplitter().convert('${buildProcessResult.stdout}'), + stderr: const LineSplitter().convert('${buildProcessResult.stderr}'), + fixRecommendation: fixRecommendation, + ); + } + + /// Information about the build failure. + late final String message; + + /// The stdout output from the build process, split into lines. + final List stdout; + + /// The stderr output from the build process, split into lines. + final List stderr; + + /// The relevant error message (if we can find one) from the Flutter build + /// output. + late final String? flutterError; + + /// An optional tip to help the user fix the build failure. + late final String? fixRecommendation; + + List _errorMessageFromOutput(List output) { + final failureHeader = + RegExp(r'.*FAILURE: Build failed with an exception\..*'); + // This precedes a stack trace + final stackTraceHeader = RegExp(r'.*\* Exception is:.*'); + + // This precedes recommendations that are not applicable to us (e.g., "Get + // more help at https://help.gradle.org.") + final suggestionsHeader = RegExp(r'.*\* Try:.*'); + + String trimLine(String line) { + return line.trim().replaceAll(RegExp(r'^\[.*\]'), ''); + } + + var inErrorOutput = false; + final ret = []; + for (final line in output) { + if (failureHeader.hasMatch(line)) { + inErrorOutput = true; + } else if (stackTraceHeader.hasMatch(line) || + suggestionsHeader.hasMatch(line)) { + inErrorOutput = false; + } + + if (inErrorOutput) { + ret.add(trimLine(line)); + } + } + + return ret; + } + + /// Maps lists of regular expressions to a recommendation. We use a list of + /// regular expressions instead of a single regular expression to allow for + /// multiple possible error messages that have the same root cause. + final _regexpToRecommendations = { + ( + [RegExp("Execution failed for task ':app:signReleaseBundle'")], + _missingKeystoreFixSuggestion, + ), + }; + + String? _recommendationFromOutput(List output) { + for (final entry in _regexpToRecommendations) { + final regexes = entry.$1; + for (final regexp in regexes) { + if (output.any(regexp.hasMatch)) { + return entry.$2; + } + } + } + + return null; + } +} diff --git a/packages/shorebird_cli/lib/src/artifact_builder.dart b/packages/shorebird_cli/lib/src/artifact_builder/artifact_builder.dart similarity index 90% rename from packages/shorebird_cli/lib/src/artifact_builder.dart rename to packages/shorebird_cli/lib/src/artifact_builder/artifact_builder.dart index 18744476..585a8ae1 100644 --- a/packages/shorebird_cli/lib/src/artifact_builder.dart +++ b/packages/shorebird_cli/lib/src/artifact_builder/artifact_builder.dart @@ -8,6 +8,7 @@ import 'package:mason_logger/mason_logger.dart'; import 'package:meta/meta.dart'; import 'package:path/path.dart' as p; import 'package:scoped_deps/scoped_deps.dart'; +import 'package:shorebird_cli/src/artifact_builder/artifact_build_exception.dart'; import 'package:shorebird_cli/src/artifact_manager.dart'; import 'package:shorebird_cli/src/logging/logging.dart'; import 'package:shorebird_cli/src/os/operating_system_interface.dart'; @@ -18,6 +19,8 @@ import 'package:shorebird_cli/src/shorebird_documentation.dart'; import 'package:shorebird_cli/src/shorebird_env.dart'; import 'package:shorebird_cli/src/shorebird_process.dart'; +export 'artifact_build_exception.dart'; + /// Used to wrap code that invokes `flutter build` with Shorebird's fork of /// Flutter. typedef ShorebirdBuildCommand = Future Function(); @@ -62,20 +65,6 @@ class MacosBuildResult { final File kernelFile; } -/// {@template artifact_build_exception} -/// Thrown when a build fails. -/// {@endtemplate} -class ArtifactBuildException implements Exception { - /// {@macro artifact_build_exception} - ArtifactBuildException(this.message); - - /// Information about the build failure. - final String message; - - @override - String toString() => message; -} - /// A reference to a [ArtifactBuilder] instance. final artifactBuilderRef = create(ArtifactBuilder.new); @@ -132,10 +121,12 @@ class ArtifactBuilder { // this format. We can use the 'Task :' line to get the current task // being run. final gradleTaskRegex = RegExp(r'^\[.*\] \> (Task :.*)$'); + final stdoutLines = []; buildProcess.stdout .transform(utf8.decoder) .transform(const LineSplitter()) .listen((line) { + stdoutLines.add(line); if (buildProgress == null) { return; } @@ -149,10 +140,13 @@ class ArtifactBuilder { .transform(utf8.decoder) .transform(const LineSplitter()) .toList(); - final stdErr = stderrLines.join('\n'); final exitCode = await buildProcess.exitCode; if (exitCode != ExitCode.success.code) { - throw ArtifactBuildException('Failed to build: $stdErr'); + throw ArtifactBuildException( + 'Failed to build', + stderr: stderrLines, + stdout: stdoutLines, + ); } }); @@ -217,8 +211,9 @@ class ArtifactBuilder { ); if (result.exitCode != ExitCode.success.code) { - throw ArtifactBuildException( - 'Failed to build: ${result.stderr}', + throw ArtifactBuildException.fromProcessResult( + 'Failed to build', + buildProcessResult: result, ); } }); @@ -269,7 +264,10 @@ class ArtifactBuilder { ); if (result.exitCode != ExitCode.success.code) { - throw ArtifactBuildException('Failed to build: ${result.stderr}'); + throw ArtifactBuildException.fromProcessResult( + 'Failed to build', + buildProcessResult: result, + ); } }); } @@ -298,21 +296,26 @@ class ArtifactBuilder { environment: base64PublicKey?.toPublicKeyEnv(), ); + final stdoutLines = []; buildProcess.stdout .transform(utf8.decoder) .transform(const LineSplitter()) .listen((line) { logger.detail(line); + stdoutLines.add(line); }); final stderrLines = await buildProcess.stderr .transform(utf8.decoder) .transform(const LineSplitter()) .toList(); - final stdErr = stderrLines.join('\n'); final exitCode = await buildProcess.exitCode; if (exitCode != ExitCode.success.code) { - throw ArtifactBuildException('Failed to build: $stdErr'); + throw ArtifactBuildException( + 'Failed to build', + stdout: stdoutLines, + stderr: stderrLines, + ); } }); } @@ -377,24 +380,25 @@ class ArtifactBuilder { .transform(utf8.decoder) .transform(const LineSplitter()) .toList(); - final stderr = stderrLines.join('\n'); final stdout = stdoutLines.join('\n'); final exitCode = await buildProcess.exitCode; if (exitCode != ExitCode.success.code) { - throw ArtifactBuildException(''' -Failed to build -stdout: $stdout -stderr: $stderr'''); + throw ArtifactBuildException( + 'Failed to build', + stdout: stdoutLines, + stderr: stderrLines, + ); } appDillPath = findAppDill(stdout: stdout); }); if (appDillPath == null) { - throw ArtifactBuildException(''' -Unable to find app.dill file. -Please file a bug at https://github.com/shorebirdtech/shorebird/issues/new with the logs for this command. -'''); + throw ArtifactBuildException( + 'Unable to find app.dill file.', + fixRecommendation: + '''Please file a bug at https://github.com/shorebirdtech/shorebird/issues/new with the logs for this command.''', + ); } return MacosBuildResult(kernelFile: File(appDillPath!)); @@ -459,23 +463,32 @@ Please file a bug at https://github.com/shorebirdtech/shorebird/issues/new with buildProgress?.updateDetailMessage(null); if (exitCode != ExitCode.success.code) { - throw ArtifactBuildException('Failed to build: $stderr'); + throw ArtifactBuildException( + 'Failed to build', + stdout: stdoutLines, + stderr: stderrLines, + ); } + // TODO(https://github.com/shorebirdtech/shorebird/issues/2855): this is + // not treated as an error by Flutter, we should not throw here. if (stderr.contains('Encountered error while creating the IPA')) { - throw ArtifactBuildException(''' -Failed to build: -$stderr'''); + throw ArtifactBuildException( + 'Failed to build', + stdout: stdoutLines, + stderr: stderrLines, + ); } appDillPath = findAppDill(stdout: stdout); }); if (appDillPath == null) { - throw ArtifactBuildException(''' -Unable to find app.dill file. -Please file a bug at https://github.com/shorebirdtech/shorebird/issues/new with the logs for this command. -'''); + throw ArtifactBuildException( + 'Unable to find app.dill file.', + fixRecommendation: + '''Please file a bug at https://github.com/shorebirdtech/shorebird/issues/new with the logs for this command.''', + ); } return IpaBuildResult(kernelFile: File(appDillPath!)); @@ -510,10 +523,11 @@ Please file a bug at https://github.com/shorebirdtech/shorebird/issues/new with }); if (appDillPath == null) { - throw ArtifactBuildException(''' -Unable to find app.dill file. -Please file a bug at https://github.com/shorebirdtech/shorebird/issues/new with the logs for this command. -'''); + throw ArtifactBuildException( + 'Unable to find app.dill file.', + fixRecommendation: + '''Please file a bug at https://github.com/shorebirdtech/shorebird/issues/new with the logs for this command.''', + ); } return IosFrameworkBuildResult(kernelFile: File(appDillPath!)); @@ -586,8 +600,9 @@ Either run `flutter pub get` manually, or follow the steps in ${cannotRunInVSCod ); if (result.exitCode != ExitCode.success.code) { - throw ArtifactBuildException( - 'Failed to create snapshot: ${result.stderr}', + throw ArtifactBuildException.fromProcessResult( + 'Failed to create snapshot', + buildProcessResult: result, ); } @@ -618,11 +633,13 @@ Either run `flutter pub get` manually, or follow the steps in ${cannotRunInVSCod environment: base64PublicKey?.toPublicKeyEnv(), ); + final stdoutLines = []; buildProcess.stdout .transform(utf8.decoder) .transform(const LineSplitter()) .listen((line) { logger.detail(line); + stdoutLines.add(line); // TODO(bryanoltman): update build progress }); @@ -630,10 +647,13 @@ Either run `flutter pub get` manually, or follow the steps in ${cannotRunInVSCod .transform(utf8.decoder) .transform(const LineSplitter()) .toList(); - final stdErr = stderrLines.join('\n'); final exitCode = await buildProcess.exitCode; if (exitCode != ExitCode.success.code) { - throw ArtifactBuildException('Failed to build: $stdErr'); + throw ArtifactBuildException( + 'Failed to build', + stderr: stderrLines, + stdout: stdoutLines, + ); } }); diff --git a/packages/shorebird_cli/lib/src/commands/patch/aar_patcher.dart b/packages/shorebird_cli/lib/src/commands/patch/aar_patcher.dart index 67fd5143..5137efae 100644 --- a/packages/shorebird_cli/lib/src/commands/patch/aar_patcher.dart +++ b/packages/shorebird_cli/lib/src/commands/patch/aar_patcher.dart @@ -6,7 +6,7 @@ import 'package:io/io.dart'; import 'package:mason_logger/mason_logger.dart'; import 'package:path/path.dart' as p; import 'package:shorebird_cli/src/archive_analysis/android_archive_differ.dart'; -import 'package:shorebird_cli/src/artifact_builder.dart'; +import 'package:shorebird_cli/src/artifact_builder/artifact_builder.dart'; import 'package:shorebird_cli/src/artifact_manager.dart'; import 'package:shorebird_cli/src/code_push_client_wrapper.dart'; import 'package:shorebird_cli/src/commands/patch/patch.dart'; diff --git a/packages/shorebird_cli/lib/src/commands/patch/android_patcher.dart b/packages/shorebird_cli/lib/src/commands/patch/android_patcher.dart index 79ddcb72..35169641 100644 --- a/packages/shorebird_cli/lib/src/commands/patch/android_patcher.dart +++ b/packages/shorebird_cli/lib/src/commands/patch/android_patcher.dart @@ -6,7 +6,7 @@ import 'package:mason_logger/mason_logger.dart'; import 'package:path/path.dart' as p; import 'package:pub_semver/pub_semver.dart'; import 'package:shorebird_cli/src/archive_analysis/android_archive_differ.dart'; -import 'package:shorebird_cli/src/artifact_builder.dart'; +import 'package:shorebird_cli/src/artifact_builder/artifact_builder.dart'; import 'package:shorebird_cli/src/artifact_manager.dart'; import 'package:shorebird_cli/src/code_push_client_wrapper.dart'; import 'package:shorebird_cli/src/code_signer.dart'; diff --git a/packages/shorebird_cli/lib/src/commands/patch/ios_framework_patcher.dart b/packages/shorebird_cli/lib/src/commands/patch/ios_framework_patcher.dart index 3b970741..a7a94d2b 100644 --- a/packages/shorebird_cli/lib/src/commands/patch/ios_framework_patcher.dart +++ b/packages/shorebird_cli/lib/src/commands/patch/ios_framework_patcher.dart @@ -7,7 +7,7 @@ import 'package:path/path.dart' as p; import 'package:platform/platform.dart'; import 'package:shorebird_cli/src/archive/directory_archive.dart'; import 'package:shorebird_cli/src/archive_analysis/apple_archive_differ.dart'; -import 'package:shorebird_cli/src/artifact_builder.dart'; +import 'package:shorebird_cli/src/artifact_builder/artifact_builder.dart'; import 'package:shorebird_cli/src/artifact_manager.dart'; import 'package:shorebird_cli/src/code_push_client_wrapper.dart'; import 'package:shorebird_cli/src/commands/patch/patch.dart'; diff --git a/packages/shorebird_cli/lib/src/commands/patch/ios_patcher.dart b/packages/shorebird_cli/lib/src/commands/patch/ios_patcher.dart index dd54416e..769fe430 100644 --- a/packages/shorebird_cli/lib/src/commands/patch/ios_patcher.dart +++ b/packages/shorebird_cli/lib/src/commands/patch/ios_patcher.dart @@ -8,7 +8,7 @@ import 'package:path/path.dart' as p; import 'package:platform/platform.dart'; import 'package:shorebird_cli/src/archive/archive.dart'; import 'package:shorebird_cli/src/archive_analysis/archive_analysis.dart'; -import 'package:shorebird_cli/src/artifact_builder.dart'; +import 'package:shorebird_cli/src/artifact_builder/artifact_builder.dart'; import 'package:shorebird_cli/src/artifact_manager.dart'; import 'package:shorebird_cli/src/code_push_client_wrapper.dart'; import 'package:shorebird_cli/src/code_signer.dart'; diff --git a/packages/shorebird_cli/lib/src/commands/patch/linux_patcher.dart b/packages/shorebird_cli/lib/src/commands/patch/linux_patcher.dart index 574d5c59..8abd211e 100644 --- a/packages/shorebird_cli/lib/src/commands/patch/linux_patcher.dart +++ b/packages/shorebird_cli/lib/src/commands/patch/linux_patcher.dart @@ -5,7 +5,7 @@ import 'package:mason_logger/mason_logger.dart'; import 'package:path/path.dart' as p; import 'package:shorebird_cli/src/archive/archive.dart'; import 'package:shorebird_cli/src/archive_analysis/linux_bundle_differ.dart'; -import 'package:shorebird_cli/src/artifact_builder.dart'; +import 'package:shorebird_cli/src/artifact_builder/artifact_builder.dart'; import 'package:shorebird_cli/src/artifact_manager.dart'; import 'package:shorebird_cli/src/code_push_client_wrapper.dart'; import 'package:shorebird_cli/src/code_signer.dart'; diff --git a/packages/shorebird_cli/lib/src/commands/patch/macos_patcher.dart b/packages/shorebird_cli/lib/src/commands/patch/macos_patcher.dart index b0552d84..bb0df8ac 100644 --- a/packages/shorebird_cli/lib/src/commands/patch/macos_patcher.dart +++ b/packages/shorebird_cli/lib/src/commands/patch/macos_patcher.dart @@ -6,7 +6,7 @@ import 'package:path/path.dart' as p; import 'package:platform/platform.dart'; import 'package:shorebird_cli/src/archive_analysis/apple_archive_differ.dart'; import 'package:shorebird_cli/src/archive_analysis/plist.dart'; -import 'package:shorebird_cli/src/artifact_builder.dart'; +import 'package:shorebird_cli/src/artifact_builder/artifact_builder.dart'; import 'package:shorebird_cli/src/artifact_manager.dart'; import 'package:shorebird_cli/src/code_push_client_wrapper.dart'; import 'package:shorebird_cli/src/code_signer.dart'; diff --git a/packages/shorebird_cli/lib/src/commands/patch/windows_patcher.dart b/packages/shorebird_cli/lib/src/commands/patch/windows_patcher.dart index 920851f4..4cb4c1f1 100644 --- a/packages/shorebird_cli/lib/src/commands/patch/windows_patcher.dart +++ b/packages/shorebird_cli/lib/src/commands/patch/windows_patcher.dart @@ -6,7 +6,7 @@ import 'package:path/path.dart' as p; import 'package:platform/platform.dart'; import 'package:shorebird_cli/src/archive/archive.dart'; import 'package:shorebird_cli/src/archive_analysis/windows_archive_differ.dart'; -import 'package:shorebird_cli/src/artifact_builder.dart'; +import 'package:shorebird_cli/src/artifact_builder/artifact_builder.dart'; import 'package:shorebird_cli/src/artifact_manager.dart'; import 'package:shorebird_cli/src/code_push_client_wrapper.dart'; import 'package:shorebird_cli/src/code_signer.dart'; diff --git a/packages/shorebird_cli/lib/src/commands/release/aar_releaser.dart b/packages/shorebird_cli/lib/src/commands/release/aar_releaser.dart index 585cdbe5..1bd3afcb 100644 --- a/packages/shorebird_cli/lib/src/commands/release/aar_releaser.dart +++ b/packages/shorebird_cli/lib/src/commands/release/aar_releaser.dart @@ -4,7 +4,7 @@ import 'package:archive/archive_io.dart'; import 'package:io/io.dart'; import 'package:mason_logger/mason_logger.dart'; import 'package:path/path.dart' as p; -import 'package:shorebird_cli/src/artifact_builder.dart'; +import 'package:shorebird_cli/src/artifact_builder/artifact_builder.dart'; import 'package:shorebird_cli/src/code_push_client_wrapper.dart'; import 'package:shorebird_cli/src/commands/release/releaser.dart'; import 'package:shorebird_cli/src/extensions/arg_results.dart'; diff --git a/packages/shorebird_cli/lib/src/commands/release/android_releaser.dart b/packages/shorebird_cli/lib/src/commands/release/android_releaser.dart index a4d3a133..6d115530 100644 --- a/packages/shorebird_cli/lib/src/commands/release/android_releaser.dart +++ b/packages/shorebird_cli/lib/src/commands/release/android_releaser.dart @@ -1,7 +1,7 @@ import 'dart:io'; import 'package:mason_logger/mason_logger.dart'; -import 'package:shorebird_cli/src/artifact_builder.dart'; +import 'package:shorebird_cli/src/artifact_builder/artifact_builder.dart'; import 'package:shorebird_cli/src/code_push_client_wrapper.dart'; import 'package:shorebird_cli/src/commands/release/release.dart'; import 'package:shorebird_cli/src/commands/release/releaser.dart'; diff --git a/packages/shorebird_cli/lib/src/commands/release/ios_framework_releaser.dart b/packages/shorebird_cli/lib/src/commands/release/ios_framework_releaser.dart index dd82a7a6..a3d701eb 100644 --- a/packages/shorebird_cli/lib/src/commands/release/ios_framework_releaser.dart +++ b/packages/shorebird_cli/lib/src/commands/release/ios_framework_releaser.dart @@ -4,7 +4,7 @@ import 'package:io/io.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/artifact_builder.dart'; +import 'package:shorebird_cli/src/artifact_builder/artifact_builder.dart'; import 'package:shorebird_cli/src/artifact_manager.dart'; import 'package:shorebird_cli/src/code_push_client_wrapper.dart'; import 'package:shorebird_cli/src/commands/release/releaser.dart'; 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 a54a7b28..09806d4e 100644 --- a/packages/shorebird_cli/lib/src/commands/release/ios_releaser.dart +++ b/packages/shorebird_cli/lib/src/commands/release/ios_releaser.dart @@ -5,7 +5,7 @@ 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/plist.dart'; -import 'package:shorebird_cli/src/artifact_builder.dart'; +import 'package:shorebird_cli/src/artifact_builder/artifact_builder.dart'; import 'package:shorebird_cli/src/artifact_manager.dart'; import 'package:shorebird_cli/src/code_push_client_wrapper.dart'; import 'package:shorebird_cli/src/commands/release/releaser.dart'; diff --git a/packages/shorebird_cli/lib/src/commands/release/linux_releaser.dart b/packages/shorebird_cli/lib/src/commands/release/linux_releaser.dart index 0f909869..9ad8e87d 100644 --- a/packages/shorebird_cli/lib/src/commands/release/linux_releaser.dart +++ b/packages/shorebird_cli/lib/src/commands/release/linux_releaser.dart @@ -2,7 +2,7 @@ import 'dart:io'; import 'package:mason_logger/mason_logger.dart'; import 'package:platform/platform.dart'; -import 'package:shorebird_cli/src/artifact_builder.dart'; +import 'package:shorebird_cli/src/artifact_builder/artifact_builder.dart'; import 'package:shorebird_cli/src/artifact_manager.dart'; import 'package:shorebird_cli/src/code_push_client_wrapper.dart'; import 'package:shorebird_cli/src/commands/release/releaser.dart'; diff --git a/packages/shorebird_cli/lib/src/commands/release/macos_releaser.dart b/packages/shorebird_cli/lib/src/commands/release/macos_releaser.dart index e4ddaf0f..20be24f8 100644 --- a/packages/shorebird_cli/lib/src/commands/release/macos_releaser.dart +++ b/packages/shorebird_cli/lib/src/commands/release/macos_releaser.dart @@ -5,7 +5,7 @@ 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/plist.dart'; -import 'package:shorebird_cli/src/artifact_builder.dart'; +import 'package:shorebird_cli/src/artifact_builder/artifact_builder.dart'; import 'package:shorebird_cli/src/artifact_manager.dart'; import 'package:shorebird_cli/src/code_push_client_wrapper.dart'; import 'package:shorebird_cli/src/commands/release/release.dart'; diff --git a/packages/shorebird_cli/lib/src/commands/release/release_command.dart b/packages/shorebird_cli/lib/src/commands/release/release_command.dart index 157ac5d2..81a19218 100644 --- a/packages/shorebird_cli/lib/src/commands/release/release_command.dart +++ b/packages/shorebird_cli/lib/src/commands/release/release_command.dart @@ -4,12 +4,14 @@ import 'dart:io'; import 'package:mason_logger/mason_logger.dart'; import 'package:meta/meta.dart'; import 'package:scoped_deps/scoped_deps.dart'; +import 'package:shorebird_cli/src/artifact_builder/artifact_builder.dart'; import 'package:shorebird_cli/src/cache.dart'; import 'package:shorebird_cli/src/code_push_client_wrapper.dart'; import 'package:shorebird_cli/src/commands/release/release.dart'; import 'package:shorebird_cli/src/common_arguments.dart'; import 'package:shorebird_cli/src/config/config.dart'; import 'package:shorebird_cli/src/extensions/arg_results.dart'; +import 'package:shorebird_cli/src/extensions/string.dart'; import 'package:shorebird_cli/src/logging/logging.dart'; import 'package:shorebird_cli/src/metadata/metadata.dart'; import 'package:shorebird_cli/src/platform.dart'; @@ -278,8 +280,29 @@ of the iOS app that is using this module. (aar and ios-framework only)''', ); final FileSystemEntity releaseArtifact; try { - releaseArtifact = await releaser.buildReleaseArtifacts(); + releaseArtifact = await releaser.buildReleaseArtifacts( + progress: buildProgress, + ); buildProgress.complete(); + } on ArtifactBuildException catch (e) { + buildProgress.fail(e.message); + logger + ..detail('stdout: ${e.stdout.join(Platform.lineTerminator)}') + ..detail('stderr: ${e.stderr.join(Platform.lineTerminator)}'); + if (!e.flutterError.isNullOrEmpty) { + logger.err(e.flutterError); + } + if (!e.fixRecommendation.isNullOrEmpty) { + logger.info(e.fixRecommendation); + } + if (e.fixRecommendation.isNullOrEmpty && + e.flutterError.isNullOrEmpty) { + // If we have no fix recommendation or were unable to parse a + // flutter error, fall back to printing the raw stderr. + logger.info(e.stderr.join(Platform.lineTerminator)); + } + + throw ProcessExit(ExitCode.software.code); } on Exception catch (e) { buildProgress.fail('Failed to build release artifacts: $e'); throw ProcessExit(ExitCode.software.code); diff --git a/packages/shorebird_cli/lib/src/commands/release/windows_releaser.dart b/packages/shorebird_cli/lib/src/commands/release/windows_releaser.dart index 2f08af8b..88768ede 100644 --- a/packages/shorebird_cli/lib/src/commands/release/windows_releaser.dart +++ b/packages/shorebird_cli/lib/src/commands/release/windows_releaser.dart @@ -4,7 +4,7 @@ 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/archive.dart'; -import 'package:shorebird_cli/src/artifact_builder.dart'; +import 'package:shorebird_cli/src/artifact_builder/artifact_builder.dart'; import 'package:shorebird_cli/src/artifact_manager.dart'; import 'package:shorebird_cli/src/code_push_client_wrapper.dart'; import 'package:shorebird_cli/src/commands/release/releaser.dart'; diff --git a/packages/shorebird_cli/test/src/artifact_builder/artifact_build_exception_test.dart b/packages/shorebird_cli/test/src/artifact_builder/artifact_build_exception_test.dart new file mode 100644 index 00000000..ca7b6a58 --- /dev/null +++ b/packages/shorebird_cli/test/src/artifact_builder/artifact_build_exception_test.dart @@ -0,0 +1,117 @@ +import 'package:shorebird_cli/src/artifact_builder/artifact_builder.dart'; +import 'package:shorebird_cli/src/shorebird_process.dart'; +import 'package:test/test.dart'; + +void main() { + group(ArtifactBuildException, () { + group('fromProcessResult', () { + test('translates stdout and stderr to lists of strings', () { + const buildProcessResult = ShorebirdProcessResult( + exitCode: 1, + stdout: 'stdout', + stderr: 'stderr', + ); + + final exception = ArtifactBuildException.fromProcessResult( + 'message', + buildProcessResult: buildProcessResult, + ); + + expect(exception.stdout, ['stdout']); + expect(exception.stderr, ['stderr']); + }); + }); + + group('when no errors are recognized', () { + test('returns a message with no fix recommendation or Flutter error', () { + final exception = ArtifactBuildException( + 'message', + stderr: ['some stderr output'], + stdout: ['some stdout output'], + ); + expect(exception.flutterError, isEmpty); + expect(exception.fixRecommendation, isNull); + }); + }); + + group('when an error is recognized but no fix recommendation is found', () { + test('has a Flutter error and no fix recommendation', () { + final exception = ArtifactBuildException( + 'message', + stderr: [ + 'some stderr output', + 'FAILURE: Build failed with an exception.', + '* Exception is:', + 'some stack trace', + '* Try:', + 'some recommendation', + ], + stdout: ['some stdout output'], + ); + expect( + exception.flutterError, + equals('FAILURE: Build failed with an exception.'), + ); + expect(exception.fixRecommendation, isNull); + }); + }); + + group('when a known error is recognized and a fix recommendation is found', + () { + test('has a Flutter error and a fix recommendation', () { + final exception = ArtifactBuildException( + 'message', + stderr: [ + 'some stderr output', + 'FAILURE: Build failed with an exception.', + '* What went wrong:', + "Execution failed for task ':app:signReleaseBundle'.", + r'''> A failure occurred while executing com.android.build.gradle.internal.tasks.FinalizeBundleTask$BundleToolRunnable''', + '> java.lang.NullPointerException (no error message)', + '* Exception is:', + 'some stack trace', + '* Try:', + 'some recommendation', + ], + stdout: ['some stdout output'], + ); + expect( + exception.flutterError, + equals(r''' +FAILURE: Build failed with an exception. +* What went wrong: +Execution failed for task ':app:signReleaseBundle'. +> A failure occurred while executing com.android.build.gradle.internal.tasks.FinalizeBundleTask$BundleToolRunnable +> java.lang.NullPointerException (no error message)'''), + ); + expect( + exception.fixRecommendation, + contains('This error is likely due to a missing keystore file'), + ); + }); + }); + + group('when a fix recommendation is provided', () { + test('does not read output to find fix recommendation', () { + final exception = ArtifactBuildException( + 'message', + fixRecommendation: 'some recommendation', + stderr: [ + 'some stderr output', + 'FAILURE: Build failed with an exception.', + '* Exception is:', + 'some stack trace', + '* Try:', + 'some recommendation', + ], + stdout: ['some stdout output'], + ); + expect( + exception.flutterError, + 'FAILURE: Build failed with an exception.', + ); + expect(exception.fixRecommendation, equals('some recommendation')); + }); + }); + }); +} diff --git a/packages/shorebird_cli/test/src/artifact_builder_test.dart b/packages/shorebird_cli/test/src/artifact_builder/artifact_builder_test.dart similarity index 96% rename from packages/shorebird_cli/test/src/artifact_builder_test.dart rename to packages/shorebird_cli/test/src/artifact_builder/artifact_builder_test.dart index c7d1b1f9..2535efae 100644 --- a/packages/shorebird_cli/test/src/artifact_builder_test.dart +++ b/packages/shorebird_cli/test/src/artifact_builder/artifact_builder_test.dart @@ -5,7 +5,7 @@ import 'package:mason_logger/mason_logger.dart'; import 'package:mocktail/mocktail.dart'; import 'package:path/path.dart' as p; import 'package:scoped_deps/scoped_deps.dart'; -import 'package:shorebird_cli/src/artifact_builder.dart'; +import 'package:shorebird_cli/src/artifact_builder/artifact_builder.dart'; import 'package:shorebird_cli/src/artifact_manager.dart'; import 'package:shorebird_cli/src/logging/logging.dart'; import 'package:shorebird_cli/src/os/operating_system_interface.dart'; @@ -17,19 +17,10 @@ import 'package:shorebird_cli/src/shorebird_env.dart'; import 'package:shorebird_cli/src/shorebird_process.dart'; import 'package:test/test.dart'; -import 'fakes.dart'; -import 'mocks.dart'; +import '../fakes.dart'; +import '../mocks.dart'; void main() { - group(ArtifactBuildException, () { - test('toString is message', () { - expect( - ArtifactBuildException('my message').toString(), - equals('my message'), - ); - }); - }); - group(ArtifactBuilder, () { final projectRoot = Directory.systemTemp.createTempSync(); late Apple apple; @@ -760,7 +751,7 @@ Either run `flutter pub get` manually, or follow the steps in ${cannotRunInVSCod isA().having( (e) => e.message, 'message', - equals('Failed to build: stderr contents'), + equals('Failed to build'), ), ), ); @@ -1002,13 +993,16 @@ Either run `flutter pub get` manually, or follow the steps in ${cannotRunInVSCod expect( () => runWithOverrides(() => builder.buildMacos(codesign: false)), throwsA( - isA().having( + isA() + .having( (e) => e.message, 'message', - ''' -Unable to find app.dill file. -Please file a bug at https://github.com/shorebirdtech/shorebird/issues/new with the logs for this command. -''', + 'Unable to find app.dill file.', + ) + .having( + (e) => e.fixRecommendation, + 'fixRecommendation', + '''Please file a bug at https://github.com/shorebirdtech/shorebird/issues/new with the logs for this command.''', ), ), ); @@ -1267,11 +1261,13 @@ error: exportArchive: No signing certificate "iOS Distribution" found''', expect( () => runWithOverrides(() => builder.buildIpa(codesign: false)), throwsA( - isA().having( - (e) => e.message, - 'message', - ''' -Failed to build: + isA() + .having((e) => e.message, 'message', 'Failed to build') + .having( + (e) => e.stderr, + 'stderr', + const LineSplitter().convert( + ''' Encountered error while creating the IPA: error: exportArchive: Communication with Apple failed error: exportArchive: No signing certificate "iOS Distribution" found @@ -1285,7 +1281,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''', - ), + ), + ), ), ); }); @@ -1321,11 +1318,16 @@ error: exportArchive No signing certificate "iOS Distribution" found''', expect( () => runWithOverrides(() => builder.buildIpa(codesign: false)), throwsA( - isA().having( - (e) => e.message, - 'message', - ''' -Failed to build: + isA() + .having( + (e) => e.message, + 'message', + 'Failed to build', + ) + .having( + (e) => e.stderr, + 'stderr', + const LineSplitter().convert(''' Encountered error while creating the IPA: error: exportArchive Communication with Apple failed error: exportArchive No signing certificate "iOS Distribution" found @@ -1338,8 +1340,8 @@ error: exportArchive No signing certificate "iOS Distribution" found 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''', - ), +error: exportArchive No signing certificate "iOS Distribution" found'''), + ), ), ); }); @@ -1361,13 +1363,16 @@ error: exportArchive No signing certificate "iOS Distribution" found''', expect( () => runWithOverrides(() => builder.buildIpa(codesign: false)), throwsA( - isA().having( + isA() + .having( (e) => e.message, 'message', - ''' -Unable to find app.dill file. -Please file a bug at https://github.com/shorebirdtech/shorebird/issues/new with the logs for this command. -''', + 'Unable to find app.dill file.', + ) + .having( + (e) => e.fixRecommendation, + 'fixRecommendation', + '''Please file a bug at https://github.com/shorebirdtech/shorebird/issues/new with the logs for this command.''', ), ), ); @@ -1479,13 +1484,16 @@ Please file a bug at https://github.com/shorebirdtech/shorebird/issues/new with expect( () => runWithOverrides(builder.buildIosFramework), throwsA( - isA().having( + isA() + .having( (e) => e.message, 'message', - ''' -Unable to find app.dill file. -Please file a bug at https://github.com/shorebirdtech/shorebird/issues/new with the logs for this command. -''', + 'Unable to find app.dill file.', + ) + .having( + (e) => e.fixRecommendation, + 'fixRecommendation', + '''Please file a bug at https://github.com/shorebirdtech/shorebird/issues/new with the logs for this command.''', ), ), ); @@ -1630,7 +1638,7 @@ Please file a bug at https://github.com/shorebirdtech/shorebird/issues/new with isA().having( (e) => e.message, 'message', - equals('Failed to build: stderr contents'), + equals('Failed to build'), ), ), ); diff --git a/packages/shorebird_cli/test/src/commands/patch/aar_patcher_test.dart b/packages/shorebird_cli/test/src/commands/patch/aar_patcher_test.dart index 24682f6f..8fe71cac 100644 --- a/packages/shorebird_cli/test/src/commands/patch/aar_patcher_test.dart +++ b/packages/shorebird_cli/test/src/commands/patch/aar_patcher_test.dart @@ -6,7 +6,7 @@ import 'package:mocktail/mocktail.dart'; import 'package:path/path.dart' as p; import 'package:scoped_deps/scoped_deps.dart'; import 'package:shorebird_cli/src/archive_analysis/android_archive_differ.dart'; -import 'package:shorebird_cli/src/artifact_builder.dart'; +import 'package:shorebird_cli/src/artifact_builder/artifact_builder.dart'; import 'package:shorebird_cli/src/artifact_manager.dart'; import 'package:shorebird_cli/src/code_push_client_wrapper.dart'; import 'package:shorebird_cli/src/commands/patch/patch.dart'; diff --git a/packages/shorebird_cli/test/src/commands/patch/android_patcher_test.dart b/packages/shorebird_cli/test/src/commands/patch/android_patcher_test.dart index cb20549d..451e84ef 100644 --- a/packages/shorebird_cli/test/src/commands/patch/android_patcher_test.dart +++ b/packages/shorebird_cli/test/src/commands/patch/android_patcher_test.dart @@ -8,7 +8,7 @@ import 'package:path/path.dart' as p; import 'package:pub_semver/pub_semver.dart'; import 'package:scoped_deps/scoped_deps.dart'; import 'package:shorebird_cli/src/archive_analysis/android_archive_differ.dart'; -import 'package:shorebird_cli/src/artifact_builder.dart'; +import 'package:shorebird_cli/src/artifact_builder/artifact_builder.dart'; import 'package:shorebird_cli/src/artifact_manager.dart'; import 'package:shorebird_cli/src/code_push_client_wrapper.dart'; import 'package:shorebird_cli/src/code_signer.dart'; diff --git a/packages/shorebird_cli/test/src/commands/patch/ios_framework_patcher_test.dart b/packages/shorebird_cli/test/src/commands/patch/ios_framework_patcher_test.dart index 64535d5b..4a9c6030 100644 --- a/packages/shorebird_cli/test/src/commands/patch/ios_framework_patcher_test.dart +++ b/packages/shorebird_cli/test/src/commands/patch/ios_framework_patcher_test.dart @@ -7,7 +7,7 @@ import 'package:path/path.dart' as p; import 'package:platform/platform.dart'; import 'package:scoped_deps/scoped_deps.dart'; import 'package:shorebird_cli/src/archive_analysis/apple_archive_differ.dart'; -import 'package:shorebird_cli/src/artifact_builder.dart'; +import 'package:shorebird_cli/src/artifact_builder/artifact_builder.dart'; import 'package:shorebird_cli/src/artifact_manager.dart'; import 'package:shorebird_cli/src/code_push_client_wrapper.dart'; import 'package:shorebird_cli/src/commands/patch/patch.dart'; diff --git a/packages/shorebird_cli/test/src/commands/patch/ios_patcher_test.dart b/packages/shorebird_cli/test/src/commands/patch/ios_patcher_test.dart index 23745de2..a65a4767 100644 --- a/packages/shorebird_cli/test/src/commands/patch/ios_patcher_test.dart +++ b/packages/shorebird_cli/test/src/commands/patch/ios_patcher_test.dart @@ -10,7 +10,7 @@ import 'package:platform/platform.dart'; import 'package:pub_semver/pub_semver.dart'; import 'package:scoped_deps/scoped_deps.dart'; import 'package:shorebird_cli/src/archive_analysis/apple_archive_differ.dart'; -import 'package:shorebird_cli/src/artifact_builder.dart'; +import 'package:shorebird_cli/src/artifact_builder/artifact_builder.dart'; import 'package:shorebird_cli/src/artifact_manager.dart'; import 'package:shorebird_cli/src/code_push_client_wrapper.dart'; import 'package:shorebird_cli/src/code_signer.dart'; diff --git a/packages/shorebird_cli/test/src/commands/patch/linux_patcher_test.dart b/packages/shorebird_cli/test/src/commands/patch/linux_patcher_test.dart index f74c8372..79927828 100644 --- a/packages/shorebird_cli/test/src/commands/patch/linux_patcher_test.dart +++ b/packages/shorebird_cli/test/src/commands/patch/linux_patcher_test.dart @@ -8,7 +8,7 @@ import 'package:path/path.dart' as p; import 'package:pub_semver/pub_semver.dart'; import 'package:scoped_deps/scoped_deps.dart'; import 'package:shorebird_cli/src/archive_analysis/linux_bundle_differ.dart'; -import 'package:shorebird_cli/src/artifact_builder.dart'; +import 'package:shorebird_cli/src/artifact_builder/artifact_builder.dart'; import 'package:shorebird_cli/src/artifact_manager.dart'; import 'package:shorebird_cli/src/code_push_client_wrapper.dart'; import 'package:shorebird_cli/src/code_signer.dart'; diff --git a/packages/shorebird_cli/test/src/commands/patch/macos_patcher_test.dart b/packages/shorebird_cli/test/src/commands/patch/macos_patcher_test.dart index 2efdc30c..713a6c56 100644 --- a/packages/shorebird_cli/test/src/commands/patch/macos_patcher_test.dart +++ b/packages/shorebird_cli/test/src/commands/patch/macos_patcher_test.dart @@ -10,7 +10,7 @@ import 'package:platform/platform.dart'; import 'package:pub_semver/pub_semver.dart'; import 'package:scoped_deps/scoped_deps.dart'; import 'package:shorebird_cli/src/archive_analysis/archive_analysis.dart'; -import 'package:shorebird_cli/src/artifact_builder.dart'; +import 'package:shorebird_cli/src/artifact_builder/artifact_builder.dart'; import 'package:shorebird_cli/src/artifact_manager.dart'; import 'package:shorebird_cli/src/code_push_client_wrapper.dart'; import 'package:shorebird_cli/src/code_signer.dart'; diff --git a/packages/shorebird_cli/test/src/commands/patch/patch_command_test.dart b/packages/shorebird_cli/test/src/commands/patch/patch_command_test.dart index c9b7a531..5a81eea2 100644 --- a/packages/shorebird_cli/test/src/commands/patch/patch_command_test.dart +++ b/packages/shorebird_cli/test/src/commands/patch/patch_command_test.dart @@ -8,7 +8,7 @@ import 'package:mocktail/mocktail.dart'; import 'package:path/path.dart' as p; import 'package:scoped_deps/scoped_deps.dart'; import 'package:shorebird_cli/src/archive_analysis/archive_analysis.dart'; -import 'package:shorebird_cli/src/artifact_builder.dart'; +import 'package:shorebird_cli/src/artifact_builder/artifact_builder.dart'; import 'package:shorebird_cli/src/artifact_manager.dart'; import 'package:shorebird_cli/src/cache.dart'; import 'package:shorebird_cli/src/code_push_client_wrapper.dart'; diff --git a/packages/shorebird_cli/test/src/commands/patch/windows_patcher_test.dart b/packages/shorebird_cli/test/src/commands/patch/windows_patcher_test.dart index b6b3463a..294d97f8 100644 --- a/packages/shorebird_cli/test/src/commands/patch/windows_patcher_test.dart +++ b/packages/shorebird_cli/test/src/commands/patch/windows_patcher_test.dart @@ -9,7 +9,7 @@ import 'package:platform/platform.dart'; import 'package:pub_semver/pub_semver.dart'; import 'package:scoped_deps/scoped_deps.dart'; import 'package:shorebird_cli/src/archive_analysis/archive_analysis.dart'; -import 'package:shorebird_cli/src/artifact_builder.dart'; +import 'package:shorebird_cli/src/artifact_builder/artifact_builder.dart'; import 'package:shorebird_cli/src/artifact_manager.dart'; import 'package:shorebird_cli/src/code_push_client_wrapper.dart'; import 'package:shorebird_cli/src/code_signer.dart'; diff --git a/packages/shorebird_cli/test/src/commands/release/aar_releaser_test.dart b/packages/shorebird_cli/test/src/commands/release/aar_releaser_test.dart index b384004d..fb2188dd 100644 --- a/packages/shorebird_cli/test/src/commands/release/aar_releaser_test.dart +++ b/packages/shorebird_cli/test/src/commands/release/aar_releaser_test.dart @@ -5,7 +5,7 @@ import 'package:mason_logger/mason_logger.dart'; import 'package:mocktail/mocktail.dart'; import 'package:path/path.dart' as p; import 'package:scoped_deps/scoped_deps.dart'; -import 'package:shorebird_cli/src/artifact_builder.dart'; +import 'package:shorebird_cli/src/artifact_builder/artifact_builder.dart'; import 'package:shorebird_cli/src/code_push_client_wrapper.dart'; import 'package:shorebird_cli/src/commands/release/aar_releaser.dart'; import 'package:shorebird_cli/src/engine_config.dart'; diff --git a/packages/shorebird_cli/test/src/commands/release/android_releaser_test.dart b/packages/shorebird_cli/test/src/commands/release/android_releaser_test.dart index 2544e1a3..9a542188 100644 --- a/packages/shorebird_cli/test/src/commands/release/android_releaser_test.dart +++ b/packages/shorebird_cli/test/src/commands/release/android_releaser_test.dart @@ -5,7 +5,7 @@ import 'package:mason_logger/mason_logger.dart'; import 'package:mocktail/mocktail.dart'; import 'package:path/path.dart' as p; import 'package:scoped_deps/scoped_deps.dart'; -import 'package:shorebird_cli/src/artifact_builder.dart'; +import 'package:shorebird_cli/src/artifact_builder/artifact_builder.dart'; import 'package:shorebird_cli/src/code_push_client_wrapper.dart'; import 'package:shorebird_cli/src/code_signer.dart'; import 'package:shorebird_cli/src/commands/release/android_releaser.dart'; diff --git a/packages/shorebird_cli/test/src/commands/release/ios_framework_releaser_test.dart b/packages/shorebird_cli/test/src/commands/release/ios_framework_releaser_test.dart index 4d3dc982..e31deca8 100644 --- a/packages/shorebird_cli/test/src/commands/release/ios_framework_releaser_test.dart +++ b/packages/shorebird_cli/test/src/commands/release/ios_framework_releaser_test.dart @@ -7,7 +7,7 @@ import 'package:path/path.dart' as p; import 'package:platform/platform.dart'; import 'package:pub_semver/pub_semver.dart'; import 'package:scoped_deps/scoped_deps.dart'; -import 'package:shorebird_cli/src/artifact_builder.dart'; +import 'package:shorebird_cli/src/artifact_builder/artifact_builder.dart'; import 'package:shorebird_cli/src/artifact_manager.dart'; import 'package:shorebird_cli/src/code_push_client_wrapper.dart'; import 'package:shorebird_cli/src/commands/release/ios_framework_releaser.dart'; 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 d99c40a0..986fdb10 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 @@ -9,7 +9,7 @@ import 'package:path/path.dart' as p; import 'package:platform/platform.dart'; import 'package:pub_semver/pub_semver.dart'; import 'package:scoped_deps/scoped_deps.dart'; -import 'package:shorebird_cli/src/artifact_builder.dart'; +import 'package:shorebird_cli/src/artifact_builder/artifact_builder.dart'; import 'package:shorebird_cli/src/artifact_manager.dart'; import 'package:shorebird_cli/src/code_push_client_wrapper.dart'; import 'package:shorebird_cli/src/code_signer.dart'; diff --git a/packages/shorebird_cli/test/src/commands/release/linux_releaser_test.dart b/packages/shorebird_cli/test/src/commands/release/linux_releaser_test.dart index 6fbd3dca..0865866e 100644 --- a/packages/shorebird_cli/test/src/commands/release/linux_releaser_test.dart +++ b/packages/shorebird_cli/test/src/commands/release/linux_releaser_test.dart @@ -7,7 +7,7 @@ import 'package:path/path.dart' as p; import 'package:platform/platform.dart'; import 'package:pub_semver/pub_semver.dart'; import 'package:scoped_deps/scoped_deps.dart'; -import 'package:shorebird_cli/src/artifact_builder.dart'; +import 'package:shorebird_cli/src/artifact_builder/artifact_builder.dart'; import 'package:shorebird_cli/src/artifact_manager.dart'; import 'package:shorebird_cli/src/code_push_client_wrapper.dart'; import 'package:shorebird_cli/src/code_signer.dart'; diff --git a/packages/shorebird_cli/test/src/commands/release/macos_releaser_test.dart b/packages/shorebird_cli/test/src/commands/release/macos_releaser_test.dart index 4e06a889..c17e91d5 100644 --- a/packages/shorebird_cli/test/src/commands/release/macos_releaser_test.dart +++ b/packages/shorebird_cli/test/src/commands/release/macos_releaser_test.dart @@ -9,7 +9,7 @@ import 'package:path/path.dart' as p; import 'package:platform/platform.dart'; import 'package:pub_semver/pub_semver.dart'; import 'package:scoped_deps/scoped_deps.dart'; -import 'package:shorebird_cli/src/artifact_builder.dart'; +import 'package:shorebird_cli/src/artifact_builder/artifact_builder.dart'; import 'package:shorebird_cli/src/artifact_manager.dart'; import 'package:shorebird_cli/src/code_push_client_wrapper.dart'; import 'package:shorebird_cli/src/commands/release/release.dart'; diff --git a/packages/shorebird_cli/test/src/commands/release/release_command_test.dart b/packages/shorebird_cli/test/src/commands/release/release_command_test.dart index fc3016cc..dc3603e4 100644 --- a/packages/shorebird_cli/test/src/commands/release/release_command_test.dart +++ b/packages/shorebird_cli/test/src/commands/release/release_command_test.dart @@ -5,6 +5,7 @@ import 'package:mason_logger/mason_logger.dart'; import 'package:mocktail/mocktail.dart'; import 'package:path/path.dart' as p; import 'package:scoped_deps/scoped_deps.dart'; +import 'package:shorebird_cli/src/artifact_builder/artifact_build_exception.dart'; import 'package:shorebird_cli/src/cache.dart'; import 'package:shorebird_cli/src/code_push_client_wrapper.dart'; import 'package:shorebird_cli/src/commands/release/release.dart'; @@ -139,7 +140,7 @@ void main() { when(() => releaser.assertPreconditions()).thenAnswer((_) async => {}); when(() => releaser.assertArgsAreValid()).thenAnswer((_) async => {}); when( - () => releaser.buildReleaseArtifacts(), + () => releaser.buildReleaseArtifacts(progress: any(named: 'progress')), ).thenAnswer((_) async => File('')); when( () => releaser.getReleaseVersion( @@ -266,7 +267,7 @@ void main() { () => logger.progress( 'Building $artifactDisplayName with Flutter $flutterRevision', ), - releaser.buildReleaseArtifacts, + () => releaser.buildReleaseArtifacts(progress: any(named: 'progress')), () => progress.complete( 'Building $artifactDisplayName with Flutter $flutterRevision', ), @@ -296,7 +297,9 @@ Note: ${lightCyan.wrap('shorebird patch --platforms=android')} without the --rel group('when build fails', () { setUp(() { when( - () => releaser.buildReleaseArtifacts(), + () => releaser.buildReleaseArtifacts( + progress: any(named: 'progress'), + ), ).thenThrow(Exception('oops')); }); @@ -311,6 +314,65 @@ Note: ${lightCyan.wrap('shorebird patch --platforms=android')} without the --rel ), ).called(1); }); + + group('when failure is an ArtifactBuildException', () { + late ArtifactBuildException exception; + + setUp(() { + exception = MockArtifactBuildException(); + when(() => exception.message).thenReturn('oops'); + when(() => exception.stderr).thenReturn(['stderr']); + when(() => exception.stdout).thenReturn(['stdout']); + when( + () => releaser.buildReleaseArtifacts( + progress: any(named: 'progress'), + ), + ).thenThrow(exception); + }); + + group('when a Flutter error was detected', () { + setUp(() { + when(() => exception.flutterError).thenReturn('flutter error'); + }); + + test('logs Flutter error at the err level', () async { + await expectLater( + () => runWithOverrides(command.run), + exitsWithCode(ExitCode.software), + ); + verify(() => logger.err('flutter error')).called(1); + }); + }); + + group('when a fix recommendation is provided', () { + setUp(() { + when(() => exception.fixRecommendation).thenReturn('fix it'); + }); + + test('logs fix recommendation at the info level', () async { + await expectLater( + () => runWithOverrides(command.run), + exitsWithCode(ExitCode.software), + ); + verify(() => logger.info('fix it')).called(1); + }); + }); + + group('when neither flutter error nor fix suggestion are provided', () { + setUp(() { + when(() => exception.flutterError).thenReturn(null); + when(() => exception.fixRecommendation).thenReturn(null); + }); + + test('logs stderr', () async { + await expectLater( + () => runWithOverrides(command.run), + exitsWithCode(ExitCode.software), + ); + verify(() => logger.info('stderr')).called(1); + }); + }); + }); }); group('when dry-run is specified', () { @@ -392,7 +454,9 @@ Note: ${lightCyan.wrap('shorebird patch --platforms=android')} without the --rel () => logger.progress( 'Building $artifactDisplayName with Flutter $flutterRevision', ), - releaser.buildReleaseArtifacts, + () => releaser.buildReleaseArtifacts( + progress: any(named: 'progress'), + ), () => progress.complete( 'Building $artifactDisplayName with Flutter $flutterRevision', ), @@ -566,7 +630,11 @@ $exception''', test( 'uses specified flutter version to build ' 'and reverts to original flutter version', () async { - when(releaser.buildReleaseArtifacts).thenAnswer((_) async { + when( + () => releaser.buildReleaseArtifacts( + progress: any(named: 'progress'), + ), + ).thenAnswer((_) async { // Ensure we're using the correct flutter version. expect(shorebirdEnv.flutterRevision, equals(revision)); return File(''); diff --git a/packages/shorebird_cli/test/src/commands/release/windows_releaser_test.dart b/packages/shorebird_cli/test/src/commands/release/windows_releaser_test.dart index 73f22e2f..35a060da 100644 --- a/packages/shorebird_cli/test/src/commands/release/windows_releaser_test.dart +++ b/packages/shorebird_cli/test/src/commands/release/windows_releaser_test.dart @@ -7,7 +7,7 @@ import 'package:path/path.dart' as p; import 'package:platform/platform.dart'; import 'package:pub_semver/pub_semver.dart'; import 'package:scoped_deps/scoped_deps.dart'; -import 'package:shorebird_cli/src/artifact_builder.dart'; +import 'package:shorebird_cli/src/artifact_builder/artifact_builder.dart'; import 'package:shorebird_cli/src/artifact_manager.dart'; import 'package:shorebird_cli/src/code_push_client_wrapper.dart'; import 'package:shorebird_cli/src/code_signer.dart'; diff --git a/packages/shorebird_cli/test/src/mocks.dart b/packages/shorebird_cli/test/src/mocks.dart index 3a15cff2..23bbd660 100644 --- a/packages/shorebird_cli/test/src/mocks.dart +++ b/packages/shorebird_cli/test/src/mocks.dart @@ -11,7 +11,7 @@ import 'package:shorebird_cli/src/android_sdk.dart'; import 'package:shorebird_cli/src/android_studio.dart'; import 'package:shorebird_cli/src/archive_analysis/archive_analysis.dart'; import 'package:shorebird_cli/src/archive_analysis/archive_differ.dart'; -import 'package:shorebird_cli/src/artifact_builder.dart'; +import 'package:shorebird_cli/src/artifact_builder/artifact_builder.dart'; import 'package:shorebird_cli/src/artifact_manager.dart'; import 'package:shorebird_cli/src/auth/auth.dart'; import 'package:shorebird_cli/src/cache.dart' show Cache; @@ -63,6 +63,9 @@ class MockArgParser extends Mock implements ArgParser {} class MockArgResults extends Mock implements ArgResults {} +class MockArtifactBuildException extends Mock + implements ArtifactBuildException {} + class MockArtifactBuilder extends Mock implements ArtifactBuilder {} class MockArtifactManager extends Mock implements ArtifactManager {}