From ba13d4a60a587197fafc32598a2fe985c2a55c9e Mon Sep 17 00:00:00 2001 From: Felix Angelov Date: Mon, 1 May 2023 11:14:53 -0400 Subject: [PATCH] feat(shorebird_cli): `shorebird build` support `--flavor` and `--target` (#409) --- .../src/commands/build/build_apk_command.dart | 23 +++++++++-- .../build/build_app_bundle_command.dart | 23 +++++++++-- .../lib/src/shorebird_build_mixin.dart | 8 +++- .../build/build_apk_command_test.dart | 38 +++++++++++++++++++ .../build/build_app_bundle_command_test.dart | 38 +++++++++++++++++++ 5 files changed, 122 insertions(+), 8 deletions(-) diff --git a/packages/shorebird_cli/lib/src/commands/build/build_apk_command.dart b/packages/shorebird_cli/lib/src/commands/build/build_apk_command.dart index af9213de2..1af87a277 100644 --- a/packages/shorebird_cli/lib/src/commands/build/build_apk_command.dart +++ b/packages/shorebird_cli/lib/src/commands/build/build_apk_command.dart @@ -23,7 +23,18 @@ class BuildApkCommand extends ShorebirdCommand required super.logger, super.auth, super.validators, - }); + }) { + argParser + ..addOption( + 'target', + abbr: 't', + help: 'The main entrypoint file of the application.', + ) + ..addOption( + 'flavor', + help: 'The product flavor to use when building the app.', + ); + } @override String get description => 'Build an Android APK file from your app.'; @@ -40,9 +51,11 @@ class BuildApkCommand extends ShorebirdCommand await logValidationIssues(); + final flavor = results['flavor'] as String?; + final target = results['target'] as String?; final buildProgress = logger.progress('Building apk'); try { - await buildApk(); + await buildApk(flavor: flavor, target: target); } on ProcessException catch (error) { buildProgress.fail('Failed to build: ${error.message}'); return ExitCode.software.code; @@ -50,9 +63,13 @@ class BuildApkCommand extends ShorebirdCommand buildProgress.complete(); + final apkPath = flavor != null + ? './build/app/outputs/apk/$flavor/release/app-$flavor-release.apk' + : './build/app/outputs/apk/release/app-release.apk'; + logger.info(''' 📦 Generated an apk at: -${lightCyan.wrap("./build/app/outputs/apk/release/app-release.apk")}'''); +${lightCyan.wrap(apkPath)}'''); return ExitCode.success.code; } diff --git a/packages/shorebird_cli/lib/src/commands/build/build_app_bundle_command.dart b/packages/shorebird_cli/lib/src/commands/build/build_app_bundle_command.dart index 88a2b3105..145b28aab 100644 --- a/packages/shorebird_cli/lib/src/commands/build/build_app_bundle_command.dart +++ b/packages/shorebird_cli/lib/src/commands/build/build_app_bundle_command.dart @@ -23,7 +23,18 @@ class BuildAppBundleCommand extends ShorebirdCommand required super.logger, super.auth, super.validators, - }); + }) { + argParser + ..addOption( + 'target', + abbr: 't', + help: 'The main entrypoint file of the application.', + ) + ..addOption( + 'flavor', + help: 'The product flavor to use when building the app.', + ); + } @override String get description => 'Build an Android App Bundle file from your app.'; @@ -40,18 +51,24 @@ class BuildAppBundleCommand extends ShorebirdCommand await logValidationIssues(); + final flavor = results['flavor'] as String?; + final target = results['target'] as String?; final buildProgress = logger.progress('Building appbundle'); try { - await buildAppBundle(); + await buildAppBundle(flavor: flavor, target: target); } on ProcessException catch (error) { buildProgress.fail('Failed to build: ${error.message}'); return ExitCode.software.code; } + final bundlePath = flavor != null + ? './build/app/outputs/bundle/${flavor}Release/app-$flavor-release.aab' + : './build/app/outputs/bundle/release/app-release.aab'; + buildProgress.complete(); logger.info(''' 📦 Generated an app bundle at: -${lightCyan.wrap("./build/app/outputs/bundle/release/app-release.aab")}'''); +${lightCyan.wrap(bundlePath)}'''); return ExitCode.success.code; } diff --git a/packages/shorebird_cli/lib/src/shorebird_build_mixin.dart b/packages/shorebird_cli/lib/src/shorebird_build_mixin.dart index 14ff7047d..4ff86b9d0 100644 --- a/packages/shorebird_cli/lib/src/shorebird_build_mixin.dart +++ b/packages/shorebird_cli/lib/src/shorebird_build_mixin.dart @@ -66,12 +66,14 @@ mixin ShorebirdBuildMixin on ShorebirdCommand { return allAndroidArchitectures; } - Future buildAppBundle() async { + Future buildAppBundle({String? flavor, String? target}) async { const executable = 'flutter'; final arguments = [ 'build', 'appbundle', '--release', + if (flavor != null) '--flavor=$flavor', + if (target != null) '--target=$target', ...results.rest, ]; @@ -91,12 +93,14 @@ mixin ShorebirdBuildMixin on ShorebirdCommand { } } - Future buildApk() async { + Future buildApk({String? flavor, String? target}) async { const executable = 'flutter'; final arguments = [ 'build', 'apk', '--release', + if (flavor != null) '--flavor=$flavor', + if (target != null) '--target=$target', ...results.rest, ]; diff --git a/packages/shorebird_cli/test/src/commands/build/build_apk_command_test.dart b/packages/shorebird_cli/test/src/commands/build/build_apk_command_test.dart index 7645c9e34..7d5697bbd 100644 --- a/packages/shorebird_cli/test/src/commands/build/build_apk_command_test.dart +++ b/packages/shorebird_cli/test/src/commands/build/build_apk_command_test.dart @@ -132,6 +132,44 @@ ${lightCyan.wrap("./build/app/outputs/apk/release/app-release.apk")}''', ).called(1); }); + test( + 'exits with code 0 when building apk succeeds ' + 'with flavor and target', () async { + const flavor = 'development'; + const target = './lib/main_development.dart'; + when(() => argResults['flavor']).thenReturn(flavor); + when(() => argResults['target']).thenReturn(target); + when(() => processResult.exitCode).thenReturn(ExitCode.success.code); + final tempDir = Directory.systemTemp.createTempSync(); + final result = await IOOverrides.runZoned( + () async => command.run(), + getCurrentDirectory: () => tempDir, + ); + + expect(result, equals(ExitCode.success.code)); + + verify( + () => shorebirdProcess.run( + 'flutter', + [ + 'build', + 'apk', + '--release', + '--flavor=$flavor', + '--target=$target', + ], + runInShell: true, + ), + ).called(1); + verify( + () => logger.info( + ''' +📦 Generated an apk at: +${lightCyan.wrap("./build/app/outputs/apk/$flavor/release/app-$flavor-release.apk")}''', + ), + ).called(1); + }); + test('prints flutter validation warnings', () async { when(() => flutterValidator.validate(any())).thenAnswer( (_) async => [ diff --git a/packages/shorebird_cli/test/src/commands/build/build_app_bundle_command_test.dart b/packages/shorebird_cli/test/src/commands/build/build_app_bundle_command_test.dart index 285297fae..5d9f3704f 100644 --- a/packages/shorebird_cli/test/src/commands/build/build_app_bundle_command_test.dart +++ b/packages/shorebird_cli/test/src/commands/build/build_app_bundle_command_test.dart @@ -133,6 +133,44 @@ ${lightCyan.wrap("./build/app/outputs/bundle/release/app-release.aab")}''', ).called(1); }); + test( + 'exits with code 0 when building appbundle succeeds ' + 'with flavor and target', () async { + const flavor = 'development'; + const target = './lib/main_development.dart'; + when(() => argResults['flavor']).thenReturn(flavor); + when(() => argResults['target']).thenReturn(target); + when(() => processResult.exitCode).thenReturn(ExitCode.success.code); + final tempDir = Directory.systemTemp.createTempSync(); + final result = await IOOverrides.runZoned( + () async => command.run(), + getCurrentDirectory: () => tempDir, + ); + + expect(result, equals(ExitCode.success.code)); + verify( + () => shorebirdProcess.run( + 'flutter', + [ + 'build', + 'appbundle', + '--release', + '--flavor=$flavor', + '--target=$target', + ], + runInShell: true, + ), + ).called(1); + + verify( + () => logger.info( + ''' +📦 Generated an app bundle at: +${lightCyan.wrap("./build/app/outputs/bundle/${flavor}Release/app-$flavor-release.aab")}''', + ), + ).called(1); + }); + test('local-engine and architectures', () async { expect(command.architectures.length, greaterThan(1));