[tool] Proposal to support dart define config from a json file (#108098)
diff --git a/packages/flutter_tools/lib/src/build_info.dart b/packages/flutter_tools/lib/src/build_info.dart
index 6f7a2ab..7fcfe44 100644
--- a/packages/flutter_tools/lib/src/build_info.dart
+++ b/packages/flutter_tools/lib/src/build_info.dart
@@ -37,6 +37,7 @@
List<String>? dartExperiments,
required this.treeShakeIcons,
this.performanceMeasurementFile,
+ this.dartDefineConfigJsonMap,
this.packagesPath = '.dart_tool/package_config.json', // TODO(zanderso): make this required and remove the default.
this.nullSafetyMode = NullSafetyMode.sound,
this.codeSizeDirectory,
@@ -130,6 +131,17 @@
/// rerun tasks.
final String? performanceMeasurementFile;
+ /// Configure a constant pool file.
+ /// Additional constant values to be made available in the Dart program.
+ ///
+ /// These values can be used with the const `fromEnvironment` constructors of
+ /// [String] the key and field are json values
+ /// json value
+ ///
+ /// An additional field `dartDefineConfigJsonMap` is provided to represent the native JSON value of the configuration file
+ ///
+ final Map<String, Object>? dartDefineConfigJsonMap;
+
/// If provided, an output directory where one or more v8-style heap snapshots
/// will be written for code size profiling.
final String? codeSizeDirectory;
@@ -247,12 +259,17 @@
};
}
+
/// Convert to a structured string encoded structure appropriate for usage as
/// environment variables or to embed in other scripts.
///
/// Fields that are `null` are excluded from this configuration.
Map<String, String> toEnvironmentConfig() {
- return <String, String>{
+ final Map<String, String> map = <String, String>{};
+ dartDefineConfigJsonMap?.forEach((String key, Object value) {
+ map[key] = '$value';
+ });
+ final Map<String, String> environmentMap = <String, String>{
if (dartDefines.isNotEmpty)
'DART_DEFINES': encodeDartDefines(dartDefines),
if (dartObfuscation != null)
@@ -276,13 +293,23 @@
if (codeSizeDirectory != null)
'CODE_SIZE_DIRECTORY': codeSizeDirectory!,
};
+ map.forEach((String key, String value) {
+ if (environmentMap.containsKey(key)) {
+ globals.printWarning(
+ 'The key: [$key] already exists, you cannot use environment variables that have been used by the system!');
+ } else {
+ // System priority is greater than user priority
+ environmentMap[key] = value;
+ }
+ });
+ return environmentMap;
}
/// Convert this config to a series of project level arguments to be passed
/// on the command line to gradle.
List<String> toGradleConfig() {
// PACKAGE_CONFIG not currently supported.
- return <String>[
+ final List<String> result = <String>[
if (dartDefines.isNotEmpty)
'-Pdart-defines=${encodeDartDefines(dartDefines)}',
if (dartObfuscation != null)
@@ -306,6 +333,20 @@
for (String projectArg in androidProjectArgs)
'-P$projectArg',
];
+ if(dartDefineConfigJsonMap != null) {
+ final List<String> items = <String>[];
+ for (final String gradleConf in result) {
+ final String key = gradleConf.split('=')[0].substring(2);
+ if (dartDefineConfigJsonMap!.containsKey(key)) {
+ globals.printWarning(
+ 'The key: [$key] already exists, you cannot use gradle variables that have been used by the system!');
+ } else {
+ items.add('-P$key=${dartDefineConfigJsonMap?[key]}');
+ }
+ }
+ result.addAll(items);
+ }
+ return result;
}
}
diff --git a/packages/flutter_tools/lib/src/commands/assemble.dart b/packages/flutter_tools/lib/src/commands/assemble.dart
index 081c381..41c3054 100644
--- a/packages/flutter_tools/lib/src/commands/assemble.dart
+++ b/packages/flutter_tools/lib/src/commands/assemble.dart
@@ -263,9 +263,29 @@
if (argumentResults.wasParsed(FlutterOptions.kExtraGenSnapshotOptions)) {
results[kExtraGenSnapshotOptions] = (argumentResults[FlutterOptions.kExtraGenSnapshotOptions] as List<String>).join(',');
}
+
+ List<String> dartDefines = <String>[];
if (argumentResults.wasParsed(FlutterOptions.kDartDefinesOption)) {
- results[kDartDefines] = (argumentResults[FlutterOptions.kDartDefinesOption] as List<String>).join(',');
+ dartDefines = argumentResults[FlutterOptions.kDartDefinesOption] as List<String>;
}
+ if (argumentResults.wasParsed(FlutterOptions.kDartDefineFromFileOption)) {
+ final String? configJsonPath = stringArg(FlutterOptions.kDartDefineFromFileOption);
+ if (configJsonPath != null && globals.fs.isFileSync(configJsonPath)) {
+ final String configJsonRaw = globals.fs.file(configJsonPath).readAsStringSync();
+ try {
+ (json.decode(configJsonRaw) as Map<String, dynamic>).forEach((String key, dynamic value) {
+ dartDefines.add('$key=$value');
+ });
+ } on FormatException catch (err) {
+ throwToolExit('Json config define file "--${FlutterOptions.kDartDefineFromFileOption}=$configJsonPath" format err, '
+ 'please fix first! format err:\n$err');
+ }
+ }
+ }
+ if(dartDefines.isNotEmpty){
+ results[kDartDefines] = dartDefines.join(',');
+ }
+
results[kDeferredComponents] = 'false';
if (FlutterProject.current().manifest.deferredComponents != null && isDeferredComponentsTargets() && !isDebug()) {
results[kDeferredComponents] = 'true';
diff --git a/packages/flutter_tools/lib/src/runner/flutter_command.dart b/packages/flutter_tools/lib/src/runner/flutter_command.dart
index b6c628b..a150911 100644
--- a/packages/flutter_tools/lib/src/runner/flutter_command.dart
+++ b/packages/flutter_tools/lib/src/runner/flutter_command.dart
@@ -19,6 +19,7 @@
import '../build_system/build_system.dart';
import '../bundle.dart' as bundle;
import '../cache.dart';
+import '../convert.dart';
import '../dart/generate_synthetic_packages.dart';
import '../dart/language_version.dart';
import '../dart/package_map.dart';
@@ -104,6 +105,7 @@
static const String kSplitDebugInfoOption = 'split-debug-info';
static const String kDartObfuscationOption = 'obfuscate';
static const String kDartDefinesOption = 'dart-define';
+ static const String kDartDefineFromFileOption = 'dart-define-from-file';
static const String kBundleSkSLPathOption = 'bundle-sksl-path';
static const String kPerformanceMeasurementFile = 'performance-measurement-file';
static const String kNullSafety = 'sound-null-safety';
@@ -591,6 +593,17 @@
valueHelp: 'foo=bar',
splitCommas: false,
);
+ useDartDefineConfigJsonFileOption();
+ }
+
+ void useDartDefineConfigJsonFileOption() {
+ argParser.addOption(
+ FlutterOptions.kDartDefineFromFileOption,
+ help: 'The path of a json format file where flutter define a global constant pool. '
+ 'Json entry will be available as constants from the String.fromEnvironment, bool.fromEnvironment, '
+ 'int.fromEnvironment, and double.fromEnvironment constructors; the key and field are json values.',
+ valueHelp: 'use-define-config.json'
+ );
}
void usesWebRendererOption() {
@@ -1122,6 +1135,27 @@
dartDefines = updateDartDefines(dartDefines, stringArgDeprecated('web-renderer')!);
}
+ Map<String, Object>? defineConfigJsonMap;
+ if (argParser.options.containsKey(FlutterOptions.kDartDefineFromFileOption)) {
+ final String? configJsonPath = stringArg(FlutterOptions.kDartDefineFromFileOption);
+ if (configJsonPath != null && globals.fs.isFileSync(configJsonPath)) {
+ final String configJsonRaw = globals.fs.file(configJsonPath).readAsStringSync();
+ try {
+ defineConfigJsonMap = <String, Object>{};
+ // Fix json convert Object value :type '_InternalLinkedHashMap<String, dynamic>' is not a subtype of type 'Map<String, Object>' in type cast
+ (json.decode(configJsonRaw) as Map<String, dynamic>).forEach((String key, dynamic value) {
+ defineConfigJsonMap?[key]=value as Object;
+ });
+ defineConfigJsonMap.forEach((String key, Object value) {
+ dartDefines.add('$key=$value');
+ });
+ } on FormatException catch (err) {
+ throwToolExit('Json config define file "--${FlutterOptions.kDartDefineFromFileOption}=$configJsonPath" format err, '
+ 'please fix first! format err:\n$err');
+ }
+ }
+ }
+
return BuildInfo(buildMode,
argParser.options.containsKey('flavor')
? stringArgDeprecated('flavor')
@@ -1146,6 +1180,7 @@
bundleSkSLPath: bundleSkSLPath,
dartExperiments: experiments,
performanceMeasurementFile: performanceMeasurementFile,
+ dartDefineConfigJsonMap: defineConfigJsonMap,
packagesPath: packagesPath ?? globals.fs.path.absolute('.dart_tool', 'package_config.json'),
nullSafetyMode: nullSafetyMode,
codeSizeDirectory: codeSizeDirectory,
diff --git a/packages/flutter_tools/test/commands.shard/hermetic/assemble_test.dart b/packages/flutter_tools/test/commands.shard/hermetic/assemble_test.dart
index 072015a..0137d7f 100644
--- a/packages/flutter_tools/test/commands.shard/hermetic/assemble_test.dart
+++ b/packages/flutter_tools/test/commands.shard/hermetic/assemble_test.dart
@@ -290,4 +290,33 @@
],
});
});
+
+ testUsingContext('test --dart-define-from-file option with err json format', () async {
+ await globals.fs.file('config.json').writeAsString(
+ '''
+ {
+ "kInt": 1,
+ "kDouble": 1.1,
+ "name": "err json format,
+ "title": "this is title from config json file"
+ }
+ '''
+ );
+ final CommandRunner<void> commandRunner = createTestCommandRunner(AssembleCommand(
+ buildSystem: TestBuildSystem.all(BuildResult(success: true)),
+ ));
+
+ expect(commandRunner.run(<String>['assemble',
+ '-o Output',
+ 'debug_macos_bundle_flutter_assets',
+ '--dart-define=k=v',
+ '--dart-define-from-file=config.json']),
+ throwsToolExit(message: 'Json config define file "--dart-define-from-file=config.json" format err, please fix first! format err:'));
+ }, overrides: <Type, Generator>{
+ Cache: () => Cache.test(processManager: FakeProcessManager.any()),
+ FileSystem: () => MemoryFileSystem.test(),
+ ProcessManager: () => FakeProcessManager.any(),
+ });
+
+
}
diff --git a/packages/flutter_tools/test/commands.shard/permeable/build_bundle_test.dart b/packages/flutter_tools/test/commands.shard/permeable/build_bundle_test.dart
index 772afcc..ce01bf1 100644
--- a/packages/flutter_tools/test/commands.shard/permeable/build_bundle_test.dart
+++ b/packages/flutter_tools/test/commands.shard/permeable/build_bundle_test.dart
@@ -4,6 +4,7 @@
import 'package:args/command_runner.dart';
import 'package:file/memory.dart';
+import 'package:flutter_tools/src/base/common.dart';
import 'package:flutter_tools/src/base/file_system.dart';
import 'package:flutter_tools/src/build_info.dart';
import 'package:flutter_tools/src/build_system/build_system.dart';
@@ -463,6 +464,64 @@
FileSystem: fsFactory,
ProcessManager: () => FakeProcessManager.any(),
});
+
+ testUsingContext('test --dart-define-from-file option', () async {
+ globals.fs.file(globals.fs.path.join('lib', 'main.dart')).createSync(recursive: true);
+ globals.fs.file('pubspec.yaml').createSync();
+ globals.fs.file('.packages').createSync();
+ await globals.fs.file('config.json').writeAsString(
+ '''
+ {
+ "kInt": 1,
+ "kDouble": 1.1,
+ "name": "denghaizhu",
+ "title": "this is title from config json file"
+ }
+ '''
+ );
+ final CommandRunner<void> runner = createTestCommandRunner(BuildBundleCommand());
+
+ await runner.run(<String>[
+ 'bundle',
+ '--no-pub',
+ '--dart-define-from-file=config.json',
+ ]);
+ }, overrides: <Type, Generator>{
+ BuildSystem: () => TestBuildSystem.all(BuildResult(success: true), (Target target, Environment environment) {
+ expect(environment.defines[kDartDefines], 'a0ludD0x,a0RvdWJsZT0xLjE=,bmFtZT1kZW5naGFpemh1,dGl0bGU9dGhpcyBpcyB0aXRsZSBmcm9tIGNvbmZpZyBqc29uIGZpbGU=');
+ }),
+ FileSystem: fsFactory,
+ ProcessManager: () => FakeProcessManager.any(),
+ });
+
+
+ testUsingContext('test --dart-define-from-file option by corrupted json', () async {
+ globals.fs.file(globals.fs.path.join('lib', 'main.dart')).createSync(recursive: true);
+ globals.fs.file('pubspec.yaml').createSync();
+ globals.fs.file('.packages').createSync();
+ await globals.fs.file('config.json').writeAsString(
+ '''
+ {
+ "kInt": 1Error json format
+ "kDouble": 1.1,
+ "name": "denghaizhu",
+ "title": "this is title from config json file"
+ }
+ '''
+ );
+ final CommandRunner<void> runner = createTestCommandRunner(BuildBundleCommand());
+
+ expect(() => runner.run(<String>[
+ 'bundle',
+ '--no-pub',
+ '--dart-define-from-file=config.json',
+ ]), throwsA(predicate<Exception>((Exception e) => e is ToolExit && e.message!.startsWith('Json config define file "--dart-define-from-file=config.json" format err'))));
+ }, overrides: <Type, Generator>{
+ FileSystem: fsFactory,
+ BuildSystem: () => TestBuildSystem.all(BuildResult(success: true)),
+ ProcessManager: () => FakeProcessManager.any(),
+ });
+
}
class FakeBundleBuilder extends Fake implements BundleBuilder {
diff --git a/packages/flutter_tools/test/general.shard/build_info_test.dart b/packages/flutter_tools/test/general.shard/build_info_test.dart
index e628b7b..0b59be0 100644
--- a/packages/flutter_tools/test/general.shard/build_info_test.dart
+++ b/packages/flutter_tools/test/general.shard/build_info_test.dart
@@ -7,6 +7,7 @@
import 'package:flutter_tools/src/build_info.dart';
import '../src/common.dart';
+import '../src/context.dart';
void main() {
late BufferLogger logger;
@@ -279,4 +280,70 @@
kDartDefines: 'MTIzMiw0NTY=,Mg==',
}, kDartDefines), <String>['1232,456', '2']);
});
+
+ group('Check repeated buildInfo variables', () {
+ testUsingContext('toEnvironmentConfig repeated variable', () async {
+ const BuildInfo buildInfo = BuildInfo(BuildMode.debug, '',
+ treeShakeIcons: true,
+ trackWidgetCreation: true,
+ dartDefines: <String>['foo=2', 'bar=2'],
+ dartDefineConfigJsonMap: <String,Object>{ 'DART_DEFINES' : 'Define a variable, but it occupies the variable name of the system'},
+ dartObfuscation: true,
+ );
+ buildInfo.toEnvironmentConfig();
+ expect(testLogger.warningText, contains('The key: [DART_DEFINES] already exists, you cannot use environment variables that have been used by the system'));
+ });
+
+ testUsingContext('toEnvironmentConfig repeated variable with DART_DEFINES not set', () async {
+ // Simulate operation flutterCommand.getBuildInfo with `dart-define-from-file` set dartDefines
+ const BuildInfo buildInfo = BuildInfo(BuildMode.debug, '',
+ treeShakeIcons: true,
+ dartDefines: <String>['DART_DEFINES=Define a variable, but it occupies the variable name of the system'],
+ trackWidgetCreation: true,
+ dartDefineConfigJsonMap: <String, Object>{ 'DART_DEFINES' : 'Define a variable, but it occupies the variable name of the system'},
+ dartObfuscation: true,
+ );
+ buildInfo.toEnvironmentConfig();
+ expect(testLogger.warningText, contains('The key: [DART_DEFINES] already exists, you cannot use environment variables that have been used by the system'));
+
+ });
+
+ testUsingContext('toGradleConfig repeated variable', () async {
+ const BuildInfo buildInfo = BuildInfo(BuildMode.debug, '',
+ treeShakeIcons: true,
+ trackWidgetCreation: true,
+ dartDefines: <String>['foo=2', 'bar=2'],
+ dartDefineConfigJsonMap: <String,Object>{ 'dart-defines' : 'Define a variable, but it occupies the variable name of the system'},
+ dartObfuscation: true,
+ );
+ buildInfo.toGradleConfig();
+ expect(testLogger.warningText, contains('he key: [dart-defines] already exists, you cannot use gradle variables that have been used by the system'));
+ });
+
+ testUsingContext('toGradleConfig repeated variable with not set', () async {
+ // Simulate operation flutterCommand.getBuildInfo with `dart-define-from-file` set dartDefines
+ const BuildInfo buildInfo = BuildInfo(BuildMode.debug, '',
+ treeShakeIcons: true,
+ trackWidgetCreation: true,
+ dartDefines: <String>['dart-defines=Define a variable, but it occupies the variable name of the system'],
+ dartDefineConfigJsonMap: <String,Object>{ 'dart-defines' : 'Define a variable, but it occupies the variable name of the system'},
+ dartObfuscation: true,
+ );
+ buildInfo.toGradleConfig();
+ expect(testLogger.warningText, contains('he key: [dart-defines] already exists, you cannot use gradle variables that have been used by the system'));
+ });
+
+ testUsingContext('toGradleConfig with androidProjectArgs override gradle project variant', () async {
+ const BuildInfo buildInfo = BuildInfo(BuildMode.debug, '',
+ treeShakeIcons: true,
+ trackWidgetCreation: true,
+ androidProjectArgs: <String>['applicationId=com.google'],
+ dartDefineConfigJsonMap: <String,Object>{ 'applicationId' : 'override applicationId'},
+ dartObfuscation: true,
+ );
+ buildInfo.toGradleConfig();
+ expect(testLogger.warningText, contains('The key: [applicationId] already exists, you cannot use gradle variables that have been used'));
+ });
+
+ });
}