Unverified Commit aa498cd5 authored by Andrew Kolos's avatar Andrew Kolos Committed by GitHub

Add API to read flavor from framework at run time (#134179)

Resolves #128046.

Adds a services API that allows flutter app developers to write app code that determines `--flavor` the app was built with.

This is implemented by having the tool adding the value of `--flavor` to its list of dart environment declarations, which will be available to the app at run time. Specifically,`FLUTTER_APP_FLAVOR` is set. I chose this implementation for its simplicity. There is some precedent for this, but only for web ([example](https://github.com/flutter/flutter/blob/cd2f3f5e78409027d3c9014172708e4dec7f2185/packages/flutter_tools/lib/src/runner/flutter_command.dart#L1231)).
parent c35515a0
...@@ -3,6 +3,7 @@ ...@@ -3,6 +3,7 @@
// found in the LICENSE file. // found in the LICENSE file.
import 'package:flavors/main.dart' as app; import 'package:flavors/main.dart' as app;
import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart'; import 'package:integration_test/integration_test.dart';
...@@ -16,6 +17,7 @@ void main() { ...@@ -16,6 +17,7 @@ void main() {
await tester.pumpAndSettle(); await tester.pumpAndSettle();
expect(find.text('paid'), findsOneWidget); expect(find.text('paid'), findsOneWidget);
expect(appFlavor, 'paid');
}); });
}); });
} }
...@@ -19,6 +19,7 @@ export 'src/services/browser_context_menu.dart'; ...@@ -19,6 +19,7 @@ export 'src/services/browser_context_menu.dart';
export 'src/services/clipboard.dart'; export 'src/services/clipboard.dart';
export 'src/services/debug.dart'; export 'src/services/debug.dart';
export 'src/services/deferred_component.dart'; export 'src/services/deferred_component.dart';
export 'src/services/flavor.dart';
export 'src/services/font_loader.dart'; export 'src/services/font_loader.dart';
export 'src/services/haptic_feedback.dart'; export 'src/services/haptic_feedback.dart';
export 'src/services/hardware_keyboard.dart'; export 'src/services/hardware_keyboard.dart';
......
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
/// The flavor this app was built with.
///
/// This is equivalent to the value argued to the `--flavor` option at build time.
/// This will be `null` if the `--flavor` option was not provided.
const String? appFlavor = String.fromEnvironment('FLUTTER_APP_FLAVOR') != '' ?
String.fromEnvironment('FLUTTER_APP_FLAVOR') : null;
...@@ -1247,10 +1247,20 @@ abstract class FlutterCommand extends Command<void> { ...@@ -1247,10 +1247,20 @@ abstract class FlutterCommand extends Command<void> {
} }
} }
final String? flavor = argParser.options.containsKey('flavor') ? stringArg('flavor') : null;
if (flavor != null) {
if (globals.platform.environment['FLUTTER_APP_FLAVOR'] != null) {
throwToolExit('FLUTTER_APP_FLAVOR is used by the framework and cannot be set in the environment.');
}
if (dartDefines.any((String define) => define.startsWith('FLUTTER_APP_FLAVOR'))) {
throwToolExit('FLUTTER_APP_FLAVOR is used by the framework and cannot be '
'set using --${FlutterOptions.kDartDefinesOption} or --${FlutterOptions.kDartDefineFromFileOption}');
}
dartDefines.add('FLUTTER_APP_FLAVOR=$flavor');
}
return BuildInfo(buildMode, return BuildInfo(buildMode,
argParser.options.containsKey('flavor') flavor,
? stringArg('flavor')
: null,
trackWidgetCreation: trackWidgetCreation, trackWidgetCreation: trackWidgetCreation,
frontendServerStarterPath: argParser.options frontendServerStarterPath: argParser.options
.containsKey(FlutterOptions.kFrontendServerStarterPath) .containsKey(FlutterOptions.kFrontendServerStarterPath)
......
...@@ -11,11 +11,14 @@ import 'package:flutter_tools/src/base/common.dart'; ...@@ -11,11 +11,14 @@ import 'package:flutter_tools/src/base/common.dart';
import 'package:flutter_tools/src/base/error_handling_io.dart'; import 'package:flutter_tools/src/base/error_handling_io.dart';
import 'package:flutter_tools/src/base/file_system.dart'; import 'package:flutter_tools/src/base/file_system.dart';
import 'package:flutter_tools/src/base/io.dart'; import 'package:flutter_tools/src/base/io.dart';
import 'package:flutter_tools/src/base/logger.dart';
import 'package:flutter_tools/src/base/platform.dart';
import 'package:flutter_tools/src/base/signals.dart'; import 'package:flutter_tools/src/base/signals.dart';
import 'package:flutter_tools/src/base/time.dart'; import 'package:flutter_tools/src/base/time.dart';
import 'package:flutter_tools/src/base/user_messages.dart'; import 'package:flutter_tools/src/base/user_messages.dart';
import 'package:flutter_tools/src/build_info.dart'; import 'package:flutter_tools/src/build_info.dart';
import 'package:flutter_tools/src/cache.dart'; import 'package:flutter_tools/src/cache.dart';
import 'package:flutter_tools/src/commands/run.dart';
import 'package:flutter_tools/src/dart/pub.dart'; import 'package:flutter_tools/src/dart/pub.dart';
import 'package:flutter_tools/src/device.dart'; import 'package:flutter_tools/src/device.dart';
import 'package:flutter_tools/src/globals.dart' as globals; import 'package:flutter_tools/src/globals.dart' as globals;
...@@ -700,6 +703,67 @@ void main() { ...@@ -700,6 +703,67 @@ void main() {
expect(testLogger.statusText, contains(UserMessages().flutterSpecifyDevice)); expect(testLogger.statusText, contains(UserMessages().flutterSpecifyDevice));
}); });
}); });
group('--flavor', () {
late _TestDeviceManager testDeviceManager;
late Logger logger;
late FileSystem fileSystem;
setUp(() {
logger = BufferLogger.test();
testDeviceManager = _TestDeviceManager(logger: logger);
fileSystem = MemoryFileSystem.test();
});
testUsingContext("tool exits when FLUTTER_APP_FLAVOR is already set in user's environment", () async {
fileSystem.file('lib/main.dart').createSync(recursive: true);
fileSystem.file('pubspec.yaml').createSync();
fileSystem.file('.packages').createSync();
final FakeDevice device = FakeDevice('name', 'id');
testDeviceManager.devices = <Device>[device];
final _TestRunCommandThatOnlyValidates command = _TestRunCommandThatOnlyValidates();
final CommandRunner<void> runner = createTestCommandRunner(command);
expect(runner.run(<String>['run', '--no-pub', '--no-hot', '--flavor=strawberry']),
throwsToolExit(message: 'FLUTTER_APP_FLAVOR is used by the framework and cannot be set in the environment.'));
}, overrides: <Type, Generator>{
DeviceManager: () => testDeviceManager,
Platform: () => FakePlatform(
environment: <String, String>{
'FLUTTER_APP_FLAVOR': 'I was already set'
}
),
Cache: () => Cache.test(processManager: FakeProcessManager.any()),
FileSystem: () => fileSystem,
ProcessManager: () => FakeProcessManager.any(),
});
testUsingContext('tool exits when FLUTTER_APP_FLAVOR is set in --dart-define or --dart-define-from-file', () async {
fileSystem.file('lib/main.dart').createSync(recursive: true);
fileSystem.file('pubspec.yaml').createSync();
fileSystem.file('.packages').createSync();
fileSystem.file('config.json')..createSync()..writeAsStringSync('{"FLUTTER_APP_FLAVOR": "strawberry"}');
final FakeDevice device = FakeDevice('name', 'id');
testDeviceManager.devices = <Device>[device];
final _TestRunCommandThatOnlyValidates command = _TestRunCommandThatOnlyValidates();
final CommandRunner<void> runner = createTestCommandRunner(command);
expect(runner.run(<String>['run', '--dart-define=FLUTTER_APP_FLAVOR=strawberry', '--no-pub', '--no-hot', '--flavor=strawberry']),
throwsToolExit(message: 'FLUTTER_APP_FLAVOR is used by the framework and cannot be set using --dart-define or --dart-define-from-file'));
expect(runner.run(<String>['run', '--dart-define-from-file=config.json', '--no-pub', '--no-hot', '--flavor=strawberry']),
throwsToolExit(message: 'FLUTTER_APP_FLAVOR is used by the framework and cannot be set using --dart-define or --dart-define-from-file'));
}, overrides: <Type, Generator>{
DeviceManager: () => testDeviceManager,
Platform: () => FakePlatform(),
Cache: () => Cache.test(processManager: FakeProcessManager.any()),
FileSystem: () => fileSystem,
ProcessManager: () => FakeProcessManager.any(),
});
});
}); });
} }
...@@ -853,3 +917,22 @@ class FakePub extends Fake implements Pub { ...@@ -853,3 +917,22 @@ class FakePub extends Fake implements Pub {
PubOutputMode outputMode = PubOutputMode.all, PubOutputMode outputMode = PubOutputMode.all,
}) async { } }) async { }
} }
class _TestDeviceManager extends DeviceManager {
_TestDeviceManager({required super.logger});
List<Device> devices = <Device>[];
@override
List<DeviceDiscovery> get deviceDiscoverers {
final FakePollingDeviceDiscovery discoverer = FakePollingDeviceDiscovery();
devices.forEach(discoverer.addDevice);
return <DeviceDiscovery>[discoverer];
}
}
class _TestRunCommandThatOnlyValidates extends RunCommand {
@override
Future<FlutterCommandResult> runCommand() async {
return FlutterCommandResult.success();
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment