feat(shorebird_cli): add flutter build failure recommendations (#3022)

This commit is contained in:
Felix Angelov
2025-03-27 16:49:49 -05:00
committed by GitHub
parent 90947a8061
commit 5f9272a7f1
2 changed files with 190 additions and 34 deletions
@@ -67,6 +67,33 @@ extension on String {
/// Builds aabs, ipas, and other artifacts produced by `flutter build`.
/// @{endtemplate}
class ArtifactBuilder {
/// A general recommendation when building artifacts fails.
static String runVanillaFlutterBuildRecommendation(String buildCommand) => '''
${styleBold.wrap('💡 Fix Recommendations')}
• Check that running `flutter build` with the same command-line arguments
completes successfully by running the following command:
${lightCyan.wrap(buildCommand)}
If the above command fails, then this is *NOT* a Shorebird issue and the
the underlying `flutter build` failure must be resolved for Shorebird to
build a release.
• If `flutter build` completes successfully, please ensure that you are
providing the desired flutter version to the release command via
the `--flutter-version` option. If you do not specify a `--flutter-version`
Shorebird will default to the latest stable version of Flutter.
We strongly encourage always specifying an explicit Flutter version:
${lightCyan.wrap('shorebird release <platform> --flutter-version=3.29.0')}
• If `flutter build` completes successfully and `shorebird release`
fails when using the same flutter version, please file an issue:
${link(uri: Uri.parse('https://github.com/shorebirdtech/shorebird/issues/new'))}
''';
/// Builds an aab using `flutter build appbundle`. Runs `flutter pub get` with
/// the system installation of Flutter to reset
/// `.dart_tool/package_config.json` after the build completes or fails.
@@ -100,10 +127,15 @@ class ArtifactBuilder {
);
if (exitCode != ExitCode.success.code) {
throw ArtifactBuildException('''
throw ArtifactBuildException(
'''
Failed to build AAB.
Command: $executable ${arguments.join(' ')}
Reason: Exited with code $exitCode.''');
Reason: Exited with code $exitCode.''',
fixRecommendation: runVanillaFlutterBuildRecommendation(
[executable, ...arguments].join(' '),
),
);
}
});
@@ -165,10 +197,15 @@ Reason: Exited with code $exitCode.''');
);
if (exitCode != ExitCode.success.code) {
throw ArtifactBuildException('''
throw ArtifactBuildException(
'''
Failed to build APK.
Command: $executable ${arguments.join(' ')}
Reason: Exited with code $exitCode.''');
Reason: Exited with code $exitCode.''',
fixRecommendation: runVanillaFlutterBuildRecommendation(
[executable, ...arguments].join(' '),
),
);
}
});
final projectRoot = shorebirdEnv.getShorebirdProjectRoot()!;
@@ -222,10 +259,15 @@ Reason: Exited with code $exitCode.''');
);
if (exitCode != ExitCode.success.code) {
throw ArtifactBuildException('''
throw ArtifactBuildException(
'''
Failed to build AAR.
Command: $executable ${arguments.join(' ')}
Reason: Exited with code $exitCode.''');
Reason: Exited with code $exitCode.''',
fixRecommendation: runVanillaFlutterBuildRecommendation(
[executable, ...arguments].join(' '),
),
);
}
});
}
@@ -257,10 +299,15 @@ Reason: Exited with code $exitCode.''');
);
if (exitCode != ExitCode.success.code) {
throw ArtifactBuildException('''
throw ArtifactBuildException(
'''
Failed to build linux app.
Command: $executable ${arguments.join(' ')}
Reason: Exited with code $exitCode.''');
Reason: Exited with code $exitCode.''',
fixRecommendation: runVanillaFlutterBuildRecommendation(
[executable, ...arguments].join(' '),
),
);
}
});
}
@@ -305,10 +352,15 @@ Reason: Exited with code $exitCode.''');
);
if (exitCode != ExitCode.success.code) {
throw ArtifactBuildException('''
throw ArtifactBuildException(
'''
Failed to build macOS app.
Command: $executable ${arguments.join(' ')}
Reason: Exited with code $exitCode.''');
Reason: Exited with code $exitCode.''',
fixRecommendation: runVanillaFlutterBuildRecommendation(
[executable, ...arguments].join(' '),
),
);
}
appDillPath = _findAppDill(projectRoot: projectRoot, after: buildStart);
@@ -366,10 +418,15 @@ Reason: Exited with code $exitCode.''');
);
if (exitCode != ExitCode.success.code) {
throw ArtifactBuildException('''
throw ArtifactBuildException(
'''
Failed to build IPA.
Command: $executable ${arguments.join(' ')}
Reason: Exited with code $exitCode.''');
Reason: Exited with code $exitCode.''',
fixRecommendation: runVanillaFlutterBuildRecommendation(
[executable, ...arguments].join(' '),
),
);
}
appDillPath = _findAppDill(projectRoot: projectRoot, after: buildStart);
@@ -420,11 +477,15 @@ Reason: Exited with code $exitCode.''');
);
if (exitCode != ExitCode.success.code) {
throw ArtifactBuildException('''
throw ArtifactBuildException(
'''
Failed to build iOS framework.
Command: $executable ${arguments.join(' ')}
Reason: Exited with code $exitCode.
''');
Reason: Exited with code $exitCode.''',
fixRecommendation: runVanillaFlutterBuildRecommendation(
[executable, ...arguments].join(' '),
),
);
}
appDillPath = _findAppDill(projectRoot: projectRoot, after: buildStart);
@@ -535,10 +596,15 @@ Either run `flutter pub get` manually, or follow the steps in ${cannotRunInVSCod
);
if (exitCode != ExitCode.success.code) {
throw ArtifactBuildException('''
throw ArtifactBuildException(
'''
Failed to build windows app.
Command: $executable ${arguments.join(' ')}
Reason: Exited with code $exitCode.''');
Reason: Exited with code $exitCode.''',
fixRecommendation: runVanillaFlutterBuildRecommendation(
[executable, ...arguments].join(' '),
),
);
}
});
@@ -342,7 +342,17 @@ Either run `flutter pub get` manually, or follow the steps in ${cannotRunInVSCod
verifyCorrectFlutterPubGet(
() => expectLater(
() => runWithOverrides(() => builder.buildAppBundle()),
throwsA(isA<ArtifactBuildException>()),
throwsA(
isA<ArtifactBuildException>().having(
(e) => e.fixRecommendation,
'recommendation',
startsWith(
ArtifactBuilder.runVanillaFlutterBuildRecommendation(
'',
).substring(0, 80),
),
),
),
),
);
});
@@ -534,7 +544,17 @@ Either run `flutter pub get` manually, or follow the steps in ${cannotRunInVSCod
verifyCorrectFlutterPubGet(
() => expectLater(
() => runWithOverrides(() => builder.buildApk()),
throwsA(isA<ArtifactBuildException>()),
throwsA(
isA<ArtifactBuildException>().having(
(e) => e.fixRecommendation,
'recommendation',
startsWith(
ArtifactBuilder.runVanillaFlutterBuildRecommendation(
'',
).substring(0, 80),
),
),
),
),
);
});
@@ -651,7 +671,17 @@ Either run `flutter pub get` manually, or follow the steps in ${cannotRunInVSCod
() async => runWithOverrides(
() => builder.buildAar(buildNumber: buildNumber),
),
throwsA(isA<ArtifactBuildException>()),
throwsA(
isA<ArtifactBuildException>().having(
(e) => e.fixRecommendation,
'recommendation',
startsWith(
ArtifactBuilder.runVanillaFlutterBuildRecommendation(
'',
).substring(0, 80),
),
),
),
),
);
});
@@ -700,14 +730,24 @@ Either run `flutter pub get` manually, or follow the steps in ${cannotRunInVSCod
expect(
() => runWithOverrides(() => builder.buildLinuxApp()),
throwsA(
isA<ArtifactBuildException>().having(
(e) => e.message,
'message',
equals('''
isA<ArtifactBuildException>()
.having(
(e) => e.message,
'message',
equals('''
Failed to build linux app.
Command: flutter build linux --release
Reason: Exited with code 70.'''),
),
)
.having(
(e) => e.fixRecommendation,
'recommendation',
startsWith(
ArtifactBuilder.runVanillaFlutterBuildRecommendation(
'',
).substring(0, 80),
),
),
),
);
});
@@ -890,7 +930,17 @@ Reason: Exited with code 70.'''),
test('throws ArtifactBuildException', () {
expect(
() => runWithOverrides(() => builder.buildMacos(codesign: false)),
throwsA(isA<ArtifactBuildException>()),
throwsA(
isA<ArtifactBuildException>().having(
(e) => e.fixRecommendation,
'recommendation',
startsWith(
ArtifactBuilder.runVanillaFlutterBuildRecommendation(
'',
).substring(0, 80),
),
),
),
);
});
});
@@ -1067,7 +1117,17 @@ Reason: Exited with code 70.'''),
test('throws ArtifactBuildException', () {
expect(
() => runWithOverrides(() => builder.buildIpa(codesign: false)),
throwsA(isA<ArtifactBuildException>()),
throwsA(
isA<ArtifactBuildException>().having(
(e) => e.fixRecommendation,
'recommendation',
startsWith(
ArtifactBuilder.runVanillaFlutterBuildRecommendation(
'',
).substring(0, 80),
),
),
),
);
});
});
@@ -1126,7 +1186,17 @@ Reason: Exited with code 70.'''),
() async => expectLater(
() async =>
runWithOverrides(() => builder.buildIpa(codesign: false)),
throwsA(isA<ArtifactBuildException>()),
throwsA(
isA<ArtifactBuildException>().having(
(e) => e.fixRecommendation,
'recommendation',
startsWith(
ArtifactBuilder.runVanillaFlutterBuildRecommendation(
'',
).substring(0, 80),
),
),
),
),
);
});
@@ -1271,7 +1341,17 @@ Reason: Exited with code 70.'''),
verifyCorrectFlutterPubGet(
() => expectLater(
() => runWithOverrides(builder.buildIosFramework),
throwsA(isA<ArtifactBuildException>()),
throwsA(
isA<ArtifactBuildException>().having(
(e) => e.fixRecommendation,
'recommendation',
startsWith(
ArtifactBuilder.runVanillaFlutterBuildRecommendation(
'',
).substring(0, 80),
),
),
),
),
);
});
@@ -1391,14 +1471,24 @@ Reason: Exited with code 70.'''),
expect(
() => runWithOverrides(() => builder.buildWindowsApp()),
throwsA(
isA<ArtifactBuildException>().having(
(e) => e.message,
'message',
equals('''
isA<ArtifactBuildException>()
.having(
(e) => e.message,
'message',
equals('''
Failed to build windows app.
Command: flutter build windows --release
Reason: Exited with code 70.'''),
),
)
.having(
(e) => e.fixRecommendation,
'recommendation',
startsWith(
ArtifactBuilder.runVanillaFlutterBuildRecommendation(
'',
).substring(0, 80),
),
),
),
);
});