From fad3b85f5dbde0a7ce5f678cf8171aa359459043 Mon Sep 17 00:00:00 2001 From: David Morgan Date: Wed, 5 Mar 2025 09:38:37 +0100 Subject: [PATCH] Add `NodeType` to `AssetNode`. --- build_runner/test/generate/watch_test.dart | 2 +- .../lib/src/asset_graph/graph.dart | 37 ++++--- .../lib/src/asset_graph/node.dart | 96 ++++++++++++------- .../lib/src/asset_graph/serialization.dart | 40 ++++---- .../lib/src/generate/build_definition.dart | 9 +- .../lib/src/generate/build_impl.dart | 4 +- .../src/generate/finalized_assets_view.dart | 2 +- .../test/asset_graph/graph_test.dart | 18 ++-- .../test/generate/build_test.dart | 4 +- 9 files changed, 127 insertions(+), 85 deletions(-) diff --git a/build_runner/test/generate/watch_test.dart b/build_runner/test/generate/watch_test.dart index db8c461b0..337345ba5 100644 --- a/build_runner/test/generate/watch_test.dart +++ b/build_runner/test/generate/watch_test.dart @@ -364,7 +364,7 @@ void main() { var builderOptionsId = makeAssetId('a|Phase0.builderOptions'); var builderOptionsNode = BuilderOptionsAssetNode( builderOptionsId, - computeBuilderOptionsDigest(defaultBuilderOptions), + lastKnownDigest: computeBuilderOptionsDigest(defaultBuilderOptions), ); expectedGraph.add(builderOptionsNode); diff --git a/build_runner_core/lib/src/asset_graph/graph.dart b/build_runner_core/lib/src/asset_graph/graph.dart index 80d11b554..4b5061571 100644 --- a/build_runner_core/lib/src/asset_graph/graph.dart +++ b/build_runner_core/lib/src/asset_graph/graph.dart @@ -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); @@ -164,7 +164,7 @@ class AssetGraph { add( BuilderOptionsAssetNode( builderOptionsIdForAction(phase, phaseNum), - computeBuilderOptionsDigest(phase.builderOptions), + lastKnownDigest: computeBuilderOptionsDigest(phase.builderOptions), ), ); } else if (phase is PostBuildPhase) { @@ -173,7 +173,9 @@ class AssetGraph { add( BuilderOptionsAssetNode( builderOptionsIdForAction(builderAction, actionNum), - computeBuilderOptionsDigest(builderAction.builderOptions), + lastKnownDigest: computeBuilderOptionsDigest( + builderAction.builderOptions, + ), ), ); actionNum++; @@ -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; @@ -274,7 +276,7 @@ class AssetGraph { /// All the source files in the graph. Iterable get sources => - allNodes.whereType().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. @@ -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.from(transitiveRemovedIds) @@ -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; @@ -480,7 +488,7 @@ class AssetGraph { ) { var phaseOutputs = {}; 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) { @@ -541,12 +549,15 @@ class AssetGraph { Set _addGeneratedOutputs( Iterable outputs, int phaseNumber, - BuilderOptionsAssetNode builderOptionsNode, + AssetNode builderOptionsNode, List buildPhases, String rootPackage, { required AssetId primaryInput, required bool isHidden, }) { + if (builderOptionsNode.type != NodeType.builderOptions) { + throw ArgumentError('Expected node of type NodeType.builderOptionsNode'); + } var removed = {}; for (var output in outputs) { AssetNode? existing; diff --git a/build_runner_core/lib/src/asset_graph/node.dart b/build_runner_core/lib/src/asset_graph/node.dart index a9feeb5aa..166212201 100644 --- a/build_runner_core/lib/src/asset_graph/node.dart +++ b/build_runner_core/lib/src/asset_graph/node.dart @@ -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 primaryOutputs = {}; @@ -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'; @@ -94,7 +100,8 @@ 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'; @@ -102,7 +109,8 @@ class InternalAssetNode extends AssetNode { /// 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'; @@ -172,42 +180,43 @@ 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'; @@ -215,22 +224,24 @@ class BuilderOptionsAssetNode extends AssetNode with _SyntheticAssetNode { /// 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; @@ -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, @@ -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. @@ -271,6 +288,12 @@ class GlobAssetNode extends InternalAssetNode implements NodeWithInputs { @override HashSet inputs; + @override + bool get isInteresting => true; + + @override + bool get isValidInput => false; + @override bool get isReadable => false; @@ -291,7 +314,8 @@ class GlobAssetNode extends InternalAssetNode implements NodeWithInputs { HashSet? 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, diff --git a/build_runner_core/lib/src/asset_graph/serialization.dart b/build_runner_core/lib/src/asset_graph/serialization.dart index 60d108310..481129e4b 100644 --- a/build_runner_core/lib/src/asset_graph/serialization.dart +++ b/build_runner_core/lib/src/asset_graph/serialization.dart @@ -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?; @@ -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); @@ -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'); diff --git a/build_runner_core/lib/src/generate/build_definition.dart b/build_runner_core/lib/src/generate/build_definition.dart index 0cf0b9b5f..340cceafa 100644 --- a/build_runner_core/lib/src/generate/build_definition.dart +++ b/build_runner_core/lib/src/generate/build_definition.dart @@ -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) { diff --git a/build_runner_core/lib/src/generate/build_impl.dart b/build_runner_core/lib/src/generate/build_impl.dart index cfc1f7a13..89804c6c7 100644 --- a/build_runner_core/lib/src/generate/build_impl.dart +++ b/build_runner_core/lib/src/generate/build_impl.dart @@ -611,8 +611,8 @@ class _SingleBuild { .toList(growable: false)) { if (node is! PostProcessAnchorNode) continue; if (node.actionNumber != actionNum) continue; - final inputNode = _assetGraph.get(node.primaryInput); - if (inputNode is SourceAssetNode || + final inputNode = _assetGraph.get(node.primaryInput)!; + if (inputNode.type == NodeType.source || inputNode is GeneratedAssetNode && inputNode.wasOutput && !inputNode.isFailure && diff --git a/build_runner_core/lib/src/generate/finalized_assets_view.dart b/build_runner_core/lib/src/generate/finalized_assets_view.dart index 2198cef85..c4ec49b3f 100644 --- a/build_runner_core/lib/src/generate/finalized_assets_view.dart +++ b/build_runner_core/lib/src/generate/finalized_assets_view.dart @@ -72,7 +72,7 @@ bool _shouldSkipNode( if (node.id.package != packageGraph.root.name) return true; } - if (node is InternalAssetNode) return true; + if (node.type == NodeType.internal || node.type == NodeType.glob) return true; if (node is GeneratedAssetNode) { if (!node.wasOutput || node.isFailure || node.state != NodeState.upToDate) { return true; diff --git a/build_runner_core/test/asset_graph/graph_test.dart b/build_runner_core/test/asset_graph/graph_test.dart index 0cc5bd836..42bf43441 100644 --- a/build_runner_core/test/asset_graph/graph_test.dart +++ b/build_runner_core/test/asset_graph/graph_test.dart @@ -107,7 +107,7 @@ void main() { var phaseNum = n; var builderOptionsNode = BuilderOptionsAssetNode( makeAssetId(), - Digest([n]), + lastKnownDigest: Digest([n]), ); graph.add(builderOptionsNode); var anchorNode = PostProcessAnchorNode.forInputAndAction( @@ -120,7 +120,7 @@ void main() { for (var g = 0; g < 5 - n; g++) { var builderOptionsNode = BuilderOptionsAssetNode( makeAssetId(), - md5.convert(utf8.encode('test')), + lastKnownDigest: md5.convert(utf8.encode('test')), ); var generatedNode = GeneratedAssetNode( @@ -280,12 +280,12 @@ void main() { expect(primaryOutputNode.inputs, isEmpty); expect(primaryOutputNode.primaryInput, primaryInputId); - var builderOptionsNode = - graph.get(builderOptionsId) as BuilderOptionsAssetNode; + var builderOptionsNode = graph.get(builderOptionsId)!; + expect(builderOptionsNode.type, NodeType.builderOptions); expect(builderOptionsNode.outputs, unorderedEquals([primaryOutputId])); - var postBuilderOptionsNode = - graph.get(postBuilderOptionsId) as BuilderOptionsAssetNode; + var postBuilderOptionsNode = graph.get(postBuilderOptionsId)!; + expect(postBuilderOptionsNode.type, NodeType.builderOptions); expect(postBuilderOptionsNode, isNotNull); expect(postBuilderOptionsNode.outputs, isEmpty); var anchorNode = @@ -369,7 +369,7 @@ void main() { ); expect(graph.contains(syntheticId), isTrue); - expect(graph.get(syntheticId), const TypeMatcher()); + expect(graph.get(syntheticId)?.type, NodeType.source); expect(graph.contains(syntheticOutputId), isTrue); expect( graph.get(syntheticOutputId), @@ -720,7 +720,9 @@ void main() { (graph.get(generatedDart) as GeneratedAssetNode) ..state = NodeState.upToDate ..inputs.addAll([generatedPart, toBeGeneratedDart]); - (graph.get(source) as SourceAssetNode).outputs.add(generatedPart); + final node = graph.get(source)!; + expect(node.type, NodeType.source); + node.outputs.add(generatedPart); await graph.updateAndInvalidate( buildPhases, diff --git a/build_runner_core/test/generate/build_test.dart b/build_runner_core/test/generate/build_test.dart index 5ede4b9de..f75ceaeae 100644 --- a/build_runner_core/test/generate/build_test.dart +++ b/build_runner_core/test/generate/build_test.dart @@ -1274,7 +1274,7 @@ void main() { var builderOptionsId = makeAssetId('a|Phase0.builderOptions'); var builderOptionsNode = BuilderOptionsAssetNode( builderOptionsId, - computeBuilderOptionsDigest(defaultBuilderOptions), + lastKnownDigest: computeBuilderOptionsDigest(defaultBuilderOptions), ); expectedGraph.add(builderOptionsNode); @@ -1318,7 +1318,7 @@ void main() { var postBuilderOptionsId = makeAssetId('a|PostPhase0.builderOptions'); var postBuilderOptionsNode = BuilderOptionsAssetNode( postBuilderOptionsId, - computeBuilderOptionsDigest(defaultBuilderOptions), + lastKnownDigest: computeBuilderOptionsDigest(defaultBuilderOptions), ); expectedGraph.add(postBuilderOptionsNode);