blob: 53cf81874ad5bfb2088db559799dd74ab06c566c [file] [log] [blame]
// Copyright (c) 2017, 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.
/// A tool that invokes the CFE to compute kernel summary files.
/// This script can be used as a command-line command or a persistent server.
/// The server is implemented using the bazel worker protocol, so it can be used
/// within bazel as is. Other tools (like pub-build and package-build) also
/// use this persistent worker via the same protocol.
import 'dart:async';
import 'dart:io';
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;
import 'package:kernel/target/targets.dart';
import 'package:vm/target/vm.dart';
import 'package:compiler/src/kernel/dart2js_target.dart';
main(List<String> args) async {
args = preprocessArgs(args);
if (args.contains('--persistent_worker')) {
if (args.length != 1) {
throw new StateError(
"unexpected args, expected only --persistent-worker but got: $args");
await new KernelWorker().run();
} else {
var result = await computeKernel(args);
if (!result.succeeded) {
exitCode = 15;
/// A bazel worker loop that can compute full or summary kernel files.
class KernelWorker extends AsyncWorkerLoop {
fe.InitializedCompilerState previousState;
Future<WorkResponse> performRequest(WorkRequest request) async {
var outputBuffer = new StringBuffer();
var response = new WorkResponse()..exitCode = 0;
try {
fe.InitializedCompilerState previousStateToPass;
if (request.arguments.contains("--reuse-compiler-result")) {
previousStateToPass = previousState;
} else {
previousState = null;
ComputeKernelResult result;
// TODO(vsm): See
// If the CFE is crashing with previous state, then clear compilation
// state and try again.
try {
result = await computeKernel(request.arguments,
isWorker: true,
outputBuffer: outputBuffer,
inputs: request.inputs,
previousState: previousStateToPass);
} catch (_) {
result = await computeKernel(request.arguments,
isWorker: true,
outputBuffer: outputBuffer,
inputs: request.inputs,
previousState: null);
previousState = result.previousState;
if (!result.succeeded) {
response.exitCode = 15;
} catch (e, s) {
response.exitCode = 15;
response.output = outputBuffer.toString();
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 {
} 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')
negatable: false,
help: 'Whether source files loaded implicitly should be included as '
'part of the summary.')
defaultsTo: true,
negatable: true,
help: 'Whether to only build summary files.')
allowed: const ['vm', 'dart2js', 'ddc'],
help: 'Build kernel for the vm, dart2js, or ddc')
..addOption('multi-root-scheme', defaultsTo: 'org-dartlang-multi-root')
..addFlag('reuse-compiler-result', defaultsTo: false)
..addFlag('use-incremental-compiler', defaultsTo: false);
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']) {
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(Uri.base.resolve).toList();
var excludeNonSources = parsedArgs['exclude-non-sources'] as bool;
var summaryOnly = parsedArgs['summary-only'] 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();
Target target;
switch (targetName) {
case 'vm':
target = new VmTarget(targetFlags);
if (summaryOnly) {
out.writeln('error: --summary-only not supported for the vm target');
case 'dart2js':
target = new Dart2jsTarget('dart2js', targetFlags);
if (summaryOnly) {
'error: --summary-only not supported for the dart2js target');
case 'ddc':
// TODO(jakemac):If `generateKernel` changes to return a summary
// component, process the component instead.
target = new DevCompilerSummaryTarget(sources, excludeNonSources);
if (!summaryOnly) {
out.writeln('error: --no-summary-only not supported for the '
'ddc target');
out.writeln('error: unsupported target: $targetName');
// TODO(sigmund,jakemac): make it mandatory. We allow null while we migrate
// existing clients of this tool.
var librariesSpec = parsedArgs['libraries-file'] == null
? null
: Uri.base.resolve(parsedArgs['libraries-file']);
List<Uri> linkedInputs = (parsedArgs['input-linked'] as List<String>)
List<Uri> summaryInputs = (parsedArgs['input-summary'] as List<String>)
fe.InitializedCompilerState state;
bool usingIncrementalCompiler = false;
if (parsedArgs['use-incremental-compiler'] && linkedInputs.isEmpty) {
usingIncrementalCompiler = true;
/// Build a map of uris to digests.
final inputDigests = <Uri, List<int>>{};
for (var input in inputs) {
var uri = Uri.parse(input.path);
if (uri.scheme.isEmpty) {
uri = Uri.parse('file://${input.path}');
inputDigests[uri] = input.digest;
state = await fe.initializeIncrementalCompiler(
} else {
state = await fe.initializeCompiler(
// TODO(sigmund): pass an old state once we can make use of it.
void onDiagnostic(fe.DiagnosticMessage message) {
fe.printDiagnosticMessage(message, out.writeln);
succeeded = false;
List<int> kernel;
if (usingIncrementalCompiler) {
state.options.onDiagnostic = onDiagnostic;
Component incrementalComponent = await state.incrementalCompiler
.computeDelta(entryPoints: sources, fullComponent: true);
kernel = await state.incrementalCompiler.context.runInContext((_) {
if (summaryOnly) {
incrementalComponent.problemsAsJson = null;
incrementalComponent.mainMethod = null;
return Future.value(fe.serializeComponent(incrementalComponent));
} else {
kernel = await fe.compile(state, sources, onDiagnostic,
summaryOnly: summaryOnly);
if (kernel != null) {
var outputFile = new File(parsedArgs['output']);
outputFile.createSync(recursive: true);
} else {
return new ComputeKernelResult(succeeded, state);
/// 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);
void performOutlineTransformations(Component component) {
if (!excludeNonSources) return;
List<Library> libraries = new List.from(component.libraries);
Set<Uri> include = sources.toSet();
for (var lib in libraries) {
if (include.contains(lib.importUri)) {
} 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.