Skip to content

Commit

Permalink
Add NodeType to AssetNode. (#3905)
Browse files Browse the repository at this point in the history
  • Loading branch information
davidmorgan authored Mar 5, 2025
1 parent 4dae408 commit 4582e2d
Show file tree
Hide file tree
Showing 9 changed files with 127 additions and 85 deletions.
2 changes: 1 addition & 1 deletion build_runner/test/generate/watch_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -364,7 +364,7 @@ void main() {
var builderOptionsId = makeAssetId('a|Phase0.builderOptions');
var builderOptionsNode = BuilderOptionsAssetNode(
builderOptionsId,
computeBuilderOptionsDigest(defaultBuilderOptions),
lastKnownDigest: computeBuilderOptionsDigest(defaultBuilderOptions),
);
expectedGraph.add(builderOptionsNode);

Expand Down
37 changes: 24 additions & 13 deletions build_runner_core/lib/src/asset_graph/graph.dart
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ class AssetGraph {
void _add(AssetNode node) {
var existing = get(node.id);
if (existing != null) {
if (existing is SyntheticSourceAssetNode) {
if (existing.type == NodeType.syntheticSource) {
// 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);
Expand Down Expand Up @@ -164,7 +164,7 @@ class AssetGraph {
add(
BuilderOptionsAssetNode(
builderOptionsIdForAction(phase, phaseNum),
computeBuilderOptionsDigest(phase.builderOptions),
lastKnownDigest: computeBuilderOptionsDigest(phase.builderOptions),
),
);
} else if (phase is PostBuildPhase) {
Expand All @@ -173,7 +173,9 @@ class AssetGraph {
add(
BuilderOptionsAssetNode(
builderOptionsIdForAction(builderAction, actionNum),
computeBuilderOptionsDigest(builderAction.builderOptions),
lastKnownDigest: computeBuilderOptionsDigest(
builderAction.builderOptions,
),
),
);
actionNum++;
Expand Down Expand Up @@ -238,7 +240,7 @@ class AssetGraph {
}
}
// Synthetic nodes need to be kept to retain dependency tracking.
if (node is! SyntheticSourceAssetNode) {
if (node.type != NodeType.syntheticSource) {
_nodesByPackage[id.package]!.remove(id.path);
}
return removedIds;
Expand Down Expand Up @@ -274,7 +276,7 @@ class AssetGraph {

/// All the source files in the graph.
Iterable<AssetId> get sources =>
allNodes.whereType<SourceAssetNode>().map((n) => n.id);
allNodes.where((n) => n.type == NodeType.source).map((n) => n.id);

/// Updates graph structure, invalidating and deleting any outputs that were
/// affected.
Expand Down Expand Up @@ -332,9 +334,12 @@ class AssetGraph {
}
}

removeIds
.where((id) => get(id) is SourceAssetNode)
.forEach(addTransitivePrimaryOutputs);
for (final id in removeIds) {
final node = get(id);
if (node != null && node.type == NodeType.source) {
addTransitivePrimaryOutputs(id);
}
}

// The generated nodes to actually delete from the file system.
var idsToDelete = Set<AssetId>.from(transitiveRemovedIds)
Expand Down Expand Up @@ -397,9 +402,12 @@ class AssetGraph {

// Remove all deleted source assets from the graph, which also recursively
// removes all their primary outputs.
for (var id in removeIds.where((id) => get(id) is SourceAssetNode)) {
invalidateNodeAndDeps(id);
_removeRecursive(id);
for (final id in removeIds) {
final node = get(id);
if (node != null && node.type == NodeType.source) {
invalidateNodeAndDeps(id);
_removeRecursive(id);
}
}

return invalidatedIds;
Expand Down Expand Up @@ -480,7 +488,7 @@ class AssetGraph {
) {
var phaseOutputs = <AssetId>{};
var buildOptionsNodeId = builderOptionsIdForAction(phase, phaseNum);
var builderOptionsNode = get(buildOptionsNodeId) as BuilderOptionsAssetNode;
var builderOptionsNode = get(buildOptionsNodeId)!;
var inputs =
allInputs.where((input) => _actionMatches(phase, input)).toList();
for (var input in inputs) {
Expand Down Expand Up @@ -541,12 +549,15 @@ class AssetGraph {
Set<AssetId> _addGeneratedOutputs(
Iterable<AssetId> outputs,
int phaseNumber,
BuilderOptionsAssetNode builderOptionsNode,
AssetNode builderOptionsNode,
List<BuildPhase> buildPhases,
String rootPackage, {
required AssetId primaryInput,
required bool isHidden,
}) {
if (builderOptionsNode.type != NodeType.builderOptions) {
throw ArgumentError('Expected node of type NodeType.builderOptionsNode');
}
var removed = <AssetId>{};
for (var output in outputs) {
AssetNode? existing;
Expand Down
96 changes: 60 additions & 36 deletions build_runner_core/lib/src/asset_graph/node.dart
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,24 @@ import 'package:glob/glob.dart';

import '../generate/phase.dart';

/// Types of [AssetNode].
enum NodeType {
builderOptions,
generated,
glob,
internal,
placeholder,
postProcessAnchor,
source,
syntheticSource,
}

/// A node in the asset graph which may be an input to other assets.
abstract class AssetNode {
final AssetId id;

final NodeType type;

/// The assets that any [Builder] in the build graph declares it may output
/// when run on this asset.
final Set<AssetId> primaryOutputs = <AssetId>{};
Expand Down Expand Up @@ -68,15 +82,7 @@ abstract class AssetNode {
/// and re-added, in which case it won't have a digest.
bool get isInteresting => outputs.isNotEmpty || lastKnownDigest != null;

AssetNode(this.id, {this.lastKnownDigest});

/// Work around issue where you can't mixin classes into a class with optional
/// constructor args.
AssetNode._forMixins(this.id);

/// Work around issue where you can't mixin classes into a class with optional
/// constructor args, this one includes the digest.
AssetNode._forMixinsWithDigest(this.id, this.lastKnownDigest);
AssetNode(this.id, {required this.type, this.lastKnownDigest});

@override
String toString() => 'AssetNode: $id';
Expand All @@ -94,15 +100,17 @@ class InternalAssetNode extends AssetNode {
@override
bool get isValidInput => false;

InternalAssetNode(super.id, {super.lastKnownDigest});
InternalAssetNode(super.id, {super.lastKnownDigest})
: super(type: NodeType.internal);

@override
String toString() => 'InternalAssetNode: $id';
}

/// A node which is an original source asset (not generated).
class SourceAssetNode extends AssetNode {
SourceAssetNode(super.id, {super.lastKnownDigest});
SourceAssetNode(super.id, {super.lastKnownDigest})
: super(type: NodeType.source);

@override
String toString() => 'SourceAssetNode: $id';
Expand Down Expand Up @@ -172,65 +180,68 @@ class GeneratedAssetNode extends AssetNode implements NodeWithInputs {
required this.isFailure,
required this.primaryInput,
required this.builderOptionsId,
}) : inputs = inputs != null ? HashSet.from(inputs) : HashSet();
}) : inputs = inputs != null ? HashSet.from(inputs) : HashSet(),
super(type: NodeType.generated);

@override
String toString() =>
'GeneratedAssetNode: $id generated from input $primaryInput.';
}

/// A node which is not a generated or source asset.
/// A non-existent source.
///
/// These are typically not readable or valid as inputs.
mixin _SyntheticAssetNode implements AssetNode {
/// Typically these are created as a result of `canRead` calls for assets that
/// don't exist in the graph. We still need to set up proper dependencies so
/// that if that asset gets added later the outputs are properly invalidated.
class SyntheticSourceAssetNode extends AssetNode {
@override
bool get isReadable => false;

@override
bool get isValidInput => false;
}

/// A [_SyntheticAssetNode] representing a non-existent source.
///
/// Typically these are created as a result of `canRead` calls for assets that
/// don't exist in the graph. We still need to set up proper dependencies so
/// that if that asset gets added later the outputs are properly invalidated.
class SyntheticSourceAssetNode extends AssetNode with _SyntheticAssetNode {
SyntheticSourceAssetNode(super.id) : super._forMixins();
SyntheticSourceAssetNode(super.id) : super(type: NodeType.syntheticSource);
}

/// A [_SyntheticAssetNode] which represents an individual [BuilderOptions]
/// object.
/// An individual [BuilderOptions] object.
///
/// These are used to track the state of a [BuilderOptions] object, and all
/// [GeneratedAssetNode]s should depend on one of these nodes, which represents
/// their configuration.
class BuilderOptionsAssetNode extends AssetNode with _SyntheticAssetNode {
BuilderOptionsAssetNode(super.id, Digest super.lastKnownDigest)
: super._forMixinsWithDigest();
class BuilderOptionsAssetNode extends AssetNode {
BuilderOptionsAssetNode(super.id, {required super.lastKnownDigest})
: super(type: NodeType.builderOptions);

@override
bool get isReadable => false;

@override
bool get isValidInput => false;

@override
String toString() => 'BuildOptionsAssetNode: $id';
}

/// Placeholder assets are magic files that are usable as inputs but are not
/// readable.
class PlaceHolderAssetNode extends AssetNode with _SyntheticAssetNode {
class PlaceHolderAssetNode extends AssetNode {
@override
bool get isReadable => false;

@override
bool get isValidInput => true;

PlaceHolderAssetNode(super.id) : super._forMixins();
PlaceHolderAssetNode(super.id) : super(type: NodeType.placeholder);

@override
String toString() => 'PlaceHolderAssetNode: $id';
}

/// A [_SyntheticAssetNode] which is created for each [primaryInput] to a
/// [PostBuildAction].
/// A [primaryInput] to a [PostBuildAction].
///
/// The [outputs] of this node are the individual outputs created for the
/// [primaryInput] during the [PostBuildAction] at index [actionNumber].
class PostProcessAnchorNode extends AssetNode with _SyntheticAssetNode {
class PostProcessAnchorNode extends AssetNode {
final int actionNumber;
final AssetId builderOptionsId;
final AssetId primaryInput;
Expand All @@ -242,7 +253,13 @@ class PostProcessAnchorNode extends AssetNode with _SyntheticAssetNode {
this.actionNumber,
this.builderOptionsId, {
this.previousInputsDigest,
}) : super._forMixins();
}) : super(type: NodeType.postProcessAnchor);

@override
bool get isReadable => false;

@override
bool get isValidInput => false;

PostProcessAnchorNode.forInputAndAction(
AssetId primaryInput,
Expand All @@ -260,7 +277,7 @@ class PostProcessAnchorNode extends AssetNode with _SyntheticAssetNode {
///
/// The [id] must always be unique to a given package, phase, and glob
/// pattern.
class GlobAssetNode extends InternalAssetNode implements NodeWithInputs {
class GlobAssetNode extends AssetNode implements NodeWithInputs {
final Glob glob;

/// All the potential inputs matching this glob.
Expand All @@ -271,6 +288,12 @@ class GlobAssetNode extends InternalAssetNode implements NodeWithInputs {
@override
HashSet<AssetId> inputs;

@override
bool get isInteresting => true;

@override
bool get isValidInput => false;

@override
bool get isReadable => false;

Expand All @@ -291,7 +314,8 @@ class GlobAssetNode extends InternalAssetNode implements NodeWithInputs {
HashSet<AssetId>? inputs,
super.lastKnownDigest,
this.results,
}) : inputs = inputs ?? HashSet();
}) : inputs = inputs ?? HashSet(),
super(type: NodeType.glob);

static AssetId createId(String package, Glob glob, int phaseNum) => AssetId(
package,
Expand Down
40 changes: 20 additions & 20 deletions build_runner_core/lib/src/asset_graph/serialization.dart
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ class _AssetGraphDeserializer {
// current nodes.
for (var node in graph.allNodes) {
// These aren't explicitly added as inputs.
if (node is BuilderOptionsAssetNode) continue;
if (node.type == NodeType.builderOptions) continue;

for (var output in node.outputs) {
var inputsNode = graph.get(output) as NodeWithInputs?;
Expand Down Expand Up @@ -168,7 +168,7 @@ class _AssetGraphDeserializer {
break;
case _NodeType.builderOptions:
assert(serializedNode.length == _WrappedAssetNode._length);
node = BuilderOptionsAssetNode(id, digest!);
node = BuilderOptionsAssetNode(id, lastKnownDigest: digest!);
break;
case _NodeType.placeholder:
assert(serializedNode.length == _WrappedAssetNode._length);
Expand Down Expand Up @@ -346,24 +346,24 @@ class _WrappedAssetNode extends Object with ListMixin implements List {
var fieldId = _AssetField.values[index];
switch (fieldId) {
case _AssetField.nodeType:
if (node is SourceAssetNode) {
return _NodeType.source.index;
} else if (node is GeneratedAssetNode) {
return _NodeType.generated.index;
} else if (node is GlobAssetNode) {
return _NodeType.glob.index;
} else if (node is SyntheticSourceAssetNode) {
return _NodeType.syntheticSource.index;
} else if (node is InternalAssetNode) {
return _NodeType.internal.index;
} else if (node is BuilderOptionsAssetNode) {
return _NodeType.builderOptions.index;
} else if (node is PlaceHolderAssetNode) {
return _NodeType.placeholder.index;
} else if (node is PostProcessAnchorNode) {
return _NodeType.postProcessAnchor.index;
} else {
throw StateError('Unrecognized node type');
switch (node.type) {
case NodeType.source:
return _NodeType.source.index;
case NodeType.generated:
return _NodeType.generated.index;
case NodeType.glob:
return _NodeType.glob.index;
case NodeType.syntheticSource:
return _NodeType.syntheticSource.index;
case NodeType.internal:
return _NodeType.internal.index;
case NodeType.builderOptions:
return _NodeType.builderOptions.index;

case NodeType.placeholder:
return _NodeType.placeholder.index;
case NodeType.postProcessAnchor:
return _NodeType.postProcessAnchor.index;
}
case _AssetField.id:
return serializer.findAssetIndex(node.id, from: node.id, field: 'id');
Expand Down
9 changes: 7 additions & 2 deletions build_runner_core/lib/src/generate/build_definition.dart
Original file line number Diff line number Diff line change
Expand Up @@ -600,8 +600,13 @@ class _Loader {
AssetId builderOptionsId,
BuilderOptions options,
) {
var builderOptionsNode =
assetGraph.get(builderOptionsId) as BuilderOptionsAssetNode;
var builderOptionsNode = assetGraph.get(builderOptionsId)!;
if (builderOptionsNode.type != NodeType.builderOptions) {
throw StateError(
'Expected node of type NodeType.builderOptionsNode:'
'$builderOptionsNode',
);
}
var oldDigest = builderOptionsNode.lastKnownDigest;
builderOptionsNode.lastKnownDigest = computeBuilderOptionsDigest(options);
if (builderOptionsNode.lastKnownDigest != oldDigest) {
Expand Down
Loading

0 comments on commit 4582e2d

Please sign in to comment.