@@ -42,16 +42,32 @@ trait GroupExecution {
4242 def getEvaluator : () => EvaluatorApi
4343 def staticBuildOverrideFiles : Map [java.nio.file.Path , String ]
4444
45- import mill .api .internal .Located
46- val staticBuildOverrides : Map [String , Located [BufferedValue ]] = staticBuildOverrideFiles
45+ import mill .api .internal .{Located , AppendLocated }
46+
47+ /** Extract append flag and actual value from wrapper object if present */
48+ private def unwrapAppendMarker (v : BufferedValue ): (BufferedValue , Boolean ) = {
49+ v match {
50+ case obj : BufferedValue .Obj =>
51+ val kvMap = obj.value0.collect { case (BufferedValue .Str (k, _), v) =>
52+ k.toString -> v
53+ }.toMap
54+ (kvMap.get(" __mill_append__" ), kvMap.get(" __mill_values__" )) match {
55+ case (Some (BufferedValue .True (_)), Some (values)) => (values, true )
56+ case _ => (v, false )
57+ }
58+ case _ => (v, false )
59+ }
60+ }
61+
62+ val staticBuildOverrides : Map [String , AppendLocated [BufferedValue ]] = staticBuildOverrideFiles
4763 .flatMap { case (path0, rawText) =>
4864 val path = os.Path (path0)
4965 val headerDataReader = mill.api.internal.HeaderData .headerDataReader(path)
5066
5167 def rec (
5268 segments : Seq [String ],
5369 bufValue : upickle.core.BufferedValue
54- ): Seq [(String , Located [BufferedValue ])] = {
70+ ): Seq [(String , AppendLocated [BufferedValue ])] = {
5571 val upickle .core.BufferedValue .Obj (kvs, _, _) = bufValue
5672 val (rawKvs, nested) = kvs.partitionMap { case (upickle.core.BufferedValue .Str (k, i), v) =>
5773 k.toString.split(" +" ) match {
@@ -60,7 +76,7 @@ trait GroupExecution {
6076 }
6177 }
6278
63- val currentResults : Seq [(String , Located [BufferedValue ])] =
79+ val currentResults : Seq [(String , AppendLocated [BufferedValue ])] =
6480 BufferedValue .transform(
6581 BufferedValue .Obj (
6682 rawKvs.map { case (k, i, v) => (BufferedValue .Str (k, i), v) }.to(mutable.ArrayBuffer ),
@@ -71,11 +87,15 @@ trait GroupExecution {
7187 )
7288 .rest
7389 .map { case (k, v) =>
74- (segments ++ Seq (k.value)).mkString(" ." ) -> Located (path, k.index, v)
90+ val (actualValue, append) = unwrapAppendMarker(v)
91+ (segments ++ Seq (k.value)).mkString(" ." ) -> AppendLocated (
92+ Located (path, k.index, actualValue),
93+ append
94+ )
7595 }
7696 .toSeq
7797
78- val nestedResults : Seq [(String , Located [BufferedValue ])] = nested.flatten.toSeq
98+ val nestedResults : Seq [(String , AppendLocated [BufferedValue ])] = nested.flatten.toSeq
7999
80100 currentResults ++ nestedResults
81101 }
@@ -190,14 +210,16 @@ trait GroupExecution {
190210 val paths = ExecutionPaths .resolve(out, labelled.ctx.segments)
191211 val dynamicBuildOverride = labelled.ctx.enclosingModule.moduleDynamicBuildOverrides
192212 staticBuildOverrides.get(labelled.ctx.segments.render)
193- .orElse(dynamicBuildOverride.get(labelled.ctx.segments.render)) match {
213+ .orElse(dynamicBuildOverride.get(labelled.ctx.segments.render).map(loc =>
214+ AppendLocated (loc, append = false )
215+ )) match {
194216
195- case Some (jsonData ) =>
196- lazy val originalText = os.read(jsonData .path)
217+ case Some (appendLocated ) =>
218+ lazy val originalText = os.read(appendLocated .path)
197219 lazy val strippedText = originalText.replace(" \n //|" , " \n " )
198220 lazy val lookupLineSuffix = fastparse
199221 .IndexedParserInput (strippedText)
200- .prettyIndex(jsonData .index)
222+ .prettyIndex(appendLocated .index)
201223 .takeWhile(_ != ':' ) // split off column since it's not that useful
202224
203225 val (execRes, serializedPaths) =
@@ -208,30 +230,68 @@ trait GroupExecution {
208230
209231 (
210232 ExecResult .Failure (
211- s " Build header config in ${jsonData .path.relativeTo(workspace)}: $lookupLineSuffix conflicts with task defined " +
233+ s " Build header config in ${appendLocated .path.relativeTo(workspace)}: $lookupLineSuffix conflicts with task defined " +
212234 s " in ${os.Path (labelled.ctx.fileName).relativeTo(workspace)}: ${labelled.ctx.lineNum}"
213235 ),
214236 Nil
215237 )
216238 } else {
217239 // apply build override
218240 try {
241+ // If !append tag is present, evaluate the task to get parent value
242+ val parentValue : Option [Seq [Any ]] = if (appendLocated.append) {
243+ val taskInputValues = labelled.inputs
244+ .map(results(_))
245+ .collect { case ExecResult .Success ((v, _)) => v }
246+
247+ if (taskInputValues.length != labelled.inputs.length) None
248+ else {
249+ val (multiLogger, _) = resolveLogger(Some (paths).map(_.log), logger)
250+ val destCreator = new GroupExecution .DestCreator (Some (paths))
251+ val args = new mill.api.TaskCtx .Impl (
252+ args = taskInputValues.map(_.value).toIndexedSeq,
253+ dest0 = () => destCreator.makeDest(),
254+ log = multiLogger,
255+ env = env,
256+ reporter = zincProblemReporter,
257+ testReporter = testReporter,
258+ workspace = workspace,
259+ _systemExitWithReason = systemExit,
260+ fork = executionContext,
261+ jobs = effectiveThreadCount,
262+ offline = offline,
263+ useFileLocks = useFileLocks
264+ )
265+ labelled.evaluate(args) match {
266+ case Result .Success (v) => Some (v.asInstanceOf [Seq [Any ]])
267+ case _ => None
268+ }
269+ }
270+ } else None
271+
219272 val (resultData, serializedPaths) = PathRef .withSerializedPaths {
220273 PathRef .currentOverrideModulePath.withValue(
221274 labelled.ctx.enclosingModule.moduleCtx.millSourcePath
222275 ) {
223- upickle.read[Any ](interpolateEnvVarsInJson(jsonData.value))(
224- using labelled.readWriterOpt.get.asInstanceOf [upickle.Reader [Any ]]
225- )
276+ val yamlValue =
277+ upickle.read[Any ](interpolateEnvVarsInJson(appendLocated.value))(
278+ using labelled.readWriterOpt.get.asInstanceOf [upickle.Reader [Any ]]
279+ )
280+ // Merge parent value with YAML value if !append was present
281+ parentValue match {
282+ case Some (parent) =>
283+ (parent ++ yamlValue.asInstanceOf [Seq [Any ]]).asInstanceOf [Any ]
284+ case None => yamlValue
285+ }
226286 }
227287 }
228288
229289 // Write build header override JSON to meta `.json` file to support `show`
230290 writeCacheJson(
231291 paths.meta,
232- upickle.core.BufferedValue .transform(jsonData .value, ujson.Value ),
292+ upickle.core.BufferedValue .transform(appendLocated .value, ujson.Value ),
233293 resultData.## ,
234- inputsHash + jsonData .value.##
294+ inputsHash + appendLocated .value.##
235295 )
236296
237297 (ExecResult .Success (Val (resultData), resultData.## ), serializedPaths)
@@ -240,14 +300,18 @@ trait GroupExecution {
240300 // Try to get more specific index from AbortException if available
241301 val errorIndex = e.getCause match {
242302 case abort : upickle.core.AbortException => abort.index
243- case _ => jsonData .value.index
303+ case _ => appendLocated .value.index
244304 }
245305
246306 val msg = s " Failed de-serializing config override: ${e.getCause.getMessage}"
247307 (
248308 ExecResult .Failure (
249309 s " Failed de-serializing config override: ${e.getCause.getMessage}" ,
250- Some (Result .Failure (msg, path = jsonData.path.toNIO, index = errorIndex))
310+ Some (Result .Failure (
311+ msg,
312+ path = appendLocated.path.toNIO,
313+ index = errorIndex
314+ ))
251315 ),
252316 Nil
253317 )
0 commit comments