diff --git a/pkg/dynamic_modules/pubspec.yaml b/pkg/dynamic_modules/pubspec.yaml index 20a9ade49c1..721249d9f59 100644 --- a/pkg/dynamic_modules/pubspec.yaml +++ b/pkg/dynamic_modules/pubspec.yaml @@ -11,4 +11,7 @@ resolution: workspace dev_dependencies: args: any expect: any + front_end: any + kernel: any lints: any + vm: any diff --git a/pkg/dynamic_modules/test/data/const_body/dynamic_interface.yaml b/pkg/dynamic_modules/test/data/const_body/dynamic_interface.yaml new file mode 100644 index 00000000000..d40a04f0408 --- /dev/null +++ b/pkg/dynamic_modules/test/data/const_body/dynamic_interface.yaml @@ -0,0 +1,14 @@ +# Copyright (c) 2024, the Dart project authors. Please see the AUTHORS file +# for details. All rights reserved. Use of this source code is governed by a +# BSD-style license that can be found in the LICENSE file. + +callable: + - library: 'shared/shared.dart' + class: 'B' + member: '' + # TODO(sigmund): This should be included by default + - library: 'dart:core' + class: 'Object' + - library: 'dart:core' + class: 'pragma' + member: '_' diff --git a/pkg/dynamic_modules/test/data/const_body/main.dart b/pkg/dynamic_modules/test/data/const_body/main.dart new file mode 100644 index 00000000000..dcd9115f31f --- /dev/null +++ b/pkg/dynamic_modules/test/data/const_body/main.dart @@ -0,0 +1,18 @@ +// Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import '../../common/testing.dart' as helper; +import 'shared/shared.dart'; + +import 'package:expect/expect.dart'; + +// Constants with a constructor body can be used from dynamic modules. +void main() async { + final c1 = (await helper.load('entry1.dart')); + final c2 = (await helper.load('entry2.dart')); + + Expect.identical((c1 as B).x, 2); + Expect.identical(c1, c2); + helper.done(); +} diff --git a/pkg/dynamic_modules/test/data/const_body/modules/entry1.dart b/pkg/dynamic_modules/test/data/const_body/modules/entry1.dart new file mode 100644 index 00000000000..df54f016b9c --- /dev/null +++ b/pkg/dynamic_modules/test/data/const_body/modules/entry1.dart @@ -0,0 +1,8 @@ +// Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import '../shared/shared.dart'; + +@pragma('dyn-module:entry-point') +Object? dynamicModuleEntrypoint() => const B(1); diff --git a/pkg/dynamic_modules/test/data/const_body/modules/entry2.dart b/pkg/dynamic_modules/test/data/const_body/modules/entry2.dart new file mode 100644 index 00000000000..df54f016b9c --- /dev/null +++ b/pkg/dynamic_modules/test/data/const_body/modules/entry2.dart @@ -0,0 +1,8 @@ +// Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import '../shared/shared.dart'; + +@pragma('dyn-module:entry-point') +Object? dynamicModuleEntrypoint() => const B(1); diff --git a/pkg/dynamic_modules/test/data/const_body/shared/shared.dart b/pkg/dynamic_modules/test/data/const_body/shared/shared.dart new file mode 100644 index 00000000000..d27e8088260 --- /dev/null +++ b/pkg/dynamic_modules/test/data/const_body/shared/shared.dart @@ -0,0 +1,8 @@ +// Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +class B { + final int x; + const B(int y) : x = y + 1; +} diff --git a/pkg/dynamic_modules/test/data/shared_const/dynamic_interface.yaml b/pkg/dynamic_modules/test/data/shared_const/dynamic_interface.yaml index c41737f2dad..d40a04f0408 100644 --- a/pkg/dynamic_modules/test/data/shared_const/dynamic_interface.yaml +++ b/pkg/dynamic_modules/test/data/shared_const/dynamic_interface.yaml @@ -3,7 +3,7 @@ # BSD-style license that can be found in the LICENSE file. callable: - - library: 'main.dart' + - library: 'shared/shared.dart' class: 'B' member: '' # TODO(sigmund): This should be included by default diff --git a/pkg/dynamic_modules/test/data/shared_const/main.dart b/pkg/dynamic_modules/test/data/shared_const/main.dart index 3a2a9384cd9..9663ec2bb55 100644 --- a/pkg/dynamic_modules/test/data/shared_const/main.dart +++ b/pkg/dynamic_modules/test/data/shared_const/main.dart @@ -3,20 +3,16 @@ // BSD-style license that can be found in the LICENSE file. import '../../common/testing.dart' as helper; +import 'shared/shared.dart'; + import 'package:expect/expect.dart'; -class B { - final int x; - const B(this.x); -} - -// Similar to `isolated_shared`, constant canonicalization distinguishes -// two constnats, even if they are created from a common library that was -// not part of the original application. +// Constants are properly canonicalized across dynamic modules. void main() async { final c1 = (await helper.load('entry1.dart')); final c2 = (await helper.load('entry2.dart')); + Expect.identical((c1 as B).x, 1); Expect.identical(c1, c2); helper.done(); } diff --git a/pkg/dynamic_modules/test/data/shared_const/modules/entry1.dart b/pkg/dynamic_modules/test/data/shared_const/modules/entry1.dart index ab084dc6d3c..cea4049eb6e 100644 --- a/pkg/dynamic_modules/test/data/shared_const/modules/entry1.dart +++ b/pkg/dynamic_modules/test/data/shared_const/modules/entry1.dart @@ -2,7 +2,7 @@ // for details. All rights reserved. Use of this source code is governed by a // BSD-style license that can be found in the LICENSE file. -import '../main.dart'; +import '../shared/shared.dart'; @pragma('dyn-module:entry-point') Object? dynamicModuleEntrypoint() => const B(1); diff --git a/pkg/dynamic_modules/test/data/shared_const/modules/entry2.dart b/pkg/dynamic_modules/test/data/shared_const/modules/entry2.dart index ab084dc6d3c..cea4049eb6e 100644 --- a/pkg/dynamic_modules/test/data/shared_const/modules/entry2.dart +++ b/pkg/dynamic_modules/test/data/shared_const/modules/entry2.dart @@ -2,7 +2,7 @@ // for details. All rights reserved. Use of this source code is governed by a // BSD-style license that can be found in the LICENSE file. -import '../main.dart'; +import '../shared/shared.dart'; @pragma('dyn-module:entry-point') Object? dynamicModuleEntrypoint() => const B(1); diff --git a/pkg/dynamic_modules/test/data/shared_const/shared/shared.dart b/pkg/dynamic_modules/test/data/shared_const/shared/shared.dart new file mode 100644 index 00000000000..fbdf67c83ea --- /dev/null +++ b/pkg/dynamic_modules/test/data/shared_const/shared/shared.dart @@ -0,0 +1,8 @@ +// Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +class B { + final int x; + const B(this.x); +} diff --git a/pkg/dynamic_modules/test/runner/aot.dart b/pkg/dynamic_modules/test/runner/aot.dart index ff3485f8ea7..50955dbac44 100644 --- a/pkg/dynamic_modules/test/runner/aot.dart +++ b/pkg/dynamic_modules/test/runner/aot.dart @@ -6,6 +6,9 @@ library; import 'dart:io'; +import 'package:front_end/src/util/trim.dart'; +import 'package:kernel/target/targets.dart'; +import 'package:vm/modular/target/vm.dart'; import '../common/testing.dart' as helper; import 'model.dart'; @@ -73,6 +76,7 @@ class AotExecutor implements TargetExecutor { '-Ddart.vm.profile=false', '-Ddart.vm.product=true', if (isAot) '--aot' else '--no-aot', + '--no-embed-sources', '--platform', vmPlatformDill.toFilePath(), '--output', @@ -105,6 +109,35 @@ class AotExecutor implements TargetExecutor { ]; await runProcess(genSnapshotBin.toFilePath(), args, testDir, _logger, 'aot snapshot ${test.name}/${test.main}'); + + // The next steps are optional, but done to test trimming of assets used + // by the bytecode compiler. + + await createTrimmedCopy(TrimOptions( + inputAppPath: "$testDir/${test.main}_no_aot.dill", + outputAppPath: "$testDir/${test.main}_no_aot_trimmed.dill", + inputPlatformPath: vmPlatformDill.toFilePath(), + outputPlatformPath: "$testDir/${test.main}_platform_trimmed.dill", + dynamicInterfaceContents: File.fromUri(test.folder + .resolve('../../data/${test.name}/dynamic_interface.yaml')) + .readAsStringSync(), + dynamicInterfaceUri: + Uri.parse('$rootScheme:/data/${test.name}/dynamic_interface.yaml'), + requiredDartLibraries: + VmTarget(TargetFlags()).extraRequiredLibraries.toSet())); + + void logSizeDiff(String path1, String path2) { + final originalSize = File(path1).statSync().size; + final trimmedSize = File(path2).statSync().size; + _logger.info('Size difference for $path2: ' + '$originalSize => $trimmedSize ' + '(${(trimmedSize * 100 / originalSize).toStringAsFixed(2)}%)'); + } + + logSizeDiff(vmPlatformDill.toFilePath(), + '$testDir/${test.main}_platform_trimmed.dill'); + logSizeDiff("$testDir/${test.main}_no_aot.dill", + "$testDir/${test.main}_no_aot_trimmed.dill"); } @override @@ -117,7 +150,7 @@ class AotExecutor implements TargetExecutor { '--disable-dart-dev', dart2bytecodeSnapshot.toFilePath(), '--platform', - vmPlatformDill.toFilePath(), + '${test.main}_platform_trimmed.dill', '--target', 'vm', '--packages', @@ -125,7 +158,7 @@ class AotExecutor implements TargetExecutor { '-Ddart.vm.profile=false', '-Ddart.vm.product=true', '--import-dill', - '${test.main}_no_aot.dill', + '${test.main}_no_aot_trimmed.dill', '--validate', '$rootScheme:/data/${test.name}/dynamic_interface.yaml', '--verbosity=all', diff --git a/pkg/dynamic_modules/test/runner/main.dart b/pkg/dynamic_modules/test/runner/main.dart index ad62536cf09..20cb00ef146 100644 --- a/pkg/dynamic_modules/test/runner/main.dart +++ b/pkg/dynamic_modules/test/runner/main.dart @@ -61,7 +61,11 @@ void main(List args) async { final results = []; for (final t in tests) { - results.add(await _runSingleTest(t, executor)); + final testResult = await _runSingleTest(t, executor); + if (testResult.status != Status.pass) { + logger.error(testResult.details); + } + results.add(testResult); } final result = _reportResults(results, writeLog: singleTest == null, diff --git a/pkg/front_end/lib/src/util/trim.dart b/pkg/front_end/lib/src/util/trim.dart new file mode 100644 index 00000000000..efece9adc8d --- /dev/null +++ b/pkg/front_end/lib/src/util/trim.dart @@ -0,0 +1,293 @@ +// Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'dart:io'; +import 'package:kernel/kernel.dart'; +import 'package:kernel/binary/ast_to_binary.dart'; +import 'package:front_end/src/kernel/dynamic_module_validator.dart'; + +/// Tool to trim an .dill file. +/// +/// This function reads full .dill files and trims them with a simple goal: +/// produce small .dill files that preserve all the information needed for +/// modular compilation by the bytecode compiler. This is done as a combination +/// of removing unnecessary dependencies and stripping out details, like method +/// bodies. This function would become simpler once CFE can directly produce +/// outlines matching what's needed by the bytecode compiler. +/// +/// Currently this function preserves method bodies of mixin declarations and +/// const constructors, which are needed by the compiler. Currently there is not +/// a fine-grain definition of which mixin or const constructors may be used, +/// but this algorithm could be extended to handle trimming in a more fine-grain +/// fashion in the future. +/// +/// This function only accepts inputs containing libraries with a Dart version +/// 3.0 or newer. That allows us to ignore legacy mixin declarations. We also +/// assume that the input .dill contains all transitive dependencies needed to +/// properly serialize and visit the AST (this typically includes platform +/// libraries). +/// +/// This function expects the caller to provide details about which libraries +/// are known entry points that need to be preserved. It doesn't do a fine-grain +/// tree-shaking, but will delete libraries that can't be reached from those +/// entry points. These entry points can be derived from the +/// `dynamic_interface.yaml` used by dynamic modules. +Future createTrimmedCopy(TrimOptions options) async { + Component component = loadComponentFromBinary(options.inputPlatformPath); + loadComponentFromBinary(options.inputAppPath, component); + Set included = {}; + + // Validate version and clear method bodies. + component.accept(new Trimmer(options.librariesToClear)); + + if (options.dynamicInterfaceContents == null) { + // Transitively include all libraries from a set of user libraries. + addReachable( + component.libraries, + (Library lib) => + _isRootFromPatterns(lib, options.requiredUserLibraries), + included); + } else { + // Transitively include all libraries declared as accessible from the + // dynamic_interface specification. + DynamicInterfaceSpecification spec = new DynamicInterfaceSpecification( + options.dynamicInterfaceContents!, + options.dynamicInterfaceUri!, + component); + Library enclosingLibrary(TreeNode node) => switch (node) { + Member() => node.enclosingLibrary, + Class() => node.enclosingLibrary, + Library() => node, + _ => throw 'Unexpected node ${node.runtimeType} $node' + }; + Set roots = { + ...spec.callable.map(enclosingLibrary), + ...spec.extendable.map(enclosingLibrary), + ...spec.canBeOverridden.map(enclosingLibrary), + }; + addReachable(component.libraries, roots.contains, included); + } + + // Transitively include libraries needed by the required platform libraries. + addReachable( + component.libraries, + (Library lib) => _isRootFromPatterns(lib, options.requiredDartLibraries), + included); + + component.uriToSource.clear(); + component.setMainMethodAndMode(null, true); + + Future emit(String path, bool isPlatform) async { + Set filteredSet = included + .where((lib) => isPlatform + ? lib.importUri.isScheme('dart') + : !lib.importUri.isScheme('dart')) + .toSet(); + IOSink sink = new File(path).openWrite(); + BinaryPrinter printer = new BinaryPrinter(sink, + libraryFilter: filteredSet.contains, + includeSources: false, + includeSourceBytes: false); + printer.writeComponentFile(component); + await sink.flush(); + await sink.close(); + } + + if (options.outputPlatformPath != null) { + await emit(options.outputPlatformPath!, true); + } + await emit(options.outputAppPath, false); +} + +/// Helper to determine whether a library is an included root, if provided +/// with [TrimOptions.requiredUserLibraries] or +/// [TrimOptions.requiredDartLibraries]. +bool _isRootFromPatterns(Library lib, Set patterns) { + List prefixPatterns = patterns + .where((p) => p.endsWith('*')) + .map((p) => p.substring(0, p.length - 1)) + .toList(); + Set exactPatterns = patterns.where((p) => !p.endsWith('*')).toSet(); + String uriString = '${lib.importUri}'; + if (exactPatterns.contains(uriString)) return true; + if (prefixPatterns.any((p) => uriString.startsWith(p))) return true; + return false; +} + +/// Validates that all libraries are 3.0 or higher, then trims contents +/// as much as possible, while enabling modular compilation later on. +/// +/// Currently we: +/// * deletes bodies of constructors and procedures, except when deemed +/// necessary for mixin applications and constants. +/// * clear libraries whose contents are unnecessary, even if reachable. +/// * clear unnecessary field initializers. +class Trimmer extends RecursiveVisitor { + /// Platform libraries that will be cleared internally. + /// + /// `Target.extraRequiredLibraries` demands that some platform libraries are + /// always included in the platform .dill file. However, there are libraries, + /// that are required by the target that are used only for non-release builds. + /// Until we can tailor the required libraries to specific configurations, we + /// add this step to remove the contents of those libraries, without removing + /// the library node itself. + final Set librariesToClear; + + /// Whether we are within a mixin declaration, and hence method bodies need to + /// be preserved. + bool withinMixin = false; + + Trimmer(this.librariesToClear); + + @override + void visitLibrary(Library node) { + Uri uri = node.importUri; + if (node.languageVersion.major < 3) { + print( + 'Error: Library "$uri" has version ${node.languageVersion.toText()}, ' + 'which is older than 3.0'); + exit(1); + } + + if (librariesToClear.contains(uri.toString())) { + node.classes.clear(); + node.procedures.clear(); + node.extensions.clear(); + node.fields.clear(); + node.typedefs.clear(); + node.extensionTypeDeclarations.clear(); + node.parts.clear(); + node.dependencies.clear(); + node.additionalExports.clear(); + return; + } + + super.visitLibrary(node); + } + + @override + void visitClass(Class node) { + withinMixin = node.isMixinClass || node.isMixinDeclaration; + super.visitClass(node); + withinMixin = false; + } + + @override + void visitConstructor(Constructor node) { + // Mixin class constructors are not needed, only mixin method bodies. + node.function.body = null; + + // Initializers can be removed in general, except for initializers of const + // constructors. Those are needed for constant evaluation in the CFE and + // proper canonicalization. + if (!node.isConst) { + node.initializers.clear(); + } + } + + @override + void visitProcedure(Procedure node) { + // Preserve method bodies of mixin declarations, these are copied when + // mixins are applied in subtypes. + if (!withinMixin) { + node.function.body = null; + } + } + + @override + void visitField(Field node) { + // Constant initializers are necessary for constant evaluation + if (node.isConst) return; + + // Unfortunately a `null` initializer may be misinterpreted by the CFE or + // the compiler. Ideally the kernel representation should have a sentinel + // marker so the actual initializer could be removed. + // + // These exceptions are a result of this issue: + // * Late final fields (may get an implicit setter) + // * Static fields (may change the code generated for accessing the field) + if (node.isLate && node.isFinal) return; + if (node.isStatic) return; + + node.initializer = null; + } +} + +/// Select [libraries] whose import belongs to any of the [patterns] and +/// any other transitively reachable library. +void addReachable(List libraries, bool Function(Library) isRoot, + Set result) { + List pending = [ + for (Library lib in libraries) + if (isRoot(lib)) lib + ]; + + while (!pending.isEmpty) { + Library lib = pending.removeLast(); + if (result.add(lib)) { + pending.addAll(lib.dependencies.map((dep) => dep.targetLibrary)); + } + } +} + +/// Options to configure the behavior of [createTrimmedCopy]. +class TrimOptions { + /// Path to the input dill file containing the application contents. + final String inputAppPath; + + /// Path to the input dill file containing the platform libraries. + final String inputPlatformPath; + + /// Path to the output dill file containing the application contents. + final String outputAppPath; + + /// Path to the output dill file containing the platform libraries. + final String? outputPlatformPath; + + /// Contents of the `dynamic_interface.yaml` file, used to compute required + /// user libraries. + /// + /// Must be null if [requiredUserLibraries] is not empty. + // Note: we do not provide a file-system path in order to support kernel files + // that use custom schemes (e.g. not `file:/`). + final String? dynamicInterfaceContents; + + /// Base uri of the `dynamic_interface.yaml` needed to resolve library + /// references within that file. This can be a `file:` or a custom scheme Uri. + final Uri? dynamicInterfaceUri; + + /// User libraries that must be preserved in the .dill file. + final Set requiredUserLibraries; + + /// Platform libraries that must be preserved in the .dill file. + /// + /// Leave empty to produce a .dill containing user-code only. + final Set requiredDartLibraries; + + /// Libraries that are not needed for production builds and that should + /// be possible to clear when trimming .dill files, even if they need + /// to be present in the dill file for other reasons. + final Set librariesToClear; + + TrimOptions({ + required this.inputAppPath, + required this.inputPlatformPath, + required this.outputAppPath, + required this.outputPlatformPath, + this.dynamicInterfaceContents, + this.dynamicInterfaceUri, + this.requiredUserLibraries = const {}, + required this.requiredDartLibraries, + this.librariesToClear = const {}, + }) { + if (dynamicInterfaceContents != null && requiredUserLibraries.isNotEmpty) { + throw new ArgumentError('Both dynamic interface and required user ' + 'libraries specified at once. Only one expected'); + } + if (dynamicInterfaceContents == null && requiredUserLibraries.isEmpty) { + throw new ArgumentError('Both dynamic interface and required user ' + 'libraries missing. Only one expected'); + } + } +} diff --git a/pkg/front_end/test/spell_checking_list_code.txt b/pkg/front_end/test/spell_checking_list_code.txt index 7a9269ce367..590c4b1d3ae 100644 --- a/pkg/front_end/test/spell_checking_list_code.txt +++ b/pkg/front_end/test/spell_checking_list_code.txt @@ -1107,6 +1107,7 @@ mini minutes misaligned misc +misinterpreted miss misses mistakes @@ -2093,6 +2094,7 @@ visitors visits visualize vm's +vmservice vn vs vtab diff --git a/pkg/front_end/test/spell_checking_list_common.txt b/pkg/front_end/test/spell_checking_list_common.txt index 63614eb2730..78b038fc39b 100644 --- a/pkg/front_end/test/spell_checking_list_common.txt +++ b/pkg/front_end/test/spell_checking_list_common.txt @@ -655,6 +655,7 @@ constuctor consume consumed consumer +consumes consuming contain contained @@ -813,6 +814,7 @@ delegates delegation delete deleted +deletes deliberately delimited delimiter @@ -1379,6 +1381,7 @@ gleaned global glorified go +goal goes going gone @@ -1387,6 +1390,7 @@ got gotten governed gracefully +grain grammar granular graph @@ -2261,6 +2265,7 @@ patches patching paths pattern +paying peek peel peeled @@ -3248,6 +3253,9 @@ trigger triggered trim trimmed +trimmer +trimming +trims trip triple triples diff --git a/pkg/front_end/tool/trim.dart b/pkg/front_end/tool/trim.dart new file mode 100644 index 00000000000..9f35e6b8c6b --- /dev/null +++ b/pkg/front_end/tool/trim.dart @@ -0,0 +1,81 @@ +#!/usr/bin/env dart +// Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'dart:io'; + +import 'package:args/args.dart'; +import 'package:front_end/src/util/trim.dart'; +import 'package:kernel/target/targets.dart'; +import 'package:vm/modular/target/flutter.dart'; + +/// Helper script to invoke [createTrimmedCopy] as a command-line tool given a +/// `dynamic_interface.yaml` input file. +Future main(List args) async { + ArgResults argResults; + try { + argResults = _argParser.parse(args); + } on ArgParserException catch (e) { + print(e.message); + print(_argParser.usage); + exit(1); + } + + if (args.length < 2) { + print(_argParser.usage); + exit(1); + } + String? dynamicInterfacePath = argResults['dynamic-interface'] as String?; + File? dynamicInterface = + dynamicInterfacePath != null ? File(dynamicInterfacePath) : null; + await createTrimmedCopy(TrimOptions( + inputAppPath: argResults['input'] as String, + inputPlatformPath: argResults['platform'] as String, + outputAppPath: argResults['output'] as String, + outputPlatformPath: argResults['output-platform'] as String?, + dynamicInterfaceUri: dynamicInterface?.uri, + dynamicInterfaceContents: dynamicInterface?.readAsStringSync(), + requiredUserLibraries: + (argResults['required-user-libraries'] as List).toSet(), + requiredDartLibraries: + FlutterTarget(TargetFlags()).extraRequiredLibraries.toSet(), + librariesToClear: + (argResults['clear-dart-library-body'] as List).toSet())); +} + +final ArgParser _argParser = ArgParser() + ..addOption('input', + help: 'Input application dill file path', mandatory: true) + ..addOption('platform', + help: 'Input platform dill file path', mandatory: true) + ..addOption('output', + help: 'Output application dill file path', mandatory: true) + ..addOption('output-platform', help: 'Output platform dill file path') + ..addOption('dynamic-interface', + help: 'Path to the dynamic_interface.yaml file') + ..addMultiOption('clear-dart-library-body', + abbr: 'c', + help: 'List of `dart:` that, even though are required, can be cleared ' + 'internally since they are only included for compatibility with ' + '"extraRequiredLibraries", but are not needed for compilation', + defaultsTo: defaultNonProductionLibraries) + ..addMultiOption('required-user-libraries', + abbr: 'u', + help: 'Alternative to providing a dynamic_interface.yaml input. ' + 'Specifies the list of necessary user written ' + 'libraries. Can be a full `package:` URI or a prefix pattern, ' + 'like `package:foo/*`.', + defaultsTo: const []); + +/// Libraries that are not needed for production builds and that should +/// be possible to clear when trimming .dill files. +const List defaultNonProductionLibraries = [ + 'dart:mirrors', + 'dart:developer', + 'dart:ffi', + 'dart:vmservice_io', + 'dart:isolate ', + 'dart:_vmservice', + 'dart:cli', +];