[kernel] Adds helper to trim dill files for modular dependencies.
Introduce a helper library to trim components based on what we believe it is needed for modular bytecode compilation. The script is configured to accept a set of entry points, so unreachable libraries can be removed entirely. The contents of the retained libraries is trimmed to remove method bodies, constructor bodies, and initializers, except for where they may be needed. In the near future, this should be expanded to: * include proper unit testing in the CFE * review whether additional trimming operations can be made * consider an explicit representation of trimmed content, to help the CFE recover when assumptions are not met (e.g. sentinel markers to establish whether a value has been trimmed) * CFE produces trimmed data directly if needed, without having to first produce the full dill. Tests that specifically stress that we don't over-trim include: apply_mixin (requires preserving method bodies), const_body (requires preserving initializers). TEST=existing and new e2e dynamic module aot tests. b/394936876 Change-Id: I26db8385bdfe1664b2aea234ec8bb896c7c21230 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/418702 Reviewed-by: Alexander Markov <alexmarkov@google.com> Commit-Queue: Sigmund Cherem <sigmund@google.com> Reviewed-by: Johnni Winther <johnniwinther@google.com>
This commit is contained in:
committed by
Commit Queue
parent
b46ee2cee8
commit
44f8e21c82
@@ -11,4 +11,7 @@ resolution: workspace
|
||||
dev_dependencies:
|
||||
args: any
|
||||
expect: any
|
||||
front_end: any
|
||||
kernel: any
|
||||
lints: any
|
||||
vm: any
|
||||
|
||||
@@ -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: '_'
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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);
|
||||
@@ -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);
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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',
|
||||
|
||||
@@ -61,7 +61,11 @@ void main(List<String> args) async {
|
||||
|
||||
final results = <DynamicModuleTestResult>[];
|
||||
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,
|
||||
|
||||
@@ -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<void> createTrimmedCopy(TrimOptions options) async {
|
||||
Component component = loadComponentFromBinary(options.inputPlatformPath);
|
||||
loadComponentFromBinary(options.inputAppPath, component);
|
||||
Set<Library> 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<Library> 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<void> emit(String path, bool isPlatform) async {
|
||||
Set<Library> 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<String> patterns) {
|
||||
List<String> prefixPatterns = patterns
|
||||
.where((p) => p.endsWith('*'))
|
||||
.map((p) => p.substring(0, p.length - 1))
|
||||
.toList();
|
||||
Set<String> 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<String> 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<Library> libraries, bool Function(Library) isRoot,
|
||||
Set<Library> result) {
|
||||
List<Library> 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<String> requiredUserLibraries;
|
||||
|
||||
/// Platform libraries that must be preserved in the .dill file.
|
||||
///
|
||||
/// Leave empty to produce a .dill containing user-code only.
|
||||
final Set<String> 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<String> 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');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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<void> main(List<String> 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<String>).toSet(),
|
||||
requiredDartLibraries:
|
||||
FlutterTarget(TargetFlags()).extraRequiredLibraries.toSet(),
|
||||
librariesToClear:
|
||||
(argResults['clear-dart-library-body'] as List<String>).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<String> defaultNonProductionLibraries = [
|
||||
'dart:mirrors',
|
||||
'dart:developer',
|
||||
'dart:ffi',
|
||||
'dart:vmservice_io',
|
||||
'dart:isolate ',
|
||||
'dart:_vmservice',
|
||||
'dart:cli',
|
||||
];
|
||||
Reference in New Issue
Block a user