blob: 4bb3e8974c439591df710d5c9799f242cad93357 [file] [log] [blame]
// 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 excuted 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 [resultId], 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 [resultId] in this list.
final List<DataId> moduleDataNeeded;
/// Data that this step produces.
final DataId resultId;
ModularStep(
{this.needsSources: true,
this.dependencyDataNeeded: const [],
this.moduleDataNeeded: const [],
this.resultId});
}
/// 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> {
final List<S> steps;
Pipeline(this.steps) {
_validate();
}
void _validate() {
// 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) {
var resultKind = step.resultId;
if (previousKinds.containsKey(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, {});
}
}
Future<void> _recursiveRun(S step, Module module,
Map<Module, Set<DataId>> computedData, Set<Module> seen) async {
if (!seen.add(module)) return;
for (var dependency in module.dependencies) {
await _recursiveRun(step, dependency, computedData, seen);
}
// Include only requested data from transitive dependencies.
Map<Module, Set<DataId>> visibleData = {};
// TODO(sigmund): consider excluding parent modules here. In particular,
// [seen] not only contains transitive dependencies, but also this module
// and parent modules. Technically we haven't computed any data for those,
// so we shouldn't be including any entries for parent modules in
// [visibleData].
seen.forEach((dep) {
if (dep == module) return;
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);
(computedData[module] ??= {}).add(step.resultId);
}
Future<void> runStep(
S step, Module module, Map<Module, Set<DataId>> visibleData);
}
class InvalidPipelineError extends Error {
final String message;
InvalidPipelineError(this.message);
String toString() => "Invalid pipeline: $message";
}