9d3988eb67
Relies on https://dart-review.googlesource.com/c/sdk/+/96782 Change-Id: I820e884f96923f9eb9a856faef6442f6ed645fe1 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/96162 Commit-Queue: Sigurd Meldgaard <sigurdm@google.com> Reviewed-by: Martin Kustermann <kustermann@google.com>
170 lines
5.7 KiB
Dart
170 lines
5.7 KiB
Dart
#!/usr/bin/env dart
|
|
// Copyright (c) 2019, 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.
|
|
|
|
/// This program will take a .dill file and do a protobuf aware tree-shaking.
|
|
///
|
|
/// All fields of GeneratedMessage subclasses that are not accessed with their
|
|
/// getter or setter will have their metadata removed from the class definition.
|
|
///
|
|
/// Then a general treeshaking will be run, and
|
|
/// all GeneratedMessage subclasses that are never used directly will be
|
|
/// removed.
|
|
///
|
|
/// The processed program will have observable differences: The tree-shaken
|
|
/// fields will be parsed as unknown fields.
|
|
/// The toString method will treat the unknown fields as missing.
|
|
///
|
|
/// Using the `GeneratedMessage.info_` field to reflect on fields will have
|
|
/// unpredictable behavior.
|
|
///
|
|
/// Constants are evaluated, this is mainly to enable detecting
|
|
/// `@pragma('vm:entry-point')`.
|
|
library vm.protobuf_aware_treeshaker;
|
|
|
|
import 'dart:async';
|
|
import 'dart:io';
|
|
import 'dart:typed_data';
|
|
|
|
import 'package:args/args.dart';
|
|
import 'package:kernel/kernel.dart';
|
|
import 'package:kernel/binary/limited_ast_to_binary.dart';
|
|
import 'package:kernel/target/targets.dart' show TargetFlags, getTarget;
|
|
import 'package:meta/meta.dart';
|
|
import 'package:vm/target/install.dart' show installAdditionalTargets;
|
|
import 'package:vm/transformations/protobuf_aware_treeshaker/transformer.dart'
|
|
as treeshaker;
|
|
|
|
ArgResults parseArgs(List<String> args) {
|
|
ArgParser argParser = ArgParser()
|
|
..addOption('platform',
|
|
valueHelp: "path/to/vm_platform.dill",
|
|
help: 'A platform.dill file to append to the input. If not given, no '
|
|
'platform.dill will be appended.')
|
|
..addOption('target',
|
|
allowed: ['dart_runner', 'flutter', 'flutter-runner', 'vm'],
|
|
defaultsTo: 'vm',
|
|
help: 'A platform.dill file to append to the input. If not given, no '
|
|
'platform.dill will be appended.')
|
|
..addFlag('write-txt',
|
|
help: 'Also write the result in kernel-text format as <out.dill>.txt',
|
|
defaultsTo: false)
|
|
..addFlag('remove-core-libs',
|
|
help:
|
|
'If set, the resulting dill file will not include `dart:` libraries',
|
|
defaultsTo: false)
|
|
..addMultiOption('define',
|
|
abbr: 'D',
|
|
help: 'Perform constant evaluation with this environment define set.',
|
|
valueHelp: 'variable=value')
|
|
..addFlag('remove-source',
|
|
help: 'Removes source code from the emitted dill', defaultsTo: false)
|
|
..addFlag('enable-asserts',
|
|
help: 'Enables asserts in the emitted dill', defaultsTo: false)
|
|
..addFlag('verbose',
|
|
help: 'Write to stdout about what classes and fields where remeoved')
|
|
..addFlag('help', help: 'Prints this help', negatable: false);
|
|
|
|
ArgResults argResults;
|
|
try {
|
|
argResults = argParser.parse(args);
|
|
} on FormatException catch (e) {
|
|
print(e.message);
|
|
}
|
|
if (argResults == null || argResults['help'] || argResults.rest.length != 2) {
|
|
String script = 'protobuf_aware_treeshaker.dart';
|
|
print(
|
|
'A tool for removing protobuf messages types that are never referred by a program');
|
|
print('Usage: $script [args] <input.dill> <output.dill>');
|
|
|
|
print(argParser.usage);
|
|
exit(-1);
|
|
}
|
|
|
|
return argResults;
|
|
}
|
|
|
|
Future main(List<String> args) async {
|
|
ArgResults argResults = parseArgs(args);
|
|
|
|
final input = argResults.rest[0];
|
|
final output = argResults.rest[1];
|
|
|
|
final Map<String, String> environment = Map.fromIterable(
|
|
argResults['define'].map((x) => x.split('=')),
|
|
key: (x) => x[0],
|
|
value: (x) => x[1]);
|
|
|
|
var bytes = File(input).readAsBytesSync();
|
|
final platformFile = argResults['platform'];
|
|
if (platformFile != null) {
|
|
bytes = concatenate(File(platformFile).readAsBytesSync(), bytes);
|
|
}
|
|
final component = loadComponentFromBytes(bytes);
|
|
|
|
installAdditionalTargets();
|
|
|
|
treeshaker.TransformationInfo info = treeshaker.transformComponent(
|
|
component, environment, getTarget(argResults['target'], TargetFlags()),
|
|
collectInfo: argResults['verbose'],
|
|
enableAsserts: argResults['enable-asserts']);
|
|
|
|
if (argResults['verbose']) {
|
|
for (String fieldName in info.removedMessageFields) {
|
|
print('Removed $fieldName');
|
|
}
|
|
for (Class removedClass in info.removedMessageClasses) {
|
|
print('Removed $removedClass');
|
|
}
|
|
}
|
|
|
|
await writeComponent(component, output,
|
|
removeCoreLibs: argResults['remove-core-libs'],
|
|
removeSource: argResults['remove-source']);
|
|
if (argResults['write-txt']) {
|
|
writeComponentToText(component, path: output + '.txt');
|
|
}
|
|
}
|
|
|
|
Uint8List concatenate(Uint8List a, Uint8List b) {
|
|
final bytes = Uint8List(a.length + b.length);
|
|
bytes.setRange(0, a.length, a);
|
|
bytes.setRange(a.length, a.length + b.length, b);
|
|
return bytes;
|
|
}
|
|
|
|
Future writeComponent(Component component, String filename,
|
|
{@required bool removeCoreLibs, @required bool removeSource}) async {
|
|
if (removeSource) {
|
|
component.uriToSource.clear();
|
|
}
|
|
|
|
for (final lib in component.libraries) {
|
|
lib.dependencies.clear();
|
|
lib.additionalExports.clear();
|
|
lib.parts.clear();
|
|
}
|
|
|
|
final sink = File(filename).openWrite();
|
|
final printer = LimitedBinaryPrinter(sink, (lib) {
|
|
if (removeCoreLibs && isCoreLibrary(lib)) return false;
|
|
if (isLibEmpty(lib)) return false;
|
|
return true;
|
|
}, /*excludeUriToSource=*/ removeSource);
|
|
|
|
printer.writeComponentFile(component);
|
|
await sink.close();
|
|
}
|
|
|
|
bool isLibEmpty(Library lib) {
|
|
return lib.classes.isEmpty &&
|
|
lib.procedures.isEmpty &&
|
|
lib.fields.isEmpty &&
|
|
lib.typedefs.isEmpty;
|
|
}
|
|
|
|
bool isCoreLibrary(Library library) {
|
|
return library.importUri.scheme == 'dart';
|
|
}
|