@@ -136,6 +136,15 @@ abstract class Target {
136
136
/// A list of zero or more depfiles, located directly under {BUILD_DIR}.
137
137
List <String > get depfiles => const < String > [];
138
138
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
+
139
148
/// Whether this target can be executed with the given [environment] .
140
149
///
141
150
/// Returning `true` will cause [build] to be skipped. This is equivalent
@@ -156,6 +165,7 @@ abstract class Target {
156
165
< Node > [
157
166
for (final Target target in dependencies) target._toNode (environment),
158
167
],
168
+ buildKey,
159
169
environment,
160
170
inputsFiles.containsNewDepfile,
161
171
);
@@ -181,9 +191,11 @@ abstract class Target {
181
191
for (final File output in outputs) {
182
192
outputPaths.add (output.path);
183
193
}
194
+ final String ? key = buildKey;
184
195
final Map <String , Object > result = < String , Object > {
185
196
'inputs' : inputPaths,
186
197
'outputs' : outputPaths,
198
+ if (key != null ) 'buildKey' : key,
187
199
};
188
200
if (! stamp.existsSync ()) {
189
201
stamp.createSync ();
@@ -218,6 +230,7 @@ abstract class Target {
218
230
/// This requires constants from the [Environment] to resolve the paths of
219
231
/// inputs and the output stamp.
220
232
Map <String , Object > toJson (Environment environment) {
233
+ final String ? key = buildKey;
221
234
return < String , Object > {
222
235
'name' : name,
223
236
'dependencies' : < String > [
@@ -229,6 +242,7 @@ abstract class Target {
229
242
'outputs' : < String > [
230
243
for (final File file in resolveOutputs (environment).sources) file.path,
231
244
],
245
+ if (key != null ) 'buildKey' : key,
232
246
'stamp' : _findStampFile (environment).absolute.path,
233
247
};
234
248
}
@@ -980,50 +994,86 @@ void verifyOutputDirectories(List<File> outputs, Environment environment, Target
980
994
981
995
/// A node in the build graph.
982
996
class 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,
988
1003
Environment environment,
989
- this . missingDepfile,
1004
+ bool missingDepfile,
990
1005
) {
991
1006
final File stamp = target._findStampFile (environment);
1007
+ Map <String , Object ?>? stampValues;
992
1008
993
1009
// If the stamp file doesn't exist, we haven't run this step before and
994
1010
// 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
+ }
1015
1022
}
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
+ }
1024
1042
}
1043
+ return Node .withNoStamp (
1044
+ target,
1045
+ inputs,
1046
+ outputs,
1047
+ dependencies,
1048
+ buildKey,
1049
+ missingDepfile,
1050
+ );
1025
1051
}
1026
1052
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
+
1027
1077
/// The resolved input files.
1028
1078
///
1029
1079
/// These files may not yet exist if they are produced by previous steps.
@@ -1034,6 +1084,11 @@ class Node {
1034
1084
/// These files may not yet exist if the target hasn't run yet.
1035
1085
final List <File > outputs;
1036
1086
1087
+ /// The current build key of the target
1088
+ ///
1089
+ /// See `buildKey` in the `Target` class for more information.
1090
+ final String ? buildKey;
1091
+
1037
1092
/// Whether this node is missing a depfile.
1038
1093
///
1039
1094
/// This requires an additional pass of source resolution after the target
@@ -1047,10 +1102,15 @@ class Node {
1047
1102
final List <Node > dependencies;
1048
1103
1049
1104
/// Output file paths from the previous invocation of this build node.
1050
- final Set <String > previousOutputs = < String > {} ;
1105
+ final Set <String > previousOutputs;
1051
1106
1052
1107
/// 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;
1054
1114
1055
1115
/// One or more reasons why a task was invalidated.
1056
1116
///
@@ -1074,6 +1134,10 @@ class Node {
1074
1134
FileSystem fileSystem,
1075
1135
Logger logger,
1076
1136
) {
1137
+ if (buildKey != previousBuildKey) {
1138
+ _invalidate (InvalidatedReasonKind .buildKeyChanged);
1139
+ _dirty = true ;
1140
+ }
1077
1141
final Set <String > currentOutputPaths = < String > {
1078
1142
for (final File file in outputs) file.path,
1079
1143
};
@@ -1173,7 +1237,8 @@ class InvalidatedReason {
1173
1237
InvalidatedReasonKind .inputChanged => 'The following inputs have updated contents: ${data .join (',' )}' ,
1174
1238
InvalidatedReasonKind .outputChanged => 'The following outputs have updated contents: ${data .join (',' )}' ,
1175
1239
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.' ,
1177
1242
};
1178
1243
}
1179
1244
}
@@ -1195,4 +1260,7 @@ enum InvalidatedReasonKind {
1195
1260
1196
1261
/// The set of expected output files changed.
1197
1262
outputSetChanged,
1263
+
1264
+ /// The build key changed
1265
+ buildKeyChanged,
1198
1266
}
0 commit comments