mirror of
https://github.com/dart-lang/sdk
synced 2024-09-05 00:13:50 +00:00
Move most of the code from u/b/kernel_worker.dart to p/frontend_server
This should allow us to import the code in cases when we want to implement the Bazel worker loop ourselves. This opens some possibilities of extra optimizations for internal use cases. I'm leaving some of the code (e.g., `main`) in the old location, so that we do not break anything that depends on it. The only thing that I've done is to pass the input-digest map to `computeKernel` instead of using `Input` objects directly, this way we can avoid the dependency on `package:bazel_worker` from `frontend_server`. So now, if we're using the worker loop, the map will be computed by `kernel_worker.dart` (in non-worker mode, we rely on the default empty map). Bug: http://b/187910394 Change-Id: I408407ebdebc17c7898552c053a8e1c269cf2df5 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/201187 Commit-Queue: Michal Terepeta <michalt@google.com> Reviewed-by: Johnni Winther <johnniwinther@google.com>
This commit is contained in:
parent
b3de23ef14
commit
659fdae898
426
pkg/frontend_server/lib/compute_kernel.dart
Normal file
426
pkg/frontend_server/lib/compute_kernel.dart
Normal file
|
@ -0,0 +1,426 @@
|
|||
// Copyright (c) 2021, 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.
|
||||
|
||||
// @dart = 2.8
|
||||
|
||||
/// A library to invoke the CFE to compute kernel summary files.
|
||||
///
|
||||
/// Used by `utils/bazel/kernel_worker.dart`.
|
||||
|
||||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:args/args.dart';
|
||||
import 'package:build_integration/file_system/multi_root.dart';
|
||||
import 'package:compiler/src/kernel/dart2js_target.dart';
|
||||
import 'package:dev_compiler/src/kernel/target.dart';
|
||||
import 'package:front_end/src/api_unstable/bazel_worker.dart' as fe;
|
||||
import 'package:kernel/ast.dart' show Component, Library, Reference;
|
||||
import 'package:kernel/target/targets.dart';
|
||||
import 'package:vm/target/flutter.dart';
|
||||
import 'package:vm/target/flutter_runner.dart';
|
||||
import 'package:vm/target/vm.dart';
|
||||
|
||||
/// If the last arg starts with `@`, this reads the file it points to and treats
|
||||
/// each line as an additional arg.
|
||||
///
|
||||
/// This is how individual work request args are differentiated from startup
|
||||
/// args in bazel (individual work request args go in that file).
|
||||
List<String> preprocessArgs(List<String> args) {
|
||||
args = new List.from(args);
|
||||
if (args.isEmpty) {
|
||||
return args;
|
||||
}
|
||||
String lastArg = args.last;
|
||||
if (lastArg.startsWith('@')) {
|
||||
File argsFile = new File(lastArg.substring(1));
|
||||
try {
|
||||
args.removeLast();
|
||||
args.addAll(argsFile.readAsLinesSync());
|
||||
} on FileSystemException catch (e) {
|
||||
throw new Exception('Failed to read file specified by $lastArg : $e');
|
||||
}
|
||||
}
|
||||
return args;
|
||||
}
|
||||
|
||||
/// An [ArgParser] for generating kernel summaries.
|
||||
final summaryArgsParser = new ArgParser()
|
||||
..addFlag('help', negatable: false, abbr: 'h')
|
||||
..addFlag('exclude-non-sources',
|
||||
negatable: false,
|
||||
help: 'Whether source files loaded implicitly should be included as '
|
||||
'part of the summary.')
|
||||
..addFlag('summary-only',
|
||||
defaultsTo: true,
|
||||
negatable: true,
|
||||
help: 'Whether to only build summary files.')
|
||||
..addOption('target',
|
||||
allowed: const [
|
||||
'vm',
|
||||
'flutter',
|
||||
'flutter_runner',
|
||||
'dart2js',
|
||||
'ddc',
|
||||
],
|
||||
help: 'Build kernel for the vm, flutter, flutter_runner, dart2js or ddc')
|
||||
..addOption('dart-sdk-summary')
|
||||
..addMultiOption('input-summary')
|
||||
..addMultiOption('input-linked')
|
||||
..addMultiOption('multi-root')
|
||||
..addOption('multi-root-scheme', defaultsTo: 'org-dartlang-multi-root')
|
||||
..addOption('libraries-file')
|
||||
..addOption('packages-file')
|
||||
..addMultiOption('source')
|
||||
..addOption('output')
|
||||
..addFlag('reuse-compiler-result', defaultsTo: false)
|
||||
..addFlag('use-incremental-compiler', defaultsTo: false)
|
||||
..addOption('used-inputs')
|
||||
..addFlag('track-widget-creation', defaultsTo: false)
|
||||
..addMultiOption('enable-experiment',
|
||||
help: 'Enable a language experiment when invoking the CFE.')
|
||||
..addMultiOption('define', abbr: 'D')
|
||||
..addFlag('verbose', defaultsTo: false)
|
||||
..addFlag('sound-null-safety', defaultsTo: false)
|
||||
..addOption('verbosity',
|
||||
defaultsTo: fe.Verbosity.defaultValue,
|
||||
help: 'Sets the verbosity level used for filtering messages during '
|
||||
'compilation.',
|
||||
allowed: fe.Verbosity.allowedValues,
|
||||
allowedHelp: fe.Verbosity.allowedValuesHelp);
|
||||
|
||||
class ComputeKernelResult {
|
||||
final bool succeeded;
|
||||
final fe.InitializedCompilerState previousState;
|
||||
|
||||
ComputeKernelResult(this.succeeded, this.previousState);
|
||||
}
|
||||
|
||||
/// Computes a kernel file based on [args].
|
||||
///
|
||||
/// If [isWorker] is true then exit codes will not be set on failure.
|
||||
///
|
||||
/// If [outputBuffer] is provided then messages will be written to that buffer
|
||||
/// instead of printed to the console.
|
||||
///
|
||||
/// Returns whether or not the summary was successfully output.
|
||||
Future<ComputeKernelResult> computeKernel(List<String> args,
|
||||
{bool isWorker: false,
|
||||
StringBuffer outputBuffer,
|
||||
Map<Uri, List<int>> inputDigests,
|
||||
fe.InitializedCompilerState previousState}) async {
|
||||
inputDigests ??= <Uri, List<int>>{};
|
||||
dynamic out = outputBuffer ?? stderr;
|
||||
bool succeeded = true;
|
||||
|
||||
var parsedArgs = summaryArgsParser.parse(args);
|
||||
|
||||
if (parsedArgs['help']) {
|
||||
out.writeln(summaryArgsParser.usage);
|
||||
if (!isWorker) exit(0);
|
||||
return new ComputeKernelResult(false, previousState);
|
||||
}
|
||||
|
||||
// Bazel creates an overlay file system where some files may be located in the
|
||||
// source tree, some in a gendir, and some in a bindir. The multi-root file
|
||||
// system hides this from the front end.
|
||||
var multiRoots = parsedArgs['multi-root'].map(Uri.base.resolve).toList();
|
||||
if (multiRoots.isEmpty) multiRoots.add(Uri.base);
|
||||
var fileSystem = new MultiRootFileSystem(parsedArgs['multi-root-scheme'],
|
||||
multiRoots, fe.StandardFileSystem.instance);
|
||||
var sources = (parsedArgs['source'] as List<String>).map(toUri).toList();
|
||||
var excludeNonSources = parsedArgs['exclude-non-sources'] as bool;
|
||||
|
||||
var nnbdMode = parsedArgs['sound-null-safety'] as bool
|
||||
? fe.NnbdMode.Strong
|
||||
: fe.NnbdMode.Weak;
|
||||
var summaryOnly = parsedArgs['summary-only'] as bool;
|
||||
var trackWidgetCreation = parsedArgs['track-widget-creation'] as bool;
|
||||
|
||||
// TODO(sigmund,jakemac): make target mandatory. We allow null to be backwards
|
||||
// compatible while we migrate existing clients of this tool.
|
||||
var targetName =
|
||||
(parsedArgs['target'] as String) ?? (summaryOnly ? 'ddc' : 'vm');
|
||||
var targetFlags = new TargetFlags(
|
||||
trackWidgetCreation: trackWidgetCreation,
|
||||
enableNullSafety: nnbdMode == fe.NnbdMode.Strong);
|
||||
Target target;
|
||||
switch (targetName) {
|
||||
case 'vm':
|
||||
target = new VmTarget(targetFlags);
|
||||
if (summaryOnly) {
|
||||
out.writeln('error: --summary-only not supported for the vm target');
|
||||
}
|
||||
break;
|
||||
case 'flutter':
|
||||
target = new FlutterTarget(targetFlags);
|
||||
if (summaryOnly) {
|
||||
throw new ArgumentError(
|
||||
'error: --summary-only not supported for the flutter target');
|
||||
}
|
||||
break;
|
||||
case 'flutter_runner':
|
||||
target = new FlutterRunnerTarget(targetFlags);
|
||||
if (summaryOnly) {
|
||||
throw new ArgumentError('error: --summary-only not supported for the '
|
||||
'flutter_runner target');
|
||||
}
|
||||
break;
|
||||
case 'dart2js':
|
||||
target = new Dart2jsTarget('dart2js', targetFlags);
|
||||
if (summaryOnly) {
|
||||
out.writeln(
|
||||
'error: --summary-only not supported for the dart2js target');
|
||||
}
|
||||
break;
|
||||
case 'ddc':
|
||||
// TODO(jakemac):If `generateKernel` changes to return a summary
|
||||
// component, process the component instead.
|
||||
target =
|
||||
new DevCompilerSummaryTarget(sources, excludeNonSources, targetFlags);
|
||||
if (!summaryOnly) {
|
||||
out.writeln('error: --no-summary-only not supported for the '
|
||||
'ddc target');
|
||||
}
|
||||
break;
|
||||
default:
|
||||
out.writeln('error: unsupported target: $targetName');
|
||||
}
|
||||
|
||||
List<Uri> linkedInputs =
|
||||
(parsedArgs['input-linked'] as List<String>).map(toUri).toList();
|
||||
|
||||
List<Uri> summaryInputs =
|
||||
(parsedArgs['input-summary'] as List<String>).map(toUri).toList();
|
||||
|
||||
fe.InitializedCompilerState state;
|
||||
bool usingIncrementalCompiler = false;
|
||||
bool recordUsedInputs = parsedArgs["used-inputs"] != null;
|
||||
var environmentDefines = _parseEnvironmentDefines(parsedArgs['define']);
|
||||
var verbose = parsedArgs['verbose'] as bool;
|
||||
var verbosity = fe.Verbosity.parseArgument(parsedArgs['verbosity']);
|
||||
|
||||
if (parsedArgs['use-incremental-compiler']) {
|
||||
usingIncrementalCompiler = true;
|
||||
|
||||
// If digests weren't given and if not in worker mode, create fake data and
|
||||
// ensure we don't have a previous state (as that wouldn't be safe with
|
||||
// fake input digests).
|
||||
if (!isWorker && inputDigests.isEmpty) {
|
||||
previousState = null;
|
||||
inputDigests[toUri(parsedArgs['dart-sdk-summary'])] = const [0];
|
||||
for (Uri uri in summaryInputs) {
|
||||
inputDigests[uri] = const [0];
|
||||
}
|
||||
for (Uri uri in linkedInputs) {
|
||||
inputDigests[uri] = const [0];
|
||||
}
|
||||
}
|
||||
|
||||
state = await fe.initializeIncrementalCompiler(
|
||||
previousState,
|
||||
{
|
||||
"target=$targetName",
|
||||
"trackWidgetCreation=$trackWidgetCreation",
|
||||
"multiRootScheme=${fileSystem.markerScheme}",
|
||||
"multiRootRoots=${fileSystem.roots}",
|
||||
},
|
||||
toUri(parsedArgs['dart-sdk-summary']),
|
||||
toUri(parsedArgs['packages-file']),
|
||||
toUri(parsedArgs['libraries-file']),
|
||||
[...summaryInputs, ...linkedInputs],
|
||||
inputDigests,
|
||||
target,
|
||||
fileSystem,
|
||||
(parsedArgs['enable-experiment'] as List<String>),
|
||||
summaryOnly,
|
||||
environmentDefines,
|
||||
trackNeededDillLibraries: recordUsedInputs,
|
||||
verbose: verbose,
|
||||
nnbdMode: nnbdMode);
|
||||
} else {
|
||||
state = await fe.initializeCompiler(
|
||||
// TODO(sigmund): pass an old state once we can make use of it.
|
||||
null,
|
||||
toUri(parsedArgs['dart-sdk-summary']),
|
||||
toUri(parsedArgs['libraries-file']),
|
||||
toUri(parsedArgs['packages-file']),
|
||||
[...summaryInputs, ...linkedInputs],
|
||||
target,
|
||||
fileSystem,
|
||||
parsedArgs['enable-experiment'] as List<String>,
|
||||
environmentDefines,
|
||||
verbose: verbose,
|
||||
nnbdMode: nnbdMode);
|
||||
}
|
||||
|
||||
void onDiagnostic(fe.DiagnosticMessage message) {
|
||||
if (fe.Verbosity.shouldPrint(verbosity, message)) {
|
||||
fe.printDiagnosticMessage(message, out.writeln);
|
||||
}
|
||||
if (message.severity == fe.Severity.error) {
|
||||
succeeded = false;
|
||||
}
|
||||
}
|
||||
|
||||
List<int> kernel;
|
||||
bool wroteUsedDills = false;
|
||||
if (usingIncrementalCompiler) {
|
||||
state.options.onDiagnostic = onDiagnostic;
|
||||
Component incrementalComponent = await state.incrementalCompiler
|
||||
.computeDelta(entryPoints: sources, fullComponent: true);
|
||||
|
||||
if (recordUsedInputs) {
|
||||
Set<Uri> usedOutlines = {};
|
||||
for (Library lib in state.incrementalCompiler.neededDillLibraries) {
|
||||
if (lib.importUri.scheme == "dart") continue;
|
||||
Uri uri = state.libraryToInputDill[lib.importUri];
|
||||
if (uri == null) {
|
||||
throw new StateError("Library ${lib.importUri} was recorded as used, "
|
||||
"but was not in the list of known libraries.");
|
||||
}
|
||||
usedOutlines.add(uri);
|
||||
}
|
||||
var outputUsedFile = new File(parsedArgs["used-inputs"]);
|
||||
outputUsedFile.createSync(recursive: true);
|
||||
outputUsedFile.writeAsStringSync(usedOutlines.join("\n"));
|
||||
wroteUsedDills = true;
|
||||
}
|
||||
|
||||
kernel = await state.incrementalCompiler.context.runInContext((_) {
|
||||
if (summaryOnly) {
|
||||
incrementalComponent.uriToSource.clear();
|
||||
incrementalComponent.problemsAsJson = null;
|
||||
incrementalComponent.setMainMethodAndMode(
|
||||
null, true, incrementalComponent.mode);
|
||||
target.performOutlineTransformations(incrementalComponent);
|
||||
makeStable(incrementalComponent);
|
||||
return Future.value(fe.serializeComponent(incrementalComponent,
|
||||
includeSources: false, includeOffsets: false));
|
||||
}
|
||||
|
||||
makeStable(incrementalComponent);
|
||||
|
||||
return Future.value(fe.serializeComponent(incrementalComponent,
|
||||
filter: excludeNonSources
|
||||
? (library) => sources.contains(library.importUri)
|
||||
: null,
|
||||
includeOffsets: true));
|
||||
});
|
||||
} else if (summaryOnly) {
|
||||
kernel = await fe.compileSummary(state, sources, onDiagnostic,
|
||||
includeOffsets: false);
|
||||
} else {
|
||||
Component component =
|
||||
await fe.compileComponent(state, sources, onDiagnostic);
|
||||
kernel = fe.serializeComponent(component,
|
||||
filter: excludeNonSources
|
||||
? (library) => sources.contains(library.importUri)
|
||||
: null,
|
||||
includeOffsets: true);
|
||||
}
|
||||
state.options.onDiagnostic = null; // See http://dartbug.com/36983.
|
||||
|
||||
if (!wroteUsedDills && recordUsedInputs) {
|
||||
// The path taken didn't record inputs used: Say we used everything.
|
||||
var outputUsedFile = new File(parsedArgs["used-inputs"]);
|
||||
outputUsedFile.createSync(recursive: true);
|
||||
Set<Uri> allFiles = {...summaryInputs, ...linkedInputs};
|
||||
outputUsedFile.writeAsStringSync(allFiles.join("\n"));
|
||||
wroteUsedDills = true;
|
||||
}
|
||||
|
||||
if (kernel != null) {
|
||||
var outputFile = new File(parsedArgs['output']);
|
||||
outputFile.createSync(recursive: true);
|
||||
outputFile.writeAsBytesSync(kernel);
|
||||
} else {
|
||||
assert(!succeeded);
|
||||
}
|
||||
|
||||
return new ComputeKernelResult(succeeded, state);
|
||||
}
|
||||
|
||||
/// Make sure the output is stable by sorting libraries and additional exports.
|
||||
void makeStable(Component c) {
|
||||
// Make sure the output is stable.
|
||||
c.libraries.sort((l1, l2) {
|
||||
return "${l1.fileUri}".compareTo("${l2.fileUri}");
|
||||
});
|
||||
c.problemsAsJson?.sort();
|
||||
c.computeCanonicalNames();
|
||||
for (Library library in c.libraries) {
|
||||
library.additionalExports.sort((Reference r1, Reference r2) {
|
||||
return "${r1.canonicalName}".compareTo("${r2.canonicalName}");
|
||||
});
|
||||
library.problemsAsJson?.sort();
|
||||
}
|
||||
}
|
||||
|
||||
/// Extends the DevCompilerTarget to transform outlines to meet the requirements
|
||||
/// of summaries in bazel and package-build.
|
||||
///
|
||||
/// Build systems like package-build may provide the same input file twice to
|
||||
/// the summary worker, but only intends to have it in one output summary. The
|
||||
/// convention is that if it is listed as a source, it is intended to be part of
|
||||
/// the output, if the source file was loaded as a dependency, then it was
|
||||
/// already included in a different summary. The transformation below ensures
|
||||
/// that the output summary doesn't include those implicit inputs.
|
||||
///
|
||||
/// Note: this transformation is destructive and is only intended to be used
|
||||
/// when generating summaries.
|
||||
class DevCompilerSummaryTarget extends DevCompilerTarget {
|
||||
final List<Uri> sources;
|
||||
final bool excludeNonSources;
|
||||
|
||||
DevCompilerSummaryTarget(
|
||||
this.sources, this.excludeNonSources, TargetFlags targetFlags)
|
||||
: super(targetFlags);
|
||||
|
||||
@override
|
||||
void performOutlineTransformations(Component component) {
|
||||
super.performOutlineTransformations(component);
|
||||
if (!excludeNonSources) return;
|
||||
|
||||
List<Library> libraries = new List.from(component.libraries);
|
||||
component.libraries.clear();
|
||||
Set<Uri> include = sources.toSet();
|
||||
for (var lib in libraries) {
|
||||
if (include.contains(lib.importUri)) {
|
||||
component.libraries.add(lib);
|
||||
} else {
|
||||
// Excluding the library also means that their canonical names will not
|
||||
// be computed as part of serialization, so we need to do that
|
||||
// preemtively here to avoid errors when serializing references to
|
||||
// elements of these libraries.
|
||||
component.root.getChildFromUri(lib.importUri).bindTo(lib.reference);
|
||||
lib.computeCanonicalNames();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Uri toUri(String uriString) {
|
||||
if (uriString == null) return null;
|
||||
// Windows-style paths use '\', so convert them to '/' in case they've been
|
||||
// concatenated with Unix-style paths.
|
||||
return Uri.base.resolve(uriString.replaceAll("\\", "/"));
|
||||
}
|
||||
|
||||
Map<String, String> _parseEnvironmentDefines(List<String> args) {
|
||||
var environment = <String, String>{};
|
||||
|
||||
for (var arg in args) {
|
||||
var eq = arg.indexOf('=');
|
||||
if (eq <= 0) {
|
||||
var kind = eq == 0 ? 'name' : 'value';
|
||||
throw FormatException('no $kind given to -D option `$arg`');
|
||||
}
|
||||
var name = arg.substring(0, eq);
|
||||
var value = arg.substring(eq + 1);
|
||||
environment[name] = value;
|
||||
}
|
||||
|
||||
return environment;
|
||||
}
|
|
@ -8,6 +8,10 @@ environment:
|
|||
|
||||
dependencies:
|
||||
args: ^1.4.4
|
||||
build_integration:
|
||||
path: ../build_integration
|
||||
compiler:
|
||||
path: ../compiler
|
||||
dev_compiler:
|
||||
path: ../dev_compiler
|
||||
front_end:
|
||||
|
|
|
@ -15,17 +15,9 @@ import 'dart:async';
|
|||
import 'dart:io';
|
||||
import 'dart:isolate';
|
||||
|
||||
import 'package:args/args.dart';
|
||||
import 'package:bazel_worker/bazel_worker.dart';
|
||||
import 'package:build_integration/file_system/multi_root.dart';
|
||||
import 'package:dev_compiler/src/kernel/target.dart';
|
||||
import 'package:front_end/src/api_unstable/bazel_worker.dart' as fe;
|
||||
import 'package:kernel/ast.dart' show Component, Library, Reference;
|
||||
import 'package:kernel/target/targets.dart';
|
||||
import 'package:vm/target/vm.dart';
|
||||
import 'package:vm/target/flutter.dart';
|
||||
import 'package:vm/target/flutter_runner.dart';
|
||||
import 'package:compiler/src/kernel/dart2js_target.dart';
|
||||
import 'package:frontend_server/compute_kernel.dart';
|
||||
|
||||
/// [sendPort] may be passed in when started in an isolate. If provided, it is
|
||||
/// used for bazel worker communication instead of stdin/stdout.
|
||||
|
@ -68,10 +60,17 @@ class KernelWorker extends AsyncWorkerLoop {
|
|||
} else {
|
||||
previousState = null;
|
||||
}
|
||||
|
||||
/// Build a map of uris to digests.
|
||||
final inputDigests = <Uri, List<int>>{};
|
||||
for (var input in request.inputs) {
|
||||
inputDigests[toUri(input.path)] = input.digest;
|
||||
}
|
||||
|
||||
var result = await computeKernel(request.arguments,
|
||||
isWorker: true,
|
||||
outputBuffer: outputBuffer,
|
||||
inputs: request.inputs,
|
||||
inputDigests: inputDigests,
|
||||
previousState: previousStateToPass);
|
||||
previousState = result.previousState;
|
||||
if (!result.succeeded) {
|
||||
|
@ -86,413 +85,3 @@ class KernelWorker extends AsyncWorkerLoop {
|
|||
return response;
|
||||
}
|
||||
}
|
||||
|
||||
/// If the last arg starts with `@`, this reads the file it points to and treats
|
||||
/// each line as an additional arg.
|
||||
///
|
||||
/// This is how individual work request args are differentiated from startup
|
||||
/// args in bazel (inidividual work request args go in that file).
|
||||
List<String> preprocessArgs(List<String> args) {
|
||||
args = new List.from(args);
|
||||
if (args.isEmpty) {
|
||||
return args;
|
||||
}
|
||||
String lastArg = args.last;
|
||||
if (lastArg.startsWith('@')) {
|
||||
File argsFile = new File(lastArg.substring(1));
|
||||
try {
|
||||
args.removeLast();
|
||||
args.addAll(argsFile.readAsLinesSync());
|
||||
} on FileSystemException catch (e) {
|
||||
throw new Exception('Failed to read file specified by $lastArg : $e');
|
||||
}
|
||||
}
|
||||
return args;
|
||||
}
|
||||
|
||||
/// An [ArgParser] for generating kernel summaries.
|
||||
final summaryArgsParser = new ArgParser()
|
||||
..addFlag('help', negatable: false, abbr: 'h')
|
||||
..addFlag('exclude-non-sources',
|
||||
negatable: false,
|
||||
help: 'Whether source files loaded implicitly should be included as '
|
||||
'part of the summary.')
|
||||
..addFlag('summary-only',
|
||||
defaultsTo: true,
|
||||
negatable: true,
|
||||
help: 'Whether to only build summary files.')
|
||||
..addOption('target',
|
||||
allowed: const [
|
||||
'vm',
|
||||
'flutter',
|
||||
'flutter_runner',
|
||||
'dart2js',
|
||||
'ddc',
|
||||
],
|
||||
help: 'Build kernel for the vm, flutter, flutter_runner, dart2js or ddc')
|
||||
..addOption('dart-sdk-summary')
|
||||
..addMultiOption('input-summary')
|
||||
..addMultiOption('input-linked')
|
||||
..addMultiOption('multi-root')
|
||||
..addOption('multi-root-scheme', defaultsTo: 'org-dartlang-multi-root')
|
||||
..addOption('libraries-file')
|
||||
..addOption('packages-file')
|
||||
..addMultiOption('source')
|
||||
..addOption('output')
|
||||
..addFlag('reuse-compiler-result', defaultsTo: false)
|
||||
..addFlag('use-incremental-compiler', defaultsTo: false)
|
||||
..addOption('used-inputs')
|
||||
..addFlag('track-widget-creation', defaultsTo: false)
|
||||
..addMultiOption('enable-experiment',
|
||||
help: 'Enable a language experiment when invoking the CFE.')
|
||||
..addMultiOption('define', abbr: 'D')
|
||||
..addFlag('verbose', defaultsTo: false)
|
||||
..addFlag('sound-null-safety', defaultsTo: false)
|
||||
..addOption('verbosity',
|
||||
defaultsTo: fe.Verbosity.defaultValue,
|
||||
help: 'Sets the verbosity level used for filtering messages during '
|
||||
'compilation.',
|
||||
allowed: fe.Verbosity.allowedValues,
|
||||
allowedHelp: fe.Verbosity.allowedValuesHelp);
|
||||
|
||||
class ComputeKernelResult {
|
||||
final bool succeeded;
|
||||
final fe.InitializedCompilerState previousState;
|
||||
|
||||
ComputeKernelResult(this.succeeded, this.previousState);
|
||||
}
|
||||
|
||||
/// Computes a kernel file based on [args].
|
||||
///
|
||||
/// If [isWorker] is true then exit codes will not be set on failure.
|
||||
///
|
||||
/// If [outputBuffer] is provided then messages will be written to that buffer
|
||||
/// instead of printed to the console.
|
||||
///
|
||||
/// Returns whether or not the summary was successfully output.
|
||||
Future<ComputeKernelResult> computeKernel(List<String> args,
|
||||
{bool isWorker: false,
|
||||
StringBuffer outputBuffer,
|
||||
Iterable<Input> inputs,
|
||||
fe.InitializedCompilerState previousState}) async {
|
||||
dynamic out = outputBuffer ?? stderr;
|
||||
bool succeeded = true;
|
||||
|
||||
var parsedArgs = summaryArgsParser.parse(args);
|
||||
|
||||
if (parsedArgs['help']) {
|
||||
out.writeln(summaryArgsParser.usage);
|
||||
if (!isWorker) exit(0);
|
||||
return new ComputeKernelResult(false, previousState);
|
||||
}
|
||||
|
||||
// Bazel creates an overlay file system where some files may be located in the
|
||||
// source tree, some in a gendir, and some in a bindir. The multi-root file
|
||||
// system hides this from the front end.
|
||||
var multiRoots = parsedArgs['multi-root'].map(Uri.base.resolve).toList();
|
||||
if (multiRoots.isEmpty) multiRoots.add(Uri.base);
|
||||
var fileSystem = new MultiRootFileSystem(parsedArgs['multi-root-scheme'],
|
||||
multiRoots, fe.StandardFileSystem.instance);
|
||||
var sources = (parsedArgs['source'] as List<String>).map(_toUri).toList();
|
||||
var excludeNonSources = parsedArgs['exclude-non-sources'] as bool;
|
||||
|
||||
var nnbdMode = parsedArgs['sound-null-safety'] as bool
|
||||
? fe.NnbdMode.Strong
|
||||
: fe.NnbdMode.Weak;
|
||||
var summaryOnly = parsedArgs['summary-only'] as bool;
|
||||
var trackWidgetCreation = parsedArgs['track-widget-creation'] as bool;
|
||||
|
||||
// TODO(sigmund,jakemac): make target mandatory. We allow null to be backwards
|
||||
// compatible while we migrate existing clients of this tool.
|
||||
var targetName =
|
||||
(parsedArgs['target'] as String) ?? (summaryOnly ? 'ddc' : 'vm');
|
||||
var targetFlags = new TargetFlags(
|
||||
trackWidgetCreation: trackWidgetCreation,
|
||||
enableNullSafety: nnbdMode == fe.NnbdMode.Strong);
|
||||
Target target;
|
||||
switch (targetName) {
|
||||
case 'vm':
|
||||
target = new VmTarget(targetFlags);
|
||||
if (summaryOnly) {
|
||||
out.writeln('error: --summary-only not supported for the vm target');
|
||||
}
|
||||
break;
|
||||
case 'flutter':
|
||||
target = new FlutterTarget(targetFlags);
|
||||
if (summaryOnly) {
|
||||
throw new ArgumentError(
|
||||
'error: --summary-only not supported for the flutter target');
|
||||
}
|
||||
break;
|
||||
case 'flutter_runner':
|
||||
target = new FlutterRunnerTarget(targetFlags);
|
||||
if (summaryOnly) {
|
||||
throw new ArgumentError('error: --summary-only not supported for the '
|
||||
'flutter_runner target');
|
||||
}
|
||||
break;
|
||||
case 'dart2js':
|
||||
target = new Dart2jsTarget('dart2js', targetFlags);
|
||||
if (summaryOnly) {
|
||||
out.writeln(
|
||||
'error: --summary-only not supported for the dart2js target');
|
||||
}
|
||||
break;
|
||||
case 'ddc':
|
||||
// TODO(jakemac):If `generateKernel` changes to return a summary
|
||||
// component, process the component instead.
|
||||
target =
|
||||
new DevCompilerSummaryTarget(sources, excludeNonSources, targetFlags);
|
||||
if (!summaryOnly) {
|
||||
out.writeln('error: --no-summary-only not supported for the '
|
||||
'ddc target');
|
||||
}
|
||||
break;
|
||||
default:
|
||||
out.writeln('error: unsupported target: $targetName');
|
||||
}
|
||||
|
||||
List<Uri> linkedInputs =
|
||||
(parsedArgs['input-linked'] as List<String>).map(_toUri).toList();
|
||||
|
||||
List<Uri> summaryInputs =
|
||||
(parsedArgs['input-summary'] as List<String>).map(_toUri).toList();
|
||||
|
||||
fe.InitializedCompilerState state;
|
||||
bool usingIncrementalCompiler = false;
|
||||
bool recordUsedInputs = parsedArgs["used-inputs"] != null;
|
||||
var environmentDefines = _parseEnvironmentDefines(parsedArgs['define']);
|
||||
var verbose = parsedArgs['verbose'] as bool;
|
||||
var verbosity = fe.Verbosity.parseArgument(parsedArgs['verbosity']);
|
||||
|
||||
if (parsedArgs['use-incremental-compiler']) {
|
||||
usingIncrementalCompiler = true;
|
||||
|
||||
/// Build a map of uris to digests.
|
||||
final inputDigests = <Uri, List<int>>{};
|
||||
if (inputs != null) {
|
||||
for (var input in inputs) {
|
||||
inputDigests[_toUri(input.path)] = input.digest;
|
||||
}
|
||||
}
|
||||
|
||||
// If digests weren't given and if not in worker mode, create fake data and
|
||||
// ensure we don't have a previous state (as that wouldn't be safe with
|
||||
// fake input digests).
|
||||
if (!isWorker && inputDigests.isEmpty) {
|
||||
previousState = null;
|
||||
inputDigests[_toUri(parsedArgs['dart-sdk-summary'])] = const [0];
|
||||
for (Uri uri in summaryInputs) {
|
||||
inputDigests[uri] = const [0];
|
||||
}
|
||||
for (Uri uri in linkedInputs) {
|
||||
inputDigests[uri] = const [0];
|
||||
}
|
||||
}
|
||||
|
||||
state = await fe.initializeIncrementalCompiler(
|
||||
previousState,
|
||||
{
|
||||
"target=$targetName",
|
||||
"trackWidgetCreation=$trackWidgetCreation",
|
||||
"multiRootScheme=${fileSystem.markerScheme}",
|
||||
"multiRootRoots=${fileSystem.roots}",
|
||||
},
|
||||
_toUri(parsedArgs['dart-sdk-summary']),
|
||||
_toUri(parsedArgs['packages-file']),
|
||||
_toUri(parsedArgs['libraries-file']),
|
||||
[...summaryInputs, ...linkedInputs],
|
||||
inputDigests,
|
||||
target,
|
||||
fileSystem,
|
||||
(parsedArgs['enable-experiment'] as List<String>),
|
||||
summaryOnly,
|
||||
environmentDefines,
|
||||
trackNeededDillLibraries: recordUsedInputs,
|
||||
verbose: verbose,
|
||||
nnbdMode: nnbdMode);
|
||||
} else {
|
||||
state = await fe.initializeCompiler(
|
||||
// TODO(sigmund): pass an old state once we can make use of it.
|
||||
null,
|
||||
_toUri(parsedArgs['dart-sdk-summary']),
|
||||
_toUri(parsedArgs['libraries-file']),
|
||||
_toUri(parsedArgs['packages-file']),
|
||||
[...summaryInputs, ...linkedInputs],
|
||||
target,
|
||||
fileSystem,
|
||||
parsedArgs['enable-experiment'] as List<String>,
|
||||
environmentDefines,
|
||||
verbose: verbose,
|
||||
nnbdMode: nnbdMode);
|
||||
}
|
||||
|
||||
void onDiagnostic(fe.DiagnosticMessage message) {
|
||||
if (fe.Verbosity.shouldPrint(verbosity, message)) {
|
||||
fe.printDiagnosticMessage(message, out.writeln);
|
||||
}
|
||||
if (message.severity == fe.Severity.error) {
|
||||
succeeded = false;
|
||||
}
|
||||
}
|
||||
|
||||
List<int> kernel;
|
||||
bool wroteUsedDills = false;
|
||||
if (usingIncrementalCompiler) {
|
||||
state.options.onDiagnostic = onDiagnostic;
|
||||
Component incrementalComponent = await state.incrementalCompiler
|
||||
.computeDelta(entryPoints: sources, fullComponent: true);
|
||||
|
||||
if (recordUsedInputs) {
|
||||
Set<Uri> usedOutlines = {};
|
||||
for (Library lib in state.incrementalCompiler.neededDillLibraries) {
|
||||
if (lib.importUri.scheme == "dart") continue;
|
||||
Uri uri = state.libraryToInputDill[lib.importUri];
|
||||
if (uri == null) {
|
||||
throw new StateError("Library ${lib.importUri} was recorded as used, "
|
||||
"but was not in the list of known libraries.");
|
||||
}
|
||||
usedOutlines.add(uri);
|
||||
}
|
||||
var outputUsedFile = new File(parsedArgs["used-inputs"]);
|
||||
outputUsedFile.createSync(recursive: true);
|
||||
outputUsedFile.writeAsStringSync(usedOutlines.join("\n"));
|
||||
wroteUsedDills = true;
|
||||
}
|
||||
|
||||
kernel = await state.incrementalCompiler.context.runInContext((_) {
|
||||
if (summaryOnly) {
|
||||
incrementalComponent.uriToSource.clear();
|
||||
incrementalComponent.problemsAsJson = null;
|
||||
incrementalComponent.setMainMethodAndMode(
|
||||
null, true, incrementalComponent.mode);
|
||||
target.performOutlineTransformations(incrementalComponent);
|
||||
makeStable(incrementalComponent);
|
||||
return Future.value(fe.serializeComponent(incrementalComponent,
|
||||
includeSources: false, includeOffsets: false));
|
||||
}
|
||||
|
||||
makeStable(incrementalComponent);
|
||||
|
||||
return Future.value(fe.serializeComponent(incrementalComponent,
|
||||
filter: excludeNonSources
|
||||
? (library) => sources.contains(library.importUri)
|
||||
: null,
|
||||
includeOffsets: true));
|
||||
});
|
||||
} else if (summaryOnly) {
|
||||
kernel = await fe.compileSummary(state, sources, onDiagnostic,
|
||||
includeOffsets: false);
|
||||
} else {
|
||||
Component component =
|
||||
await fe.compileComponent(state, sources, onDiagnostic);
|
||||
kernel = fe.serializeComponent(component,
|
||||
filter: excludeNonSources
|
||||
? (library) => sources.contains(library.importUri)
|
||||
: null,
|
||||
includeOffsets: true);
|
||||
}
|
||||
state.options.onDiagnostic = null; // See http://dartbug.com/36983.
|
||||
|
||||
if (!wroteUsedDills && recordUsedInputs) {
|
||||
// The path taken didn't record inputs used: Say we used everything.
|
||||
var outputUsedFile = new File(parsedArgs["used-inputs"]);
|
||||
outputUsedFile.createSync(recursive: true);
|
||||
Set<Uri> allFiles = {...summaryInputs, ...linkedInputs};
|
||||
outputUsedFile.writeAsStringSync(allFiles.join("\n"));
|
||||
wroteUsedDills = true;
|
||||
}
|
||||
|
||||
if (kernel != null) {
|
||||
var outputFile = new File(parsedArgs['output']);
|
||||
outputFile.createSync(recursive: true);
|
||||
outputFile.writeAsBytesSync(kernel);
|
||||
} else {
|
||||
assert(!succeeded);
|
||||
}
|
||||
|
||||
return new ComputeKernelResult(succeeded, state);
|
||||
}
|
||||
|
||||
/// Make sure the output is stable by sorting libraries and additional exports.
|
||||
void makeStable(Component c) {
|
||||
// Make sure the output is stable.
|
||||
c.libraries.sort((l1, l2) {
|
||||
return "${l1.fileUri}".compareTo("${l2.fileUri}");
|
||||
});
|
||||
c.problemsAsJson?.sort();
|
||||
c.computeCanonicalNames();
|
||||
for (Library library in c.libraries) {
|
||||
library.additionalExports.sort((Reference r1, Reference r2) {
|
||||
return "${r1.canonicalName}".compareTo("${r2.canonicalName}");
|
||||
});
|
||||
library.problemsAsJson?.sort();
|
||||
}
|
||||
}
|
||||
|
||||
/// Extends the DevCompilerTarget to transform outlines to meet the requirements
|
||||
/// of summaries in bazel and package-build.
|
||||
///
|
||||
/// Build systems like package-build may provide the same input file twice to
|
||||
/// the summary worker, but only intends to have it in one output summary. The
|
||||
/// convention is that if it is listed as a source, it is intended to be part of
|
||||
/// the output, if the source file was loaded as a dependency, then it was
|
||||
/// already included in a different summary. The transformation below ensures
|
||||
/// that the output summary doesn't include those implicit inputs.
|
||||
///
|
||||
/// Note: this transformation is destructive and is only intended to be used
|
||||
/// when generating summaries.
|
||||
class DevCompilerSummaryTarget extends DevCompilerTarget {
|
||||
final List<Uri> sources;
|
||||
final bool excludeNonSources;
|
||||
|
||||
DevCompilerSummaryTarget(
|
||||
this.sources, this.excludeNonSources, TargetFlags targetFlags)
|
||||
: super(targetFlags);
|
||||
|
||||
@override
|
||||
void performOutlineTransformations(Component component) {
|
||||
super.performOutlineTransformations(component);
|
||||
if (!excludeNonSources) return;
|
||||
|
||||
List<Library> libraries = new List.from(component.libraries);
|
||||
component.libraries.clear();
|
||||
Set<Uri> include = sources.toSet();
|
||||
for (var lib in libraries) {
|
||||
if (include.contains(lib.importUri)) {
|
||||
component.libraries.add(lib);
|
||||
} else {
|
||||
// Excluding the library also means that their canonical names will not
|
||||
// be computed as part of serialization, so we need to do that
|
||||
// preemtively here to avoid errors when serializing references to
|
||||
// elements of these libraries.
|
||||
component.root.getChildFromUri(lib.importUri).bindTo(lib.reference);
|
||||
lib.computeCanonicalNames();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Uri _toUri(String uriString) {
|
||||
if (uriString == null) return null;
|
||||
// Windows-style paths use '\', so convert them to '/' in case they've been
|
||||
// concatenated with Unix-style paths.
|
||||
return Uri.base.resolve(uriString.replaceAll("\\", "/"));
|
||||
}
|
||||
|
||||
Map<String, String> _parseEnvironmentDefines(List<String> args) {
|
||||
var environment = <String, String>{};
|
||||
|
||||
for (var arg in args) {
|
||||
var eq = arg.indexOf('=');
|
||||
if (eq <= 0) {
|
||||
var kind = eq == 0 ? 'name' : 'value';
|
||||
throw FormatException('no $kind given to -D option `$arg`');
|
||||
}
|
||||
var name = arg.substring(0, eq);
|
||||
var value = arg.substring(eq + 1);
|
||||
environment[name] = value;
|
||||
}
|
||||
|
||||
return environment;
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue