|  | // 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. | 
|  |  | 
|  | /// API needed by `utils/front_end/summary_worker.dart`, a tool used to compute | 
|  | /// summaries in build systems like bazel, pub-build, and package-build. | 
|  |  | 
|  | import 'dart:async' show Future; | 
|  |  | 
|  | import 'package:kernel/kernel.dart' show Component, CanonicalName; | 
|  |  | 
|  | import 'package:kernel/target/targets.dart' show Target; | 
|  |  | 
|  | import '../api_prototype/compiler_options.dart' | 
|  | show CompilerOptions, parseExperimentalFlags; | 
|  |  | 
|  | import '../api_prototype/diagnostic_message.dart' show DiagnosticMessageHandler; | 
|  |  | 
|  | import '../api_prototype/experimental_flags.dart' show ExperimentalFlag; | 
|  |  | 
|  | import '../api_prototype/file_system.dart' show FileSystem; | 
|  |  | 
|  | import '../base/processed_options.dart' show ProcessedOptions; | 
|  |  | 
|  | import '../fasta/compiler_context.dart' show CompilerContext; | 
|  |  | 
|  | import '../fasta/incremental_compiler.dart' show IncrementalCompiler; | 
|  |  | 
|  | import '../kernel_generator_impl.dart' show CompilerResult, generateKernel; | 
|  |  | 
|  | import 'compiler_state.dart' | 
|  | show InitializedCompilerState, WorkerInputComponent, digestsEqual; | 
|  |  | 
|  | export '../api_prototype/diagnostic_message.dart' show DiagnosticMessage; | 
|  |  | 
|  | export '../api_prototype/standard_file_system.dart' show StandardFileSystem; | 
|  |  | 
|  | export '../api_prototype/terminal_color_support.dart' | 
|  | show printDiagnosticMessage; | 
|  |  | 
|  | export '../fasta/kernel/utils.dart' show serializeComponent; | 
|  |  | 
|  | export '../fasta/severity.dart' show Severity; | 
|  |  | 
|  | export 'compiler_state.dart' show InitializedCompilerState; | 
|  |  | 
|  | import 'util.dart' show equalMaps; | 
|  |  | 
|  | /// Initializes the compiler for a modular build. | 
|  | /// | 
|  | /// Re-uses cached components from [_workerInputCache], and reloads them | 
|  | /// as necessary based on [workerInputDigests]. | 
|  | Future<InitializedCompilerState> initializeIncrementalCompiler( | 
|  | InitializedCompilerState oldState, | 
|  | Uri sdkSummary, | 
|  | Uri packagesFile, | 
|  | Uri librariesSpecificationUri, | 
|  | List<Uri> summaryInputs, | 
|  | Map<Uri, List<int>> workerInputDigests, | 
|  | Target target, | 
|  | FileSystem fileSystem, | 
|  | Iterable<String> experiments, | 
|  | bool outlineOnly) async { | 
|  | final List<int> sdkDigest = workerInputDigests[sdkSummary]; | 
|  | if (sdkDigest == null) { | 
|  | throw new StateError("Expected to get digest for $sdkSummary"); | 
|  | } | 
|  | IncrementalCompiler incrementalCompiler; | 
|  | CompilerOptions options; | 
|  | ProcessedOptions processedOpts; | 
|  | WorkerInputComponent cachedSdkInput; | 
|  | Map<Uri, WorkerInputComponent> workerInputCache = | 
|  | oldState?.workerInputCache ?? new Map<Uri, WorkerInputComponent>(); | 
|  | bool startOver = false; | 
|  | Map<ExperimentalFlag, bool> experimentalFlags = | 
|  | parseExperimentalFlags(experiments, (e) => throw e); | 
|  |  | 
|  | if (oldState == null || | 
|  | oldState.incrementalCompiler == null || | 
|  | oldState.incrementalCompiler.outlineOnly != outlineOnly || | 
|  | !equalMaps(oldState.options.experimentalFlags, experimentalFlags)) { | 
|  | // No - or immediately not correct - previous state. | 
|  | startOver = true; | 
|  |  | 
|  | // We'll load a new sdk, anything loaded already will have a wrong root. | 
|  | workerInputCache.clear(); | 
|  | } else { | 
|  | // We do have a previous state. | 
|  | cachedSdkInput = workerInputCache[sdkSummary]; | 
|  | if (cachedSdkInput == null || | 
|  | !digestsEqual(cachedSdkInput.digest, sdkDigest)) { | 
|  | // The sdk is out of date. | 
|  | startOver = true; | 
|  | // We'll load a new sdk, anything loaded already will have a wrong root. | 
|  | workerInputCache.clear(); | 
|  | } | 
|  | } | 
|  |  | 
|  | if (startOver) { | 
|  | // The sdk was either not cached or it has changed. | 
|  | options = new CompilerOptions() | 
|  | ..sdkSummary = sdkSummary | 
|  | ..packagesFileUri = packagesFile | 
|  | ..librariesSpecificationUri = librariesSpecificationUri | 
|  | ..target = target | 
|  | ..fileSystem = fileSystem | 
|  | ..omitPlatform = true | 
|  | ..environmentDefines = const {}; | 
|  |  | 
|  | processedOpts = new ProcessedOptions(options: options); | 
|  | cachedSdkInput = WorkerInputComponent( | 
|  | sdkDigest, await processedOpts.loadSdkSummary(null)); | 
|  | workerInputCache[sdkSummary] = cachedSdkInput; | 
|  |  | 
|  | incrementalCompiler = new IncrementalCompiler.fromComponent( | 
|  | new CompilerContext(processedOpts), | 
|  | cachedSdkInput.component, | 
|  | outlineOnly); | 
|  | } else { | 
|  | options = oldState.options; | 
|  | processedOpts = oldState.processedOpts; | 
|  | var sdkComponent = cachedSdkInput.component; | 
|  | // Reset the state of the component. | 
|  | for (var lib in sdkComponent.libraries) { | 
|  | lib.isExternal = cachedSdkInput.externalLibs.contains(lib.importUri); | 
|  | } | 
|  |  | 
|  | // Make sure the canonical name root knows about the sdk - otherwise we | 
|  | // won't be able to link to it when loading more outlines. | 
|  | sdkComponent.adoptChildren(); | 
|  |  | 
|  | // TODO(jensj): This is - at least currently - neccessary, | 
|  | // although it's not entirely obvious why. | 
|  | // It likely has to do with several outlines containing the same libraries. | 
|  | // Once that stops (and we check for it) we can probably remove this, | 
|  | // and instead only do it when about to reuse an outline in the | 
|  | // 'inputSummaries.add(component);' line further down. | 
|  | for (WorkerInputComponent cachedInput in workerInputCache.values) { | 
|  | cachedInput.component.adoptChildren(); | 
|  | } | 
|  |  | 
|  | // Reuse the incremental compiler, but reset as needed. | 
|  | incrementalCompiler = oldState.incrementalCompiler; | 
|  | incrementalCompiler.invalidateAllSources(); | 
|  | options.packagesFileUri = packagesFile; | 
|  | options.fileSystem = fileSystem; | 
|  | processedOpts.clearFileSystemCache(); | 
|  | } | 
|  |  | 
|  | // Then read all the input summary components. | 
|  | CanonicalName nameRoot = cachedSdkInput.component.root; | 
|  | final inputSummaries = <Component>[]; | 
|  | List<Uri> loadFromDill = new List<Uri>(); | 
|  | for (Uri summary in summaryInputs) { | 
|  | var cachedInput = workerInputCache[summary]; | 
|  | var summaryDigest = workerInputDigests[summary]; | 
|  | if (summaryDigest == null) { | 
|  | throw new StateError("Expected to get digest for $summary"); | 
|  | } | 
|  | if (cachedInput == null || | 
|  | cachedInput.component.root != nameRoot || | 
|  | !digestsEqual(cachedInput.digest, summaryDigest)) { | 
|  | loadFromDill.add(summary); | 
|  | } else { | 
|  | // Need to reset cached components so they are usable again. | 
|  | var component = cachedInput.component; | 
|  | for (var lib in component.libraries) { | 
|  | lib.isExternal = cachedInput.externalLibs.contains(lib.importUri); | 
|  | } | 
|  | inputSummaries.add(component); | 
|  | } | 
|  | } | 
|  |  | 
|  | for (int i = 0; i < loadFromDill.length; i++) { | 
|  | Uri summary = loadFromDill[i]; | 
|  | List<int> summaryDigest = workerInputDigests[summary]; | 
|  | if (summaryDigest == null) { | 
|  | throw new StateError("Expected to get digest for $summary"); | 
|  | } | 
|  | WorkerInputComponent cachedInput = WorkerInputComponent( | 
|  | summaryDigest, | 
|  | await processedOpts.loadComponent( | 
|  | await fileSystem.entityForUri(summary).readAsBytes(), nameRoot, | 
|  | alwaysCreateNewNamedNodes: true)); | 
|  | workerInputCache[summary] = cachedInput; | 
|  | inputSummaries.add(cachedInput.component); | 
|  | } | 
|  |  | 
|  | incrementalCompiler.setModulesToLoadOnNextComputeDelta(inputSummaries); | 
|  |  | 
|  | return new InitializedCompilerState(options, processedOpts, | 
|  | workerInputCache: workerInputCache, | 
|  | incrementalCompiler: incrementalCompiler); | 
|  | } | 
|  |  | 
|  | Future<InitializedCompilerState> initializeCompiler( | 
|  | InitializedCompilerState oldState, | 
|  | Uri sdkSummary, | 
|  | Uri librariesSpecificationUri, | 
|  | Uri packagesFile, | 
|  | List<Uri> summaryInputs, | 
|  | List<Uri> linkedInputs, | 
|  | Target target, | 
|  | FileSystem fileSystem, | 
|  | Iterable<String> experiments) async { | 
|  | // TODO(sigmund): use incremental compiler when it supports our use case. | 
|  | // Note: it is common for the summary worker to invoke the compiler with the | 
|  | // same input summary URIs, but with different contents, so we'd need to be | 
|  | // able to track shas or modification time-stamps to be able to invalidate the | 
|  | // old state appropriately. | 
|  | CompilerOptions options = new CompilerOptions() | 
|  | ..sdkSummary = sdkSummary | 
|  | ..packagesFileUri = packagesFile | 
|  | ..librariesSpecificationUri = librariesSpecificationUri | 
|  | ..inputSummaries = summaryInputs | 
|  | ..linkedDependencies = linkedInputs | 
|  | ..target = target | 
|  | ..fileSystem = fileSystem | 
|  | ..environmentDefines = const {} | 
|  | ..experimentalFlags = parseExperimentalFlags(experiments, (e) => throw e); | 
|  |  | 
|  | ProcessedOptions processedOpts = new ProcessedOptions(options: options); | 
|  |  | 
|  | return new InitializedCompilerState(options, processedOpts); | 
|  | } | 
|  |  | 
|  | Future<CompilerResult> _compile(InitializedCompilerState compilerState, | 
|  | List<Uri> inputs, DiagnosticMessageHandler diagnosticMessageHandler, | 
|  | {bool summaryOnly, bool includeOffsets: true}) { | 
|  | summaryOnly ??= true; | 
|  | CompilerOptions options = compilerState.options; | 
|  | options..onDiagnostic = diagnosticMessageHandler; | 
|  |  | 
|  | ProcessedOptions processedOpts = compilerState.processedOpts; | 
|  | processedOpts.inputs.clear(); | 
|  | processedOpts.inputs.addAll(inputs); | 
|  |  | 
|  | return generateKernel(processedOpts, | 
|  | buildSummary: summaryOnly, | 
|  | buildComponent: !summaryOnly, | 
|  | includeOffsets: includeOffsets); | 
|  | } | 
|  |  | 
|  | Future<List<int>> compileSummary(InitializedCompilerState compilerState, | 
|  | List<Uri> inputs, DiagnosticMessageHandler diagnosticMessageHandler, | 
|  | {bool includeOffsets: false}) async { | 
|  | var result = await _compile(compilerState, inputs, diagnosticMessageHandler, | 
|  | summaryOnly: true, includeOffsets: includeOffsets); | 
|  | return result?.summary; | 
|  | } | 
|  |  | 
|  | Future<Component> compileComponent(InitializedCompilerState compilerState, | 
|  | List<Uri> inputs, DiagnosticMessageHandler diagnosticMessageHandler) async { | 
|  | var result = await _compile(compilerState, inputs, diagnosticMessageHandler, | 
|  | summaryOnly: false); | 
|  |  | 
|  | var component = result?.component; | 
|  | if (component != null) { | 
|  | for (var lib in component.libraries) { | 
|  | if (!inputs.contains(lib.importUri)) { | 
|  | // Excluding the library also means that their canonical names will not | 
|  | // be computed as part of serialization, so we need to do that | 
|  | // preemptively here to avoid errors when serializing references to | 
|  | // elements of these libraries. | 
|  | component.root.getChildFromUri(lib.importUri).bindTo(lib.reference); | 
|  | lib.computeCanonicalNames(); | 
|  | } | 
|  | } | 
|  | } | 
|  | return component; | 
|  | } |