Skip to content
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

Use a combined inputs hash to check if we need to run a build #606

Merged
merged 15 commits into from
Nov 14, 2017
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
5 changes: 4 additions & 1 deletion build_compilers/lib/src/summary_builder.dart
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,8 @@ Future createLinkedSummary(Module module, BuildStep buildStep,
var scratchSpace = await buildStep.fetchResource(scratchSpaceResource);

var allAssetIds = new Set<AssetId>()
// TODO: Why can't we just add the unlinked summary?
// That would help invalidation.
..addAll(module.sources)
..addAll(transitiveLinkedSummaryDeps)
..addAll(transitiveUnlinkedSummaryDeps);
Expand All @@ -141,7 +143,8 @@ Future createLinkedSummary(Module module, BuildStep buildStep,
request.arguments.addAll(_analyzerSourceArgsForModule(module, scratchSpace));
var analyzer = await buildStep.fetchResource(analyzerDriverResource);
var response = await analyzer.doWork(request);
if (response.exitCode == EXIT_CODE_ERROR) {
var summaryFile = scratchSpace.fileFor(module.linkedSummaryId);
if (response.exitCode == EXIT_CODE_ERROR || !await summaryFile.exists()) {
throw new AnalyzerSummaryException(module.linkedSummaryId, response.output);
}

Expand Down
13 changes: 10 additions & 3 deletions build_runner/lib/src/asset/reader.dart
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ abstract class Md5DigestReader implements DigestAssetReader {
///
/// Tracks the assets and globs read during this step for input dependency
/// tracking.
class SingleStepReader implements AssetReader {
class SingleStepReader implements DigestAssetReader {
final AssetGraph _assetGraph;
final _assetsRead = new Set<AssetId>();
final DigestAssetReader _delegate;
Expand Down Expand Up @@ -95,6 +95,13 @@ class SingleStepReader implements AssetReader {
return result;
}

@override
Future<Digest> digest(AssetId id) async {
if (!_isReadable(id)) throw new AssetNotFoundException(id);
await _ensureAssetIsBuilt(id);
return _ensureDigest(id);
}

@override
Future<List<int>> readAsBytes(AssetId id) async {
if (!_isReadable(id)) throw new AssetNotFoundException(id);
Expand Down Expand Up @@ -135,8 +142,8 @@ class SingleStepReader implements AssetReader {
}
}

Future<Null> _ensureDigest(AssetId id) async {
Future<Digest> _ensureDigest(AssetId id) async {
var node = _assetGraph.get(id);
node.lastKnownDigest ??= await _delegate.digest(id);
return node.lastKnownDigest ??= await _delegate.digest(id);
}
}
61 changes: 42 additions & 19 deletions build_runner/lib/src/asset_graph/graph.dart
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,9 @@ class AssetGraph {
var existing = get(node.id);
if (existing != null) {
if (existing is SyntheticAssetNode) {
_remove(existing.id);
// Don't call _removeRecursive, that recursively removes all transitive
// primary outputs. We only want to remove this node.
_nodesByPackage[existing.id.package].remove(existing.id.path);
node.outputs.addAll(existing.outputs);
node.primaryOutputs.addAll(existing.primaryOutputs);
} else {
Expand Down Expand Up @@ -100,14 +102,16 @@ class AssetGraph {
/// Removes the node representing [id] from the graph, and all of its
/// `primaryOutput`s.
///
/// Also removes all edges between all removed nodes and other nodes.
///
/// Returns a [Set<AssetId>] of all removed nodes.
Set<AssetId> _remove(AssetId id, {Set<AssetId> removedIds}) {
Set<AssetId> _removeRecursive(AssetId id, {Set<AssetId> removedIds}) {
removedIds ??= new Set<AssetId>();
var node = get(id);
if (node == null) return removedIds;
removedIds.add(id);
for (var output in node.primaryOutputs) {
_remove(output, removedIds: removedIds);
_removeRecursive(output, removedIds: removedIds);
}
for (var output in node.outputs) {
var generatedNode = get(output) as GeneratedAssetNode;
Expand Down Expand Up @@ -146,30 +150,24 @@ class AssetGraph {
Future delete(AssetId id),
DigestAssetReader digestReader) async {
var invalidatedIds = new Set<AssetId>();
// All the assets that should be deleted.
var idsToDelete = new Set<AssetId>();

// Builds up `idsToDelete` and `idsToRemove` by recursively invalidating
// the outputs of `id`.
void clearNodeAndDeps(AssetId id, ChangeType rootChangeType) {
// Transitively invalidates all assets.
void invalidateNodeAndDeps(AssetId id, ChangeType rootChangeType) {
var node = this.get(id);
if (node == null) return;
if (!invalidatedIds.add(id)) return;

if (node is GeneratedAssetNode) {
idsToDelete.add(id);
node.needsUpdate = true;
node.wasOutput = false;
node.globs = new Set();
}

// Update all outputs of this asset as well.
for (var output in node.outputs) {
clearNodeAndDeps(output, rootChangeType);
invalidateNodeAndDeps(output, rootChangeType);
}
}

updates.forEach(clearNodeAndDeps);
updates.forEach(invalidateNodeAndDeps);

var newIds = new Set<AssetId>();
var modifyIds = new Set<AssetId>();
Expand Down Expand Up @@ -197,10 +195,33 @@ class AssetGraph {
newAndModifiedNodes.where((node) => node.outputs.isNotEmpty),
digestReader);

// Collects the set of all transitive ids to be removed from the graph,
// based on the removed `SourceAssetNode`s by following the
// `primaryOutputs`.
var transitiveRemovedIds = new Set<AssetId>();
addTransitivePrimaryOutputs(AssetId id) {
transitiveRemovedIds.add(id);
get(id).primaryOutputs.forEach(addTransitivePrimaryOutputs);
}

removeIds
.where((id) => get(id) is SourceAssetNode)
.forEach(addTransitivePrimaryOutputs);

// The generated nodes to actually delete from the file system.
var idsToDelete = new Set<AssetId>.from(transitiveRemovedIds)
..removeAll(removeIds);

// For manually deleted generated outputs, we bash away their
// `previousInputsDigest` to make sure they actually get regenerated.
for (var deletedOutput
in removeIds.where((id) => get(id) is GeneratedAssetNode)) {
(get(deletedOutput) as GeneratedAssetNode).previousInputsDigest = null;
}

var allNewAndDeletedIds =
_addOutputsForSources(buildActions, newIds, rootPackage)
..addAll(removeIds)
..addAll(idsToDelete);
..addAll(transitiveRemovedIds);

// For all new or deleted assets, check if they match any globs.
for (var id in allNewAndDeletedIds) {
Expand All @@ -211,7 +232,7 @@ class AssetGraph {
.globs
.any((glob) => glob.matches(id.path))) {
// The change type is irrelevant here.
clearNodeAndDeps(node.id, null);
invalidateNodeAndDeps(node.id, null);
}
}
}
Expand All @@ -223,7 +244,9 @@ class AssetGraph {

// Remove all deleted source assets from the graph, which also recursively
// removes all their primary outputs.
removeIds.where((id) => get(id) is SourceAssetNode).forEach(_remove);
removeIds
.where((id) => get(id) is SourceAssetNode)
.forEach(_removeRecursive);

return invalidatedIds;
}
Expand Down Expand Up @@ -290,7 +313,7 @@ class AssetGraph {
// outputs, and replace them with a `GeneratedAssetNode`.
if (contains(output)) {
assert(get(output) is! GeneratedAssetNode);
_remove(output, removedIds: removed);
_removeRecursive(output, removedIds: removed);
}

_add(new GeneratedAssetNode(
Expand All @@ -304,5 +327,5 @@ class AssetGraph {

// TODO remove once tests are updated
void add(AssetNode node) => _add(node);
Set<AssetId> remove(AssetId id) => _remove(id);
Set<AssetId> remove(AssetId id) => _removeRecursive(id);
}
22 changes: 19 additions & 3 deletions build_runner/lib/src/asset_graph/node.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
// 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.

import 'dart:collection';

import 'package:build/build.dart';
import 'package:crypto/crypto.dart';
import 'package:glob/glob.dart';
Expand Down Expand Up @@ -74,13 +76,27 @@ class GeneratedAssetNode extends AssetNode {

/// All the inputs that were read when generating this asset, or deciding not
/// to generate it.
final Set<AssetId> inputs;
///
/// This needs to be an ordered set because we compute combined input digests
/// using this later on.
final SplayTreeSet<AssetId> inputs;

/// A digest combining all digests of all previous inputs.
///
/// Used to determine whether all the inputs to a build step are identical to
/// the previous run, indicating that the previous output is still valid.
Digest previousInputsDigest;

GeneratedAssetNode(this.phaseNumber, this.primaryInput, this.needsUpdate,
this.wasOutput, AssetId id,
{Digest lastKnownDigest, Set<Glob> globs, Iterable<AssetId> inputs})
{Digest lastKnownDigest,
Set<Glob> globs,
Iterable<AssetId> inputs,
this.previousInputsDigest})
: this.globs = globs ?? new Set<Glob>(),
this.inputs = inputs?.toSet() ?? new Set<AssetId>(),
this.inputs = inputs != null
? new SplayTreeSet.from(inputs)
: new SplayTreeSet<AssetId>(),
super(id, lastKnownDigest: lastKnownDigest);

@override
Expand Down
38 changes: 23 additions & 15 deletions build_runner/lib/src/asset_graph/serialization.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ part of 'graph.dart';
///
/// This should be incremented any time the serialize/deserialize formats
/// change.
const _version = 10;
const _version = 11;

/// Deserializes an [AssetGraph] from a [Map].
class _AssetGraphDeserializer {
Expand Down Expand Up @@ -57,9 +57,7 @@ class _AssetGraphDeserializer {
var typeId = _NodeType.values[serializedNode[_Field.NodeType.index] as int];
var id = _idToAssetId[serializedNode[_Field.Id.index] as int];
var serializedDigest = serializedNode[_Field.Digest.index] as String;
var digest = serializedDigest == null
? null
: new Digest(BASE64.decode(serializedDigest));
var digest = _deserializeDigest(serializedDigest);
switch (typeId) {
case _NodeType.Source:
assert(serializedNode.length == _WrappedAssetNode._length);
Expand All @@ -72,16 +70,17 @@ class _AssetGraphDeserializer {
case _NodeType.Generated:
assert(serializedNode.length == _WrappedGeneratedAssetNode._length);
node = new GeneratedAssetNode(
serializedNode[_Field.PhaseNumber.index] as int,
_idToAssetId[serializedNode[_Field.PrimaryInput.index] as int],
_deserializeBool(serializedNode[_Field.NeedsUpdate.index] as int),
_deserializeBool(serializedNode[_Field.WasOutput.index] as int),
id,
globs: (serializedNode[_Field.Globs.index] as Iterable<String>)
.map((pattern) => new Glob(pattern))
.toSet(),
lastKnownDigest: digest,
);
serializedNode[_Field.PhaseNumber.index] as int,
_idToAssetId[serializedNode[_Field.PrimaryInput.index] as int],
_deserializeBool(serializedNode[_Field.NeedsUpdate.index] as int),
_deserializeBool(serializedNode[_Field.WasOutput.index] as int),
id,
globs: (serializedNode[_Field.Globs.index] as Iterable<String>)
.map((pattern) => new Glob(pattern))
.toSet(),
lastKnownDigest: digest,
previousInputsDigest: _deserializeDigest(
serializedNode[_Field.PreviousInputsDigest.index] as String));
break;
}
node.outputs.addAll(_deserializeAssetIds(
Expand All @@ -95,6 +94,10 @@ class _AssetGraphDeserializer {
serializedIds.map((id) => _idToAssetId[id]).toList();

bool _deserializeBool(int value) => value == 0 ? false : true;

Digest _deserializeDigest(String serializedDigest) => serializedDigest == null
? null
: new Digest(BASE64.decode(serializedDigest));
}

/// Serializes an [AssetGraph] into a [Map].
Expand Down Expand Up @@ -159,7 +162,8 @@ enum _Field {
WasOutput,
PhaseNumber,
Globs,
NeedsUpdate
NeedsUpdate,
PreviousInputsDigest
}

/// Wraps an [AssetNode] in a class that implements [List] instead of
Expand Down Expand Up @@ -253,6 +257,10 @@ class _WrappedGeneratedAssetNode extends _WrappedAssetNode {
return generatedNode.globs.map((glob) => glob.pattern).toList();
case _Field.NeedsUpdate:
return _serializeBool(generatedNode.needsUpdate);
case _Field.PreviousInputsDigest:
return generatedNode.previousInputsDigest == null
? null
: BASE64.encode(generatedNode.previousInputsDigest.bytes);
default:
throw new RangeError.index(index, this);
}
Expand Down
Loading