From e90e4888b856f8f6249e1db79145806ea01da1da Mon Sep 17 00:00:00 2001 From: Andrew Kolos Date: Fri, 5 Jan 2024 13:47:58 -0800 Subject: [PATCH] in `flutter run`, throw tool exit when `--flavor` is provided but is not supported on the target device (#139045) Fixes https://github.com/flutter/flutter/issues/134197 --- .../lib/src/android/android_device.dart | 3 ++ .../flutter_tools/lib/src/commands/run.dart | 7 ++++ .../flutter_tools/lib/src/commands/test.dart | 4 ++ packages/flutter_tools/lib/src/device.dart | 3 ++ .../flutter_tools/lib/src/ios/devices.dart | 3 ++ .../flutter_tools/lib/src/ios/simulators.dart | 3 ++ .../lib/src/macos/macos_device.dart | 3 ++ .../lib/src/macos/macos_ipad_device.dart | 3 ++ .../lib/src/tester/flutter_tester.dart | 3 ++ .../commands.shard/hermetic/run_test.dart | 37 ++++++++++++++++++- .../commands.shard/hermetic/test_test.dart | 9 ++++- .../runner/flutter_command_test.dart | 14 ++++++- .../flutter_tools/test/src/fake_devices.dart | 6 +++ 13 files changed, 93 insertions(+), 5 deletions(-) diff --git a/packages/flutter_tools/lib/src/android/android_device.dart b/packages/flutter_tools/lib/src/android/android_device.dart index 3c2837c9a5..90f029371e 100644 --- a/packages/flutter_tools/lib/src/android/android_device.dart +++ b/packages/flutter_tools/lib/src/android/android_device.dart @@ -372,6 +372,9 @@ class AndroidDevice extends Device { @override String get name => modelID; + @override + bool get supportsFlavors => true; + @override Future isAppInstalled( ApplicationPackage app, { diff --git a/packages/flutter_tools/lib/src/commands/run.dart b/packages/flutter_tools/lib/src/commands/run.dart index 7236d3f5b5..45cca98877 100644 --- a/packages/flutter_tools/lib/src/commands/run.dart +++ b/packages/flutter_tools/lib/src/commands/run.dart @@ -610,6 +610,13 @@ class RunCommand extends RunCommandBase { webMode = featureFlags.isWebEnabled && devices!.length == 1 && await devices!.single.targetPlatform == TargetPlatform.web_javascript; + + final String? flavor = stringArg('flavor'); + final bool flavorsSupportedOnEveryDevice = devices! + .every((Device device) => device.supportsFlavors); + if (flavor != null && !flavorsSupportedOnEveryDevice) { + throwToolExit('--flavor is only supported for Android, macOS, and iOS devices.'); + } } @visibleForTesting diff --git a/packages/flutter_tools/lib/src/commands/test.dart b/packages/flutter_tools/lib/src/commands/test.dart index ca6bbffe51..1c5f4abec5 100644 --- a/packages/flutter_tools/lib/src/commands/test.dart +++ b/packages/flutter_tools/lib/src/commands/test.dart @@ -469,6 +469,10 @@ class TestCommand extends FlutterCommand with DeviceBasedDevelopmentArtifacts { ' sdk: flutter\n', ); } + + if (stringArg('flavor') != null && !integrationTestDevice.supportsFlavors) { + throwToolExit('--flavor is only supported for Android, macOS, and iOS devices.'); + } } final Stopwatch? testRunnerTimeRecorderStopwatch = testTimeRecorder?.start(TestTimePhases.TestRunner); diff --git a/packages/flutter_tools/lib/src/device.dart b/packages/flutter_tools/lib/src/device.dart index a4ccc3e13b..2639448d0d 100644 --- a/packages/flutter_tools/lib/src/device.dart +++ b/packages/flutter_tools/lib/src/device.dart @@ -758,6 +758,9 @@ abstract class Device { /// Whether the device supports the '--fast-start' development mode. bool get supportsFastStart => false; + /// Whether the Flavors feature ('--flavor') is supported for this device. + bool get supportsFlavors => false; + /// Stop an app package on the current device. /// /// Specify [userIdentifier] to stop app installed to a profile (Android only). diff --git a/packages/flutter_tools/lib/src/ios/devices.dart b/packages/flutter_tools/lib/src/ios/devices.dart index 394f5a39a9..298ac50cfc 100644 --- a/packages/flutter_tools/lib/src/ios/devices.dart +++ b/packages/flutter_tools/lib/src/ios/devices.dart @@ -358,6 +358,9 @@ class IOSDevice extends Device { @override bool get supportsStartPaused => false; + @override + bool get supportsFlavors => true; + @override Future isAppInstalled( ApplicationPackage app, { diff --git a/packages/flutter_tools/lib/src/ios/simulators.dart b/packages/flutter_tools/lib/src/ios/simulators.dart index c332964d49..5ebd629f03 100644 --- a/packages/flutter_tools/lib/src/ios/simulators.dart +++ b/packages/flutter_tools/lib/src/ios/simulators.dart @@ -392,6 +392,9 @@ class IOSSimulator extends Device { @override bool get supportsHotRestart => true; + @override + bool get supportsFlavors => true; + @override Future get supportsHardwareRendering async => false; diff --git a/packages/flutter_tools/lib/src/macos/macos_device.dart b/packages/flutter_tools/lib/src/macos/macos_device.dart index 43ada0af08..8ca2f6bd55 100644 --- a/packages/flutter_tools/lib/src/macos/macos_device.dart +++ b/packages/flutter_tools/lib/src/macos/macos_device.dart @@ -47,6 +47,9 @@ class MacOSDevice extends DesktopDevice { @override String get name => 'macOS'; + @override + bool get supportsFlavors => true; + @override Future get targetPlatform async => TargetPlatform.darwin; diff --git a/packages/flutter_tools/lib/src/macos/macos_ipad_device.dart b/packages/flutter_tools/lib/src/macos/macos_ipad_device.dart index a8a61de44d..4e5d3f7a8d 100644 --- a/packages/flutter_tools/lib/src/macos/macos_ipad_device.dart +++ b/packages/flutter_tools/lib/src/macos/macos_ipad_device.dart @@ -48,6 +48,9 @@ class MacOSDesignedForIPadDevice extends DesktopDevice { @override bool isSupported() => _operatingSystemUtils.hostPlatform == HostPlatform.darwin_arm64; + @override + bool get supportsFlavors => true; + @override bool isSupportedForProject(FlutterProject flutterProject) { return flutterProject.ios.existsSync() && _operatingSystemUtils.hostPlatform == HostPlatform.darwin_arm64; diff --git a/packages/flutter_tools/lib/src/tester/flutter_tester.dart b/packages/flutter_tools/lib/src/tester/flutter_tester.dart index 241b132a75..fee54df958 100644 --- a/packages/flutter_tools/lib/src/tester/flutter_tester.dart +++ b/packages/flutter_tools/lib/src/tester/flutter_tester.dart @@ -90,6 +90,9 @@ class FlutterTesterDevice extends Device { @override bool supportsRuntimeMode(BuildMode buildMode) => buildMode == BuildMode.debug; + @override + bool get supportsFlavors => true; + @override Future get targetPlatform async => TargetPlatform.tester; diff --git a/packages/flutter_tools/test/commands.shard/hermetic/run_test.dart b/packages/flutter_tools/test/commands.shard/hermetic/run_test.dart index 44023e517a..45473a325c 100644 --- a/packages/flutter_tools/test/commands.shard/hermetic/run_test.dart +++ b/packages/flutter_tools/test/commands.shard/hermetic/run_test.dart @@ -431,6 +431,35 @@ void main() { Cache: () => Cache.test(processManager: FakeProcessManager.any()), }); + testUsingContext('fails when --flavor is used with an unsupported target platform', () async { + const List runCommand = [ + 'run', + '--no-pub', + '--no-hot', + '--flavor=vanilla', + '-d', + 'all', + ]; + + // Useful for test readability. + // ignore: avoid_redundant_argument_values + final FakeDevice deviceWithoutFlavorSupport = FakeDevice(supportsFlavors: false); + final FakeDevice deviceWithFlavorSupport = FakeDevice(supportsFlavors: true); + testDeviceManager.devices = [deviceWithoutFlavorSupport, deviceWithFlavorSupport]; + + await expectLater( + () => createTestCommandRunner(RunCommand()).run(runCommand), + throwsToolExit( + message: '--flavor is only supported for Android, macOS, and iOS devices.', + ), + ); + }, overrides: { + DeviceManager: () => testDeviceManager, + FileSystem: () => fs, + ProcessManager: () => FakeProcessManager.any(), + Cache: () => Cache.test(processManager: FakeProcessManager.any()), + }); + testUsingContext('forwards --uninstall-only to DebuggingOptions', () async { final RunCommand command = RunCommand(); final FakeDevice mockDevice = FakeDevice( @@ -1306,11 +1335,13 @@ class FakeDevice extends Fake implements Device { String sdkNameAndVersion = '', PlatformType platformType = PlatformType.ios, bool isSupported = true, + bool supportsFlavors = false, }): _isLocalEmulator = isLocalEmulator, _targetPlatform = targetPlatform, _sdkNameAndVersion = sdkNameAndVersion, _platformType = platformType, - _isSupported = isSupported; + _isSupported = isSupported, + _supportsFlavors = supportsFlavors; static const int kSuccess = 1; static const int kFailure = -1; @@ -1319,6 +1350,7 @@ class FakeDevice extends Fake implements Device { final String _sdkNameAndVersion; final PlatformType _platformType; final bool _isSupported; + final bool _supportsFlavors; @override Category get category => Category.mobile; @@ -1346,6 +1378,9 @@ class FakeDevice extends Fake implements Device { @override bool get supportsFastStart => false; + @override + bool get supportsFlavors => _supportsFlavors; + @override bool get ephemeral => true; diff --git a/packages/flutter_tools/test/commands.shard/hermetic/test_test.dart b/packages/flutter_tools/test/commands.shard/hermetic/test_test.dart index 8039b19e92..378c9815ab 100644 --- a/packages/flutter_tools/test/commands.shard/hermetic/test_test.dart +++ b/packages/flutter_tools/test/commands.shard/hermetic/test_test.dart @@ -864,8 +864,13 @@ dev_dependencies: FileSystem: () => fs, ProcessManager: () => FakeProcessManager.any(), DeviceManager: () => _FakeDeviceManager([ - FakeDevice('ephemeral', 'ephemeral', type: PlatformType.android), - ]), + FakeDevice( + 'ephemeral', + 'ephemeral', + type: PlatformType.android, + supportsFlavors: true, + ), + ]), }); testUsingContext('Builds the asset manifest by default', () async { diff --git a/packages/flutter_tools/test/general.shard/runner/flutter_command_test.dart b/packages/flutter_tools/test/general.shard/runner/flutter_command_test.dart index 596c00a205..54550044b7 100644 --- a/packages/flutter_tools/test/general.shard/runner/flutter_command_test.dart +++ b/packages/flutter_tools/test/general.shard/runner/flutter_command_test.dart @@ -1164,7 +1164,12 @@ void main() { fileSystem.file('pubspec.yaml').createSync(); fileSystem.file('.packages').createSync(); - final FakeDevice device = FakeDevice('name', 'id'); + final FakeDevice device = FakeDevice( + 'name', + 'id', + type: PlatformType.android, + supportsFlavors: true, + ); testDeviceManager.devices = [device]; final _TestRunCommandThatOnlyValidates command = _TestRunCommandThatOnlyValidates(); final CommandRunner runner = createTestCommandRunner(command); @@ -1190,7 +1195,12 @@ void main() { fileSystem.file('.packages').createSync(); fileSystem.file('config.json')..createSync()..writeAsStringSync('{"FLUTTER_APP_FLAVOR": "strawberry"}'); - final FakeDevice device = FakeDevice('name', 'id'); + final FakeDevice device = FakeDevice( + 'name', + 'id', + type: PlatformType.android, + supportsFlavors: true, + ); testDeviceManager.devices = [device]; final _TestRunCommandThatOnlyValidates command = _TestRunCommandThatOnlyValidates(); final CommandRunner runner = createTestCommandRunner(command); diff --git a/packages/flutter_tools/test/src/fake_devices.dart b/packages/flutter_tools/test/src/fake_devices.dart index b29169c90a..cb72de2a34 100644 --- a/packages/flutter_tools/test/src/fake_devices.dart +++ b/packages/flutter_tools/test/src/fake_devices.dart @@ -119,9 +119,11 @@ class FakeDevice extends Device { PlatformType type = PlatformType.web, LaunchResult? launchResult, this.deviceLogReader, + bool supportsFlavors = false, }) : _isSupported = isSupported, _isSupportedForProject = isSupportedForProject, _launchResult = launchResult ?? LaunchResult.succeeded(), + _supportsFlavors = supportsFlavors, super( id, platformType: type, @@ -131,6 +133,7 @@ class FakeDevice extends Device { final bool _isSupported; final bool _isSupportedForProject; + final bool _supportsFlavors; final LaunchResult _launchResult; DeviceLogReader? deviceLogReader; @@ -174,6 +177,9 @@ class FakeDevice extends Device { @override bool isSupported() => _isSupported; + @override + bool get supportsFlavors => _supportsFlavors; + @override bool isConnected;