Skip to content

Update the phases api, add PhaseGroup, Phase, BuildAction. #73

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Mar 4, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 3 additions & 7 deletions e2e_example/lib/copy_builder.dart
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,9 @@ class CopyBuilder extends Builder {
List<AssetId> declareOutputs(AssetId inputId) => [_copiedAssetId(inputId)];

/// Only runs on the root package, and copies all *.txt files.
static List<Phase> buildPhases(PackageGraph graph) {
var phase = new Phase([
new CopyBuilder()
], [
new InputSet(graph.root.name, filePatterns: ['**/*.txt'])
]);
return [phase];
static void addPhases(PhaseGroup group, PackageGraph graph) {
group.newPhase().addAction(
new CopyBuilder(), new InputSet(graph.root.name, ['**/*.txt']));
}
}

Expand Down
5 changes: 3 additions & 2 deletions e2e_example/tool/build.dart
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,14 @@ import 'package:e2e_example/copy_builder.dart';
main() async {
/// Builds a full package dependency graph for the current package.
var graph = new PackageGraph.forThisPackage();
var phases = new PhaseGroup();

/// Give [Builder]s access to a [PackageGraph] so they can choose which
/// packages to run on. This simplifies user code a lot, and helps to mitigate
/// the transitive deps issue.
var phases = CopyBuilder.buildPhases(graph);
CopyBuilder.addPhases(phases, graph);

var result = await build([phases]);
var result = await build(phases);

if (result.status == BuildStatus.Success) {
stdout.writeln(result);
Expand Down
5 changes: 3 additions & 2 deletions e2e_example/tool/watch.dart
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,14 @@ import 'package:e2e_example/copy_builder.dart';
main() async {
/// Builds a full package dependency graph for the current package.
var graph = new PackageGraph.forThisPackage();
var phases = new PhaseGroup();

/// Give [Builder]s access to a [PackageGraph] so they can choose which
/// packages to run on. This simplifies user code a lot, and helps to mitigate
/// the transitive deps issue.
var phases = CopyBuilder.buildPhases(graph);
CopyBuilder.addPhases(phases, graph);

await for (var result in watch([phases])) {
await for (var result in watch(phases)) {
if (result.status == BuildStatus.Success) {
stdout.writeln(result);
} else {
Expand Down
12 changes: 6 additions & 6 deletions lib/src/generate/build.dart
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ import 'watch_impl.dart';
/// By default the [ProcessSignal.SIGINT] stream is used. In this mode, it
/// will simply consume the first event and allow the build to continue.
/// Multiple termination events will cause a normal shutdown.
Future<BuildResult> build(List<List<Phase>> phaseGroups,
Future<BuildResult> build(PhaseGroup phaseGroup,
{PackageGraph packageGraph,
AssetReader reader,
AssetWriter writer,
Expand All @@ -50,7 +50,7 @@ Future<BuildResult> build(List<List<Phase>> phaseGroups,
writer: writer,
logLevel: logLevel,
onLog: onLog);
var buildImpl = new BuildImpl(options, phaseGroups);
var buildImpl = new BuildImpl(options, phaseGroup);

/// Run the build!
var futureResult = buildImpl.runBuild();
Expand Down Expand Up @@ -82,7 +82,7 @@ Future<BuildResult> build(List<List<Phase>> phaseGroups,
/// first event will allow any ongoing builds to finish, and then the program
/// will complete normally. Subsequent events are not handled (and will
/// typically cause a shutdown).
Stream<BuildResult> watch(List<List<Phase>> phaseGroups,
Stream<BuildResult> watch(PhaseGroup phaseGroup,
{PackageGraph packageGraph,
AssetReader reader,
AssetWriter writer,
Expand All @@ -99,7 +99,7 @@ Stream<BuildResult> watch(List<List<Phase>> phaseGroups,
onLog: onLog,
debounceDelay: debounceDelay,
directoryWatcherFactory: directoryWatcherFactory);
var watchImpl = new WatchImpl(options, phaseGroups);
var watchImpl = new WatchImpl(options, phaseGroup);

var resultStream = watchImpl.runWatch();

Expand All @@ -119,7 +119,7 @@ Stream<BuildResult> watch(List<List<Phase>> phaseGroups,
/// By default a static server will be set up to serve [directory] at
/// [address]:[port], but instead a [requestHandler] may be provided for custom
/// behavior.
Stream<BuildResult> serve(List<List<Phase>> phaseGroups,
Stream<BuildResult> serve(PhaseGroup phaseGroup,
{PackageGraph packageGraph,
AssetReader reader,
AssetWriter writer,
Expand All @@ -143,7 +143,7 @@ Stream<BuildResult> serve(List<List<Phase>> phaseGroups,
directory: directory,
address: address,
port: port);
var watchImpl = new WatchImpl(options, phaseGroups);
var watchImpl = new WatchImpl(options, phaseGroup);

var resultStream = watchImpl.runWatch();
var serverStarted = startServer(watchImpl, options);
Expand Down
81 changes: 36 additions & 45 deletions lib/src/generate/build_impl.dart
Original file line number Diff line number Diff line change
Expand Up @@ -37,17 +37,18 @@ class BuildImpl {
final AssetReader _reader;
final AssetWriter _writer;
final PackageGraph _packageGraph;
final List<List<Phase>> _phaseGroups;
final List<List<BuildAction>> _buildActions;
final _inputsByPackage = <String, Set<AssetId>>{};
bool _buildRunning = false;
final _logger = new Logger('Build');

bool _isFirstBuild = true;

BuildImpl(BuildOptions options, this._phaseGroups)
BuildImpl(BuildOptions options, PhaseGroup phaseGroup)
: _reader = options.reader,
_writer = options.writer,
_packageGraph = options.packageGraph;
_packageGraph = options.packageGraph,
_buildActions = phaseGroup.buildActions;

/// Runs a build
///
Expand Down Expand Up @@ -313,20 +314,17 @@ class BuildImpl {
// No cache file exists, run `declareOutputs` on all phases and collect all
// outputs which conflict with existing assets.
final conflictingOutputs = new Set<AssetId>();
for (var group in _phaseGroups) {
for (var phase in _buildActions) {
final groupOutputIds = <AssetId>[];
for (var phase in group) {
var inputs = _matchingInputs(phase.inputSets);
for (var action in phase) {
var inputs = _matchingInputs(action.inputSet);
for (var input in inputs) {
for (var builder in phase.builders) {
var outputs = builder.declareOutputs(input);

groupOutputIds.addAll(outputs);
for (var output in outputs) {
if (tempInputsByPackage[output.package]?.contains(output) ==
true) {
conflictingOutputs.add(output);
}
var outputs = action.builder.declareOutputs(input);

groupOutputIds.addAll(outputs);
for (var output in outputs) {
if (tempInputsByPackage[output.package]?.contains(output) == true) {
conflictingOutputs.add(output);
}
}
}
Expand Down Expand Up @@ -375,53 +373,48 @@ class BuildImpl {
}
}

/// Runs the [_phaseGroups] and returns a [Future<BuildResult>] which
/// completes once all [Phase]s are done.
/// Runs the [Phase]s in [_buildActions] and returns a [Future<BuildResult>]
/// which completes once all [BuildAction]s are done.
Future<BuildResult> _runPhases() async {
final outputs = <Asset>[];
int phaseGroupNum = 0;
for (var group in _phaseGroups) {
for (var phase in _buildActions) {
/// Collects all the ids for files which are output by this stage. This
/// also includes files which didn't get regenerated because they weren't,
/// dirty unlike [outputs] which only gets files which were explicitly
/// generated in this build.
final groupOutputIds = new Set<AssetId>();
for (var phase in group) {
var inputs = _matchingInputs(phase.inputSets);
for (var builder in phase.builders) {
// TODO(jakemac): Optimize, we can run all the builders in a phase
// at the same time instead of sequentially.
await for (var output
in _runBuilder(builder, inputs, phaseGroupNum, groupOutputIds)) {
outputs.add(output);
}
final phaseOutputIds = new Set<AssetId>();
for (var action in phase) {
var inputs = _matchingInputs(action.inputSet);
// TODO(jakemac): Optimize, we can run all the builders in a phase
// at the same time instead of sequentially.
await for (var output
in _runBuilder(action.builder, inputs, phaseOutputIds)) {
outputs.add(output);
}
}

/// Once the group is done, add all outputs so they can be used in the next
/// phase.
for (var outputId in groupOutputIds) {
for (var outputId in phaseOutputIds) {
_inputsByPackage.putIfAbsent(
outputId.package, () => new Set<AssetId>());
_inputsByPackage[outputId.package].add(outputId);
}
phaseGroupNum++;
}
return new BuildResult(BuildStatus.Success, BuildType.Full, outputs);
}

/// Initializes the map of all the available inputs by package.
Future _initializeInputsByPackage() async {
final packages = new Set<String>();
for (var group in _phaseGroups) {
for (var phase in group) {
for (var inputSet in phase.inputSets) {
packages.add(inputSet.package);
}
for (var phase in _buildActions) {
for (var action in phase) {
packages.add(action.inputSet.package);
}
}

var inputSets = packages.map((package) => new InputSet(package));
var inputSets = packages.map((package) => new InputSet(
package, [package == _packageGraph.root.name ? '**/*' : 'lib/**']));
var allInputs = await _reader.listAssetIds(inputSets).toList();
_inputsByPackage.clear();
for (var input in allInputs) {
Expand All @@ -434,14 +427,12 @@ class BuildImpl {
}

/// Gets a list of all inputs matching [inputSets].
Set<AssetId> _matchingInputs(Iterable<InputSet> inputSets) {
Set<AssetId> _matchingInputs(InputSet inputSet) {
var inputs = new Set<AssetId>();
for (var inputSet in inputSets) {
assert(_inputsByPackage.containsKey(inputSet.package));
for (var input in _inputsByPackage[inputSet.package]) {
if (inputSet.globs.any((g) => g.matches(input.path))) {
inputs.add(input);
}
assert(_inputsByPackage.containsKey(inputSet.package));
for (var input in _inputsByPackage[inputSet.package]) {
if (inputSet.globs.any((g) => g.matches(input.path))) {
inputs.add(input);
}
}
return inputs;
Expand All @@ -458,7 +449,7 @@ class BuildImpl {

/// Runs [builder] with [inputs] as inputs.
Stream<Asset> _runBuilder(Builder builder, Iterable<AssetId> primaryInputs,
int phaseGroupNum, Set<AssetId> groupOutputs) async* {
Set<AssetId> groupOutputs) async* {
for (var input in primaryInputs) {
var expectedOutputs = builder.declareOutputs(input);

Expand Down
19 changes: 12 additions & 7 deletions lib/src/generate/input_set.dart
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,17 @@ class InputSet {
/// listed.
final List<Glob> globs;

InputSet(this.package, {Iterable<String> filePatterns})
: this.globs = _globsFor(filePatterns);
}
InputSet(this.package, Iterable<String> globs)
: this.globs =
new List.unmodifiable(globs.map((pattern) => new Glob(pattern)));

List<Glob> _globsFor(Iterable<String> filePatterns) {
filePatterns ??= ['**/*'];
return new List.unmodifiable(
filePatterns.map((pattern) => new Glob(pattern)));
String toString() {
var buffer = new StringBuffer()
..write('InputSet: package `$package` with globs');
for (var glob in globs) {
buffer.write(' `${glob.pattern}`');
}
buffer.writeln('');
return buffer.toString();
}
}
76 changes: 66 additions & 10 deletions lib/src/generate/phase.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,73 @@
import '../builder/builder.dart';
import 'input_set.dart';

/// A single phase in the build process. None of the [Builder]s in a single
/// phase should depend on any of the outputs of other [Builder]s in that same
/// phase.
/// One action in a [Phase].
///
/// Comprised of a single [Builder] and a single [InputSet].
///
/// These should be constructed using [Phase#addAction].
class BuildAction {
final Builder builder;
final InputSet inputSet;

BuildAction._(this.builder, this.inputSet);
}

/// A collection of [BuildAction]s that can be ran simultaneously.
///
/// None of the [BuildAction]s in a phase may read the outputs of other
/// [BuildAction]s in the same phase.
class Phase {
/// The list of all [Builder]s that should be run on all [InputSet]s.
final List<Builder> builders;
final _actions = <BuildAction>[];
List<BuildAction> get actions => new List.unmodifiable(_actions);

/// Creates a new [BuildAction] and adds it to this [Phase].
addAction(Builder builder, InputSet inputSet) {
_actions.add(new BuildAction._(builder, inputSet));
}
}

/// A list of [Phase]s which will be ran sequentially. Later [Phase]s may read
/// the outputs of previous [Phase]s.
class PhaseGroup {
final _phases = <Phase>[];
List<List<BuildAction>> get buildActions =>
new List.unmodifiable(_phases.map((phase) => phase.actions));

PhaseGroup();

/// Helper method for the simple use case of a single [Builder] and single
/// [InputSet].
factory PhaseGroup.singleAction(Builder builder, InputSet inputSet) {
var group = new PhaseGroup();
group.newPhase().addAction(builder, inputSet);
return group;
}

/// Creates a new [Phase] and adds it to this [PhaseGroup].
Phase newPhase() {
var phase = new Phase();
_phases.add(phase);
return phase;
}

/// Adds a [Phase] to this [PhaseGroup].
void addPhase(Phase phase) {
_phases.add(phase);
}

/// The list of all [InputSet]s that should be used as primary inputs.
final List<InputSet> inputSets;
String toString() {
var buffer = new StringBuffer();
for (int i = 0; i < _phases.length; i++) {
buffer.writeln('Phase $i:');
for (var action in _phases[i].actions) {
buffer
..writeln(' Action:')
..writeln(' Builder: ${action.builder}')
..writeln(' ${action.inputSet}');
}
}

Phase(List<Builder> builders, List<InputSet> inputSets)
: builders = new List.unmodifiable(builders),
inputSets = new List.unmodifiable(inputSets);
return buffer.toString();
}
}
4 changes: 2 additions & 2 deletions lib/src/generate/watch_impl.dart
Original file line number Diff line number Diff line change
Expand Up @@ -61,12 +61,12 @@ class WatchImpl {
/// Whether we are in the process of terminating.
bool _terminating = false;

WatchImpl(BuildOptions options, List<List<Phase>> phaseGroups)
WatchImpl(BuildOptions options, PhaseGroup phaseGroup)
: _directoryWatcherFactory = options.directoryWatcherFactory,
_debounceDelay = options.debounceDelay,
_writer = options.writer,
_packageGraph = options.packageGraph,
_buildImpl = new BuildImpl(options, phaseGroups);
_buildImpl = new BuildImpl(options, phaseGroup);

/// Completes after the current build is done, and stops further builds from
/// happening.
Expand Down
Loading