| // Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file |
| // 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 'dart:convert'; |
| |
| import 'package:build/build.dart'; |
| import 'package:crypto/crypto.dart'; |
| import 'package:glob/glob.dart'; |
| import 'package:meta/meta.dart'; |
| |
| import '../generate/phase.dart'; |
| |
| /// A node in the asset graph which may be an input to other assets. |
| abstract class AssetNode { |
| final AssetId id; |
| |
| /// The assets that any [Builder] in the build graph declares it may output |
| /// when run on this asset. |
| final Set<AssetId> primaryOutputs = <AssetId>{}; |
| |
| /// The [AssetId]s of all generated assets which are output by a [Builder] |
| /// which reads this asset. |
| final Set<AssetId> outputs = <AssetId>{}; |
| |
| /// The [AssetId]s of all [PostProcessAnchorNode] assets for which this node |
| /// is the primary input. |
| final Set<AssetId> anchorOutputs = <AssetId>{}; |
| |
| /// The [Digest] for this node in its last known state. |
| /// |
| /// May be `null` if this asset has no outputs, or if it doesn't actually |
| /// exist. |
| Digest lastKnownDigest; |
| |
| /// Whether or not this node was an output of this build. |
| bool get isGenerated => false; |
| |
| /// Whether or not this asset type can be read. |
| /// |
| /// This does not indicate whether or not this specific node actually exists |
| /// at this moment in time. |
| bool get isReadable => true; |
| |
| /// The IDs of the [PostProcessAnchorNode] for post process builder which |
| /// requested to delete this asset. |
| final Set<AssetId> deletedBy = <AssetId>{}; |
| |
| /// Whether the node is deleted. |
| /// |
| /// Deleted nodes are ignored in the final merge step and watch handlers. |
| bool get isDeleted => deletedBy.isNotEmpty; |
| |
| /// Whether or not this node can be read by a builder as a primary or |
| /// secondary input. |
| /// |
| /// Some nodes are valid primary inputs but are not readable (see |
| /// [PlaceHolderAssetNode]), while others are readable in the overall build |
| /// system but are not valid builder inputs (see [InternalAssetNode]). |
| bool get isValidInput => true; |
| |
| /// Whether or not changes to this node will have any effect on other nodes. |
| /// |
| /// Be default, if we haven't computed a digest for this asset and it has no |
| /// outputs, then it isn't interesting. |
| /// |
| /// Checking for a digest alone isn't enough because a file may be deleted |
| /// 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); |
| |
| @override |
| String toString() => 'AssetNode: $id'; |
| } |
| |
| /// A node representing some internal asset. |
| /// |
| /// These nodes are not used as primary inputs, but they are tracked in the |
| /// asset graph and are readable. |
| class InternalAssetNode extends AssetNode { |
| // These don't have [outputs] but they are interesting regardless. |
| @override |
| bool get isInteresting => true; |
| |
| @override |
| bool get isValidInput => false; |
| |
| InternalAssetNode(AssetId id, {Digest lastKnownDigest}) |
| : super(id, lastKnownDigest: lastKnownDigest); |
| |
| @override |
| String toString() => 'InternalAssetNode: $id'; |
| } |
| |
| /// A node which is an original source asset (not generated). |
| class SourceAssetNode extends AssetNode { |
| SourceAssetNode(AssetId id, {Digest lastKnownDigest}) |
| : super(id, lastKnownDigest: lastKnownDigest); |
| |
| @override |
| String toString() => 'SourceAssetNode: $id'; |
| } |
| |
| /// States for nodes that can be invalidated. |
| enum NodeState { |
| // This node does not need an update, and no checks need to be performed. |
| upToDate, |
| // This node may need an update, the inputs hash should be checked for |
| // changes. |
| mayNeedUpdate, |
| // This node definitely needs an update, the inputs hash check can be skipped. |
| definitelyNeedsUpdate, |
| } |
| |
| /// A generated node in the asset graph. |
| class GeneratedAssetNode extends AssetNode implements NodeWithInputs { |
| @override |
| bool get isGenerated => true; |
| |
| @override |
| final int phaseNumber; |
| |
| /// The primary input which generated this node. |
| final AssetId primaryInput; |
| |
| @override |
| NodeState state; |
| |
| /// Whether the asset was actually output. |
| bool wasOutput; |
| |
| /// All the inputs that were read when generating this asset, or deciding not |
| /// to generate it. |
| /// |
| /// This needs to be an ordered set because we compute combined input digests |
| /// using this later on. |
| @override |
| HashSet<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; |
| |
| /// Whether the action which did or would produce this node failed. |
| bool isFailure; |
| |
| /// The [AssetId] of the node representing the [BuilderOptions] used to create |
| /// this node. |
| final AssetId builderOptionsId; |
| |
| /// Whether the asset should be placed in the build cache. |
| final bool isHidden; |
| |
| GeneratedAssetNode( |
| AssetId id, { |
| Digest lastKnownDigest, |
| Iterable<AssetId> inputs, |
| this.previousInputsDigest, |
| @required this.isHidden, |
| @required this.state, |
| @required this.phaseNumber, |
| @required this.wasOutput, |
| @required this.isFailure, |
| @required this.primaryInput, |
| @required this.builderOptionsId, |
| }) : inputs = inputs != null ? HashSet.from(inputs) : HashSet(), |
| super(id, lastKnownDigest: lastKnownDigest); |
| |
| @override |
| String toString() => |
| 'GeneratedAssetNode: $id generated from input $primaryInput.'; |
| } |
| |
| /// A node which is not a generated or source asset. |
| /// |
| /// These are typically not readable or valid as inputs. |
| abstract class _SyntheticAssetNode implements 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(AssetId id) : super._forMixins(id); |
| } |
| |
| /// A [_SyntheticAssetNode] which represents 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(AssetId id, Digest lastKnownDigest) |
| : super._forMixinsWithDigest(id, lastKnownDigest); |
| |
| @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 { |
| @override |
| bool get isValidInput => true; |
| |
| PlaceHolderAssetNode(AssetId id) : super._forMixins(id); |
| |
| @override |
| String toString() => 'PlaceHolderAssetNode: $id'; |
| } |
| |
| /// A [_SyntheticAssetNode] which is created for each [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 { |
| final int actionNumber; |
| final AssetId builderOptionsId; |
| final AssetId primaryInput; |
| Digest previousInputsDigest; |
| |
| PostProcessAnchorNode( |
| AssetId id, this.primaryInput, this.actionNumber, this.builderOptionsId, |
| {this.previousInputsDigest}) |
| : super._forMixins(id); |
| |
| factory PostProcessAnchorNode.forInputAndAction( |
| AssetId primaryInput, int actionNumber, AssetId builderOptionsId) { |
| return PostProcessAnchorNode( |
| primaryInput.addExtension('.post_anchor.$actionNumber'), |
| primaryInput, |
| actionNumber, |
| builderOptionsId); |
| } |
| } |
| |
| /// A node representing a glob ran from a builder. |
| /// |
| /// The [id] must always be unique to a given package, phase, and glob |
| /// pattern. |
| class GlobAssetNode extends InternalAssetNode implements NodeWithInputs { |
| final Glob glob; |
| |
| /// All the potential inputs matching this glob. |
| /// |
| /// This field differs from [results] in that [GeneratedAssetNode] which may |
| /// have been readable but were not output are included here and not in |
| /// [results]. |
| @override |
| HashSet<AssetId> inputs; |
| |
| @override |
| bool get isReadable => false; |
| |
| @override |
| final int phaseNumber; |
| |
| /// The actual results of the glob. |
| List<AssetId> results; |
| |
| @override |
| NodeState state; |
| |
| GlobAssetNode(AssetId id, this.glob, this.phaseNumber, this.state, |
| {this.inputs, Digest lastKnownDigest, this.results}) |
| : super(id, lastKnownDigest: lastKnownDigest); |
| |
| static AssetId createId(String package, Glob glob, int phaseNum) => AssetId( |
| package, 'glob.$phaseNum.${base64.encode(utf8.encode(glob.pattern))}'); |
| } |
| |
| /// A node which has [inputs], a [NodeState], and a [phaseNumber]. |
| abstract class NodeWithInputs implements AssetNode { |
| HashSet<AssetId> inputs; |
| |
| int get phaseNumber; |
| |
| NodeState state; |
| } |