1. ArgParser背景
在flutter中通过如下命令行生成并运行App到手机
flutter clean && flutter run --verbose
在编译App的过程中执行如下命令,用于将dart代码编译成dill文件(查看终端输出)
/Users/xxx/Library/Android/flutter/bin/cache/dart-sdk/bin/dart --disable-dart-dev
/Users/xxx/Library/Android/flutter/bin/cache/artifacts/engine/darwin-x64/frontend_server.dart.snapshot
--sdk-root /Users/xxx/Library/Android/flutter/bin/cache/artifacts/engine/common/flutter_patched_sdk/
--target=flutter
-Ddart.developer.causal_async_stacks=true
-Ddart.vm.profile=false
-Ddart.vm.product=false
--bytecode-options=source-positions,local-var-info,debugger-stops,instance-field-initializers,keep-unreachable-code,avoid-closure-call-instructions
--enable-asserts
--track-widget-creation
--no-link-platform
--packages /Users/xxx/flutter-workspace/flutter_develop/flutter_tools/example/.packages
--output-dill /Users/xxx/flutter-workspace/flutter_develop/flutter_tools/example/.dart_tool/flutter_build/3ee0d67081b9933cfebe1ac3e5a4992d/app.dill
--depfile /Users/xxx/flutter-workspace/flutter_develop/flutter_tools/example/.dart_tool/flutter_build/3ee0d67081b9933cfebe1ac3e5a4992d/kernel_snapshot.d
package:example/main.dart
可以看得出,使用dart命令携带大量的参数执行frontend_server.dart.snapshot文件(相关的java -jar xxx.jar),而ArgParser类的作用就是用于解析这堆参数。
frontend_server.dart.snapshot文件的入口main方法位于flutter/engine(github.com/flutter/eng…)仓库 flutter_frontend_server/bin/starter.dart文件中,源码如下:
starter.dart
//List<String>就是上面的参数列表:--sdk-root...
void main(List<String> args) async {
//执行了server.dart中的starter方法
final int exitCode = await starter(args);
if (exitCode != 0) {
exit(exitCode);
}
}
server.dart
Future<int> starter(List<String> args, {
frontend.CompilerInterface compiler,
Stream<List<int>> input,
StringSink output,
frontend.ProgramTransformer transformer,
}) async {
ArgResults options;
//argParser对象就是 ArgParser的实例,位于frontend_server.dart文件中,具体看下一个源码块
//该方法就是补充更多可使用的参数
frontend.argParser.addMultiOption(
'delete-tostring-package-uri',
help: 'Replaces implementations of `toString` with `super.toString()` for '
'specified package',
valueHelp: 'dart:ui',
defaultsTo: const <String>[],
);
try {
//解析参数
options = frontend.argParser.parse(args);
} catch (error) {
print('ERROR: $error\n');
print(frontend.usage);
return 1;
}
...
//使用参数
final Set<String> deleteToStringPackageUris =
(options['delete-tostring-package-uri'] as List<String>).toSet();
...
}
frontend_server.dart
...
ArgParser argParser = ArgParser(allowTrailingOptions: true)
..addFlag('train',
help: 'Run through sample command line to produce snapshot',
negatable: false)
...
ArgParser源码分析
ArgParser
class ArgParser {
final Map<String, Option> _options;
//将上面的_options包装成不可变对象,内部数据是一致的
final Map<String, Option> options;
factory ArgParser({bool allowTrailingOptions = true, int usageLineLength}) =>
ArgParser._(
<String, Option>{},
<String, ArgParser>{},
...);
ArgParser._(Map<String, Option> options, Map<String, ArgParser> commands,
{bool allowTrailingOptions = true, this.usageLineLength})
: _options = options,
options = UnmodifiableMapView(options);
//添加flag类型的options
void addFlag(String name,
{String abbr,
String help,
bool defaultsTo = false,
bool negatable = true,
void Function(bool) callback,
bool hide = false}) {
_addOption(name,abbr,help,null,null,null,defaultsTo,
callback == null ? null : (value) => callback(value as bool),
OptionType.flag,
negatable: negatable,
hide: hide);
}
//添加多参数的options,比如--bytecode-options=source-positions,local-var-info,debugger-stops,可选值用,隔开
void addMultiOption(String name,
{String abbr,
String help,
String valueHelp,
Iterable<String> allowed,
Map<String, String> allowedHelp,
Iterable<String> defaultsTo,
void Function(List<String>) callback,
bool splitCommas = true,
bool hide = false}) {
_addOption(name,abbr,help,valueHelp,
allowed,
allowedHelp,
defaultsTo?.toList() ?? <String>[],
callback == null ? null : (value) => callback(value as List<String>),
OptionType.multiple,
splitCommas: splitCommas,
hide: hide);
}
//添加Options
void _addOption(
String name,
String abbr,
String help,
String valueHelp,
Iterable<String> allowed,
Map<String, String> allowedHelp,
defaultsTo,
Function callback,
OptionType type,
{bool negatable = false,
bool splitCommas,
bool hide = false}) {
...
var option = newOption(name, abbr, help, valueHelp, allowed, allowedHelp,
defaultsTo, callback, type,
negatable: negatable, splitCommas: splitCommas, hide: hide);
_options[name] = option;
_optionsAndSeparators.add(option);
}
//根据option的名称获取该options的默认值
dynamic getDefault(String option) {
if (!options.containsKey(option)) {
throw ArgumentError('No option named $option');
}
return options[option].defaultsTo;
}
//通过option配置的缩写,搜索options对象
Option findByAbbreviation(String abbr) {
return options.values
.firstWhere((option) => option.abbr == abbr, orElse: () => null);
}
//解析命令行中的参数列表,并返回解析结果对象 ArgResults
//真的的解析过程交给Parser类处理, 从这里可以看出ArgsParser相当于Parser的包装类
ArgResults parse(Iterable<String> args) =>
Parser(null, this, Queue.of(args)).parse();
//以下用于控制调用--help或者-h的输出内容
final _optionsAndSeparators = [];
void addSeparator(String text) {
_optionsAndSeparators.add(text);
}
String get usage {
return Usage(_optionsAndSeparators, lineLength: usageLineLength).generate();
}
}
Parser
class Parser {
//把终端的所有的参数都解析完成以后,除去option项剩余的参数都存在rest中
final rest = <String>[];
final ArgParser grammar;
final Queue<String> args;
//存放解析好的Option.name 和 value 键值对
final Map<String, dynamic> results = <String, dynamic>{};
Parser(this.grammar, this.args);
//获取参数列表的第一个
String get current => args.first;
ArgResults parse() {
var arguments = args.toList();
...
ArgResults commandResults;
while (args.isNotEmpty) {
if (current == '--') {
//如果参数列表第一个是 --, 则停止解析,剩余没有解析参数直接放入rest列表中
args.removeFirst();
break;
}
...
//解析到Option,分为2种情况:
//非Flag类型的Option: 比如:...-d test ..., 则从ArgParser中获取缩写为‘d’的Option对象,并把后面的值test赋值给option.value
//Flag类型的Option: 比如 -f,等同于Option.value=true
if (parseSoloOption()) continue;
//解析到option,分为2种情况:
//非Flag类型的Option: 比如 -mRelease, 则从ArgParser中获取缩写为‘m’的Option对象,并把后面的值Release赋值给option.value
//Flag类型的Option: 比如 -ftrue
if (parseAbbreviation(this)) continue;
//解析到option,这里包括4种情况, 注意从ArgParer中获取option对象是根据全名,而不是缩写
//1. --mode=release
//2. --mode release
//3. --opt ,名称是opt的Flag类型Option,将值(option.value)设置为true
//4. --no-opt,名称是opt的Flag类型Option,将值(option.value)设置为false
if (parseLongOption()) continue;
//如果以上的条件成立,内部会调用 args.removeFirst(),
...
//没有作为Option的参数添加到rest列表中
rest.add(args.removeFirst());
}
//没有作为Option的参数添加到rest列表中
rest.addAll(args);
args.clear();
return newArgResults(
grammar, results, commandName, commandResults, rest, arguments);
}
}
前面知道了forntent_server.dart中创建了ArgParser实例,代码如下,具体参数含义,不做过多解释。
ArgParser argParser = ArgParser(allowTrailingOptions: true)
..addFlag('train',
help: 'Run through sample command line to produce snapshot',
negatable: false)
..addFlag('incremental',
help: 'Run compiler in incremental mode', defaultsTo: false)
..addOption('sdk-root',
help: 'Path to sdk root',
defaultsTo: '../../out/android_debug/flutter_patched_sdk')
..addOption('platform', help: 'Platform kernel filename')
..addFlag('aot',
help: 'Run compiler in AOT mode (enables whole-program transformations)',
defaultsTo: false)
..addFlag('tfa',
help:
'Enable global type flow analysis and related transformations in AOT mode.',
defaultsTo: false)
..addFlag('tree-shake-write-only-fields',
help: 'Enable tree shaking of fields which are only written in AOT mode.',
defaultsTo: true)
..addFlag('protobuf-tree-shaker',
help: 'Enable protobuf tree shaker transformation in AOT mode.',
defaultsTo: false)
..addFlag('protobuf-tree-shaker-v2',
help: 'Enable protobuf tree shaker v2 in AOT mode.', defaultsTo: false)
..addFlag('minimal-kernel',
help: 'Produce minimal tree-shaken kernel file.', defaultsTo: false)
..addFlag('link-platform',
help:
'When in batch mode, link platform kernel file into result kernel file.'
' Intended use is to satisfy different loading strategies implemented'
' by gen_snapshot(which needs platform embedded) vs'
' Flutter engine(which does not)',
defaultsTo: true)
..addOption('import-dill',
help: 'Import libraries from existing dill file', defaultsTo: null)
..addOption('from-dill',
help: 'Read existing dill file instead of compiling from sources',
defaultsTo: null)
..addOption('output-dill',
help: 'Output path for the generated dill', defaultsTo: null)
..addOption('output-incremental-dill',
help: 'Output path for the generated incremental dill', defaultsTo: null)
..addOption('depfile',
help: 'Path to output Ninja depfile. Only used in batch mode.')
..addOption('packages',
help: '.packages file to use for compilation', defaultsTo: null)
..addOption('target',
help: 'Target model that determines what core libraries are available',
allowed: <String>[
'vm',
'flutter',
'flutter_runner',
'dart_runner',
'dartdevc'
],
defaultsTo: 'vm')
..addMultiOption('filesystem-root',
help: 'File path that is used as a root in virtual filesystem used in'
' compiled kernel files. When used --output-dill should be provided'
' as well.',
hide: true)
..addOption('filesystem-scheme',
help:
'Scheme that is used in virtual filesystem set up via --filesystem-root'
' option',
defaultsTo: 'org-dartlang-root',
hide: true)
..addFlag('enable-http-uris',
defaultsTo: false, hide: true, help: 'Enables support for http uris.')
..addFlag('verbose', help: 'Enables verbose output from the compiler.')
..addOption('initialize-from-dill',
help: 'Normally the output dill is used to specify which dill to '
'initialize from, but it can be overwritten here.',
defaultsTo: null,
hide: true)
..addMultiOption('define',
abbr: 'D',
help: 'The values for the environment constants (e.g. -Dkey=value).')
..addFlag('embed-source-text',
help: 'Includes sources into generated dill file. Having sources'
' allows to effectively use observatory to debug produced'
' application, produces better stack traces on exceptions.',
defaultsTo: true)
..addFlag('unsafe-package-serialization',
help: '*Deprecated* '
'Potentially unsafe: Does not allow for invalidating packages, '
'additionally the output dill file might include more libraries than '
'needed. The use case is test-runs, where invalidation is not really '
'used, and where dill filesize does not matter, and the gain is '
'improved speed.',
defaultsTo: false,
hide: true)
..addFlag('incremental-serialization',
help: 'Re-use previously serialized data when serializing. '
'The output dill file might include more libraries than strictly '
'needed, but the serialization phase will generally be much faster.',
defaultsTo: true,
negatable: true,
hide: true)
..addFlag('track-widget-creation',
help: 'Run a kernel transformer to track creation locations for widgets.',
defaultsTo: false)
..addFlag('gen-bytecode', help: 'Generate bytecode', defaultsTo: false)
..addMultiOption('bytecode-options',
help: 'Specify options for bytecode generation:',
valueHelp: 'opt1,opt2,...',
allowed: BytecodeOptions.commandLineFlags.keys,
allowedHelp: BytecodeOptions.commandLineFlags)
..addFlag('drop-ast',
help: 'Include only bytecode into the output file', defaultsTo: true)
..addFlag('enable-asserts',
help: 'Whether asserts will be enabled.', defaultsTo: false)
..addFlag('null-safety',
help:
'Respect the nullability of types at runtime in casts and instance checks.',
defaultsTo: null)
..addMultiOption('enable-experiment',
help: 'Comma separated list of experimental features, eg set-literals.',
hide: true)
..addFlag('split-output-by-packages',
help:
'Split resulting kernel file into multiple files (one per package).',
defaultsTo: false)
..addOption('component-name', help: 'Name of the Fuchsia component')
..addOption('data-dir',
help: 'Name of the subdirectory of //data for output files')
..addOption('far-manifest', help: 'Path to output Fuchsia package manifest')
..addOption('libraries-spec',
help: 'A path or uri to the libraries specification JSON file')
..addFlag('debugger-module-names',
help: 'Use debugger-friendly modules names', defaultsTo: false)
..addFlag('experimental-emit-debug-metadata',
help: 'Emit module and library metadata for the debugger',
defaultsTo: false)
..addOption('dartdevc-module-format',
help: 'The module format to use on for the dartdevc compiler',
defaultsTo: 'amd');
需要注意的时,-D的参数使用,但是由于编译时执行frontend_server.dart.snapshot命令,其中参数我们无法直接添加,但好在flutter给我们留了其他的入口:
flutter run --verbose --dart-define=test=true //key: dart-define value:test=true
最后这个dart-define配置会透传到执行frontend_server.dart.snapshot命令中:
/Users/xxx/Library/Android/flutter/bin/cache/dart-sdk/bin/dart --disable-dart-dev /Users/xxx/Library/Android/flutter/bin/cache/artifacts/engine/darwin-x64/frontend_server.dart.snapshot -Dtest=true ...
程序使用时,需要定义一个常量字段,参考: flutter/foundation.dart中的kRelease 字段
ArgParser.parse方法,最后会返回一个 ArgResult 对象
ArgResult
class ArgResults {
//解析option获取到的name:value键值对,比如--target=flutter,则name=target value=flutter
final Map<String, dynamic> _parsed;
//剩余没有解析的字段
final List<String> rest;
ArgResults._(... this._parsed, this.rest...);
//定义操作符,支持argResult['target']获取对应的值
dynamic operator [](String name) {
...
return _parser.options[name].getOrDefault(_parsed[name]);
}
}