|  | // 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. | 
|  |  | 
|  | /// Abstraction for a compilation pipeline. | 
|  | /// | 
|  | /// A pipeline defines how modular steps are executed and ensures that a step | 
|  | /// only has access to the data it declares. | 
|  | /// | 
|  | /// The abstract implementation validates how the data is declared, and the | 
|  | /// underlying implementations enforce the access to data in different ways. | 
|  | /// | 
|  | /// The IO-based implementation ensures hermeticity by copying data to different | 
|  | /// directories. The memory-based implementation ensures hemeticity by filtering | 
|  | /// out the data before invoking the next step. | 
|  | import 'suite.dart'; | 
|  |  | 
|  | /// Describes a step in a modular compilation pipeline. | 
|  | class ModularStep { | 
|  | /// Whether this step needs to read the source files in the module. | 
|  | final bool needsSources; | 
|  |  | 
|  | /// Data that this step needs to read about dependencies. | 
|  | /// | 
|  | /// This can be data produced on a previous stage of the pipeline | 
|  | /// or produced by this same step when it was run on a dependency. | 
|  | /// | 
|  | /// If this list includes any data from [resultData], then the modular-step | 
|  | /// has to be run on dependencies before it is run on a module. Otherwise, it | 
|  | /// could be run in parallel. | 
|  | final List<DataId> dependencyDataNeeded; | 
|  |  | 
|  | /// Data that this step needs to read about the module itself. | 
|  | /// | 
|  | /// This is meant to be data produced in earlier stages of the modular | 
|  | /// pipeline. It is an error to include any id from [resultData] in this list. | 
|  | final List<DataId> moduleDataNeeded; | 
|  |  | 
|  | /// Data that this step produces. | 
|  | final List<DataId> resultData; | 
|  |  | 
|  | /// Whether this step is only executed on the main module. | 
|  | final bool onlyOnMain; | 
|  |  | 
|  | /// Whether this step is only executed on the SDK. | 
|  | final bool onlyOnSdk; | 
|  |  | 
|  | /// Whether this step is not executed on the SDK. | 
|  | final bool notOnSdk; | 
|  |  | 
|  | ModularStep( | 
|  | {this.needsSources = true, | 
|  | this.dependencyDataNeeded = const [], | 
|  | this.moduleDataNeeded = const [], | 
|  | this.resultData = const [], | 
|  | this.onlyOnMain = false, | 
|  | this.onlyOnSdk = false, | 
|  | this.notOnSdk = false}); | 
|  |  | 
|  | /// Notifies that the step was not executed, but cached instead. | 
|  | void notifyCached(Module module) {} | 
|  | } | 
|  |  | 
|  | /// An object to uniquely identify modular data produced by a modular step. | 
|  | /// | 
|  | /// Two modular steps on the same pipeline cannot emit the same data. | 
|  | class DataId { | 
|  | final String name; | 
|  |  | 
|  | const DataId(this.name); | 
|  |  | 
|  | @override | 
|  | String toString() => name; | 
|  | } | 
|  |  | 
|  | abstract class Pipeline<S extends ModularStep> { | 
|  | /// Whether to cache the result of shared modules (e.g. shard packages and sdk | 
|  | /// libraries) when multiple tests are run by this pipeline. | 
|  | final bool cacheSharedModules; | 
|  |  | 
|  | final List<S> steps; | 
|  |  | 
|  | Pipeline(this.steps, this.cacheSharedModules) { | 
|  | _validate(); | 
|  | } | 
|  |  | 
|  | void _validate() { | 
|  | // Whether or not two steps run on mutually exclusive input data. | 
|  | bool areMutuallyExclusive(S a, S b) { | 
|  | return (a.onlyOnSdk && b.notOnSdk) || (b.onlyOnSdk && a.notOnSdk); | 
|  | } | 
|  |  | 
|  | // Ensure that steps consume only data that was produced by previous steps | 
|  | // or by the same step on a dependency. | 
|  | Map<DataId, S> previousKinds = {}; | 
|  | for (var step in steps) { | 
|  | if (step.resultData.isEmpty) { | 
|  | _validationError( | 
|  | "'${step.runtimeType}' needs to declare what data it produces."); | 
|  | } | 
|  | for (var resultKind in step.resultData) { | 
|  | if (previousKinds.containsKey(resultKind) && | 
|  | !areMutuallyExclusive(step, previousKinds[resultKind]!)) { | 
|  | _validationError("Cannot produce the same data on two modular steps." | 
|  | " '$resultKind' was previously produced by " | 
|  | "'${previousKinds[resultKind].runtimeType}' but " | 
|  | "'${step.runtimeType}' also produces the same data."); | 
|  | } | 
|  | previousKinds[resultKind] = step; | 
|  | for (var dataId in step.dependencyDataNeeded) { | 
|  | if (!previousKinds.containsKey(dataId)) { | 
|  | _validationError( | 
|  | "Step '${step.runtimeType}' needs data '$dataId', but the " | 
|  | "data is not produced by this or a preceding step."); | 
|  | } | 
|  | } | 
|  | for (var dataId in step.moduleDataNeeded) { | 
|  | if (!previousKinds.containsKey(dataId)) { | 
|  | _validationError( | 
|  | "Step '${step.runtimeType}' needs data '$dataId', but the " | 
|  | "data is not produced by a preceding step."); | 
|  | } | 
|  | if (dataId == resultKind) { | 
|  | _validationError("Circular dependency on '$dataId' " | 
|  | "in step '${step.runtimeType}'"); | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | void _validationError(String s) => throw InvalidPipelineError(s); | 
|  |  | 
|  | Future<void> run(ModularTest test) async { | 
|  | // TODO(sigmund): validate that [ModularTest] has no cycles. | 
|  | Map<Module, Set<DataId>> computedData = {}; | 
|  | for (var step in steps) { | 
|  | await _recursiveRun(step, test.mainModule, computedData, {}, test.flags); | 
|  | } | 
|  | } | 
|  |  | 
|  | Future<void> _recursiveRun( | 
|  | S step, | 
|  | Module module, | 
|  | Map<Module, Set<DataId>> computedData, | 
|  | Map<Module, Set<Module>> transitiveDependencies, | 
|  | List<String> flags) async { | 
|  | if (transitiveDependencies.containsKey(module)) return; | 
|  | var deps = transitiveDependencies[module] = {}; | 
|  | for (var dependency in module.dependencies) { | 
|  | await _recursiveRun( | 
|  | step, dependency, computedData, transitiveDependencies, flags); | 
|  | deps.add(dependency); | 
|  | deps.addAll(transitiveDependencies[dependency]!); | 
|  | } | 
|  |  | 
|  | if ((step.onlyOnMain && !module.isMain) || | 
|  | (step.onlyOnSdk && !module.isSdk) || | 
|  | (step.notOnSdk && module.isSdk)) return; | 
|  | // Include only requested data from transitive dependencies. | 
|  | Map<Module, Set<DataId>> visibleData = {}; | 
|  |  | 
|  | for (var dep in deps) { | 
|  | visibleData[dep] = {}; | 
|  | for (var dataId in step.dependencyDataNeeded) { | 
|  | if (computedData[dep]!.contains(dataId)) { | 
|  | visibleData[dep]!.add(dataId); | 
|  | } | 
|  | } | 
|  | } | 
|  | visibleData[module] = {}; | 
|  | for (var dataId in step.moduleDataNeeded) { | 
|  | if (computedData[module]!.contains(dataId)) { | 
|  | visibleData[module]!.add(dataId); | 
|  | } | 
|  | } | 
|  | await runStep(step, module, visibleData, flags); | 
|  | (computedData[module] ??= {}).addAll(step.resultData); | 
|  | } | 
|  |  | 
|  | Future<void> runStep(S step, Module module, | 
|  | Map<Module, Set<DataId>> visibleData, List<String> flags); | 
|  | } | 
|  |  | 
|  | class InvalidPipelineError extends Error { | 
|  | final String message; | 
|  | InvalidPipelineError(this.message); | 
|  | @override | 
|  | String toString() => "Invalid pipeline: $message"; | 
|  | } |