Unverified Commit cbfe5a52 authored by haizhu's avatar haizhu Committed by GitHub

[tool] Proposal to support dart define config from a json file (#108098)

parent 50cd7377
...@@ -37,6 +37,7 @@ class BuildInfo { ...@@ -37,6 +37,7 @@ class BuildInfo {
List<String>? dartExperiments, List<String>? dartExperiments,
required this.treeShakeIcons, required this.treeShakeIcons,
this.performanceMeasurementFile, this.performanceMeasurementFile,
this.dartDefineConfigJsonMap,
this.packagesPath = '.dart_tool/package_config.json', // TODO(zanderso): make this required and remove the default. this.packagesPath = '.dart_tool/package_config.json', // TODO(zanderso): make this required and remove the default.
this.nullSafetyMode = NullSafetyMode.sound, this.nullSafetyMode = NullSafetyMode.sound,
this.codeSizeDirectory, this.codeSizeDirectory,
...@@ -130,6 +131,17 @@ class BuildInfo { ...@@ -130,6 +131,17 @@ class BuildInfo {
/// rerun tasks. /// rerun tasks.
final String? performanceMeasurementFile; 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 /// If provided, an output directory where one or more v8-style heap snapshots
/// will be written for code size profiling. /// will be written for code size profiling.
final String? codeSizeDirectory; final String? codeSizeDirectory;
...@@ -247,12 +259,17 @@ class BuildInfo { ...@@ -247,12 +259,17 @@ class BuildInfo {
}; };
} }
/// Convert to a structured string encoded structure appropriate for usage as /// Convert to a structured string encoded structure appropriate for usage as
/// environment variables or to embed in other scripts. /// environment variables or to embed in other scripts.
/// ///
/// Fields that are `null` are excluded from this configuration. /// Fields that are `null` are excluded from this configuration.
Map<String, String> toEnvironmentConfig() { 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) if (dartDefines.isNotEmpty)
'DART_DEFINES': encodeDartDefines(dartDefines), 'DART_DEFINES': encodeDartDefines(dartDefines),
if (dartObfuscation != null) if (dartObfuscation != null)
...@@ -276,13 +293,23 @@ class BuildInfo { ...@@ -276,13 +293,23 @@ class BuildInfo {
if (codeSizeDirectory != null) if (codeSizeDirectory != null)
'CODE_SIZE_DIRECTORY': codeSizeDirectory!, '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 /// Convert this config to a series of project level arguments to be passed
/// on the command line to gradle. /// on the command line to gradle.
List<String> toGradleConfig() { List<String> toGradleConfig() {
// PACKAGE_CONFIG not currently supported. // PACKAGE_CONFIG not currently supported.
return <String>[ final List<String> result = <String>[
if (dartDefines.isNotEmpty) if (dartDefines.isNotEmpty)
'-Pdart-defines=${encodeDartDefines(dartDefines)}', '-Pdart-defines=${encodeDartDefines(dartDefines)}',
if (dartObfuscation != null) if (dartObfuscation != null)
...@@ -306,6 +333,20 @@ class BuildInfo { ...@@ -306,6 +333,20 @@ class BuildInfo {
for (String projectArg in androidProjectArgs) for (String projectArg in androidProjectArgs)
'-P$projectArg', '-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;
} }
} }
......
...@@ -263,9 +263,29 @@ class AssembleCommand extends FlutterCommand { ...@@ -263,9 +263,29 @@ class AssembleCommand extends FlutterCommand {
if (argumentResults.wasParsed(FlutterOptions.kExtraGenSnapshotOptions)) { if (argumentResults.wasParsed(FlutterOptions.kExtraGenSnapshotOptions)) {
results[kExtraGenSnapshotOptions] = (argumentResults[FlutterOptions.kExtraGenSnapshotOptions] as List<String>).join(','); results[kExtraGenSnapshotOptions] = (argumentResults[FlutterOptions.kExtraGenSnapshotOptions] as List<String>).join(',');
} }
List<String> dartDefines = <String>[];
if (argumentResults.wasParsed(FlutterOptions.kDartDefinesOption)) { 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'; results[kDeferredComponents] = 'false';
if (FlutterProject.current().manifest.deferredComponents != null && isDeferredComponentsTargets() && !isDebug()) { if (FlutterProject.current().manifest.deferredComponents != null && isDeferredComponentsTargets() && !isDebug()) {
results[kDeferredComponents] = 'true'; results[kDeferredComponents] = 'true';
......
...@@ -19,6 +19,7 @@ import '../build_info.dart'; ...@@ -19,6 +19,7 @@ import '../build_info.dart';
import '../build_system/build_system.dart'; import '../build_system/build_system.dart';
import '../bundle.dart' as bundle; import '../bundle.dart' as bundle;
import '../cache.dart'; import '../cache.dart';
import '../convert.dart';
import '../dart/generate_synthetic_packages.dart'; import '../dart/generate_synthetic_packages.dart';
import '../dart/language_version.dart'; import '../dart/language_version.dart';
import '../dart/package_map.dart'; import '../dart/package_map.dart';
...@@ -104,6 +105,7 @@ class FlutterOptions { ...@@ -104,6 +105,7 @@ class FlutterOptions {
static const String kSplitDebugInfoOption = 'split-debug-info'; static const String kSplitDebugInfoOption = 'split-debug-info';
static const String kDartObfuscationOption = 'obfuscate'; static const String kDartObfuscationOption = 'obfuscate';
static const String kDartDefinesOption = 'dart-define'; static const String kDartDefinesOption = 'dart-define';
static const String kDartDefineFromFileOption = 'dart-define-from-file';
static const String kBundleSkSLPathOption = 'bundle-sksl-path'; static const String kBundleSkSLPathOption = 'bundle-sksl-path';
static const String kPerformanceMeasurementFile = 'performance-measurement-file'; static const String kPerformanceMeasurementFile = 'performance-measurement-file';
static const String kNullSafety = 'sound-null-safety'; static const String kNullSafety = 'sound-null-safety';
...@@ -591,6 +593,17 @@ abstract class FlutterCommand extends Command<void> { ...@@ -591,6 +593,17 @@ abstract class FlutterCommand extends Command<void> {
valueHelp: 'foo=bar', valueHelp: 'foo=bar',
splitCommas: false, 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() { void usesWebRendererOption() {
...@@ -1122,6 +1135,27 @@ abstract class FlutterCommand extends Command<void> { ...@@ -1122,6 +1135,27 @@ abstract class FlutterCommand extends Command<void> {
dartDefines = updateDartDefines(dartDefines, stringArgDeprecated('web-renderer')!); 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, return BuildInfo(buildMode,
argParser.options.containsKey('flavor') argParser.options.containsKey('flavor')
? stringArgDeprecated('flavor') ? stringArgDeprecated('flavor')
...@@ -1146,6 +1180,7 @@ abstract class FlutterCommand extends Command<void> { ...@@ -1146,6 +1180,7 @@ abstract class FlutterCommand extends Command<void> {
bundleSkSLPath: bundleSkSLPath, bundleSkSLPath: bundleSkSLPath,
dartExperiments: experiments, dartExperiments: experiments,
performanceMeasurementFile: performanceMeasurementFile, performanceMeasurementFile: performanceMeasurementFile,
dartDefineConfigJsonMap: defineConfigJsonMap,
packagesPath: packagesPath ?? globals.fs.path.absolute('.dart_tool', 'package_config.json'), packagesPath: packagesPath ?? globals.fs.path.absolute('.dart_tool', 'package_config.json'),
nullSafetyMode: nullSafetyMode, nullSafetyMode: nullSafetyMode,
codeSizeDirectory: codeSizeDirectory, codeSizeDirectory: codeSizeDirectory,
......
...@@ -290,4 +290,33 @@ void main() { ...@@ -290,4 +290,33 @@ void main() {
], ],
}); });
}); });
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(),
});
} }
...@@ -4,6 +4,7 @@ ...@@ -4,6 +4,7 @@
import 'package:args/command_runner.dart'; import 'package:args/command_runner.dart';
import 'package:file/memory.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/base/file_system.dart';
import 'package:flutter_tools/src/build_info.dart'; import 'package:flutter_tools/src/build_info.dart';
import 'package:flutter_tools/src/build_system/build_system.dart'; import 'package:flutter_tools/src/build_system/build_system.dart';
...@@ -463,6 +464,64 @@ void main() { ...@@ -463,6 +464,64 @@ void main() {
FileSystem: fsFactory, FileSystem: fsFactory,
ProcessManager: () => FakeProcessManager.any(), 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 { class FakeBundleBuilder extends Fake implements BundleBuilder {
......
...@@ -7,6 +7,7 @@ import 'package:flutter_tools/src/base/logger.dart'; ...@@ -7,6 +7,7 @@ import 'package:flutter_tools/src/base/logger.dart';
import 'package:flutter_tools/src/build_info.dart'; import 'package:flutter_tools/src/build_info.dart';
import '../src/common.dart'; import '../src/common.dart';
import '../src/context.dart';
void main() { void main() {
late BufferLogger logger; late BufferLogger logger;
...@@ -279,4 +280,70 @@ void main() { ...@@ -279,4 +280,70 @@ void main() {
kDartDefines: 'MTIzMiw0NTY=,Mg==', kDartDefines: 'MTIzMiw0NTY=,Mg==',
}, kDartDefines), <String>['1232,456', '2']); }, 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'));
});
});
} }
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