@@ -136,6 +136,15 @@ abstract class Target {
136136 /// A list of zero or more depfiles, located directly under {BUILD_DIR}.
137137 List <String > get depfiles => const < String > [];
138138
139+ /// A string that differentiates different build variants from each other
140+ /// with regards to build flags or settings on the target. This string should
141+ /// represent each build variant as a different unique value. If this value
142+ /// changes between builds, the target will be invalidated and rebuilt.
143+ ///
144+ /// By default, this returns null, which indicates there is only one build
145+ /// variant, and the target won't invalidate or rebuild due to this property.
146+ String ? get buildKey => null ;
147+
139148 /// Whether this target can be executed with the given [environment] .
140149 ///
141150 /// Returning `true` will cause [build] to be skipped. This is equivalent
@@ -156,6 +165,7 @@ abstract class Target {
156165 < Node > [
157166 for (final Target target in dependencies) target._toNode (environment),
158167 ],
168+ buildKey,
159169 environment,
160170 inputsFiles.containsNewDepfile,
161171 );
@@ -181,9 +191,11 @@ abstract class Target {
181191 for (final File output in outputs) {
182192 outputPaths.add (output.path);
183193 }
194+ final String ? key = buildKey;
184195 final Map <String , Object > result = < String , Object > {
185196 'inputs' : inputPaths,
186197 'outputs' : outputPaths,
198+ if (key != null ) 'buildKey' : key,
187199 };
188200 if (! stamp.existsSync ()) {
189201 stamp.createSync ();
@@ -218,6 +230,7 @@ abstract class Target {
218230 /// This requires constants from the [Environment] to resolve the paths of
219231 /// inputs and the output stamp.
220232 Map <String , Object > toJson (Environment environment) {
233+ final String ? key = buildKey;
221234 return < String , Object > {
222235 'name' : name,
223236 'dependencies' : < String > [
@@ -229,6 +242,7 @@ abstract class Target {
229242 'outputs' : < String > [
230243 for (final File file in resolveOutputs (environment).sources) file.path,
231244 ],
245+ if (key != null ) 'buildKey' : key,
232246 'stamp' : _findStampFile (environment).absolute.path,
233247 };
234248 }
@@ -980,50 +994,86 @@ void verifyOutputDirectories(List<File> outputs, Environment environment, Target
980994
981995/// A node in the build graph.
982996class Node {
983- Node (
984- this .target,
985- this .inputs,
986- this .outputs,
987- this .dependencies,
997+ factory Node (
998+ Target target,
999+ List <File > inputs,
1000+ List <File > outputs,
1001+ List <Node > dependencies,
1002+ String ? buildKey,
9881003 Environment environment,
989- this . missingDepfile,
1004+ bool missingDepfile,
9901005 ) {
9911006 final File stamp = target._findStampFile (environment);
1007+ Map <String , Object ?>? stampValues;
9921008
9931009 // If the stamp file doesn't exist, we haven't run this step before and
9941010 // all inputs were added.
995- if (! stamp.existsSync ()) {
996- // No stamp file, not safe to skip.
997- _dirty = true ;
998- return ;
999- }
1000- final String content = stamp.readAsStringSync ();
1001- // Something went wrong writing the stamp file.
1002- if (content.isEmpty) {
1003- stamp.deleteSync ();
1004- // Malformed stamp file, not safe to skip.
1005- _dirty = true ;
1006- return ;
1007- }
1008- Map <String , Object ?>? values;
1009- try {
1010- values = castStringKeyedMap (json.decode (content));
1011- } on FormatException {
1012- // The json is malformed in some way.
1013- _dirty = true ;
1014- return ;
1011+ if (stamp.existsSync ()) {
1012+ final String content = stamp.readAsStringSync ();
1013+ if (content.isEmpty) {
1014+ stamp.deleteSync ();
1015+ } else {
1016+ try {
1017+ stampValues = castStringKeyedMap (json.decode (content));
1018+ } on FormatException {
1019+ // The json is malformed in some way.
1020+ }
1021+ }
10151022 }
1016- final Object ? inputs = values? ['inputs' ];
1017- final Object ? outputs = values? ['outputs' ];
1018- if (inputs is List <Object ?> && outputs is List <Object ?>) {
1019- inputs.cast <String ?>().whereType <String >().forEach (previousInputs.add);
1020- outputs.cast <String ?>().whereType <String >().forEach (previousOutputs.add);
1021- } else {
1022- // The json is malformed in some way.
1023- _dirty = true ;
1023+ if (stampValues != null ) {
1024+ final String ? previousBuildKey = stampValues['buildKey' ] as String ? ;
1025+ final Object ? stampInputs = stampValues['inputs' ];
1026+ final Object ? stampOutputs = stampValues['outputs' ];
1027+ if (stampInputs is List <Object ?> && stampOutputs is List <Object ?>) {
1028+ final Set <String > previousInputs = stampInputs.whereType <String >().toSet ();
1029+ final Set <String > previousOutputs = stampOutputs.whereType <String >().toSet ();
1030+ return Node .withStamp (
1031+ target,
1032+ inputs,
1033+ previousInputs,
1034+ outputs,
1035+ previousOutputs,
1036+ dependencies,
1037+ buildKey,
1038+ previousBuildKey,
1039+ missingDepfile,
1040+ );
1041+ }
10241042 }
1043+ return Node .withNoStamp (
1044+ target,
1045+ inputs,
1046+ outputs,
1047+ dependencies,
1048+ buildKey,
1049+ missingDepfile,
1050+ );
10251051 }
10261052
1053+ Node .withNoStamp (
1054+ this .target,
1055+ this .inputs,
1056+ this .outputs,
1057+ this .dependencies,
1058+ this .buildKey,
1059+ this .missingDepfile,
1060+ ) : previousInputs = < String > {},
1061+ previousOutputs = < String > {},
1062+ previousBuildKey = null ,
1063+ _dirty = true ;
1064+
1065+ Node .withStamp (
1066+ this .target,
1067+ this .inputs,
1068+ this .previousInputs,
1069+ this .outputs,
1070+ this .previousOutputs,
1071+ this .dependencies,
1072+ this .buildKey,
1073+ this .previousBuildKey,
1074+ this .missingDepfile,
1075+ ) : _dirty = false ;
1076+
10271077 /// The resolved input files.
10281078 ///
10291079 /// These files may not yet exist if they are produced by previous steps.
@@ -1034,6 +1084,11 @@ class Node {
10341084 /// These files may not yet exist if the target hasn't run yet.
10351085 final List <File > outputs;
10361086
1087+ /// The current build key of the target
1088+ ///
1089+ /// See `buildKey` in the `Target` class for more information.
1090+ final String ? buildKey;
1091+
10371092 /// Whether this node is missing a depfile.
10381093 ///
10391094 /// This requires an additional pass of source resolution after the target
@@ -1047,10 +1102,15 @@ class Node {
10471102 final List <Node > dependencies;
10481103
10491104 /// Output file paths from the previous invocation of this build node.
1050- final Set <String > previousOutputs = < String > {} ;
1105+ final Set <String > previousOutputs;
10511106
10521107 /// Input file paths from the previous invocation of this build node.
1053- final Set <String > previousInputs = < String > {};
1108+ final Set <String > previousInputs;
1109+
1110+ /// The buildKey from the previous invocation of this build node.
1111+ ///
1112+ /// See `buildKey` in the `Target` class for more information.
1113+ final String ? previousBuildKey;
10541114
10551115 /// One or more reasons why a task was invalidated.
10561116 ///
@@ -1074,6 +1134,10 @@ class Node {
10741134 FileSystem fileSystem,
10751135 Logger logger,
10761136 ) {
1137+ if (buildKey != previousBuildKey) {
1138+ _invalidate (InvalidatedReasonKind .buildKeyChanged);
1139+ _dirty = true ;
1140+ }
10771141 final Set <String > currentOutputPaths = < String > {
10781142 for (final File file in outputs) file.path,
10791143 };
@@ -1173,7 +1237,8 @@ class InvalidatedReason {
11731237 InvalidatedReasonKind .inputChanged => 'The following inputs have updated contents: ${data .join (',' )}' ,
11741238 InvalidatedReasonKind .outputChanged => 'The following outputs have updated contents: ${data .join (',' )}' ,
11751239 InvalidatedReasonKind .outputMissing => 'The following outputs were missing: ${data .join (',' )}' ,
1176- InvalidatedReasonKind .outputSetChanged => 'The following outputs were removed from the output set: ${data .join (',' )}'
1240+ InvalidatedReasonKind .outputSetChanged => 'The following outputs were removed from the output set: ${data .join (',' )}' ,
1241+ InvalidatedReasonKind .buildKeyChanged => 'The target build key changed.' ,
11771242 };
11781243 }
11791244}
@@ -1195,4 +1260,7 @@ enum InvalidatedReasonKind {
11951260
11961261 /// The set of expected output files changed.
11971262 outputSetChanged,
1263+
1264+ /// The build key changed
1265+ buildKeyChanged,
11981266}
0 commit comments