diff --git a/pkg/frontend_server/lib/compute_kernel.dart b/pkg/frontend_server/lib/compute_kernel.dart deleted file mode 100644 index b6c5616f805..00000000000 --- a/pkg/frontend_server/lib/compute_kernel.dart +++ /dev/null @@ -1,426 +0,0 @@ -// 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 preprocessArgs(List 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 computeKernel(List args, - {bool isWorker: false, - StringBuffer outputBuffer, - Map> inputDigests, - fe.InitializedCompilerState previousState}) async { - inputDigests ??= >{}; - 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).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 linkedInputs = - (parsedArgs['input-linked'] as List).map(toUri).toList(); - - List summaryInputs = - (parsedArgs['input-summary'] as List).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), - 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, - 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 kernel; - bool wroteUsedDills = false; - if (usingIncrementalCompiler) { - state.options.onDiagnostic = onDiagnostic; - Component incrementalComponent = await state.incrementalCompiler - .computeDelta(entryPoints: sources, fullComponent: true); - - if (recordUsedInputs) { - Set 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 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 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 libraries = new List.from(component.libraries); - component.libraries.clear(); - Set 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 _parseEnvironmentDefines(List args) { - var environment = {}; - - 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; -} diff --git a/pkg/frontend_server/pubspec.yaml b/pkg/frontend_server/pubspec.yaml index 504c493d3e8..7932dab8da2 100644 --- a/pkg/frontend_server/pubspec.yaml +++ b/pkg/frontend_server/pubspec.yaml @@ -8,10 +8,6 @@ environment: dependencies: args: ^1.4.4 - build_integration: - path: ../build_integration - compiler: - path: ../compiler dev_compiler: path: ../dev_compiler front_end: diff --git a/utils/bazel/kernel_worker.dart b/utils/bazel/kernel_worker.dart index 3e68ac6bf29..96f1e9ae6eb 100644 --- a/utils/bazel/kernel_worker.dart +++ b/utils/bazel/kernel_worker.dart @@ -15,9 +15,17 @@ 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:frontend_server/compute_kernel.dart'; +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'; /// [sendPort] may be passed in when started in an isolate. If provided, it is /// used for bazel worker communication instead of stdin/stdout. @@ -60,17 +68,10 @@ class KernelWorker extends AsyncWorkerLoop { } else { previousState = null; } - - /// Build a map of uris to digests. - final inputDigests = >{}; - for (var input in request.inputs) { - inputDigests[toUri(input.path)] = input.digest; - } - var result = await computeKernel(request.arguments, isWorker: true, outputBuffer: outputBuffer, - inputDigests: inputDigests, + inputs: request.inputs, previousState: previousStateToPass); previousState = result.previousState; if (!result.succeeded) { @@ -85,3 +86,413 @@ 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 preprocessArgs(List 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 computeKernel(List args, + {bool isWorker: false, + StringBuffer outputBuffer, + Iterable 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).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 linkedInputs = + (parsedArgs['input-linked'] as List).map(_toUri).toList(); + + List summaryInputs = + (parsedArgs['input-summary'] as List).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 = >{}; + 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), + 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, + 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 kernel; + bool wroteUsedDills = false; + if (usingIncrementalCompiler) { + state.options.onDiagnostic = onDiagnostic; + Component incrementalComponent = await state.incrementalCompiler + .computeDelta(entryPoints: sources, fullComponent: true); + + if (recordUsedInputs) { + Set 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 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 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 libraries = new List.from(component.libraries); + component.libraries.clear(); + Set 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 _parseEnvironmentDefines(List args) { + var environment = {}; + + 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; +}