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

Add NodeType to AssetNode. #3905

Merged
merged 1 commit into from
Mar 5, 2025
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
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
Loading