From 6bcdda130e8740f3f71776c1f2738f7a0eac54e8 Mon Sep 17 00:00:00 2001 From: Tobias Roeser Date: Thu, 23 Oct 2025 21:20:32 +0200 Subject: [PATCH 01/45] Use path mapping when json-serializing PathRefs --- core/api/src/mill/api/PathRef.scala | 41 +++++++++- core/api/test/src/mill/api/PathRefTests.scala | 77 +++++++++++++++---- 2 files changed, 99 insertions(+), 19 deletions(-) diff --git a/core/api/src/mill/api/PathRef.scala b/core/api/src/mill/api/PathRef.scala index 83bbc3b14cfe..0b5befb1710f 100644 --- a/core/api/src/mill/api/PathRef.scala +++ b/core/api/src/mill/api/PathRef.scala @@ -38,15 +38,20 @@ case class PathRef private[mill] ( def withRevalidate(revalidate: PathRef.Revalidate): PathRef = copy(revalidate = revalidate) def withRevalidateOnce: PathRef = copy(revalidate = PathRef.Revalidate.Once) - override def toString: String = { + private def toStringPrefix = { val quick = if (this.quick) "qref:" else "ref:" + val valid = revalidate match { case PathRef.Revalidate.Never => "v0:" case PathRef.Revalidate.Once => "v1:" case PathRef.Revalidate.Always => "vn:" } val sig = String.format("%08x", this.sig: Integer) - quick + valid + sig + ":" + path.toString() + quick + valid + sig + ":" + } + + override def toString: String = { + toStringPrefix + path.toString() } } @@ -189,13 +194,41 @@ object PathRef { } } + private[api] val outPathOverride: DynamicVariable[Option[os.Path]] = DynamicVariable(None) + + // TODO: avoid recomputations + private[api] def knownRoots = LazyList( + outPathOverride.value.getOrElse(Evaluator.currentEvaluator.outPath) -> "$MILL_OUT", + BuildCtx.workspaceRoot -> "$WORKSPACE", + // TODO: add coursier here + os.home -> "$HOME" + ) + + private[api] def encodeKnownRoots(pr: PathRef): String = + // TODO: Do we need to check for '$' and mask it ? + knownRoots.collectFirst { + case (root, replacement) if pr.path.startsWith(root) => + pr.toStringPrefix + replacement + ( + if (pr.path != root) { + "/" + pr.path.subRelativeTo(root).toString() + } else "" + ) + }.getOrElse(pr.toString) + + private[api] def decodeKnownRoots(encoded: String): String = { + knownRoots.collectFirst { + case (root, replacement) if encoded.containsSlice(replacement) => + encoded.replace(replacement, root.toString()) + }.getOrElse(encoded) + } + /** * Default JSON formatter for [[PathRef]]. */ implicit def jsonFormatter: RW[PathRef] = upickle.readwriter[String].bimap[PathRef]( p => { storeSerializedPaths(p) - p.toString() + encodeKnownRoots(p) }, { case s"$prefix:$valid0:$hex:$pathString" if prefix == "ref" || prefix == "qref" => @@ -219,7 +252,7 @@ object PathRef { pr case s => mill.api.BuildCtx.withFilesystemCheckerDisabled( - PathRef(os.Path(s, currentOverrideModulePath.value)) + PathRef(os.Path(decodeKnownRoots(s), currentOverrideModulePath.value)) ) } ) diff --git a/core/api/test/src/mill/api/PathRefTests.scala b/core/api/test/src/mill/api/PathRefTests.scala index dd8717a6949d..c85f45927b29 100644 --- a/core/api/test/src/mill/api/PathRefTests.scala +++ b/core/api/test/src/mill/api/PathRefTests.scala @@ -81,27 +81,74 @@ object PathRefTests extends TestSuite { } test("json") { - def check(quick: Boolean) = withTmpDir { tmpDir => - val file = tmpDir / "foo.txt" - os.write(file, "hello") - val pr = PathRef(file, quick) - val prFile = pr.path.toString().replace("\\", "\\\\") - val json = upickle.write(pr) - if (quick) { - assert(json.startsWith(""""qref:v0:""")) - assert(json.endsWith(s""":${prFile}"""")) - } else { - val hash = if (Properties.isWin) "86df6a6a" else "4c7ef487" - val expected = s""""ref:v0:${hash}:${prFile}"""" - assert(json == expected) + def check(quick: Boolean) = withTmpDir { outDir => + PathRef.outPathOverride.withValue(Some(outDir)) { + withTmpDir { tmpDir => + val file = tmpDir / "foo.txt" + os.write(file, "hello") + val pr = PathRef(file, quick) + val prFile = pr.path.toString().replace("\\", "\\\\") + val json = upickle.write(pr) + if (quick) { + assert(json.startsWith(""""qref:v0:""")) + assert(json.endsWith(s""":${prFile}"""")) + } else { + val hash = if (Properties.isWin) "86df6a6a" else "4c7ef487" + val expected = s""""ref:v0:${hash}:${prFile}"""" + assert(json == expected) + } + val pr1 = upickle.read[PathRef](json) + assert(pr == pr1) + } } - val pr1 = upickle.read[PathRef](json) - assert(pr == pr1) } test("qref") - check(quick = true) test("ref") - check(quick = false) } + + test("encode") { + withTmpDir { tmpDir => + val workspaceDir = tmpDir / "workspace" + BuildCtx.workspaceRoot0.withValue(workspaceDir) { + val outDir = workspaceDir / "out" + PathRef.outPathOverride.withValue(Some(outDir)) { + + def check(file: os.Path, contains: Seq[String], containsNot: Seq[String]) = { + val pr = PathRef(file) + val enc = PathRef.encodeKnownRoots(pr) + val dec = PathRef.decodeKnownRoots(enc) + assert(pr.toString == dec) + contains.foreach(s => enc.containsSlice(s)) + containsNot.foreach(s => !enc.containsSlice(s)) + + file -> enc + } + + val file1 = tmpDir / "file1" + val file2 = workspaceDir / "file2" + val file3 = outDir / "file3" + + assert( + PathRef.encodeKnownRoots(PathRef(file1)).containsSlice("ref:"), + !PathRef.encodeKnownRoots(PathRef(file1)).containsSlice("$WORKSPACE"), + !PathRef.encodeKnownRoots(PathRef(file1)).containsSlice("$MILL_OUT"), + PathRef.encodeKnownRoots(PathRef(file2)).containsSlice("$WORKSPACE/file2"), + !PathRef.encodeKnownRoots(PathRef(file2)).containsSlice("$MILL_OUT"), + PathRef.encodeKnownRoots(PathRef(file3)).containsSlice("$MILL_OUT/file3"), + !PathRef.encodeKnownRoots(PathRef(file3)).containsSlice("WORKSPACE") + ) + + Seq( + "mapping" -> PathRef.knownRoots, + check(file1, Seq("ref:v0:", file1.toString), Seq("$WORKSPACE", "$MILL_OUT")), + check (file2, Seq("ref:v0:", "$WORKSPACE/file2"), Seq("$MILL_OUT")), + check (file3, Seq("ref:v0:", "$MILL_OUT/file3"), Seq("$WORKSPACE")), + ) + } + } + } + } } private def withTmpDir[T](body: os.Path => T): T = { From 38bc0a4a3f850c6302fc5d76995a04dfb1dbcf9c Mon Sep 17 00:00:00 2001 From: Tobias Roeser Date: Thu, 23 Oct 2025 21:31:48 +0200 Subject: [PATCH 02/45] Use path-roots mapping for path serialization --- core/api/src/mill/api/JsonFormatters.scala | 4 ++-- core/api/src/mill/api/PathRef.scala | 18 ++++++++-------- core/api/test/src/mill/api/PathRefTests.scala | 21 +++++-------------- 3 files changed, 16 insertions(+), 27 deletions(-) diff --git a/core/api/src/mill/api/JsonFormatters.scala b/core/api/src/mill/api/JsonFormatters.scala index 2ebb0bdf127a..d19e18880b4e 100644 --- a/core/api/src/mill/api/JsonFormatters.scala +++ b/core/api/src/mill/api/JsonFormatters.scala @@ -23,8 +23,8 @@ trait JsonFormatters { implicit val pathReadWrite: RW[os.Path] = upickle.readwriter[String] .bimap[os.Path]( - _.toString, - os.Path(_) + p => PathRef.encodeKnownRootsInPath(p), + s => os.Path(PathRef.decodeKnownRootsInPath(s)) ) implicit val relPathRW: RW[os.RelPath] = upickle.readwriter[String] diff --git a/core/api/src/mill/api/PathRef.scala b/core/api/src/mill/api/PathRef.scala index 0b5befb1710f..cb99a852ec21 100644 --- a/core/api/src/mill/api/PathRef.scala +++ b/core/api/src/mill/api/PathRef.scala @@ -204,18 +204,18 @@ object PathRef { os.home -> "$HOME" ) - private[api] def encodeKnownRoots(pr: PathRef): String = + private[api] def encodeKnownRootsInPath(p: os.Path): String = // TODO: Do we need to check for '$' and mask it ? knownRoots.collectFirst { - case (root, replacement) if pr.path.startsWith(root) => - pr.toStringPrefix + replacement + ( - if (pr.path != root) { - "/" + pr.path.subRelativeTo(root).toString() + case (root, replacement) if p.startsWith(root) => + replacement + ( + if (p != root) { + "/" + p.subRelativeTo(root).toString() } else "" ) - }.getOrElse(pr.toString) + }.getOrElse(p.toString) - private[api] def decodeKnownRoots(encoded: String): String = { + private[api] def decodeKnownRootsInPath(encoded: String): String = { knownRoots.collectFirst { case (root, replacement) if encoded.containsSlice(replacement) => encoded.replace(replacement, root.toString()) @@ -228,7 +228,7 @@ object PathRef { implicit def jsonFormatter: RW[PathRef] = upickle.readwriter[String].bimap[PathRef]( p => { storeSerializedPaths(p) - encodeKnownRoots(p) + p.toStringPrefix + encodeKnownRootsInPath(p.path) }, { case s"$prefix:$valid0:$hex:$pathString" if prefix == "ref" || prefix == "qref" => @@ -252,7 +252,7 @@ object PathRef { pr case s => mill.api.BuildCtx.withFilesystemCheckerDisabled( - PathRef(os.Path(decodeKnownRoots(s), currentOverrideModulePath.value)) + PathRef(os.Path(decodeKnownRootsInPath(s), currentOverrideModulePath.value)) ) } ) diff --git a/core/api/test/src/mill/api/PathRefTests.scala b/core/api/test/src/mill/api/PathRefTests.scala index c85f45927b29..6079624b01f3 100644 --- a/core/api/test/src/mill/api/PathRefTests.scala +++ b/core/api/test/src/mill/api/PathRefTests.scala @@ -114,31 +114,20 @@ object PathRefTests extends TestSuite { val outDir = workspaceDir / "out" PathRef.outPathOverride.withValue(Some(outDir)) { - def check(file: os.Path, contains: Seq[String], containsNot: Seq[String]) = { - val pr = PathRef(file) - val enc = PathRef.encodeKnownRoots(pr) - val dec = PathRef.decodeKnownRoots(enc) - assert(pr.toString == dec) + def check(path: os.Path, contains: Seq[String], containsNot: Seq[String]) = { + val enc = PathRef.encodeKnownRootsInPath(path) + val dec = PathRef.decodeKnownRootsInPath(enc) + assert(path.toString == dec) contains.foreach(s => enc.containsSlice(s)) containsNot.foreach(s => !enc.containsSlice(s)) - file -> enc + path -> enc } val file1 = tmpDir / "file1" val file2 = workspaceDir / "file2" val file3 = outDir / "file3" - assert( - PathRef.encodeKnownRoots(PathRef(file1)).containsSlice("ref:"), - !PathRef.encodeKnownRoots(PathRef(file1)).containsSlice("$WORKSPACE"), - !PathRef.encodeKnownRoots(PathRef(file1)).containsSlice("$MILL_OUT"), - PathRef.encodeKnownRoots(PathRef(file2)).containsSlice("$WORKSPACE/file2"), - !PathRef.encodeKnownRoots(PathRef(file2)).containsSlice("$MILL_OUT"), - PathRef.encodeKnownRoots(PathRef(file3)).containsSlice("$MILL_OUT/file3"), - !PathRef.encodeKnownRoots(PathRef(file3)).containsSlice("WORKSPACE") - ) - Seq( "mapping" -> PathRef.knownRoots, check(file1, Seq("ref:v0:", file1.toString), Seq("$WORKSPACE", "$MILL_OUT")), From 91dfcacb7c67198ddd5b3a9b10e5b8e425babf4e Mon Sep 17 00:00:00 2001 From: Tobias Roeser Date: Fri, 24 Oct 2025 09:33:40 +0200 Subject: [PATCH 03/45] Hack: explicitly set outPath before executing anything --- core/api/src/mill/api/PathRef.scala | 2 +- core/exec/src/mill/exec/GroupExecution.scala | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/core/api/src/mill/api/PathRef.scala b/core/api/src/mill/api/PathRef.scala index cb99a852ec21..bfb468092d73 100644 --- a/core/api/src/mill/api/PathRef.scala +++ b/core/api/src/mill/api/PathRef.scala @@ -194,7 +194,7 @@ object PathRef { } } - private[api] val outPathOverride: DynamicVariable[Option[os.Path]] = DynamicVariable(None) + private[mill] val outPathOverride: DynamicVariable[Option[os.Path]] = DynamicVariable(None) // TODO: avoid recomputations private[api] def knownRoots = LazyList( diff --git a/core/exec/src/mill/exec/GroupExecution.scala b/core/exec/src/mill/exec/GroupExecution.scala index 7c73cb8985f4..21d04bd629e4 100644 --- a/core/exec/src/mill/exec/GroupExecution.scala +++ b/core/exec/src/mill/exec/GroupExecution.scala @@ -83,7 +83,7 @@ trait GroupExecution { executionContext: mill.api.TaskCtx.Fork.Api, exclusive: Boolean, upstreamPathRefs: Seq[PathRef] - ): GroupExecution.Results = { + ): GroupExecution.Results = PathRef.outPathOverride.withValue(Some(outPath)) { val inputsHash = { val externalInputsHash = MurmurHash3.orderedHash( From 793c899805cc56e7a7a0cf252fe2fec6419c62bf Mon Sep 17 00:00:00 2001 From: Tobias Roeser Date: Fri, 24 Oct 2025 09:52:39 +0200 Subject: [PATCH 04/45] Set oupath when writing `mill-runner-state.json` --- core/api/test/src/mill/api/PathRefTests.scala | 4 ++-- .../daemon/src/mill/daemon/MillBuildBootstrap.scala | 12 +++++++----- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/core/api/test/src/mill/api/PathRefTests.scala b/core/api/test/src/mill/api/PathRefTests.scala index 6079624b01f3..0e353a296494 100644 --- a/core/api/test/src/mill/api/PathRefTests.scala +++ b/core/api/test/src/mill/api/PathRefTests.scala @@ -131,8 +131,8 @@ object PathRefTests extends TestSuite { Seq( "mapping" -> PathRef.knownRoots, check(file1, Seq("ref:v0:", file1.toString), Seq("$WORKSPACE", "$MILL_OUT")), - check (file2, Seq("ref:v0:", "$WORKSPACE/file2"), Seq("$MILL_OUT")), - check (file3, Seq("ref:v0:", "$MILL_OUT/file3"), Seq("$WORKSPACE")), + check(file2, Seq("ref:v0:", "$WORKSPACE/file2"), Seq("$MILL_OUT")), + check(file3, Seq("ref:v0:", "$MILL_OUT/file3"), Seq("$WORKSPACE")) ) } } diff --git a/runner/daemon/src/mill/daemon/MillBuildBootstrap.scala b/runner/daemon/src/mill/daemon/MillBuildBootstrap.scala index 3a19d2ce9721..d71866df4456 100644 --- a/runner/daemon/src/mill/daemon/MillBuildBootstrap.scala +++ b/runner/daemon/src/mill/daemon/MillBuildBootstrap.scala @@ -70,11 +70,13 @@ class MillBuildBootstrap( val runnerState = evaluateRec(0) for ((frame, depth) <- runnerState.frames.zipWithIndex) { - os.write.over( - recOut(output, depth) / millRunnerState, - upickle.write(frame.loggedData, indent = 4), - createFolders = true - ) + PathRef.outPathOverride.withValue(Some(output)) { + os.write.over( + recOut(output, depth) / millRunnerState, + upickle.write(frame.loggedData, indent = 4), + createFolders = true + ) + } } Watching.Result( From db57502817fb33e513d8545fe3a90c76ac41afe0 Mon Sep 17 00:00:00 2001 From: Tobias Roeser Date: Fri, 7 Nov 2025 08:45:33 +0100 Subject: [PATCH 05/45] Fix outpath propagation to test runner --- core/api/src/mill/api/PathRef.scala | 45 +++++++++++-------- .../src/mill/javalib/TestModuleUtil.scala | 7 +-- .../testrunner/entrypoint/TestRunnerMain.java | 5 +++ .../javalib/testrunner/TestRunnerMain0.scala | 7 ++- 4 files changed, 40 insertions(+), 24 deletions(-) diff --git a/core/api/src/mill/api/PathRef.scala b/core/api/src/mill/api/PathRef.scala index bfb468092d73..2f761dbc1a67 100644 --- a/core/api/src/mill/api/PathRef.scala +++ b/core/api/src/mill/api/PathRef.scala @@ -196,30 +196,39 @@ object PathRef { private[mill] val outPathOverride: DynamicVariable[Option[os.Path]] = DynamicVariable(None) - // TODO: avoid recomputations - private[api] def knownRoots = LazyList( - outPathOverride.value.getOrElse(Evaluator.currentEvaluator.outPath) -> "$MILL_OUT", - BuildCtx.workspaceRoot -> "$WORKSPACE", - // TODO: add coursier here - os.home -> "$HOME" - ) + private[api] type KnownRoots = Seq[(replacement: String, root: os.Path)] + + private[api] def knownRoots: KnownRoots = { + // order is important! + Seq( + ("$MILL_OUT", outPathOverride.value.getOrElse(Evaluator.currentEvaluator.outPath)), + ("$WORKSPACE", BuildCtx.workspaceRoot), + // TODO: add coursier here + ("$HOME", os.home) + ) + } - private[api] def encodeKnownRootsInPath(p: os.Path): String = + private[api] def encodeKnownRootsInPath(p: os.Path): String = { // TODO: Do we need to check for '$' and mask it ? knownRoots.collectFirst { - case (root, replacement) if p.startsWith(root) => - replacement + ( - if (p != root) { - "/" + p.subRelativeTo(root).toString() - } else "" - ) + case rep if p.startsWith(rep.root) => + s"${rep.replacement}${ + if (p != rep.root) { + s"/${p.subRelativeTo(rep.root).toString()}" + } else "" + }" }.getOrElse(p.toString) + } private[api] def decodeKnownRootsInPath(encoded: String): String = { - knownRoots.collectFirst { - case (root, replacement) if encoded.containsSlice(replacement) => - encoded.replace(replacement, root.toString()) - }.getOrElse(encoded) + if (encoded.startsWith("$")) { + knownRoots.collectFirst { + case rep if encoded.startsWith(rep.replacement) => + s"${rep.root.toString}${encoded.substring(rep.replacement.length)}" + }.getOrElse(encoded) + } else { + encoded + } } /** diff --git a/libs/javalib/src/mill/javalib/TestModuleUtil.scala b/libs/javalib/src/mill/javalib/TestModuleUtil.scala index cb6b01498e4c..e3dcf654e506 100644 --- a/libs/javalib/src/mill/javalib/TestModuleUtil.scala +++ b/libs/javalib/src/mill/javalib/TestModuleUtil.scala @@ -1,7 +1,6 @@ package mill.javalib -import mill.api.{PathRef, TaskCtx} -import mill.api.Result +import mill.api.{BuildCtx, Evaluator, Logger, PathRef, Result, TaskCtx} import mill.api.daemon.internal.TestReporter import mill.util.Jvm import mill.api.internal.Util @@ -13,8 +12,6 @@ import java.time.temporal.ChronoUnit import java.time.{Instant, LocalDateTime, ZoneId} import scala.xml.Elem import scala.collection.mutable -import mill.api.Logger - import java.util.concurrent.ConcurrentHashMap import mill.api.BuildCtx import mill.javalib.api.internal.ZincOp @@ -148,7 +145,7 @@ final class TestModuleUtil( classPath = (runClasspath ++ testrunnerEntrypointClasspath).map(_.path), jvmArgs = jvmArgs, env = (if (propagateEnv) Task.env else Map()) ++ forkEnv, - mainArgs = Seq(testRunnerClasspathArg, argsFile.toString), + mainArgs = Seq(testRunnerClasspathArg, argsFile.toString, PathRef.outPathOverride.value.get.toString), cwd = if (testSandboxWorkingDir) sandbox else forkWorkingDir, cpPassingJarPath = Option.when(useArgsFile)( os.temp(prefix = "run-", suffix = ".jar", deleteOnExit = false) diff --git a/libs/javalib/testrunner/entrypoint/src/mill/javalib/testrunner/entrypoint/TestRunnerMain.java b/libs/javalib/testrunner/entrypoint/src/mill/javalib/testrunner/entrypoint/TestRunnerMain.java index 5930e98e6198..8857ca5e7101 100644 --- a/libs/javalib/testrunner/entrypoint/src/mill/javalib/testrunner/entrypoint/TestRunnerMain.java +++ b/libs/javalib/testrunner/entrypoint/src/mill/javalib/testrunner/entrypoint/TestRunnerMain.java @@ -14,6 +14,11 @@ * nested classloaders. */ public class TestRunnerMain { + /** + * + * @param args arg1: classpath, arg2 testArgs-file, arg2 Mill out path + * @throws Exception + */ public static void main(String[] args) throws Exception { URL[] testRunnerClasspath = Stream.of(args[0].split(",")) .map(s -> { diff --git a/libs/javalib/testrunner/src/mill/javalib/testrunner/TestRunnerMain0.scala b/libs/javalib/testrunner/src/mill/javalib/testrunner/TestRunnerMain0.scala index 8c1b4722e78c..30e978fb05db 100644 --- a/libs/javalib/testrunner/src/mill/javalib/testrunner/TestRunnerMain0.scala +++ b/libs/javalib/testrunner/src/mill/javalib/testrunner/TestRunnerMain0.scala @@ -1,11 +1,16 @@ package mill.javalib.testrunner +import mill.api.PathRef import mill.api.daemon.internal.{TestReporter, internal} @internal object TestRunnerMain0 { def main0(args: Array[String], classLoader: ClassLoader): Unit = { try { - val testArgs = upickle.read[TestArgs](os.read(os.Path(args(1)))) + val millOutPath = os.Path(args(2)) + val testArgs = + PathRef.outPathOverride.withValue(Some(millOutPath)) { + upickle.read[TestArgs](os.read(os.Path(args(1)))) + } testArgs.sysProps.foreach { case (k, v) => System.setProperty(k, v) } val result = testArgs.globSelectors match { From c3fb7745e9ff8733546b4708d4ead055c8a62e9e Mon Sep 17 00:00:00 2001 From: Tobias Roeser Date: Fri, 24 Oct 2025 16:28:06 +0200 Subject: [PATCH 06/45] fix expected show output --- .../androidlib/java/1-hello-world/build.mill | 2 +- .../androidlib/java/2-app-bundle/build.mill | 2 +- .../androidlib/java/4-sum-lib-java/build.mill | 2 +- .../androidlib/java/6-native-libs/build.mill | 2 +- .../kotlin/1-hello-kotlin/build.mill | 2 +- .../androidlib/kotlin/2-compose/build.mill | 2 +- .../kotlin/4-sum-lib-kotlin/build.mill | 2 +- .../builtins/1-builtin-commands/build.mill | 24 +++++++++---------- example/depth/sandbox/1-task/build.mill | 8 +++---- example/depth/sandbox/2-test/build.mill | 6 ++--- .../extending/imports/1-mvn-deps/build.mill | 2 +- .../imports/2-mvn-deps-scala/build.mill | 2 +- .../metabuild/4-meta-build/build.mill | 2 +- .../python/4-python-libs-bundle/build.mill | 2 +- .../typescript/4-npm-deps-bundle/build.mill | 2 +- .../cross/10-static-blog/build.mill | 2 +- .../fundamentals/libraries/1-oslib/build.mill | 4 ++-- .../libraries/2-upickle/build.mill | 4 ++-- .../modules/8-diy-java-modules/build.mill | 4 ++-- .../tasks/1-task-graph/build.mill | 2 +- .../tasks/2-primary-tasks/build.mill | 2 +- example/javalib/basic/1-script/build.mill | 2 +- example/javalib/basic/10-realistic/build.mill | 2 +- example/javalib/publishing/5-jlink/build.mill | 2 +- .../javalib/publishing/6-jpackage/build.mill | 2 +- .../publishing/9-repackage-config/build.mill | 2 +- example/kotlinlib/basic/1-script/build.mill | 2 +- .../kotlinlib/basic/10-realistic/build.mill | 2 +- .../module/10-dependency-injection/build.mill | 4 ++-- example/pythonlib/basic/1-simple/build.mill | 2 +- .../module/1-common-config/build.mill | 2 +- .../pythonlib/module/6-pex-config/build.mill | 2 +- .../publishing/1-publish-module/build.mill | 4 ++-- example/scalalib/basic/1-script/build.mill | 2 +- .../scalalib/basic/10-realistic/build.mill | 2 +- example/scalalib/basic/3-simple/build.mill | 2 +- .../scalalib/basic/6-programmatic/build.mill | 2 +- .../config/1-common-config/build.mill | 2 +- example/scalalib/module/15-unidoc/build.mill | 2 +- .../module/2-common-config/build.mill | 2 +- example/scalalib/native/1-simple/build.mill | 2 +- .../spark/3-semi-realistic/build.mill | 2 +- .../scalalib/web/4-scalajs-module/build.mill | 2 +- example/scalalib/web/9-wasm/build.mill | 2 +- .../android-endless-tunnel/build.mill | 2 +- .../src/mill/javalib/TestModuleUtil.scala | 8 +++++-- .../pages/12-direct-style-build-tool.adoc | 2 +- .../pages/7-graal-native-executables.adoc | 8 +++---- .../pages/9-mill-faster-assembly-jars.adoc | 4 ++-- 49 files changed, 79 insertions(+), 75 deletions(-) diff --git a/example/androidlib/java/1-hello-world/build.mill b/example/androidlib/java/1-hello-world/build.mill index ce26457d6b95..30fcd05ef4f8 100644 --- a/example/androidlib/java/1-hello-world/build.mill +++ b/example/androidlib/java/1-hello-world/build.mill @@ -64,7 +64,7 @@ object app extends AndroidAppModule { /** Usage > ./mill show app.androidApk -".../out/app/androidApk.dest/app.apk" +"...$OUT/app/androidApk.dest/app.apk" */ diff --git a/example/androidlib/java/2-app-bundle/build.mill b/example/androidlib/java/2-app-bundle/build.mill index f05d58782d5d..db8e043ec646 100644 --- a/example/androidlib/java/2-app-bundle/build.mill +++ b/example/androidlib/java/2-app-bundle/build.mill @@ -37,7 +37,7 @@ object bundle extends AndroidAppBundle { /** Usage > ./mill show bundle.androidBundle -".../out/bundle/androidBundle.dest/signedBundle.aab" +"...$OUT/bundle/androidBundle.dest/signedBundle.aab" */ diff --git a/example/androidlib/java/4-sum-lib-java/build.mill b/example/androidlib/java/4-sum-lib-java/build.mill index 774ff1bdb271..54400585e90c 100644 --- a/example/androidlib/java/4-sum-lib-java/build.mill +++ b/example/androidlib/java/4-sum-lib-java/build.mill @@ -129,7 +129,7 @@ Publish ... to /home/.../.m2/repository/... }, "payload": [ [ - ".../out/lib/androidAar.dest/library.aar", + "...$OUT/lib/androidAar.dest/library.aar", "lib-0.0.1.aar" ], ... diff --git a/example/androidlib/java/6-native-libs/build.mill b/example/androidlib/java/6-native-libs/build.mill index 669fee9f566a..2245540ec38b 100644 --- a/example/androidlib/java/6-native-libs/build.mill +++ b/example/androidlib/java/6-native-libs/build.mill @@ -66,7 +66,7 @@ object app extends AndroidNativeAppModule { // <1> /** Usage > ./mill show app.androidApk -".../out/app/androidApk.dest/app.apk" +"...$OUT/app/androidApk.dest/app.apk" > ./mill show app.createAndroidVirtualDevice ...Name: java-test, DeviceId: medium_phone... diff --git a/example/androidlib/kotlin/1-hello-kotlin/build.mill b/example/androidlib/kotlin/1-hello-kotlin/build.mill index b7b2c311a8a6..d27c4051ba90 100644 --- a/example/androidlib/kotlin/1-hello-kotlin/build.mill +++ b/example/androidlib/kotlin/1-hello-kotlin/build.mill @@ -72,7 +72,7 @@ object app extends AndroidAppKotlinModule { /** Usage > ./mill show app.androidApk -".../out/app/androidApk.dest/app.apk" +"...$OUT/app/androidApk.dest/app.apk" */ diff --git a/example/androidlib/kotlin/2-compose/build.mill b/example/androidlib/kotlin/2-compose/build.mill index 64fb8212e99e..c84423140516 100644 --- a/example/androidlib/kotlin/2-compose/build.mill +++ b/example/androidlib/kotlin/2-compose/build.mill @@ -60,7 +60,7 @@ object app extends AndroidAppKotlinModule { /** Usage > ./mill show app.androidApk -".../out/app/androidApk.dest/app.apk" +"...$OUT/app/androidApk.dest/app.apk" > ./mill show app.createAndroidVirtualDevice diff --git a/example/androidlib/kotlin/4-sum-lib-kotlin/build.mill b/example/androidlib/kotlin/4-sum-lib-kotlin/build.mill index 013d5f7df22e..61271f0fe840 100644 --- a/example/androidlib/kotlin/4-sum-lib-kotlin/build.mill +++ b/example/androidlib/kotlin/4-sum-lib-kotlin/build.mill @@ -131,7 +131,7 @@ Publish ... to /home/.../.m2/repository/... }, "payload": [ [ - ".../out/lib/androidAar.dest/library.aar", + "...$OUT/lib/androidAar.dest/library.aar", "lib-0.0.1.aar" ], ... diff --git a/example/cli/builtins/1-builtin-commands/build.mill b/example/cli/builtins/1-builtin-commands/build.mill index 4ac5ba3b98a4..02c12db347ea 100644 --- a/example/cli/builtins/1-builtin-commands/build.mill +++ b/example/cli/builtins/1-builtin-commands/build.mill @@ -146,8 +146,8 @@ Inputs: > ./mill show foo.compile { - "analysisFile": ".../out/foo/compile.dest/...", - "classes": ".../out/foo/compile.dest/classes" + "analysisFile": "...$OUT/foo/compile.dest/...", + "classes": "...$OUT/foo/compile.dest/classes" } */ @@ -287,11 +287,11 @@ foo.compileClasspath /** Usage > ./mill visualize foo._ [ - ".../out/visualize.dest/out.dot", - ".../out/visualize.dest/out.json", - ".../out/visualize.dest/out.png", - ".../out/visualize.dest/out.svg", - ".../out/visualize.dest/out.txt" + "...$OUT/visualize.dest/out.dot", + "...$OUT/visualize.dest/out.json", + "...$OUT/visualize.dest/out.png", + "...$OUT/visualize.dest/out.svg", + "...$OUT/visualize.dest/out.txt" ] */ // @@ -342,11 +342,11 @@ graph ["rankdir"="LR"] /** Usage > ./mill visualizePlan foo.run [ - ".../out/visualizePlan.dest/out.dot", - ".../out/visualizePlan.dest/out.json", - ".../out/visualizePlan.dest/out.png", - ".../out/visualizePlan.dest/out.svg", - ".../out/visualizePlan.dest/out.txt" + "...$OUT/visualizePlan.dest/out.dot", + "...$OUT/visualizePlan.dest/out.json", + "...$OUT/visualizePlan.dest/out.png", + "...$OUT/visualizePlan.dest/out.svg", + "...$OUT/visualizePlan.dest/out.txt" ] */ // diff --git a/example/depth/sandbox/1-task/build.mill b/example/depth/sandbox/1-task/build.mill index 7e6bbd50c3ac..2cfe2570239c 100644 --- a/example/depth/sandbox/1-task/build.mill +++ b/example/depth/sandbox/1-task/build.mill @@ -14,7 +14,7 @@ object foo extends Module { /** Usage > ./mill foo.tDestTask -.../out/foo/tDestTask.dest +...$OUT/foo/tDestTask.dest */ // If you really need to reference paths outside of the `Task.dest`, you can do @@ -95,7 +95,7 @@ def osPwdTask = Task { println(os.pwd.toString) } /** Usage > ./mill osPwdTask -.../out/osPwdTask.dest +...$OUT/osPwdTask.dest */ // The redirection of `os.pwd` applies to `os.proc`, `os.call`, and `os.spawn` methods @@ -108,7 +108,7 @@ def osProcTask = Task { /** Usage > ./mill osProcTask -.../out/osProcTask.dest +...$OUT/osProcTask.dest */ // === Non-task `os.pwd` redirection @@ -122,5 +122,5 @@ def externalPwdTask = Task { println(externalPwd.toString) } /** Usage > ./mill externalPwdTask -.../out/mill-daemon/sandbox +...$OUT/mill-daemon/sandbox */ diff --git a/example/depth/sandbox/2-test/build.mill b/example/depth/sandbox/2-test/build.mill index 70f409653e32..3ec7642ef6a5 100644 --- a/example/depth/sandbox/2-test/build.mill +++ b/example/depth/sandbox/2-test/build.mill @@ -47,8 +47,8 @@ object bar extends MyModule /** Usage > find . | grep generated.html -.../out/foo/test/testForked.dest/sandbox/generated.html -.../out/bar/test/testForked.dest/sandbox/generated.html +...$OUT/foo/test/testForked.dest/sandbox/generated.html +...$OUT/bar/test/testForked.dest/sandbox/generated.html > cat out/foo/test/testForked.dest/sandbox/generated.html

hello

@@ -81,7 +81,7 @@ object qux extends JavaModule { > find . | grep .html ... -.../out/qux/test/testForked.dest/sandbox/foo.html +...$OUT/qux/test/testForked.dest/sandbox/foo.html > cat out/qux/test/testForked.dest/sandbox/foo.html

foo

diff --git a/example/extending/imports/1-mvn-deps/build.mill b/example/extending/imports/1-mvn-deps/build.mill index fda9104f287d..b4d340962ac3 100644 --- a/example/extending/imports/1-mvn-deps/build.mill +++ b/example/extending/imports/1-mvn-deps/build.mill @@ -44,7 +44,7 @@ compiling 1 Java source... generated snippet.txt resource:

hello

world

> ./mill show foo.assembly -".../out/foo/assembly.dest/out.jar" +"...$OUT/foo/assembly.dest/out.jar" > ./out/foo/assembly.dest/out.jar # mac/linux generated snippet.txt resource:

hello

world

diff --git a/example/extending/imports/2-mvn-deps-scala/build.mill b/example/extending/imports/2-mvn-deps-scala/build.mill index da924f68e671..08b530f5b1a1 100644 --- a/example/extending/imports/2-mvn-deps-scala/build.mill +++ b/example/extending/imports/2-mvn-deps-scala/build.mill @@ -34,7 +34,7 @@ compiling 1 Scala source... generated snippet.txt resource:

hello

world

> ./mill show bar.assembly -".../out/bar/assembly.dest/out.jar" +"...$OUT/bar/assembly.dest/out.jar" > ./out/bar/assembly.dest/out.jar # mac/linux generated snippet.txt resource:

hello

world

diff --git a/example/extending/metabuild/4-meta-build/build.mill b/example/extending/metabuild/4-meta-build/build.mill index b8b0a56cfd43..11763d109735 100644 --- a/example/extending/metabuild/4-meta-build/build.mill +++ b/example/extending/metabuild/4-meta-build/build.mill @@ -48,7 +48,7 @@ Build-time HTML snippet:

hello

Run-time HTML snippet:

world

> ./mill show assembly -".../out/assembly.dest/out.jar" +"...$OUT/assembly.dest/out.jar" > ./out/assembly.dest/out.jar # mac/linux Build-time HTML snippet:

hello

diff --git a/example/extending/python/4-python-libs-bundle/build.mill b/example/extending/python/4-python-libs-bundle/build.mill index b1da6f0571a3..09ffa52355e1 100644 --- a/example/extending/python/4-python-libs-bundle/build.mill +++ b/example/extending/python/4-python-libs-bundle/build.mill @@ -109,7 +109,7 @@ object qux extends PythonModule { Numpy : Sum: 150 | Pandas: Mean: 30.0, Max: 50 > ./mill show qux.bundle -".../out/qux/bundle.dest/bundle.pex" +"...$OUT/qux/bundle.dest/bundle.pex" > out/qux/bundle.dest/bundle.pex # running the PEX binary outside of Mill Numpy : Sum: 150 | Pandas: Mean: 30.0, Max: 50 diff --git a/example/extending/typescript/4-npm-deps-bundle/build.mill b/example/extending/typescript/4-npm-deps-bundle/build.mill index 006a4749c8a0..ef9aa107a775 100644 --- a/example/extending/typescript/4-npm-deps-bundle/build.mill +++ b/example/extending/typescript/4-npm-deps-bundle/build.mill @@ -117,7 +117,7 @@ object qux extends TypeScriptModule { Hello James Bond Professor > ./mill show qux.bundle -".../out/qux/bundle.dest/bundle.js" +"...$OUT/qux/bundle.dest/bundle.js" > node out/qux/bundle.dest/bundle.js James Bond prof Hello James Bond Professor diff --git a/example/fundamentals/cross/10-static-blog/build.mill b/example/fundamentals/cross/10-static-blog/build.mill index 0cb186843ed6..34ea6514f151 100644 --- a/example/fundamentals/cross/10-static-blog/build.mill +++ b/example/fundamentals/cross/10-static-blog/build.mill @@ -113,7 +113,7 @@ def dist = Task { /** Usage > ./mill show "post[1-My-First-Post.md].render" -".../out/post/1-My-First-Post.md/render.dest/1-my-first-post.html" +"...$OUT/post/1-My-First-Post.md/render.dest/1-my-first-post.html" > cat out/post/1-My-First-Post.md/render.dest/1-my-first-post.html ... diff --git a/example/fundamentals/libraries/1-oslib/build.mill b/example/fundamentals/libraries/1-oslib/build.mill index 98cbbaecb037..964f0cf11575 100644 --- a/example/fundamentals/libraries/1-oslib/build.mill +++ b/example/fundamentals/libraries/1-oslib/build.mill @@ -35,9 +35,9 @@ def command = Task { /** Usage > ./mill command # mac/linux -.../out/task1.dest/file.txt +...$OUT/task1.dest/file.txt hello -.../out/task2.dest/file.txt +...$OUT/task2.dest/file.txt world */ diff --git a/example/fundamentals/libraries/2-upickle/build.mill b/example/fundamentals/libraries/2-upickle/build.mill index fcac91f6de98..c11f024e438f 100644 --- a/example/fundamentals/libraries/2-upickle/build.mill +++ b/example/fundamentals/libraries/2-upickle/build.mill @@ -74,7 +74,7 @@ def taskPath = Task { /** Usage > ./mill show taskPath -".../out/taskPath.dest/file.txt" +"...$OUT/taskPath.dest/file.txt" */ @@ -92,7 +92,7 @@ def taskPathRef = Task { /** Usage > ./mill show taskPathRef -"ref.../out/taskPathRef.dest/file.txt" +"ref...$OUT/taskPathRef.dest/file.txt" */ diff --git a/example/fundamentals/modules/8-diy-java-modules/build.mill b/example/fundamentals/modules/8-diy-java-modules/build.mill index 4b6a508fd561..02b67b10a6eb 100644 --- a/example/fundamentals/modules/8-diy-java-modules/build.mill +++ b/example/fundamentals/modules/8-diy-java-modules/build.mill @@ -150,7 +150,7 @@ object qux extends DiyJavaModule { } > ./mill show qux.assembly -".../out/qux/assembly.dest/assembly.jar" +"...$OUT/qux/assembly.dest/assembly.jar" > java -jar out/qux/assembly.dest/assembly.jar Foo.value: 31337 @@ -158,7 +158,7 @@ Bar.value: 271828 Qux.value: 9000 > ./mill show foo.assembly -".../out/foo/assembly.dest/assembly.jar" +"...$OUT/foo/assembly.dest/assembly.jar" > java -jar out/foo/assembly.dest/assembly.jar Foo.value: 31337 diff --git a/example/fundamentals/tasks/1-task-graph/build.mill b/example/fundamentals/tasks/1-task-graph/build.mill index a512437f7652..633c288f190b 100644 --- a/example/fundamentals/tasks/1-task-graph/build.mill +++ b/example/fundamentals/tasks/1-task-graph/build.mill @@ -55,7 +55,7 @@ def run(args: String*) = Task.Command { /** Usage > ./mill show assembly -".../out/assembly.dest/assembly.jar" +"...$OUT/assembly.dest/assembly.jar" > java -jar out/assembly.dest/assembly.jar i am cow Foo.value: 31337 diff --git a/example/fundamentals/tasks/2-primary-tasks/build.mill b/example/fundamentals/tasks/2-primary-tasks/build.mill index 570dd57b59ce..251dc6df5423 100644 --- a/example/fundamentals/tasks/2-primary-tasks/build.mill +++ b/example/fundamentals/tasks/2-primary-tasks/build.mill @@ -167,7 +167,7 @@ Generating classfiles Generating jar > ./mill show jar -".../out/jar.dest/foo.jar" +"...$OUT/jar.dest/foo.jar" */ diff --git a/example/javalib/basic/1-script/build.mill b/example/javalib/basic/1-script/build.mill index 9f7c5dadb44d..7deb1bea0c70 100644 --- a/example/javalib/basic/1-script/build.mill +++ b/example/javalib/basic/1-script/build.mill @@ -18,7 +18,7 @@ compiling 1 Java source to... /** Usage > ./mill show Foo.java:assembly # show the output of the assembly task -".../out/Foo.java/assembly.dest/out.jar" +"...$OUT/Foo.java/assembly.dest/out.jar" > java -jar ./out/Foo.java/assembly.dest/out.jar --text hello

hello

diff --git a/example/javalib/basic/10-realistic/build.mill b/example/javalib/basic/10-realistic/build.mill index f23038a5b082..6266b897e8b4 100644 --- a/example/javalib/basic/10-realistic/build.mill +++ b/example/javalib/basic/10-realistic/build.mill @@ -101,7 +101,7 @@ Publishing Artifact(com.lihaoyi,qux,0.0.1) to ivy repo... ... > ./mill show foo.assembly # mac/linux -".../out/foo/assembly.dest/out.jar" +"...$OUT/foo/assembly.dest/out.jar" > ./out/foo/assembly.dest/out.jar # mac/linux foo version 0.0.1 diff --git a/example/javalib/publishing/5-jlink/build.mill b/example/javalib/publishing/5-jlink/build.mill index d9031d13d04f..44679674b4c5 100644 --- a/example/javalib/publishing/5-jlink/build.mill +++ b/example/javalib/publishing/5-jlink/build.mill @@ -47,7 +47,7 @@ object foo extends JavaModule, JlinkModule { > ./mill foo.jlinkAppImage > ./mill show foo.jlinkAppImage -".../out/foo/jlinkAppImage.dest/jlink-runtime" +"...$OUT/foo/jlinkAppImage.dest/jlink-runtime" > ./out/foo/jlinkAppImage.dest/jlink-runtime/bin/jlink ... foo.Bar main diff --git a/example/javalib/publishing/6-jpackage/build.mill b/example/javalib/publishing/6-jpackage/build.mill index 150c9f8be48a..a52a32b6f4cc 100644 --- a/example/javalib/publishing/6-jpackage/build.mill +++ b/example/javalib/publishing/6-jpackage/build.mill @@ -50,7 +50,7 @@ object foo extends JavaModule, JpackageModule { > ./mill foo.jpackageAppImage > ./mill show foo.jpackageAppImage -".../out/foo/jpackageAppImage.dest/image" +"...$OUT/foo/jpackageAppImage.dest/image" */ // diff --git a/example/javalib/publishing/9-repackage-config/build.mill b/example/javalib/publishing/9-repackage-config/build.mill index 26d2b4706319..762db39362cb 100644 --- a/example/javalib/publishing/9-repackage-config/build.mill +++ b/example/javalib/publishing/9-repackage-config/build.mill @@ -45,7 +45,7 @@ Qux.value: 31337 ...Test run bar.BarTests finished: 0 failed, 0 ignored, 1 total, ...s > ./mill show foo.repackagedJar -".../out/foo/repackagedJar.dest/out.jar" +"...$OUT/foo/repackagedJar.dest/out.jar" > ./out/foo/repackagedJar.dest/out.jar Foo.value:

hello

diff --git a/example/kotlinlib/basic/1-script/build.mill b/example/kotlinlib/basic/1-script/build.mill index 679cd5d33ce1..ec8af6f0c7ca 100644 --- a/example/kotlinlib/basic/1-script/build.mill +++ b/example/kotlinlib/basic/1-script/build.mill @@ -18,7 +18,7 @@ Compiling 1 Kotlin sources to... /** Usage > ./mill show Foo.kt:assembly # show the output of the assembly task -".../out/Foo.kt/assembly.dest/out.jar" +"...$OUT/Foo.kt/assembly.dest/out.jar" > java -jar ./out/Foo.kt/assembly.dest/out.jar --text hello

hello

diff --git a/example/kotlinlib/basic/10-realistic/build.mill b/example/kotlinlib/basic/10-realistic/build.mill index 7cc8bd8d94a9..626d39d04b90 100644 --- a/example/kotlinlib/basic/10-realistic/build.mill +++ b/example/kotlinlib/basic/10-realistic/build.mill @@ -109,7 +109,7 @@ Publishing Artifact(com.lihaoyi,qux,0.0.1) to ivy repo... ... > ./mill show foo.assembly # mac/linux -".../out/foo/assembly.dest/out.jar" +"...$OUT/foo/assembly.dest/out.jar" > ./out/foo/assembly.dest/out.jar # mac/linux foo version 0.0.1 diff --git a/example/kotlinlib/module/10-dependency-injection/build.mill b/example/kotlinlib/module/10-dependency-injection/build.mill index ed47d4eafa03..855d0b98014f 100644 --- a/example/kotlinlib/module/10-dependency-injection/build.mill +++ b/example/kotlinlib/module/10-dependency-injection/build.mill @@ -47,8 +47,8 @@ object dagger extends KspModule { > ./mill show dagger.generatedSources [ - "ref:v0:.../out/dagger/generatedSourcesWithKsp2.dest/generated/java", - "ref:v0:.../out/dagger/generatedSourcesWithKsp2.dest/generated/kotlin" + "ref:v0:...$OUT/dagger/generatedSourcesWithKsp2.dest/generated/java", + "ref:v0:...$OUT/dagger/generatedSourcesWithKsp2.dest/generated/kotlin" ] > ls out/dagger/generatedSourcesWithKsp2.dest/generated/java/com/example/dagger/ diff --git a/example/pythonlib/basic/1-simple/build.mill b/example/pythonlib/basic/1-simple/build.mill index 21ee94f61468..20b6db8678f3 100644 --- a/example/pythonlib/basic/1-simple/build.mill +++ b/example/pythonlib/basic/1-simple/build.mill @@ -87,7 +87,7 @@ OK ... > ./mill show foo.bundle # Creates Bundle for the python file -".../out/foo/bundle.dest/bundle.pex" +"...$OUT/foo/bundle.dest/bundle.pex" > out/foo/bundle.dest/bundle.pex --text "Hello Mill" # running the PEX binary outside of Mill

Hello Mill

diff --git a/example/pythonlib/module/1-common-config/build.mill b/example/pythonlib/module/1-common-config/build.mill index a399d898f067..6fab0f7968fb 100644 --- a/example/pythonlib/module/1-common-config/build.mill +++ b/example/pythonlib/module/1-common-config/build.mill @@ -75,7 +75,7 @@ MY_CUSTOM_ENV: my-env-value ... > ./mill show foo.bundle -".../out/foo/bundle.dest/bundle.pex" +"...$OUT/foo/bundle.dest/bundle.pex" > out/foo/bundle.dest/bundle.pex ... diff --git a/example/pythonlib/module/6-pex-config/build.mill b/example/pythonlib/module/6-pex-config/build.mill index ef6454532025..b94ec98d8a46 100644 --- a/example/pythonlib/module/6-pex-config/build.mill +++ b/example/pythonlib/module/6-pex-config/build.mill @@ -44,7 +44,7 @@ object foo extends PythonModule { /** Usage > ./mill show foo.bundle -".../out/foo/bundle.dest/bundle.pex" +"...$OUT/foo/bundle.dest/bundle.pex" > out/foo/bundle.dest/bundle.pex ... diff --git a/example/pythonlib/publishing/1-publish-module/build.mill b/example/pythonlib/publishing/1-publish-module/build.mill index 75f91333158e..1c8874a8cfd8 100644 --- a/example/pythonlib/publishing/1-publish-module/build.mill +++ b/example/pythonlib/publishing/1-publish-module/build.mill @@ -44,10 +44,10 @@ object `package` extends PythonModule, PublishModule { /** Usage > ./mill show sdist -".../out/sdist.dest/dist/testpkg_mill-0.0.2.tar.gz" +"...$OUT/sdist.dest/dist/testpkg_mill-0.0.2.tar.gz" > ./mill show wheel -".../out/wheel.dest/dist/testpkg_mill-0.0.2-py3-none-any.whl" +"...$OUT/wheel.dest/dist/testpkg_mill-0.0.2-py3-none-any.whl" */ // These files can then be `pip-installed` by other projects, or, if you're using Mill, you can diff --git a/example/scalalib/basic/1-script/build.mill b/example/scalalib/basic/1-script/build.mill index f582a2cf5af9..6ec1278c8000 100644 --- a/example/scalalib/basic/1-script/build.mill +++ b/example/scalalib/basic/1-script/build.mill @@ -66,7 +66,7 @@ Jvm Version: 11.0.28 /** Usage > ./mill show Foo.scala:assembly # show the output of the assembly task -".../out/Foo.scala/assembly.dest/out.jar" +"...$OUT/Foo.scala/assembly.dest/out.jar" > java -jar ./out/Foo.scala/assembly.dest/out.jar --text hello

hello

diff --git a/example/scalalib/basic/10-realistic/build.mill b/example/scalalib/basic/10-realistic/build.mill index b9ea519a6e6e..31fbf6ca7d4c 100644 --- a/example/scalalib/basic/10-realistic/build.mill +++ b/example/scalalib/basic/10-realistic/build.mill @@ -126,7 +126,7 @@ Publishing Artifact(com.lihaoyi,bar_3,0.0.1) to ivy repo... Publishing Artifact(com.lihaoyi,qux,0.0.1) to ivy repo... > ./mill show foo[2.13.16].assembly # mac/linux -".../out/foo/2.13.16/assembly.dest/out.jar" +"...$OUT/foo/2.13.16/assembly.dest/out.jar" > ./out/foo/2.13.16/assembly.dest/out.jar # mac/linux foo version 0.0.1 diff --git a/example/scalalib/basic/3-simple/build.mill b/example/scalalib/basic/3-simple/build.mill index 1138995a9734..96244f4b8fae 100644 --- a/example/scalalib/basic/3-simple/build.mill +++ b/example/scalalib/basic/3-simple/build.mill @@ -101,7 +101,7 @@ compiling 1 Scala source to... > ./mill assembly # bundle classfiles and libraries into a jar for deployment > ./mill show assembly # show the output of the assembly task -".../out/assembly.dest/out.jar" +"...$OUT/assembly.dest/out.jar" > java -jar ./out/assembly.dest/out.jar --text hello

hello

diff --git a/example/scalalib/basic/6-programmatic/build.mill b/example/scalalib/basic/6-programmatic/build.mill index 8a2bcb597a9c..6d956e8a371f 100644 --- a/example/scalalib/basic/6-programmatic/build.mill +++ b/example/scalalib/basic/6-programmatic/build.mill @@ -83,7 +83,7 @@ foo.run > ./mill foo.assembly # bundle classfiles and libraries into a jar for deployment > ./mill show foo.assembly # show the output of the assembly task -".../out/foo/assembly.dest/out.jar" +"...$OUT/foo/assembly.dest/out.jar" > java -jar ./out/foo/assembly.dest/out.jar --text hello

hello

diff --git a/example/scalalib/config/1-common-config/build.mill b/example/scalalib/config/1-common-config/build.mill index 0a8cee6fc8f7..f02015273d87 100644 --- a/example/scalalib/config/1-common-config/build.mill +++ b/example/scalalib/config/1-common-config/build.mill @@ -14,7 +14,7 @@ my.custom.property: my-prop-value MY_CUSTOM_ENV: my-env-value > ./mill show assembly -".../out/assembly.dest/out.jar" +"...$OUT/assembly.dest/out.jar" > ./out/assembly.dest/out.jar # mac/linux Foo2.value:

hello2

diff --git a/example/scalalib/module/15-unidoc/build.mill b/example/scalalib/module/15-unidoc/build.mill index 94f1f27fb1d2..8bc8cd90e02f 100644 --- a/example/scalalib/module/15-unidoc/build.mill +++ b/example/scalalib/module/15-unidoc/build.mill @@ -34,7 +34,7 @@ object foo extends ScalaModule, UnidocModule { /** Usage > ./mill show foo.unidocLocal -".../out/foo/unidocLocal.dest" +"...$OUT/foo/unidocLocal.dest" > cat out/foo/unidocLocal.dest/foo/Foo.html ... diff --git a/example/scalalib/module/2-common-config/build.mill b/example/scalalib/module/2-common-config/build.mill index 6103b6ff4cea..94be6f93cabd 100644 --- a/example/scalalib/module/2-common-config/build.mill +++ b/example/scalalib/module/2-common-config/build.mill @@ -105,7 +105,7 @@ my.custom.property: my-prop-value MY_CUSTOM_ENV: my-env-value > ./mill show assembly -".../out/assembly.dest/out.jar" +"...$OUT/assembly.dest/out.jar" > ./out/assembly.dest/out.jar # mac/linux Foo2.value:

hello2

diff --git a/example/scalalib/native/1-simple/build.mill b/example/scalalib/native/1-simple/build.mill index f6a43a8451e8..101506fddd82 100644 --- a/example/scalalib/native/1-simple/build.mill +++ b/example/scalalib/native/1-simple/build.mill @@ -26,7 +26,7 @@ object `package` extends ScalaNativeModule {

hello

> ./mill show nativeLink # Build and link native binary -".../out/nativeLink.dest/out" +"...$OUT/nativeLink.dest/out" > ./out/nativeLink.dest/out --text hello # Run the executable

hello

diff --git a/example/scalalib/spark/3-semi-realistic/build.mill b/example/scalalib/spark/3-semi-realistic/build.mill index 73668b29a008..27a1f3aae0d2 100644 --- a/example/scalalib/spark/3-semi-realistic/build.mill +++ b/example/scalalib/spark/3-semi-realistic/build.mill @@ -46,7 +46,7 @@ Summary Statistics by Category: > chmod +x spark-submit.sh > ./mill show assembly # prepare for spark-submit -".../out/assembly.dest/out.jar" +"...$OUT/assembly.dest/out.jar" > ./spark-submit.sh out/assembly.dest/out.jar foo.Foo resources/transactions.csv ... diff --git a/example/scalalib/web/4-scalajs-module/build.mill b/example/scalalib/web/4-scalajs-module/build.mill index b9eae71f61c6..f81181d58777 100644 --- a/example/scalalib/web/4-scalajs-module/build.mill +++ b/example/scalalib/web/4-scalajs-module/build.mill @@ -41,7 +41,7 @@ stringifiedJsObject: ["hello","world","!"] { ... ..."jsFileName": "main.js", - "dest": ".../out/foo/fullLinkJS.dest" + "dest": "...$OUT/foo/fullLinkJS.dest" } > node out/foo/fullLinkJS.dest/main.js # mac/linux diff --git a/example/scalalib/web/9-wasm/build.mill b/example/scalalib/web/9-wasm/build.mill index f85403e81e10..e2a56573c20d 100644 --- a/example/scalalib/web/9-wasm/build.mill +++ b/example/scalalib/web/9-wasm/build.mill @@ -27,7 +27,7 @@ object wasm extends ScalaJSModule { ... ..."jsFileName": "main.js", ... - "dest": ".../out/wasm/fastLinkJS.dest" + "dest": "...$OUT/wasm/fastLinkJS.dest" } > node --experimental-wasm-exnref out/wasm/fastLinkJS.dest/main.js # mac/linux diff --git a/example/thirdparty/android-endless-tunnel/build.mill b/example/thirdparty/android-endless-tunnel/build.mill index 7bbec86eb8fc..aa8b7c2dff7c 100644 --- a/example/thirdparty/android-endless-tunnel/build.mill +++ b/example/thirdparty/android-endless-tunnel/build.mill @@ -53,7 +53,7 @@ object `endless-tunnel` extends mill.api.Module { /** Usage > ./mill show endless-tunnel.app.androidApk -".../out/endless-tunnel/app/androidApk.dest/app.apk" +"...$OUT/endless-tunnel/app/androidApk.dest/app.apk" > ./mill show endless-tunnel.app.createAndroidVirtualDevice ...Name: cpp-test, DeviceId: medium_phone... diff --git a/libs/javalib/src/mill/javalib/TestModuleUtil.scala b/libs/javalib/src/mill/javalib/TestModuleUtil.scala index e3dcf654e506..74051569e36c 100644 --- a/libs/javalib/src/mill/javalib/TestModuleUtil.scala +++ b/libs/javalib/src/mill/javalib/TestModuleUtil.scala @@ -1,6 +1,6 @@ package mill.javalib -import mill.api.{BuildCtx, Evaluator, Logger, PathRef, Result, TaskCtx} +import mill.api.{BuildCtx, Logger, PathRef, Result, TaskCtx} import mill.api.daemon.internal.TestReporter import mill.util.Jvm import mill.api.internal.Util @@ -145,7 +145,11 @@ final class TestModuleUtil( classPath = (runClasspath ++ testrunnerEntrypointClasspath).map(_.path), jvmArgs = jvmArgs, env = (if (propagateEnv) Task.env else Map()) ++ forkEnv, - mainArgs = Seq(testRunnerClasspathArg, argsFile.toString, PathRef.outPathOverride.value.get.toString), + mainArgs = Seq( + testRunnerClasspathArg, + argsFile.toString, + PathRef.outPathOverride.value.get.toString + ), cwd = if (testSandboxWorkingDir) sandbox else forkWorkingDir, cpPassingJarPath = Option.when(useArgsFile)( os.temp(prefix = "run-", suffix = ".jar", deleteOnExit = false) diff --git a/website/blog/modules/ROOT/pages/12-direct-style-build-tool.adoc b/website/blog/modules/ROOT/pages/12-direct-style-build-tool.adoc index a58837e17ede..c17e53d44f63 100644 --- a/website/blog/modules/ROOT/pages/12-direct-style-build-tool.adoc +++ b/website/blog/modules/ROOT/pages/12-direct-style-build-tool.adoc @@ -74,7 +74,7 @@ Test foo.FooTest.testSimple finished, ... 0 failed, 0 ignored, 2 total, ... > ./mill show foo.assembly -".../out/foo/assembly.dest/out.jar" +"...$OUT/foo/assembly.dest/out.jar" > ./out/foo/assembly.dest/out.jar --text hello

hello

diff --git a/website/blog/modules/ROOT/pages/7-graal-native-executables.adoc b/website/blog/modules/ROOT/pages/7-graal-native-executables.adoc index 2eeb3eff3cf9..ef9926ef3912 100644 --- a/website/blog/modules/ROOT/pages/7-graal-native-executables.adoc +++ b/website/blog/modules/ROOT/pages/7-graal-native-executables.adoc @@ -107,7 +107,7 @@ outside of the build tool: [source,console] ---- $ ./mill show foo.assembly -".../out/foo/assembly.dest/out.jar" +"...$OUT/foo/assembly.dest/out.jar" $ out/foo/assembly.dest/out.jar --text "hello world"

hello world

@@ -159,7 +159,7 @@ Now, we can use build a native image using `foo.nativeImage`: [source,console] ---- $ ./mill show foo.nativeImage -".../out/foo/nativeImage.dest/native-executable" +"...$OUT/foo/nativeImage.dest/native-executable" $ out/foo/nativeImage.dest/native-executable --text "hello world"

hello world

@@ -229,7 +229,7 @@ _Executable Assembly_ ---- $ time ./mill show foo.assembly [1-41] [info] compiling 1 Java source... -".../out/foo/assembly.dest/out.jar" +"...$OUT/foo/assembly.dest/out.jar" ./mill show foo.assembly 0.12s user 0.06s system 21% cpu 0.818 total ---- @@ -243,7 +243,7 @@ $ time ./mill show foo.nativeImage [1-50] [2/8] Performing analysis... [****] (7.9s @ 0.77GB) ... [1-50] Finished generating 'native-executable' in 26.0s. -".../out/foo/nativeImage.dest/native-executable" +"...$OUT/foo/nativeImage.dest/native-executable" ./mill show foo.nativeImage 0.70s user 1.11s system 7% cpu 24.762 total ---- diff --git a/website/blog/modules/ROOT/pages/9-mill-faster-assembly-jars.adoc b/website/blog/modules/ROOT/pages/9-mill-faster-assembly-jars.adoc index 2818d48fb406..fe8944e559a9 100644 --- a/website/blog/modules/ROOT/pages/9-mill-faster-assembly-jars.adoc +++ b/website/blog/modules/ROOT/pages/9-mill-faster-assembly-jars.adoc @@ -124,7 +124,7 @@ to build an assembly that we can run using `java -jar`: [source,console] ---- > ./mill show assembly -".../out/assembly.dest/out.jar" +"...$OUT/assembly.dest/out.jar" Total time: 27s $ ls -lh out/assembly.dest/out.jar @@ -325,7 +325,7 @@ the code and re-build the assembly: > echo "class dummy" >> src/main/scala/foo/Foo.scala > ./mill show assembly -".../out/assembly.dest/out.jar" +"...$OUT/assembly.dest/out.jar" Total time: 1s > sbt assembly From 4f0bbca81923546a76ed4bfa30ceafa14994fc16 Mon Sep 17 00:00:00 2001 From: Tobias Roeser Date: Sat, 25 Oct 2025 09:34:08 +0200 Subject: [PATCH 07/45] Name tuple items --- core/exec/src/mill/exec/GroupExecution.scala | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/core/exec/src/mill/exec/GroupExecution.scala b/core/exec/src/mill/exec/GroupExecution.scala index 21d04bd629e4..afac154a4781 100644 --- a/core/exec/src/mill/exec/GroupExecution.scala +++ b/core/exec/src/mill/exec/GroupExecution.scala @@ -242,8 +242,8 @@ trait GroupExecution { newEvaluated = newEvaluated.toSeq, cached = if (labelled.isInstanceOf[Task.Input[?]]) null else false, inputsHash = inputsHash, - previousInputsHash = cached.map(_._1).getOrElse(-1), - valueHashChanged = !cached.map(_._3).contains(valueHash), + previousInputsHash = cached.map(_.inputHash).getOrElse(-1), + valueHashChanged = !cached.map(_.valueHash).contains(valueHash), serializedPaths = serializedPaths ) } @@ -453,7 +453,7 @@ trait GroupExecution { inputsHash: Int, labelled: Task.Named[?], paths: ExecutionPaths - ): Option[(Int, Option[(Val, Seq[PathRef])], Int)] = { + ): Option[(inputHash: Int, valOpt: Option[(Val, Seq[PathRef])], valueHash: Int)] = { for { cached <- try Some(upickle.read[Cached](paths.meta.toIO, trace = false)) @@ -618,7 +618,7 @@ object GroupExecution { classLoader: ClassLoader )(t: => T): T = { // Tasks must be allowed to write to upstream worker's dest folders, because - // the point of workers is to manualy manage long-lived state which includes + // the point of workers is to manually manage long-lived state which includes // state on disk. val validWriteDests = deps.collect { case n: Task.Worker[?] => From 426b7706e664aa9baea9af74cf531e443ecaedb0 Mon Sep 17 00:00:00 2001 From: Tobias Roeser Date: Sat, 25 Oct 2025 09:38:33 +0200 Subject: [PATCH 08/45] Don't use current evaluator to get the current output path --- core/api/src/mill/api/PathRef.scala | 7 ++++++- libs/javalib/test/src/mill/javalib/HelloJavaTests.scala | 2 +- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/core/api/src/mill/api/PathRef.scala b/core/api/src/mill/api/PathRef.scala index 2f761dbc1a67..e3a2a94a3525 100644 --- a/core/api/src/mill/api/PathRef.scala +++ b/core/api/src/mill/api/PathRef.scala @@ -201,7 +201,12 @@ object PathRef { private[api] def knownRoots: KnownRoots = { // order is important! Seq( - ("$MILL_OUT", outPathOverride.value.getOrElse(Evaluator.currentEvaluator.outPath)), + ( + "$MILL_OUT", + outPathOverride.value.getOrElse( + throw RuntimeException("Can't substitute $MILL_OUT, output path is not configured.") + ) + ), ("$WORKSPACE", BuildCtx.workspaceRoot), // TODO: add coursier here ("$HOME", os.home) diff --git a/libs/javalib/test/src/mill/javalib/HelloJavaTests.scala b/libs/javalib/test/src/mill/javalib/HelloJavaTests.scala index 303dc58ad854..fada1674bca3 100644 --- a/libs/javalib/test/src/mill/javalib/HelloJavaTests.scala +++ b/libs/javalib/test/src/mill/javalib/HelloJavaTests.scala @@ -40,9 +40,9 @@ object HelloJavaTests extends TestSuite { assert( result1.value == result2.value, + result1.evalCount != 0, result2.evalCount == 0, result3.evalCount != 0, - result3.evalCount != 0, os.walk(result1.value.classes.path).exists(_.last == "Core.class"), !os.walk(result1.value.classes.path).exists(_.last == "Main.class"), os.walk(result3.value.classes.path).exists(_.last == "Main.class"), From b2252cea91063d7f2497c053fcc019e0864e9e97 Mon Sep 17 00:00:00 2001 From: Tobias Roeser Date: Sat, 25 Oct 2025 17:11:58 +0200 Subject: [PATCH 09/45] Stabilize PathRef hashcode by using the encoded path --- core/api/src/mill/api/MillTaskHash.scala | 5 +++ core/api/src/mill/api/PathRef.scala | 21 ++++++++-- .../javalib/HelloJavaMinimalCacheTests.scala | 41 +++++++++++++++++++ 3 files changed, 64 insertions(+), 3 deletions(-) create mode 100644 core/api/src/mill/api/MillTaskHash.scala create mode 100644 libs/javalib/test/src/mill/javalib/HelloJavaMinimalCacheTests.scala diff --git a/core/api/src/mill/api/MillTaskHash.scala b/core/api/src/mill/api/MillTaskHash.scala new file mode 100644 index 000000000000..32f59f21c607 --- /dev/null +++ b/core/api/src/mill/api/MillTaskHash.scala @@ -0,0 +1,5 @@ +package mill.api + +trait MillTaskHash { + def millCacheHash: Int +} diff --git a/core/api/src/mill/api/PathRef.scala b/core/api/src/mill/api/PathRef.scala index e3a2a94a3525..fe2088491397 100644 --- a/core/api/src/mill/api/PathRef.scala +++ b/core/api/src/mill/api/PathRef.scala @@ -10,6 +10,7 @@ import java.util.concurrent.ConcurrentHashMap import scala.annotation.nowarn import scala.language.implicitConversions import scala.util.DynamicVariable +import scala.util.hashing.MurmurHash3 /** * A wrapper around `os.Path` that calculates it's hashcode based @@ -24,6 +25,8 @@ case class PathRef private[mill] ( ) extends PathRefApi { private[mill] def javaPath = path.toNIO + val pathVal: String = PathRef.encodeKnownRootsInPath(path) + def recomputeSig(): Int = PathRef.apply(path, quick).sig def validate(): Boolean = recomputeSig() == sig @@ -53,6 +56,18 @@ case class PathRef private[mill] ( override def toString: String = { toStringPrefix + path.toString() } + + // Instead of using `path` we need to use `pathVal`, to make the hashcode stable as cache key + override def hashCode(): Int = { + var h = MurmurHash3.productSeed + h = MurmurHash3.mix(h, "PathRef".hashCode) + h = MurmurHash3.mix(h, pathVal.hashCode) + h = MurmurHash3.mix(h, quick.##) + h = MurmurHash3.mix(h, sig.##) + h = MurmurHash3.mix(h, revalidate.##) + MurmurHash3.finalizeHash(h, 4) + } + } object PathRef { @@ -242,12 +257,12 @@ object PathRef { implicit def jsonFormatter: RW[PathRef] = upickle.readwriter[String].bimap[PathRef]( p => { storeSerializedPaths(p) - p.toStringPrefix + encodeKnownRootsInPath(p.path) + p.toStringPrefix + p.pathVal }, { - case s"$prefix:$valid0:$hex:$pathString" if prefix == "ref" || prefix == "qref" => + case s"$prefix:$valid0:$hex:$pathVal" if prefix == "ref" || prefix == "qref" => - val path = os.Path(pathString) + val path = os.Path(decodeKnownRootsInPath(pathVal)) val quick = prefix match { case "qref" => true case "ref" => false diff --git a/libs/javalib/test/src/mill/javalib/HelloJavaMinimalCacheTests.scala b/libs/javalib/test/src/mill/javalib/HelloJavaMinimalCacheTests.scala new file mode 100644 index 000000000000..4183fae29609 --- /dev/null +++ b/libs/javalib/test/src/mill/javalib/HelloJavaMinimalCacheTests.scala @@ -0,0 +1,41 @@ +package mill.javalib + +import mill.* +import mill.api.{Discover, Task} +import mill.testkit.{TestRootModule, UnitTester} +import utest.* +import utest.framework.TestPath + +/** + * Reproduce cache-miss when a cache value for a PathRef-result should be present. + * This is an issue with out pathref-mangling to replace known root variables + */ +object HelloJavaMinimalCacheTests extends TestSuite { + + object HelloJava extends TestRootModule { + object core extends JavaModule + protected lazy val millDiscover = Discover[this.type] + } + + val resourcePath = os.Path(sys.env("MILL_TEST_RESOURCE_DIR").trim()) / "hello-java" + + def testEval() = UnitTester(HelloJava, resourcePath, debugEnabled = true) + def tests: Tests = Tests { + test("javacOptions") { + println("Test: " + summon[TestPath].value.mkString(".")) + testEval().scoped { eval => + + val Right(result1) = eval.apply(HelloJava.core.compileResources): @unchecked + val Right(result2) = eval.apply(HelloJava.core.compileResources): @unchecked + val Right(result3) = eval.apply(HelloJava.core.compileResources): @unchecked + + assert( + result1.value == result2.value, + result1.evalCount != 0, + result2.evalCount == 0, + result3.evalCount == 0 + ) + } + } + } +} From a93fedf288d8cfa37a1b3ae2ad529b9870546725 Mon Sep 17 00:00:00 2001 From: Tobias Roeser Date: Sat, 25 Oct 2025 17:24:55 +0200 Subject: [PATCH 10/45] Fix tests --- core/api/test/src/mill/api/PathRefTests.scala | 89 +++++++++++-------- 1 file changed, 52 insertions(+), 37 deletions(-) diff --git a/core/api/test/src/mill/api/PathRefTests.scala b/core/api/test/src/mill/api/PathRefTests.scala index 0e353a296494..623263c0b350 100644 --- a/core/api/test/src/mill/api/PathRefTests.scala +++ b/core/api/test/src/mill/api/PathRefTests.scala @@ -10,14 +10,16 @@ object PathRefTests extends TestSuite { val tests: Tests = Tests { test("sig") { def check(quick: Boolean) = withTmpDir { tmpDir => - val file = tmpDir / "foo.txt" - os.write.over(file, "hello") - val sig1 = PathRef(file, quick).sig - val sig1b = PathRef(file, quick).sig - assert(sig1 == sig1b) - os.write.over(file, "hello world") - val sig2 = PathRef(file, quick).sig - assert(sig1 != sig2) + PathRef.outPathOverride.withValue(Some(tmpDir / "out")) { + val file = tmpDir / "foo.txt" + os.write.over(file, "hello") + val sig1 = PathRef(file, quick).sig + val sig1b = PathRef(file, quick).sig + assert(sig1 == sig1b) + os.write.over(file, "hello world") + val sig2 = PathRef(file, quick).sig + assert(sig1 != sig2) + } } test("qref") - check(quick = true) test("ref") - check(quick = false) @@ -25,13 +27,15 @@ object PathRefTests extends TestSuite { test("same-sig-other-file") { def check(quick: Boolean) = withTmpDir { tmpDir => - val file = tmpDir / "foo.txt" - os.write.over(file, "hello") - val sig1 = PathRef(file, quick).sig - val file2 = tmpDir / "bar.txt" - os.copy(file, file2) - val sig1b = PathRef(file2, quick).sig - assert(sig1 == sig1b) + PathRef.outPathOverride.withValue(Some(tmpDir / "out")) { + val file = tmpDir / "foo.txt" + os.write.over(file, "hello") + val sig1 = PathRef(file, quick).sig + val file2 = tmpDir / "bar.txt" + os.copy(file, file2) + val sig1b = PathRef(file2, quick).sig + assert(sig1 == sig1b) + } } // test("qref") - check(quick = true) test("ref") - check(quick = false) @@ -40,18 +44,26 @@ object PathRefTests extends TestSuite { test("perms") { def check(quick: Boolean) = if (isPosixFs()) withTmpDir { tmpDir => - val file = tmpDir / "foo.txt" - val content = "hello" - os.write.over(file, content) - Files.setPosixFilePermissions(file.wrapped, PosixFilePermissions.fromString("rw-rw----")) - val rwSig = PathRef(file, quick).sig - val rwSigb = PathRef(file, quick).sig - assert(rwSig == rwSigb) + PathRef.outPathOverride.withValue(Some(tmpDir / "out")) { + val file = tmpDir / "foo.txt" + val content = "hello" + os.write.over(file, content) + Files.setPosixFilePermissions( + file.wrapped, + PosixFilePermissions.fromString("rw-rw----") + ) + val rwSig = PathRef(file, quick).sig + val rwSigb = PathRef(file, quick).sig + assert(rwSig == rwSigb) - Files.setPosixFilePermissions(file.wrapped, PosixFilePermissions.fromString("rwxrw----")) - val rwxSig = PathRef(file, quick).sig + Files.setPosixFilePermissions( + file.wrapped, + PosixFilePermissions.fromString("rwxrw----") + ) + val rwxSig = PathRef(file, quick).sig - assert(rwSig != rwxSig) + assert(rwSig != rwxSig) + } } else "Test Skipped on non-POSIX host" @@ -61,20 +73,22 @@ object PathRefTests extends TestSuite { test("symlinks") { def check(quick: Boolean) = withTmpDir { tmpDir => - // invalid symlink - os.symlink(tmpDir / "nolink", tmpDir / "nonexistant") + PathRef.outPathOverride.withValue(Some(tmpDir / "out")) { + // invalid symlink + os.symlink(tmpDir / "nolink", tmpDir / "nonexistant") - // symlink to empty dir - os.symlink(tmpDir / "emptylink", tmpDir / "empty") - os.makeDir(tmpDir / "empty") + // symlink to empty dir + os.symlink(tmpDir / "emptylink", tmpDir / "empty") + os.makeDir(tmpDir / "empty") - // recursive symlinks - os.symlink(tmpDir / "rlink1", tmpDir / "rlink2") - os.symlink(tmpDir / "rlink2", tmpDir / "rlink1") + // recursive symlinks + os.symlink(tmpDir / "rlink1", tmpDir / "rlink2") + os.symlink(tmpDir / "rlink2", tmpDir / "rlink1") - val sig1 = PathRef(tmpDir, quick).sig - val sig2 = PathRef(tmpDir, quick).sig - assert(sig1 == sig2) + val sig1 = PathRef(tmpDir, quick).sig + val sig2 = PathRef(tmpDir, quick).sig + assert(sig1 == sig2) + } } test("qref") - check(quick = true) test("ref") - check(quick = false) @@ -87,7 +101,8 @@ object PathRefTests extends TestSuite { val file = tmpDir / "foo.txt" os.write(file, "hello") val pr = PathRef(file, quick) - val prFile = pr.path.toString().replace("\\", "\\\\") + val prFile = + pr.path.toString().replace(outDir.toString(), "$MILL_OUT").replace("\\", "\\\\") val json = upickle.write(pr) if (quick) { assert(json.startsWith(""""qref:v0:""")) From 1c250ae92a7e3bef60f671db33d500681636a4a3 Mon Sep 17 00:00:00 2001 From: Tobias Roeser Date: Sun, 26 Oct 2025 10:15:48 +0100 Subject: [PATCH 11/45] Refactor outDir init and propagation --- core/api/src/mill/api/PathRef.scala | 2 +- .../src/mill/daemon/MillDaemonMain.scala | 66 +++++++++++-------- runner/daemon/src/mill/daemon/MillMain0.scala | 42 ++++++------ .../src/mill/daemon/MillNoDaemonMain.scala | 10 +-- .../src/mill/launcher/MillLauncherMain.java | 12 ++-- .../mill/launcher/MillProcessLauncher.java | 17 +++-- .../src/mill/server/MillDaemonServer.scala | 4 +- .../src/mill/server/ClientServerTests.scala | 11 ++-- 8 files changed, 89 insertions(+), 75 deletions(-) diff --git a/core/api/src/mill/api/PathRef.scala b/core/api/src/mill/api/PathRef.scala index fe2088491397..53b3a8b734c6 100644 --- a/core/api/src/mill/api/PathRef.scala +++ b/core/api/src/mill/api/PathRef.scala @@ -25,7 +25,7 @@ case class PathRef private[mill] ( ) extends PathRefApi { private[mill] def javaPath = path.toNIO - val pathVal: String = PathRef.encodeKnownRootsInPath(path) + private[mill] val pathVal: String = PathRef.encodeKnownRootsInPath(path) def recomputeSig(): Int = PathRef.apply(path, quick).sig def validate(): Boolean = recomputeSig() == sig diff --git a/runner/daemon/src/mill/daemon/MillDaemonMain.scala b/runner/daemon/src/mill/daemon/MillDaemonMain.scala index 0eeaaecb876e..1919cd680b7e 100644 --- a/runner/daemon/src/mill/daemon/MillDaemonMain.scala +++ b/runner/daemon/src/mill/daemon/MillDaemonMain.scala @@ -1,27 +1,31 @@ package mill.daemon import mill.api.{BuildCtx, SystemStreams} +import mill.api.{PathRef, SystemStreams} +import mill.client.ClientUtil import mill.client.lock.{Lock, Locks} -import mill.constants.{OutFiles, OutFolderMode} +import mill.constants.OutFolderMode import mill.server.Server import scala.concurrent.duration.* import scala.util.{Failure, Properties, Success, Try} object MillDaemonMain { - case class Args(daemonDir: os.Path, outMode: OutFolderMode, rest: Seq[String]) + case class Args(daemonDir: os.Path, outMode: OutFolderMode, outDir: os.Path, rest: Seq[String]) + object Args { def apply(appName: String, args: Array[String]): Either[String, Args] = { def usage(extra: String = "") = - s"usage: $appName $extra" + s"usage: $appName $extra" args match { - case Array(daemonDir, outModeStr, rest*) => + case Array(daemonDir, outModeStr, outDir, rest*) => Try(OutFolderMode.fromString(outModeStr)) match { case Failure(_) => val possibleValues = OutFolderMode.values.map(_.asString).mkString(", ") Left(usage(s"\n\n must be one of $possibleValues but was '$outModeStr'")) - case Success(outMode) => Right(apply(os.Path(daemonDir), outMode, rest)) + case Success(outMode) => + Right(apply(os.Path(daemonDir), outMode, os.Path(outDir), rest)) } case _ => Left(usage()) } @@ -36,27 +40,30 @@ object MillDaemonMain { val args = Args(getClass.getName, args0).fold(err => throw IllegalArgumentException(err), identity) - // temporarily disabling FFM use by coursier, which has issues with the way - // Mill manages class loaders, throwing things like - // UnsatisfiedLinkError: Native Library C:\Windows\System32\ole32.dll already loaded in another classloader - if (Properties.isWin) sys.props("coursier.windows.disable-ffm") = "true" + PathRef.outPathOverride.withValue(Some(args.outDir)) { + // temporarily disabling FFM use by coursier, which has issues with the way + // Mill manages class loaders, throwing things like + // UnsatisfiedLinkError: Native Library C:\Windows\System32\ole32.dll already loaded in another classloader + if (Properties.isWin) sys.props("coursier.windows.disable-ffm") = "true" - coursier.Resolve.proxySetup() // Take into account proxy-related Java properties + coursier.Resolve.proxySetup() // Take into account proxy-related Java properties - mill.api.SystemStreamsUtils.withTopLevelSystemStreamProxy { - Server.overrideSigIntHandling() + mill.api.SystemStreamsUtils.withTopLevelSystemStreamProxy { + Server.overrideSigIntHandling() - val acceptTimeout = - Try(System.getProperty("mill.server_timeout").toInt.millis).getOrElse(30.minutes) + val acceptTimeout = + Try(System.getProperty("mill.server_timeout").toInt.millis).getOrElse(30.minutes) - val exitCode = new MillDaemonMain( - daemonDir = args.daemonDir, - acceptTimeout = acceptTimeout, - Locks.files(args.daemonDir.toString), - outMode = args.outMode - ).run().getOrElse(0) + val exitCode = new MillDaemonMain( + daemonDir = args.daemonDir, + acceptTimeout = acceptTimeout, + Locks.files(args.daemonDir.toString), + outMode = args.outMode, + outDir = args.outDir + ).run().getOrElse(0) - System.exit(exitCode) + System.exit(exitCode) + } } } } @@ -65,18 +72,18 @@ class MillDaemonMain( daemonDir: os.Path, acceptTimeout: FiniteDuration, locks: Locks, - outMode: OutFolderMode + outMode: OutFolderMode, + outDir: os.Path ) extends mill.server.MillDaemonServer[RunnerState]( - daemonDir, - acceptTimeout, - locks + daemonDir = daemonDir, + acceptTimeout = acceptTimeout, + locks = locks, + outDir = outDir ) { def initialStateCache = RunnerState.empty - val outFolder: os.Path = os.Path(OutFiles.outFor(outMode), BuildCtx.workspaceRoot) - - val outLock = MillMain0.doubleLock(outFolder) + val outLock = MillMain0.doubleLock(outDir) def main0( args: Array[String], @@ -100,7 +107,8 @@ class MillDaemonMain( initialSystemProperties = initialSystemProperties, systemExit = systemExit, daemonDir = daemonDir, - outLock = outLock + outLock = outLock, + outDir = outDir ) catch MillMain0.handleMillException(streams.err, stateCache) } diff --git a/runner/daemon/src/mill/daemon/MillMain0.scala b/runner/daemon/src/mill/daemon/MillMain0.scala index a4d721231e2a..8e6c9ce62a77 100644 --- a/runner/daemon/src/mill/daemon/MillMain0.scala +++ b/runner/daemon/src/mill/daemon/MillMain0.scala @@ -3,11 +3,10 @@ package mill.daemon import ch.epfl.scala.bsp4j.BuildClient import mill.api.daemon.internal.bsp.BspServerHandle import mill.api.daemon.internal.{CompileProblemReporter, EvaluatorApi} -import mill.api.{Logger, MillException, Result, SystemStreams} +import mill.api.{BuildCtx, Logger, MillException, PathRef, Result, SystemStreams} import mill.bsp.BSP import mill.client.lock.{DoubleLock, Lock} -import mill.constants.{DaemonFiles, OutFiles, OutFolderMode} -import mill.api.BuildCtx +import mill.constants.{DaemonFiles, OutFiles} import mill.internal.{ Colors, JsonArrayLogger, @@ -65,12 +64,12 @@ object MillMain0 { private def withStreams[T]( bspMode: Boolean, - streams: SystemStreams + streams: SystemStreams, + outDir: os.Path )(thunk: SystemStreams => T): T = if (bspMode) { // In BSP mode, don't let anything other than the BSP server write to stdout and read from stdin - val outDir = BuildCtx.workspaceRoot / os.RelPath(OutFiles.outFor(OutFolderMode.BSP)) val outFileStream = os.write.outputStream( outDir / "mill-bsp/out.log", createFolders = true @@ -109,8 +108,9 @@ object MillMain0 { initialSystemProperties: Map[String, String], systemExit: Server.StopServer, daemonDir: os.Path, - outLock: Lock - ): (Boolean, RunnerState) = + outLock: Lock, + outDir: os.Path + ): (Boolean, RunnerState) = PathRef.outPathOverride.withValue(Some(outDir)) { mill.api.daemon.internal.MillScalaParser.current.withValue(MillScalaParserImpl) { os.SubProcess.env.withValue(env) { val parserResult = MillCliConfig.parse(args) @@ -121,7 +121,7 @@ object MillMain0 { // This is especially helpful if anything unexpectedly goes wrong // early on, when developing on Mill or debugging things for example. val bspMode = parserResult.toOption.exists(_.bsp.value) - withStreams(bspMode, streams0) { streams => + withStreams(bspMode, streams0, outDir) { streams => parserResult match { // Cannot parse args case Result.Failure(msg) => @@ -178,7 +178,7 @@ object MillMain0 { // special BSP mode, in which we spawn a server and register the current evaluator when-ever we start to eval a dedicated command val bspMode = config.bsp.value && config.leftoverArgs.value.isEmpty - val outMode = if (bspMode) OutFolderMode.BSP else OutFolderMode.REGULAR + val bspInstallModeJobCountOpt = { def defaultJobCount = maybeThreadCount.toOption.getOrElse(BSP.defaultJobCount) @@ -241,7 +241,6 @@ object MillMain0 { if (threadCount == 1) None else Some(mill.exec.ExecutionContexts.createExecutor(threadCount)) - val out = os.Path(OutFiles.outFor(outMode), BuildCtx.workspaceRoot) Using.resources(new TailManager(daemonDir), createEc()) { (tailManager, ec) => def runMillBootstrap( skipSelectiveExecution: Boolean, @@ -257,7 +256,7 @@ object MillMain0 { ): Watching.Result[RunnerState] = MillDaemonServer.withOutLock( noBuildLock = config.noBuildLock.value, noWaitForBuildLock = config.noWaitForBuildLock.value, - out = out, + out = outDir, millActiveCommandMessage = millActiveCommandMessage, streams = streams, outLock = outLock, @@ -270,7 +269,8 @@ object MillMain0 { // Do this by removing the file rather than disabling selective execution, // because we still want to generate the selective execution metadata json // for subsequent runs that may use it - if (skipSelectiveExecution) os.remove(out / OutFiles.millSelectiveExecution) + if (skipSelectiveExecution) + os.remove(outDir / OutFiles.millSelectiveExecution) mill.api.SystemStreamsUtils.withStreams(logger.streams) { mill.api.FilesystemCheckerEnabled.withValue( !config.noFilesystemChecker.value @@ -278,7 +278,7 @@ object MillMain0 { tailManager.withOutErr(logger.streams.out, logger.streams.err) { new MillBuildBootstrap( projectRoot = BuildCtx.workspaceRoot, - output = out, + output = outDir, // In BSP server, we want to evaluate as many tasks as possible, // in order to give as many results as available in BSP responses keepGoing = bspMode || config.keepGoing.value, @@ -313,7 +313,7 @@ object MillMain0 { daemonDir = daemonDir, colored = colored, colors = colors, - out = out + out = outDir )) { logger => proceed(logger) } @@ -358,7 +358,7 @@ object MillMain0 { val bspLogger = getBspLogger(streams, config) var prevRunnerStateOpt = Option.empty[RunnerState] val (bspServerHandle, buildClient) = - startBspServer(streams0, outLock, bspLogger) + startBspServer(streams0, outLock, bspLogger, outDir) var keepGoing = true var errored = false val initCommandLogger = new PrefixLogger(bspLogger, Seq("init")) @@ -510,6 +510,7 @@ object MillMain0 { } } } + if (config.ringBell.value) { if (success) println("\u0007") else { @@ -524,6 +525,7 @@ object MillMain0 { } } } + } /** * Starts the BSP server @@ -533,13 +535,13 @@ object MillMain0 { def startBspServer( bspStreams: SystemStreams, outLock: Lock, - bspLogger: Logger + bspLogger: Logger, + outDir: os.Path ): (BspServerHandle, BuildClient) = { bspLogger.info("Trying to load BSP server...") - val wsRoot = BuildCtx.workspaceRoot - val outFolder = wsRoot / os.RelPath(OutFiles.outFor(OutFolderMode.BSP)) - val logDir = outFolder / "mill-bsp" + BuildCtx.workspaceRoot + val logDir = outDir / "mill-bsp" os.makeDir.all(logDir) val bspServerHandleRes = @@ -550,7 +552,7 @@ object MillMain0 { true, outLock, bspLogger, - outFolder + outDir ).get bspLogger.info("BSP server started") diff --git a/runner/daemon/src/mill/daemon/MillNoDaemonMain.scala b/runner/daemon/src/mill/daemon/MillNoDaemonMain.scala index f70dbd2282a7..bcbb5754876a 100644 --- a/runner/daemon/src/mill/daemon/MillNoDaemonMain.scala +++ b/runner/daemon/src/mill/daemon/MillNoDaemonMain.scala @@ -2,13 +2,13 @@ package mill.daemon import mill.constants.{DaemonFiles, OutFiles, Util} import mill.daemon.MillMain0.{handleMillException, main0} -import mill.api.BuildCtx import mill.server.Server import scala.jdk.CollectionConverters.* import scala.util.Properties object MillNoDaemonMain { + def main(args0: Array[String]): Unit = mill.api.SystemStreamsUtils.withTopLevelSystemStreamProxy { val initialSystemStreams = mill.api.SystemStreams.original @@ -28,9 +28,8 @@ object MillNoDaemonMain { .fold(err => throw IllegalArgumentException(err), identity) val processId = Server.computeProcessId() - val out = os.Path(OutFiles.outFor(args.outMode), BuildCtx.workspaceRoot) Server.watchProcessIdFile( - out / OutFiles.millNoDaemon / s"pid-$processId" / DaemonFiles.processId, + args.outDir / OutFiles.millNoDaemon / s"pid-$processId" / DaemonFiles.processId, processId, running = () => true, exit = msg => { @@ -39,7 +38,7 @@ object MillNoDaemonMain { } ) - val outLock = MillMain0.doubleLock(out) + val outLock = MillMain0.doubleLock(args.outDir) val (result, _) = try main0( @@ -53,7 +52,8 @@ object MillNoDaemonMain { initialSystemProperties = sys.props.toMap, systemExit = ( /*reason*/ _, exitCode) => sys.exit(exitCode), daemonDir = args.daemonDir, - outLock = outLock + outLock = outLock, + outDir = args.outDir ) catch handleMillException(initialSystemStreams.err, ()) diff --git a/runner/launcher/src/mill/launcher/MillLauncherMain.java b/runner/launcher/src/mill/launcher/MillLauncherMain.java index a914102421af..233a0369424b 100644 --- a/runner/launcher/src/mill/launcher/MillLauncherMain.java +++ b/runner/launcher/src/mill/launcher/MillLauncherMain.java @@ -1,5 +1,6 @@ package mill.launcher; +import java.io.File; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; @@ -54,6 +55,7 @@ public static void main(String[] args) throws Exception { var outMode = bspMode ? OutFolderMode.BSP : OutFolderMode.REGULAR; exitInTestsAfterBspCheck(); var outDir = OutFiles.outFor(outMode); + var outPath = new File(outDir).getAbsoluteFile(); if (outMode == OutFolderMode.BSP) { System.err.println( @@ -87,8 +89,8 @@ public static void main(String[] args) throws Exception { if (runNoDaemon) { String mainClass = bspMode ? "mill.daemon.MillBspMain" : "mill.daemon.MillNoDaemonMain"; // start in no-server mode - int exitCode = - MillProcessLauncher.launchMillNoDaemon(args, outMode, runnerClasspath, mainClass); + int exitCode = MillProcessLauncher.launchMillNoDaemon( + args, outMode, outPath, runnerClasspath, mainClass); System.exit(exitCode); } else { var logs = new java.util.ArrayList(); @@ -107,9 +109,9 @@ public static void main(String[] args) throws Exception { Optional.empty(), -1) { public LaunchedServer initServer(Path daemonDir, Locks locks) throws Exception { - return new LaunchedServer.OsProcess( - MillProcessLauncher.launchMillDaemon(daemonDir, outMode, runnerClasspath) - .toHandle()); + return new LaunchedServer.OsProcess(MillProcessLauncher.launchMillDaemon( + daemonDir, outMode, outPath, runnerClasspath) + .toHandle()); } }; diff --git a/runner/launcher/src/mill/launcher/MillProcessLauncher.java b/runner/launcher/src/mill/launcher/MillProcessLauncher.java index 2042ec335ce9..3e8d7291a19d 100644 --- a/runner/launcher/src/mill/launcher/MillProcessLauncher.java +++ b/runner/launcher/src/mill/launcher/MillProcessLauncher.java @@ -20,7 +20,7 @@ public class MillProcessLauncher { static int launchMillNoDaemon( - String[] args, OutFolderMode outMode, String[] runnerClasspath, String mainClass) + String[] args, OutFolderMode outMode, File outDir, String[] runnerClasspath, String mainClass) throws Exception { final String sig = String.format("%08x", UUID.randomUUID().hashCode()); final Path processDir = @@ -35,6 +35,7 @@ static int launchMillNoDaemon( l.add(mainClass); l.add(processDir.toAbsolutePath().toString()); l.add(outMode.asString()); + l.add(outDir.toString()); l.addAll(millOpts(outMode)); l.addAll(Arrays.asList(args)); @@ -60,12 +61,14 @@ static int launchMillNoDaemon( } } - static Process launchMillDaemon(Path daemonDir, OutFolderMode outMode, String[] runnerClasspath) + static Process launchMillDaemon( + Path daemonDir, OutFolderMode outMode, File outDir, String[] runnerClasspath) throws Exception { List l = new ArrayList<>(millLaunchJvmCommand(outMode, runnerClasspath)); l.add("mill.daemon.MillDaemonMain"); l.add(daemonDir.toFile().getCanonicalPath()); l.add(outMode.asString()); + l.add(outDir.toString()); ProcessBuilder builder = new ProcessBuilder() .command(l) @@ -265,11 +268,6 @@ static List millLaunchJvmCommand(OutFolderMode outMode, String[] runnerC return vmOptions; } - static String[] cachedComputedValue( - OutFolderMode outMode, String name, String key, Supplier block) { - return cachedComputedValue0(outMode, name, key, block, arr -> true); - } - static String[] cachedComputedValue0( OutFolderMode outMode, String name, @@ -307,6 +305,11 @@ static String[] cachedComputedValue0( } } + static String[] cachedComputedValue( + OutFolderMode outMode, String name, String key, Supplier block) { + return cachedComputedValue0(outMode, name, key, block, arr -> true); + } + static int getTerminalDim(String s, boolean inheritError) throws Exception { Process proc = new ProcessBuilder() .command("tput", s) diff --git a/runner/server/src/mill/server/MillDaemonServer.scala b/runner/server/src/mill/server/MillDaemonServer.scala index 76a9c386661b..a9dab2f857b4 100644 --- a/runner/server/src/mill/server/MillDaemonServer.scala +++ b/runner/server/src/mill/server/MillDaemonServer.scala @@ -25,6 +25,7 @@ abstract class MillDaemonServer[State]( daemonDir: os.Path, acceptTimeout: FiniteDuration, locks: Locks, + outDir: os.Path, testLogEvenWhenServerIdWrong: Boolean = false ) extends Server[DaemonServerData, Int](Server.Args( daemonDir = daemonDir, @@ -35,7 +36,6 @@ abstract class MillDaemonServer[State]( )) { def outLock: mill.client.lock.Lock - def outFolder: os.Path private var stateCache: State = initialStateCache @@ -90,7 +90,7 @@ abstract class MillDaemonServer[State]( MillDaemonServer.withOutLock( noBuildLock = false, noWaitForBuildLock = false, - out = outFolder, + out = outDir, millActiveCommandMessage = "checking server mill version and java version", streams = new mill.api.daemon.SystemStreams( new PrintStream(mill.api.daemon.DummyOutputStream), diff --git a/runner/server/test/src/mill/server/ClientServerTests.scala b/runner/server/test/src/mill/server/ClientServerTests.scala index d5ce210e4399..b0d87e9989fd 100644 --- a/runner/server/test/src/mill/server/ClientServerTests.scala +++ b/runner/server/test/src/mill/server/ClientServerTests.scala @@ -26,16 +26,15 @@ object ClientServerTests extends TestSuite { testLogEvenWhenServerIdWrong: Boolean, commandSleepMillis: Int = 0 ) extends MillDaemonServer[Option[Int]]( - daemonDir, - 1000.millis, - locks, - testLogEvenWhenServerIdWrong + daemonDir = daemonDir, + acceptTimeout = 1000.millis, + locks = locks, + outDir = os.temp.dir(), + testLogEvenWhenServerIdWrong = testLogEvenWhenServerIdWrong ) { override def outLock = mill.client.lock.Lock.memory() - override def outFolder = os.temp.dir() - def initialStateCache = None override def serverLog0(s: String) = { From 936662c1e1ad303df34b65900933528b7efd2b8a Mon Sep 17 00:00:00 2001 From: Tobias Roeser Date: Sun, 26 Oct 2025 11:20:13 +0100 Subject: [PATCH 12/45] Replace expected literals in itests --- .../androidlib/java/1-hello-world/build.mill | 2 +- .../androidlib/java/2-app-bundle/build.mill | 2 +- .../androidlib/java/4-sum-lib-java/build.mill | 2 +- .../androidlib/java/6-native-libs/build.mill | 2 +- .../kotlin/1-hello-kotlin/build.mill | 2 +- .../androidlib/kotlin/2-compose/build.mill | 2 +- .../kotlin/4-sum-lib-kotlin/build.mill | 2 +- .../builtins/1-builtin-commands/build.mill | 24 +++++++++---------- example/depth/sandbox/1-task/build.mill | 8 +++---- example/depth/sandbox/2-test/build.mill | 6 ++--- .../extending/imports/1-mvn-deps/build.mill | 2 +- .../imports/2-mvn-deps-scala/build.mill | 2 +- .../metabuild/4-meta-build/build.mill | 2 +- .../python/4-python-libs-bundle/build.mill | 2 +- .../typescript/4-npm-deps-bundle/build.mill | 2 +- .../cross/10-static-blog/build.mill | 2 +- .../fundamentals/libraries/1-oslib/build.mill | 4 ++-- .../libraries/2-upickle/build.mill | 4 ++-- .../modules/8-diy-java-modules/build.mill | 4 ++-- .../tasks/1-task-graph/build.mill | 2 +- .../tasks/2-primary-tasks/build.mill | 2 +- example/javalib/basic/1-script/build.mill | 2 +- example/javalib/basic/10-realistic/build.mill | 2 +- example/javalib/publishing/5-jlink/build.mill | 2 +- .../javalib/publishing/6-jpackage/build.mill | 2 +- .../publishing/9-repackage-config/build.mill | 2 +- example/kotlinlib/basic/1-script/build.mill | 2 +- .../kotlinlib/basic/10-realistic/build.mill | 2 +- .../module/10-dependency-injection/build.mill | 4 ++-- example/pythonlib/basic/1-simple/build.mill | 2 +- .../module/1-common-config/build.mill | 2 +- .../pythonlib/module/6-pex-config/build.mill | 2 +- .../publishing/1-publish-module/build.mill | 4 ++-- example/scalalib/basic/1-script/build.mill | 2 +- .../scalalib/basic/10-realistic/build.mill | 2 +- example/scalalib/basic/3-simple/build.mill | 2 +- .../scalalib/basic/6-programmatic/build.mill | 2 +- .../config/1-common-config/build.mill | 2 +- example/scalalib/module/15-unidoc/build.mill | 2 +- .../module/2-common-config/build.mill | 2 +- example/scalalib/native/1-simple/build.mill | 2 +- .../spark/3-semi-realistic/build.mill | 2 +- .../scalalib/web/4-scalajs-module/build.mill | 2 +- example/scalalib/web/9-wasm/build.mill | 2 +- .../android-endless-tunnel/build.mill | 2 +- .../pages/12-direct-style-build-tool.adoc | 2 +- .../pages/7-graal-native-executables.adoc | 8 +++---- .../pages/9-mill-faster-assembly-jars.adoc | 4 ++-- 48 files changed, 73 insertions(+), 73 deletions(-) diff --git a/example/androidlib/java/1-hello-world/build.mill b/example/androidlib/java/1-hello-world/build.mill index 30fcd05ef4f8..b4745fbad870 100644 --- a/example/androidlib/java/1-hello-world/build.mill +++ b/example/androidlib/java/1-hello-world/build.mill @@ -64,7 +64,7 @@ object app extends AndroidAppModule { /** Usage > ./mill show app.androidApk -"...$OUT/app/androidApk.dest/app.apk" +"...$MILL_OUT/app/androidApk.dest/app.apk" */ diff --git a/example/androidlib/java/2-app-bundle/build.mill b/example/androidlib/java/2-app-bundle/build.mill index db8e043ec646..007a41475265 100644 --- a/example/androidlib/java/2-app-bundle/build.mill +++ b/example/androidlib/java/2-app-bundle/build.mill @@ -37,7 +37,7 @@ object bundle extends AndroidAppBundle { /** Usage > ./mill show bundle.androidBundle -"...$OUT/bundle/androidBundle.dest/signedBundle.aab" +"...$MILL_OUT/bundle/androidBundle.dest/signedBundle.aab" */ diff --git a/example/androidlib/java/4-sum-lib-java/build.mill b/example/androidlib/java/4-sum-lib-java/build.mill index 54400585e90c..1ab668d29318 100644 --- a/example/androidlib/java/4-sum-lib-java/build.mill +++ b/example/androidlib/java/4-sum-lib-java/build.mill @@ -129,7 +129,7 @@ Publish ... to /home/.../.m2/repository/... }, "payload": [ [ - "...$OUT/lib/androidAar.dest/library.aar", + "...$MILL_OUT/lib/androidAar.dest/library.aar", "lib-0.0.1.aar" ], ... diff --git a/example/androidlib/java/6-native-libs/build.mill b/example/androidlib/java/6-native-libs/build.mill index 2245540ec38b..1ddbfedf2271 100644 --- a/example/androidlib/java/6-native-libs/build.mill +++ b/example/androidlib/java/6-native-libs/build.mill @@ -66,7 +66,7 @@ object app extends AndroidNativeAppModule { // <1> /** Usage > ./mill show app.androidApk -"...$OUT/app/androidApk.dest/app.apk" +"...$MILL_OUT/app/androidApk.dest/app.apk" > ./mill show app.createAndroidVirtualDevice ...Name: java-test, DeviceId: medium_phone... diff --git a/example/androidlib/kotlin/1-hello-kotlin/build.mill b/example/androidlib/kotlin/1-hello-kotlin/build.mill index d27c4051ba90..28a31b530ed8 100644 --- a/example/androidlib/kotlin/1-hello-kotlin/build.mill +++ b/example/androidlib/kotlin/1-hello-kotlin/build.mill @@ -72,7 +72,7 @@ object app extends AndroidAppKotlinModule { /** Usage > ./mill show app.androidApk -"...$OUT/app/androidApk.dest/app.apk" +"...$MILL_OUT/app/androidApk.dest/app.apk" */ diff --git a/example/androidlib/kotlin/2-compose/build.mill b/example/androidlib/kotlin/2-compose/build.mill index c84423140516..4d3e9b9b8326 100644 --- a/example/androidlib/kotlin/2-compose/build.mill +++ b/example/androidlib/kotlin/2-compose/build.mill @@ -60,7 +60,7 @@ object app extends AndroidAppKotlinModule { /** Usage > ./mill show app.androidApk -"...$OUT/app/androidApk.dest/app.apk" +"...$MILL_OUT/app/androidApk.dest/app.apk" > ./mill show app.createAndroidVirtualDevice diff --git a/example/androidlib/kotlin/4-sum-lib-kotlin/build.mill b/example/androidlib/kotlin/4-sum-lib-kotlin/build.mill index 61271f0fe840..74e8f68f538e 100644 --- a/example/androidlib/kotlin/4-sum-lib-kotlin/build.mill +++ b/example/androidlib/kotlin/4-sum-lib-kotlin/build.mill @@ -131,7 +131,7 @@ Publish ... to /home/.../.m2/repository/... }, "payload": [ [ - "...$OUT/lib/androidAar.dest/library.aar", + "...$MILL_OUT/lib/androidAar.dest/library.aar", "lib-0.0.1.aar" ], ... diff --git a/example/cli/builtins/1-builtin-commands/build.mill b/example/cli/builtins/1-builtin-commands/build.mill index 02c12db347ea..805c75ab8cfa 100644 --- a/example/cli/builtins/1-builtin-commands/build.mill +++ b/example/cli/builtins/1-builtin-commands/build.mill @@ -146,8 +146,8 @@ Inputs: > ./mill show foo.compile { - "analysisFile": "...$OUT/foo/compile.dest/...", - "classes": "...$OUT/foo/compile.dest/classes" + "analysisFile": "...$MILL_OUT/foo/compile.dest/...", + "classes": "...$MILL_OUT/foo/compile.dest/classes" } */ @@ -287,11 +287,11 @@ foo.compileClasspath /** Usage > ./mill visualize foo._ [ - "...$OUT/visualize.dest/out.dot", - "...$OUT/visualize.dest/out.json", - "...$OUT/visualize.dest/out.png", - "...$OUT/visualize.dest/out.svg", - "...$OUT/visualize.dest/out.txt" + "...$MILL_OUT/visualize.dest/out.dot", + "...$MILL_OUT/visualize.dest/out.json", + "...$MILL_OUT/visualize.dest/out.png", + "...$MILL_OUT/visualize.dest/out.svg", + "...$MILL_OUT/visualize.dest/out.txt" ] */ // @@ -342,11 +342,11 @@ graph ["rankdir"="LR"] /** Usage > ./mill visualizePlan foo.run [ - "...$OUT/visualizePlan.dest/out.dot", - "...$OUT/visualizePlan.dest/out.json", - "...$OUT/visualizePlan.dest/out.png", - "...$OUT/visualizePlan.dest/out.svg", - "...$OUT/visualizePlan.dest/out.txt" + "...$MILL_OUT/visualizePlan.dest/out.dot", + "...$MILL_OUT/visualizePlan.dest/out.json", + "...$MILL_OUT/visualizePlan.dest/out.png", + "...$MILL_OUT/visualizePlan.dest/out.svg", + "...$MILL_OUT/visualizePlan.dest/out.txt" ] */ // diff --git a/example/depth/sandbox/1-task/build.mill b/example/depth/sandbox/1-task/build.mill index 2cfe2570239c..53c8b0002d68 100644 --- a/example/depth/sandbox/1-task/build.mill +++ b/example/depth/sandbox/1-task/build.mill @@ -14,7 +14,7 @@ object foo extends Module { /** Usage > ./mill foo.tDestTask -...$OUT/foo/tDestTask.dest +...$MILL_OUT/foo/tDestTask.dest */ // If you really need to reference paths outside of the `Task.dest`, you can do @@ -95,7 +95,7 @@ def osPwdTask = Task { println(os.pwd.toString) } /** Usage > ./mill osPwdTask -...$OUT/osPwdTask.dest +...$MILL_OUT/osPwdTask.dest */ // The redirection of `os.pwd` applies to `os.proc`, `os.call`, and `os.spawn` methods @@ -108,7 +108,7 @@ def osProcTask = Task { /** Usage > ./mill osProcTask -...$OUT/osProcTask.dest +...$MILL_OUT/osProcTask.dest */ // === Non-task `os.pwd` redirection @@ -122,5 +122,5 @@ def externalPwdTask = Task { println(externalPwd.toString) } /** Usage > ./mill externalPwdTask -...$OUT/mill-daemon/sandbox +...$MILL_OUT/mill-daemon/sandbox */ diff --git a/example/depth/sandbox/2-test/build.mill b/example/depth/sandbox/2-test/build.mill index 3ec7642ef6a5..730f11298044 100644 --- a/example/depth/sandbox/2-test/build.mill +++ b/example/depth/sandbox/2-test/build.mill @@ -47,8 +47,8 @@ object bar extends MyModule /** Usage > find . | grep generated.html -...$OUT/foo/test/testForked.dest/sandbox/generated.html -...$OUT/bar/test/testForked.dest/sandbox/generated.html +...$MILL_OUT/foo/test/testForked.dest/sandbox/generated.html +...$MILL_OUT/bar/test/testForked.dest/sandbox/generated.html > cat out/foo/test/testForked.dest/sandbox/generated.html

hello

@@ -81,7 +81,7 @@ object qux extends JavaModule { > find . | grep .html ... -...$OUT/qux/test/testForked.dest/sandbox/foo.html +...$MILL_OUT/qux/test/testForked.dest/sandbox/foo.html > cat out/qux/test/testForked.dest/sandbox/foo.html

foo

diff --git a/example/extending/imports/1-mvn-deps/build.mill b/example/extending/imports/1-mvn-deps/build.mill index b4d340962ac3..b47f2011ff0c 100644 --- a/example/extending/imports/1-mvn-deps/build.mill +++ b/example/extending/imports/1-mvn-deps/build.mill @@ -44,7 +44,7 @@ compiling 1 Java source... generated snippet.txt resource:

hello

world

> ./mill show foo.assembly -"...$OUT/foo/assembly.dest/out.jar" +"...$MILL_OUT/foo/assembly.dest/out.jar" > ./out/foo/assembly.dest/out.jar # mac/linux generated snippet.txt resource:

hello

world

diff --git a/example/extending/imports/2-mvn-deps-scala/build.mill b/example/extending/imports/2-mvn-deps-scala/build.mill index 08b530f5b1a1..96352e65d657 100644 --- a/example/extending/imports/2-mvn-deps-scala/build.mill +++ b/example/extending/imports/2-mvn-deps-scala/build.mill @@ -34,7 +34,7 @@ compiling 1 Scala source... generated snippet.txt resource:

hello

world

> ./mill show bar.assembly -"...$OUT/bar/assembly.dest/out.jar" +"...$MILL_OUT/bar/assembly.dest/out.jar" > ./out/bar/assembly.dest/out.jar # mac/linux generated snippet.txt resource:

hello

world

diff --git a/example/extending/metabuild/4-meta-build/build.mill b/example/extending/metabuild/4-meta-build/build.mill index 11763d109735..4beaa6e39c7e 100644 --- a/example/extending/metabuild/4-meta-build/build.mill +++ b/example/extending/metabuild/4-meta-build/build.mill @@ -48,7 +48,7 @@ Build-time HTML snippet:

hello

Run-time HTML snippet:

world

> ./mill show assembly -"...$OUT/assembly.dest/out.jar" +"...$MILL_OUT/assembly.dest/out.jar" > ./out/assembly.dest/out.jar # mac/linux Build-time HTML snippet:

hello

diff --git a/example/extending/python/4-python-libs-bundle/build.mill b/example/extending/python/4-python-libs-bundle/build.mill index 09ffa52355e1..cfb86a1cf5b1 100644 --- a/example/extending/python/4-python-libs-bundle/build.mill +++ b/example/extending/python/4-python-libs-bundle/build.mill @@ -109,7 +109,7 @@ object qux extends PythonModule { Numpy : Sum: 150 | Pandas: Mean: 30.0, Max: 50 > ./mill show qux.bundle -"...$OUT/qux/bundle.dest/bundle.pex" +"...$MILL_OUT/qux/bundle.dest/bundle.pex" > out/qux/bundle.dest/bundle.pex # running the PEX binary outside of Mill Numpy : Sum: 150 | Pandas: Mean: 30.0, Max: 50 diff --git a/example/extending/typescript/4-npm-deps-bundle/build.mill b/example/extending/typescript/4-npm-deps-bundle/build.mill index ef9aa107a775..d44808e68ffb 100644 --- a/example/extending/typescript/4-npm-deps-bundle/build.mill +++ b/example/extending/typescript/4-npm-deps-bundle/build.mill @@ -117,7 +117,7 @@ object qux extends TypeScriptModule { Hello James Bond Professor > ./mill show qux.bundle -"...$OUT/qux/bundle.dest/bundle.js" +"...$MILL_OUT/qux/bundle.dest/bundle.js" > node out/qux/bundle.dest/bundle.js James Bond prof Hello James Bond Professor diff --git a/example/fundamentals/cross/10-static-blog/build.mill b/example/fundamentals/cross/10-static-blog/build.mill index 34ea6514f151..77d53d2886cd 100644 --- a/example/fundamentals/cross/10-static-blog/build.mill +++ b/example/fundamentals/cross/10-static-blog/build.mill @@ -113,7 +113,7 @@ def dist = Task { /** Usage > ./mill show "post[1-My-First-Post.md].render" -"...$OUT/post/1-My-First-Post.md/render.dest/1-my-first-post.html" +"...$MILL_OUT/post/1-My-First-Post.md/render.dest/1-my-first-post.html" > cat out/post/1-My-First-Post.md/render.dest/1-my-first-post.html ... diff --git a/example/fundamentals/libraries/1-oslib/build.mill b/example/fundamentals/libraries/1-oslib/build.mill index 964f0cf11575..65ea587b53e8 100644 --- a/example/fundamentals/libraries/1-oslib/build.mill +++ b/example/fundamentals/libraries/1-oslib/build.mill @@ -35,9 +35,9 @@ def command = Task { /** Usage > ./mill command # mac/linux -...$OUT/task1.dest/file.txt +...$MILL_OUT/task1.dest/file.txt hello -...$OUT/task2.dest/file.txt +...$MILL_OUT/task2.dest/file.txt world */ diff --git a/example/fundamentals/libraries/2-upickle/build.mill b/example/fundamentals/libraries/2-upickle/build.mill index c11f024e438f..1a0dc0aa6a92 100644 --- a/example/fundamentals/libraries/2-upickle/build.mill +++ b/example/fundamentals/libraries/2-upickle/build.mill @@ -74,7 +74,7 @@ def taskPath = Task { /** Usage > ./mill show taskPath -"...$OUT/taskPath.dest/file.txt" +"...$MILL_OUT/taskPath.dest/file.txt" */ @@ -92,7 +92,7 @@ def taskPathRef = Task { /** Usage > ./mill show taskPathRef -"ref...$OUT/taskPathRef.dest/file.txt" +"ref...$MILL_OUT/taskPathRef.dest/file.txt" */ diff --git a/example/fundamentals/modules/8-diy-java-modules/build.mill b/example/fundamentals/modules/8-diy-java-modules/build.mill index 02b67b10a6eb..524bf846a5c4 100644 --- a/example/fundamentals/modules/8-diy-java-modules/build.mill +++ b/example/fundamentals/modules/8-diy-java-modules/build.mill @@ -150,7 +150,7 @@ object qux extends DiyJavaModule { } > ./mill show qux.assembly -"...$OUT/qux/assembly.dest/assembly.jar" +"...$MILL_OUT/qux/assembly.dest/assembly.jar" > java -jar out/qux/assembly.dest/assembly.jar Foo.value: 31337 @@ -158,7 +158,7 @@ Bar.value: 271828 Qux.value: 9000 > ./mill show foo.assembly -"...$OUT/foo/assembly.dest/assembly.jar" +"...$MILL_OUT/foo/assembly.dest/assembly.jar" > java -jar out/foo/assembly.dest/assembly.jar Foo.value: 31337 diff --git a/example/fundamentals/tasks/1-task-graph/build.mill b/example/fundamentals/tasks/1-task-graph/build.mill index 633c288f190b..1c855464b708 100644 --- a/example/fundamentals/tasks/1-task-graph/build.mill +++ b/example/fundamentals/tasks/1-task-graph/build.mill @@ -55,7 +55,7 @@ def run(args: String*) = Task.Command { /** Usage > ./mill show assembly -"...$OUT/assembly.dest/assembly.jar" +"...$MILL_OUT/assembly.dest/assembly.jar" > java -jar out/assembly.dest/assembly.jar i am cow Foo.value: 31337 diff --git a/example/fundamentals/tasks/2-primary-tasks/build.mill b/example/fundamentals/tasks/2-primary-tasks/build.mill index 251dc6df5423..115a64d1657f 100644 --- a/example/fundamentals/tasks/2-primary-tasks/build.mill +++ b/example/fundamentals/tasks/2-primary-tasks/build.mill @@ -167,7 +167,7 @@ Generating classfiles Generating jar > ./mill show jar -"...$OUT/jar.dest/foo.jar" +"...$MILL_OUT/jar.dest/foo.jar" */ diff --git a/example/javalib/basic/1-script/build.mill b/example/javalib/basic/1-script/build.mill index 7deb1bea0c70..9ca3528a3096 100644 --- a/example/javalib/basic/1-script/build.mill +++ b/example/javalib/basic/1-script/build.mill @@ -18,7 +18,7 @@ compiling 1 Java source to... /** Usage > ./mill show Foo.java:assembly # show the output of the assembly task -"...$OUT/Foo.java/assembly.dest/out.jar" +"...$MILL_OUT/Foo.java/assembly.dest/out.jar" > java -jar ./out/Foo.java/assembly.dest/out.jar --text hello

hello

diff --git a/example/javalib/basic/10-realistic/build.mill b/example/javalib/basic/10-realistic/build.mill index 6266b897e8b4..5a10ccda2820 100644 --- a/example/javalib/basic/10-realistic/build.mill +++ b/example/javalib/basic/10-realistic/build.mill @@ -101,7 +101,7 @@ Publishing Artifact(com.lihaoyi,qux,0.0.1) to ivy repo... ... > ./mill show foo.assembly # mac/linux -"...$OUT/foo/assembly.dest/out.jar" +"...$MILL_OUT/foo/assembly.dest/out.jar" > ./out/foo/assembly.dest/out.jar # mac/linux foo version 0.0.1 diff --git a/example/javalib/publishing/5-jlink/build.mill b/example/javalib/publishing/5-jlink/build.mill index 44679674b4c5..f44598072418 100644 --- a/example/javalib/publishing/5-jlink/build.mill +++ b/example/javalib/publishing/5-jlink/build.mill @@ -47,7 +47,7 @@ object foo extends JavaModule, JlinkModule { > ./mill foo.jlinkAppImage > ./mill show foo.jlinkAppImage -"...$OUT/foo/jlinkAppImage.dest/jlink-runtime" +"...$MILL_OUT/foo/jlinkAppImage.dest/jlink-runtime" > ./out/foo/jlinkAppImage.dest/jlink-runtime/bin/jlink ... foo.Bar main diff --git a/example/javalib/publishing/6-jpackage/build.mill b/example/javalib/publishing/6-jpackage/build.mill index a52a32b6f4cc..db1d7535fb3a 100644 --- a/example/javalib/publishing/6-jpackage/build.mill +++ b/example/javalib/publishing/6-jpackage/build.mill @@ -50,7 +50,7 @@ object foo extends JavaModule, JpackageModule { > ./mill foo.jpackageAppImage > ./mill show foo.jpackageAppImage -"...$OUT/foo/jpackageAppImage.dest/image" +"...$MILL_OUT/foo/jpackageAppImage.dest/image" */ // diff --git a/example/javalib/publishing/9-repackage-config/build.mill b/example/javalib/publishing/9-repackage-config/build.mill index 762db39362cb..68b5b61fab2a 100644 --- a/example/javalib/publishing/9-repackage-config/build.mill +++ b/example/javalib/publishing/9-repackage-config/build.mill @@ -45,7 +45,7 @@ Qux.value: 31337 ...Test run bar.BarTests finished: 0 failed, 0 ignored, 1 total, ...s > ./mill show foo.repackagedJar -"...$OUT/foo/repackagedJar.dest/out.jar" +"...$MILL_OUT/foo/repackagedJar.dest/out.jar" > ./out/foo/repackagedJar.dest/out.jar Foo.value:

hello

diff --git a/example/kotlinlib/basic/1-script/build.mill b/example/kotlinlib/basic/1-script/build.mill index ec8af6f0c7ca..b76ba7996a1b 100644 --- a/example/kotlinlib/basic/1-script/build.mill +++ b/example/kotlinlib/basic/1-script/build.mill @@ -18,7 +18,7 @@ Compiling 1 Kotlin sources to... /** Usage > ./mill show Foo.kt:assembly # show the output of the assembly task -"...$OUT/Foo.kt/assembly.dest/out.jar" +"...$MILL_OUT/Foo.kt/assembly.dest/out.jar" > java -jar ./out/Foo.kt/assembly.dest/out.jar --text hello

hello

diff --git a/example/kotlinlib/basic/10-realistic/build.mill b/example/kotlinlib/basic/10-realistic/build.mill index 626d39d04b90..92110ad9afb5 100644 --- a/example/kotlinlib/basic/10-realistic/build.mill +++ b/example/kotlinlib/basic/10-realistic/build.mill @@ -109,7 +109,7 @@ Publishing Artifact(com.lihaoyi,qux,0.0.1) to ivy repo... ... > ./mill show foo.assembly # mac/linux -"...$OUT/foo/assembly.dest/out.jar" +"...$MILL_OUT/foo/assembly.dest/out.jar" > ./out/foo/assembly.dest/out.jar # mac/linux foo version 0.0.1 diff --git a/example/kotlinlib/module/10-dependency-injection/build.mill b/example/kotlinlib/module/10-dependency-injection/build.mill index 855d0b98014f..febeef53bc85 100644 --- a/example/kotlinlib/module/10-dependency-injection/build.mill +++ b/example/kotlinlib/module/10-dependency-injection/build.mill @@ -47,8 +47,8 @@ object dagger extends KspModule { > ./mill show dagger.generatedSources [ - "ref:v0:...$OUT/dagger/generatedSourcesWithKsp2.dest/generated/java", - "ref:v0:...$OUT/dagger/generatedSourcesWithKsp2.dest/generated/kotlin" + "ref:v0:...$MILL_OUT/dagger/generatedSourcesWithKsp2.dest/generated/java", + "ref:v0:...$MILL_OUT/dagger/generatedSourcesWithKsp2.dest/generated/kotlin" ] > ls out/dagger/generatedSourcesWithKsp2.dest/generated/java/com/example/dagger/ diff --git a/example/pythonlib/basic/1-simple/build.mill b/example/pythonlib/basic/1-simple/build.mill index 20b6db8678f3..0982f38afd7a 100644 --- a/example/pythonlib/basic/1-simple/build.mill +++ b/example/pythonlib/basic/1-simple/build.mill @@ -87,7 +87,7 @@ OK ... > ./mill show foo.bundle # Creates Bundle for the python file -"...$OUT/foo/bundle.dest/bundle.pex" +"...$MILL_OUT/foo/bundle.dest/bundle.pex" > out/foo/bundle.dest/bundle.pex --text "Hello Mill" # running the PEX binary outside of Mill

Hello Mill

diff --git a/example/pythonlib/module/1-common-config/build.mill b/example/pythonlib/module/1-common-config/build.mill index 6fab0f7968fb..95bf44f45294 100644 --- a/example/pythonlib/module/1-common-config/build.mill +++ b/example/pythonlib/module/1-common-config/build.mill @@ -75,7 +75,7 @@ MY_CUSTOM_ENV: my-env-value ... > ./mill show foo.bundle -"...$OUT/foo/bundle.dest/bundle.pex" +"...$MILL_OUT/foo/bundle.dest/bundle.pex" > out/foo/bundle.dest/bundle.pex ... diff --git a/example/pythonlib/module/6-pex-config/build.mill b/example/pythonlib/module/6-pex-config/build.mill index b94ec98d8a46..f27112c56e7a 100644 --- a/example/pythonlib/module/6-pex-config/build.mill +++ b/example/pythonlib/module/6-pex-config/build.mill @@ -44,7 +44,7 @@ object foo extends PythonModule { /** Usage > ./mill show foo.bundle -"...$OUT/foo/bundle.dest/bundle.pex" +"...$MILL_OUT/foo/bundle.dest/bundle.pex" > out/foo/bundle.dest/bundle.pex ... diff --git a/example/pythonlib/publishing/1-publish-module/build.mill b/example/pythonlib/publishing/1-publish-module/build.mill index 1c8874a8cfd8..b6df9a67e92b 100644 --- a/example/pythonlib/publishing/1-publish-module/build.mill +++ b/example/pythonlib/publishing/1-publish-module/build.mill @@ -44,10 +44,10 @@ object `package` extends PythonModule, PublishModule { /** Usage > ./mill show sdist -"...$OUT/sdist.dest/dist/testpkg_mill-0.0.2.tar.gz" +"...$MILL_OUT/sdist.dest/dist/testpkg_mill-0.0.2.tar.gz" > ./mill show wheel -"...$OUT/wheel.dest/dist/testpkg_mill-0.0.2-py3-none-any.whl" +"...$MILL_OUT/wheel.dest/dist/testpkg_mill-0.0.2-py3-none-any.whl" */ // These files can then be `pip-installed` by other projects, or, if you're using Mill, you can diff --git a/example/scalalib/basic/1-script/build.mill b/example/scalalib/basic/1-script/build.mill index 6ec1278c8000..2bb637e95d35 100644 --- a/example/scalalib/basic/1-script/build.mill +++ b/example/scalalib/basic/1-script/build.mill @@ -66,7 +66,7 @@ Jvm Version: 11.0.28 /** Usage > ./mill show Foo.scala:assembly # show the output of the assembly task -"...$OUT/Foo.scala/assembly.dest/out.jar" +"...$MILL_OUT/Foo.scala/assembly.dest/out.jar" > java -jar ./out/Foo.scala/assembly.dest/out.jar --text hello

hello

diff --git a/example/scalalib/basic/10-realistic/build.mill b/example/scalalib/basic/10-realistic/build.mill index 31fbf6ca7d4c..2ec7435d5e2f 100644 --- a/example/scalalib/basic/10-realistic/build.mill +++ b/example/scalalib/basic/10-realistic/build.mill @@ -126,7 +126,7 @@ Publishing Artifact(com.lihaoyi,bar_3,0.0.1) to ivy repo... Publishing Artifact(com.lihaoyi,qux,0.0.1) to ivy repo... > ./mill show foo[2.13.16].assembly # mac/linux -"...$OUT/foo/2.13.16/assembly.dest/out.jar" +"...$MILL_OUT/foo/2.13.16/assembly.dest/out.jar" > ./out/foo/2.13.16/assembly.dest/out.jar # mac/linux foo version 0.0.1 diff --git a/example/scalalib/basic/3-simple/build.mill b/example/scalalib/basic/3-simple/build.mill index 96244f4b8fae..976dd2c6bab3 100644 --- a/example/scalalib/basic/3-simple/build.mill +++ b/example/scalalib/basic/3-simple/build.mill @@ -101,7 +101,7 @@ compiling 1 Scala source to... > ./mill assembly # bundle classfiles and libraries into a jar for deployment > ./mill show assembly # show the output of the assembly task -"...$OUT/assembly.dest/out.jar" +"...$MILL_OUT/assembly.dest/out.jar" > java -jar ./out/assembly.dest/out.jar --text hello

hello

diff --git a/example/scalalib/basic/6-programmatic/build.mill b/example/scalalib/basic/6-programmatic/build.mill index 6d956e8a371f..85824ede4b1c 100644 --- a/example/scalalib/basic/6-programmatic/build.mill +++ b/example/scalalib/basic/6-programmatic/build.mill @@ -83,7 +83,7 @@ foo.run > ./mill foo.assembly # bundle classfiles and libraries into a jar for deployment > ./mill show foo.assembly # show the output of the assembly task -"...$OUT/foo/assembly.dest/out.jar" +"...$MILL_OUT/foo/assembly.dest/out.jar" > java -jar ./out/foo/assembly.dest/out.jar --text hello

hello

diff --git a/example/scalalib/config/1-common-config/build.mill b/example/scalalib/config/1-common-config/build.mill index f02015273d87..40240b0f8319 100644 --- a/example/scalalib/config/1-common-config/build.mill +++ b/example/scalalib/config/1-common-config/build.mill @@ -14,7 +14,7 @@ my.custom.property: my-prop-value MY_CUSTOM_ENV: my-env-value > ./mill show assembly -"...$OUT/assembly.dest/out.jar" +"...$MILL_OUT/assembly.dest/out.jar" > ./out/assembly.dest/out.jar # mac/linux Foo2.value:

hello2

diff --git a/example/scalalib/module/15-unidoc/build.mill b/example/scalalib/module/15-unidoc/build.mill index 8bc8cd90e02f..38d594591792 100644 --- a/example/scalalib/module/15-unidoc/build.mill +++ b/example/scalalib/module/15-unidoc/build.mill @@ -34,7 +34,7 @@ object foo extends ScalaModule, UnidocModule { /** Usage > ./mill show foo.unidocLocal -"...$OUT/foo/unidocLocal.dest" +"...$MILL_OUT/foo/unidocLocal.dest" > cat out/foo/unidocLocal.dest/foo/Foo.html ... diff --git a/example/scalalib/module/2-common-config/build.mill b/example/scalalib/module/2-common-config/build.mill index 94be6f93cabd..30d516f56333 100644 --- a/example/scalalib/module/2-common-config/build.mill +++ b/example/scalalib/module/2-common-config/build.mill @@ -105,7 +105,7 @@ my.custom.property: my-prop-value MY_CUSTOM_ENV: my-env-value > ./mill show assembly -"...$OUT/assembly.dest/out.jar" +"...$MILL_OUT/assembly.dest/out.jar" > ./out/assembly.dest/out.jar # mac/linux Foo2.value:

hello2

diff --git a/example/scalalib/native/1-simple/build.mill b/example/scalalib/native/1-simple/build.mill index 101506fddd82..0566d3473c52 100644 --- a/example/scalalib/native/1-simple/build.mill +++ b/example/scalalib/native/1-simple/build.mill @@ -26,7 +26,7 @@ object `package` extends ScalaNativeModule {

hello

> ./mill show nativeLink # Build and link native binary -"...$OUT/nativeLink.dest/out" +"...$MILL_OUT/nativeLink.dest/out" > ./out/nativeLink.dest/out --text hello # Run the executable

hello

diff --git a/example/scalalib/spark/3-semi-realistic/build.mill b/example/scalalib/spark/3-semi-realistic/build.mill index 27a1f3aae0d2..66d4c8678555 100644 --- a/example/scalalib/spark/3-semi-realistic/build.mill +++ b/example/scalalib/spark/3-semi-realistic/build.mill @@ -46,7 +46,7 @@ Summary Statistics by Category: > chmod +x spark-submit.sh > ./mill show assembly # prepare for spark-submit -"...$OUT/assembly.dest/out.jar" +"...$MILL_OUT/assembly.dest/out.jar" > ./spark-submit.sh out/assembly.dest/out.jar foo.Foo resources/transactions.csv ... diff --git a/example/scalalib/web/4-scalajs-module/build.mill b/example/scalalib/web/4-scalajs-module/build.mill index f81181d58777..6d56f90ae0d4 100644 --- a/example/scalalib/web/4-scalajs-module/build.mill +++ b/example/scalalib/web/4-scalajs-module/build.mill @@ -41,7 +41,7 @@ stringifiedJsObject: ["hello","world","!"] { ... ..."jsFileName": "main.js", - "dest": "...$OUT/foo/fullLinkJS.dest" + "dest": "...$MILL_OUT/foo/fullLinkJS.dest" } > node out/foo/fullLinkJS.dest/main.js # mac/linux diff --git a/example/scalalib/web/9-wasm/build.mill b/example/scalalib/web/9-wasm/build.mill index e2a56573c20d..a8443d6e1643 100644 --- a/example/scalalib/web/9-wasm/build.mill +++ b/example/scalalib/web/9-wasm/build.mill @@ -27,7 +27,7 @@ object wasm extends ScalaJSModule { ... ..."jsFileName": "main.js", ... - "dest": "...$OUT/wasm/fastLinkJS.dest" + "dest": "...$MILL_OUT/wasm/fastLinkJS.dest" } > node --experimental-wasm-exnref out/wasm/fastLinkJS.dest/main.js # mac/linux diff --git a/example/thirdparty/android-endless-tunnel/build.mill b/example/thirdparty/android-endless-tunnel/build.mill index aa8b7c2dff7c..081b3aefb782 100644 --- a/example/thirdparty/android-endless-tunnel/build.mill +++ b/example/thirdparty/android-endless-tunnel/build.mill @@ -53,7 +53,7 @@ object `endless-tunnel` extends mill.api.Module { /** Usage > ./mill show endless-tunnel.app.androidApk -"...$OUT/endless-tunnel/app/androidApk.dest/app.apk" +"...$MILL_OUT/endless-tunnel/app/androidApk.dest/app.apk" > ./mill show endless-tunnel.app.createAndroidVirtualDevice ...Name: cpp-test, DeviceId: medium_phone... diff --git a/website/blog/modules/ROOT/pages/12-direct-style-build-tool.adoc b/website/blog/modules/ROOT/pages/12-direct-style-build-tool.adoc index c17e53d44f63..d16806d8ee01 100644 --- a/website/blog/modules/ROOT/pages/12-direct-style-build-tool.adoc +++ b/website/blog/modules/ROOT/pages/12-direct-style-build-tool.adoc @@ -74,7 +74,7 @@ Test foo.FooTest.testSimple finished, ... 0 failed, 0 ignored, 2 total, ... > ./mill show foo.assembly -"...$OUT/foo/assembly.dest/out.jar" +"...$MILL_OUT/foo/assembly.dest/out.jar" > ./out/foo/assembly.dest/out.jar --text hello

hello

diff --git a/website/blog/modules/ROOT/pages/7-graal-native-executables.adoc b/website/blog/modules/ROOT/pages/7-graal-native-executables.adoc index ef9926ef3912..1e952c1aae48 100644 --- a/website/blog/modules/ROOT/pages/7-graal-native-executables.adoc +++ b/website/blog/modules/ROOT/pages/7-graal-native-executables.adoc @@ -107,7 +107,7 @@ outside of the build tool: [source,console] ---- $ ./mill show foo.assembly -"...$OUT/foo/assembly.dest/out.jar" +"...$MILL_OUT/foo/assembly.dest/out.jar" $ out/foo/assembly.dest/out.jar --text "hello world"

hello world

@@ -159,7 +159,7 @@ Now, we can use build a native image using `foo.nativeImage`: [source,console] ---- $ ./mill show foo.nativeImage -"...$OUT/foo/nativeImage.dest/native-executable" +"...$MILL_OUT/foo/nativeImage.dest/native-executable" $ out/foo/nativeImage.dest/native-executable --text "hello world"

hello world

@@ -229,7 +229,7 @@ _Executable Assembly_ ---- $ time ./mill show foo.assembly [1-41] [info] compiling 1 Java source... -"...$OUT/foo/assembly.dest/out.jar" +"...$MILL_OUT/foo/assembly.dest/out.jar" ./mill show foo.assembly 0.12s user 0.06s system 21% cpu 0.818 total ---- @@ -243,7 +243,7 @@ $ time ./mill show foo.nativeImage [1-50] [2/8] Performing analysis... [****] (7.9s @ 0.77GB) ... [1-50] Finished generating 'native-executable' in 26.0s. -"...$OUT/foo/nativeImage.dest/native-executable" +"...$MILL_OUT/foo/nativeImage.dest/native-executable" ./mill show foo.nativeImage 0.70s user 1.11s system 7% cpu 24.762 total ---- diff --git a/website/blog/modules/ROOT/pages/9-mill-faster-assembly-jars.adoc b/website/blog/modules/ROOT/pages/9-mill-faster-assembly-jars.adoc index fe8944e559a9..69f00125d926 100644 --- a/website/blog/modules/ROOT/pages/9-mill-faster-assembly-jars.adoc +++ b/website/blog/modules/ROOT/pages/9-mill-faster-assembly-jars.adoc @@ -124,7 +124,7 @@ to build an assembly that we can run using `java -jar`: [source,console] ---- > ./mill show assembly -"...$OUT/assembly.dest/out.jar" +"...$MILL_OUT/assembly.dest/out.jar" Total time: 27s $ ls -lh out/assembly.dest/out.jar @@ -325,7 +325,7 @@ the code and re-build the assembly: > echo "class dummy" >> src/main/scala/foo/Foo.scala > ./mill show assembly -"...$OUT/assembly.dest/out.jar" +"...$MILL_OUT/assembly.dest/out.jar" Total time: 1s > sbt assembly From e4feeb5ae51854c23e6047ac38fdbe69d257d2a2 Mon Sep 17 00:00:00 2001 From: Tobias Roeser Date: Sun, 26 Oct 2025 15:12:31 +0100 Subject: [PATCH 13/45] Set outPath in UnitTester --- testkit/src/mill/testkit/UnitTester.scala | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/testkit/src/mill/testkit/UnitTester.scala b/testkit/src/mill/testkit/UnitTester.scala index 3b90d4771683..678b63045cf2 100644 --- a/testkit/src/mill/testkit/UnitTester.scala +++ b/testkit/src/mill/testkit/UnitTester.scala @@ -1,12 +1,20 @@ package mill.testkit import mill.Task -import mill.api.{BuildCtx, DummyInputStream, ExecResult, Result, SystemStreams, Val} +import mill.api.{ + BuildCtx, + DummyInputStream, + Evaluator, + ExecResult, + PathRef, + Result, + SelectMode, + SystemStreams, + Val +} import mill.api.ExecResult.OuterStack import mill.constants.OutFiles.millChromeProfile import mill.constants.OutFiles.millProfile -import mill.api.Evaluator -import mill.api.SelectMode import mill.internal.JsonArrayLogger import mill.resolve.Resolve @@ -229,7 +237,9 @@ class UnitTester( def scoped[T](tester: UnitTester => T): T = { try { BuildCtx.workspaceRoot0.withValue(module.moduleDir) { - tester(this) + PathRef.outPathOverride.withValue(Some(outPath)) { + tester(this) + } } } finally close() } From 963e723054d9c6d95517145720246db330715298 Mon Sep 17 00:00:00 2001 From: Tobias Roeser Date: Mon, 27 Oct 2025 10:28:21 +0100 Subject: [PATCH 14/45] Change design: root path mapping is now dynamic --- core/api/src/mill/api/PathRef.scala | 89 ++++++--- core/api/test/src/mill/api/PathRefTests.scala | 175 ++++++++---------- .../src/mill/constants/PathVars.java | 22 +++ core/exec/src/mill/exec/GroupExecution.scala | 2 +- .../src/mill/javalib/TestModuleUtil.scala | 3 +- .../javalib/testrunner/TestRunnerMain0.scala | 2 +- .../src/mill/daemon/MillBuildBootstrap.scala | 2 +- .../src/mill/daemon/MillDaemonMain.scala | 2 +- runner/daemon/src/mill/daemon/MillMain0.scala | 2 +- testkit/src/mill/testkit/UnitTester.scala | 2 +- 10 files changed, 171 insertions(+), 130 deletions(-) create mode 100644 core/constants/src/mill/constants/PathVars.java diff --git a/core/api/src/mill/api/PathRef.scala b/core/api/src/mill/api/PathRef.scala index 53b3a8b734c6..40bd4eee3717 100644 --- a/core/api/src/mill/api/PathRef.scala +++ b/core/api/src/mill/api/PathRef.scala @@ -2,6 +2,7 @@ package mill.api import mill.api.DummyOutputStream import mill.api.daemon.internal.PathRefApi +import mill.constants.PathVars import upickle.ReadWriter as RW import java.nio.file as jnio @@ -25,7 +26,13 @@ case class PathRef private[mill] ( ) extends PathRefApi { private[mill] def javaPath = path.toNIO - private[mill] val pathVal: String = PathRef.encodeKnownRootsInPath(path) + private[mill] val mappedPath: String = PathRef.encodeKnownRootsInPath(path) + + /** + * Apply the current contextual path mapping to this PathRef. + * Updates [[mappedPath]] but does not recalculate the sig`. + */ + def remap: PathRef = PathRef(path, quick, sig, revalidate) def recomputeSig(): Int = PathRef.apply(path, quick).sig def validate(): Boolean = recomputeSig() == sig @@ -41,7 +48,13 @@ case class PathRef private[mill] ( def withRevalidate(revalidate: PathRef.Revalidate): PathRef = copy(revalidate = revalidate) def withRevalidateOnce: PathRef = copy(revalidate = PathRef.Revalidate.Once) - private def toStringPrefix = { + /** + * Renders a toString represntation. + * @param map if `true`, the rendered path may contain placeholders for configured root paths. + * See [[PathRef.mappedRoots]]. + * If `false`, the path is local and absolute. + */ + private def renderToString(map: Boolean): String = { val quick = if (this.quick) "qref:" else "ref:" val valid = revalidate match { @@ -50,18 +63,19 @@ case class PathRef private[mill] ( case PathRef.Revalidate.Always => "vn:" } val sig = String.format("%08x", this.sig: Integer) - quick + valid + sig + ":" + val p = if (map) mappedPath else path.toString() + s"${quick}${valid}${sig}:${p}" } override def toString: String = { - toStringPrefix + path.toString() + renderToString(false) } // Instead of using `path` we need to use `pathVal`, to make the hashcode stable as cache key override def hashCode(): Int = { var h = MurmurHash3.productSeed h = MurmurHash3.mix(h, "PathRef".hashCode) - h = MurmurHash3.mix(h, pathVal.hashCode) + h = MurmurHash3.mix(h, mappedPath.hashCode) h = MurmurHash3.mix(h, quick.##) h = MurmurHash3.mix(h, sig.##) h = MurmurHash3.mix(h, revalidate.##) @@ -209,42 +223,59 @@ object PathRef { } } - private[mill] val outPathOverride: DynamicVariable[Option[os.Path]] = DynamicVariable(None) + private[api] type MappedRoots = Seq[(key: String, path: os.Path)] - private[api] type KnownRoots = Seq[(replacement: String, root: os.Path)] + object mappedRoots { + private[PathRef] val rootMapping: DynamicVariable[MappedRoots] = DynamicVariable(Seq()) - private[api] def knownRoots: KnownRoots = { - // order is important! - Seq( - ( - "$MILL_OUT", - outPathOverride.value.getOrElse( - throw RuntimeException("Can't substitute $MILL_OUT, output path is not configured.") - ) - ), - ("$WORKSPACE", BuildCtx.workspaceRoot), - // TODO: add coursier here - ("$HOME", os.home) - ) + def get: MappedRoots = rootMapping.value + + def toMap: Map[String, os.Path] = get.map(m => (m.key, m.path)).toMap + + def withMillDefaults[T]( + outPath: os.Path, + workspacePath: os.Path = BuildCtx.workspaceRoot, + homePath: os.Path = os.home + )(thunk: => T): T = withMapping( + Seq( + ("MILL_OUT", outPath), + ("$WORKSPACE", workspacePath), + // TODO: add coursier here + ("$HOME", homePath) + ) + )(thunk) + + def withMapping[T](mapping: MappedRoots)(thunk: => T): T = withMapping(_ => mapping)(thunk) + + def withMapping[T](mapping: MappedRoots => MappedRoots)(thunk: => T): T = { + val newMapping = mapping(rootMapping.value) + newMapping.foreach { case m => + require(!m.key.startsWith("$"), "Key must not start with a `$`.") + require(m.key != PathVars.ROOT, s"Invalid key, '${PathVars.ROOT}' is a reserved key name.") + } + rootMapping.withValue(newMapping)(thunk) + } } private[api] def encodeKnownRootsInPath(p: os.Path): String = { // TODO: Do we need to check for '$' and mask it ? - knownRoots.collectFirst { - case rep if p.startsWith(rep.root) => - s"${rep.replacement}${ - if (p != rep.root) { - s"/${p.subRelativeTo(rep.root).toString()}" + mappedRoots.get.collectFirst { + case rep if p.startsWith(rep.path) => + s"$$${rep.key}${ + if (p != rep.path) { + s"/${p.subRelativeTo(rep.path).toString()}" } else "" }" }.getOrElse(p.toString) } private[api] def decodeKnownRootsInPath(encoded: String): String = { + pprint.err.log(encoded) if (encoded.startsWith("$")) { - knownRoots.collectFirst { - case rep if encoded.startsWith(rep.replacement) => - s"${rep.root.toString}${encoded.substring(rep.replacement.length)}" + val offset = 1 // "$".length + mappedRoots.get.collectFirst { + case mapping if encoded.startsWith(mapping.key, offset) => + s"${mapping.path.toString}${encoded.substring(mapping.key.length + offset)}" }.getOrElse(encoded) } else { encoded @@ -257,7 +288,7 @@ object PathRef { implicit def jsonFormatter: RW[PathRef] = upickle.readwriter[String].bimap[PathRef]( p => { storeSerializedPaths(p) - p.toStringPrefix + p.pathVal + p.renderToString(true) }, { case s"$prefix:$valid0:$hex:$pathVal" if prefix == "ref" || prefix == "qref" => diff --git a/core/api/test/src/mill/api/PathRefTests.scala b/core/api/test/src/mill/api/PathRefTests.scala index 623263c0b350..4fc47074dfb1 100644 --- a/core/api/test/src/mill/api/PathRefTests.scala +++ b/core/api/test/src/mill/api/PathRefTests.scala @@ -10,16 +10,14 @@ object PathRefTests extends TestSuite { val tests: Tests = Tests { test("sig") { def check(quick: Boolean) = withTmpDir { tmpDir => - PathRef.outPathOverride.withValue(Some(tmpDir / "out")) { - val file = tmpDir / "foo.txt" - os.write.over(file, "hello") - val sig1 = PathRef(file, quick).sig - val sig1b = PathRef(file, quick).sig - assert(sig1 == sig1b) - os.write.over(file, "hello world") - val sig2 = PathRef(file, quick).sig - assert(sig1 != sig2) - } + val file = tmpDir / "foo.txt" + os.write.over(file, "hello") + val sig1 = PathRef(file, quick).sig + val sig1b = PathRef(file, quick).sig + assert(sig1 == sig1b) + os.write.over(file, "hello world") + val sig2 = PathRef(file, quick).sig + assert(sig1 != sig2) } test("qref") - check(quick = true) test("ref") - check(quick = false) @@ -27,15 +25,13 @@ object PathRefTests extends TestSuite { test("same-sig-other-file") { def check(quick: Boolean) = withTmpDir { tmpDir => - PathRef.outPathOverride.withValue(Some(tmpDir / "out")) { - val file = tmpDir / "foo.txt" - os.write.over(file, "hello") - val sig1 = PathRef(file, quick).sig - val file2 = tmpDir / "bar.txt" - os.copy(file, file2) - val sig1b = PathRef(file2, quick).sig - assert(sig1 == sig1b) - } + val file = tmpDir / "foo.txt" + os.write.over(file, "hello") + val sig1 = PathRef(file, quick).sig + val file2 = tmpDir / "bar.txt" + os.copy(file, file2) + val sig1b = PathRef(file2, quick).sig + assert(sig1 == sig1b) } // test("qref") - check(quick = true) test("ref") - check(quick = false) @@ -44,26 +40,24 @@ object PathRefTests extends TestSuite { test("perms") { def check(quick: Boolean) = if (isPosixFs()) withTmpDir { tmpDir => - PathRef.outPathOverride.withValue(Some(tmpDir / "out")) { - val file = tmpDir / "foo.txt" - val content = "hello" - os.write.over(file, content) - Files.setPosixFilePermissions( - file.wrapped, - PosixFilePermissions.fromString("rw-rw----") - ) - val rwSig = PathRef(file, quick).sig - val rwSigb = PathRef(file, quick).sig - assert(rwSig == rwSigb) - - Files.setPosixFilePermissions( - file.wrapped, - PosixFilePermissions.fromString("rwxrw----") - ) - val rwxSig = PathRef(file, quick).sig - - assert(rwSig != rwxSig) - } + val file = tmpDir / "foo.txt" + val content = "hello" + os.write.over(file, content) + Files.setPosixFilePermissions( + file.wrapped, + PosixFilePermissions.fromString("rw-rw----") + ) + val rwSig = PathRef(file, quick).sig + val rwSigb = PathRef(file, quick).sig + assert(rwSig == rwSigb) + + Files.setPosixFilePermissions( + file.wrapped, + PosixFilePermissions.fromString("rwxrw----") + ) + val rwxSig = PathRef(file, quick).sig + + assert(rwSig != rwxSig) } else "Test Skipped on non-POSIX host" @@ -73,22 +67,20 @@ object PathRefTests extends TestSuite { test("symlinks") { def check(quick: Boolean) = withTmpDir { tmpDir => - PathRef.outPathOverride.withValue(Some(tmpDir / "out")) { - // invalid symlink - os.symlink(tmpDir / "nolink", tmpDir / "nonexistant") + // invalid symlink + os.symlink(tmpDir / "nolink", tmpDir / "nonexistant") - // symlink to empty dir - os.symlink(tmpDir / "emptylink", tmpDir / "empty") - os.makeDir(tmpDir / "empty") + // symlink to empty dir + os.symlink(tmpDir / "emptylink", tmpDir / "empty") + os.makeDir(tmpDir / "empty") - // recursive symlinks - os.symlink(tmpDir / "rlink1", tmpDir / "rlink2") - os.symlink(tmpDir / "rlink2", tmpDir / "rlink1") + // recursive symlinks + os.symlink(tmpDir / "rlink1", tmpDir / "rlink2") + os.symlink(tmpDir / "rlink2", tmpDir / "rlink1") - val sig1 = PathRef(tmpDir, quick).sig - val sig2 = PathRef(tmpDir, quick).sig - assert(sig1 == sig2) - } + val sig1 = PathRef(tmpDir, quick).sig + val sig2 = PathRef(tmpDir, quick).sig + assert(sig1 == sig2) } test("qref") - check(quick = true) test("ref") - check(quick = false) @@ -96,25 +88,23 @@ object PathRefTests extends TestSuite { test("json") { def check(quick: Boolean) = withTmpDir { outDir => - PathRef.outPathOverride.withValue(Some(outDir)) { - withTmpDir { tmpDir => - val file = tmpDir / "foo.txt" - os.write(file, "hello") - val pr = PathRef(file, quick) - val prFile = - pr.path.toString().replace(outDir.toString(), "$MILL_OUT").replace("\\", "\\\\") - val json = upickle.write(pr) - if (quick) { - assert(json.startsWith(""""qref:v0:""")) - assert(json.endsWith(s""":${prFile}"""")) - } else { - val hash = if (Properties.isWin) "86df6a6a" else "4c7ef487" - val expected = s""""ref:v0:${hash}:${prFile}"""" - assert(json == expected) - } - val pr1 = upickle.read[PathRef](json) - assert(pr == pr1) + withTmpDir { tmpDir => + val file = tmpDir / "foo.txt" + os.write(file, "hello") + val pr = PathRef(file, quick) + val prFile = + pr.path.toString().replace(outDir.toString(), "$MILL_OUT").replace("\\", "\\\\") + val json = upickle.write(pr) + if (quick) { + assert(json.startsWith(""""qref:v0:""")) + assert(json.endsWith(s""":${prFile}"""")) + } else { + val hash = if (Properties.isWin) "86df6a6a" else "4c7ef487" + val expected = s""""ref:v0:${hash}:${prFile}"""" + assert(json == expected) } + val pr1 = upickle.read[PathRef](json) + assert(pr == pr1) } } @@ -125,31 +115,28 @@ object PathRefTests extends TestSuite { test("encode") { withTmpDir { tmpDir => val workspaceDir = tmpDir / "workspace" - BuildCtx.workspaceRoot0.withValue(workspaceDir) { - val outDir = workspaceDir / "out" - PathRef.outPathOverride.withValue(Some(outDir)) { - - def check(path: os.Path, contains: Seq[String], containsNot: Seq[String]) = { - val enc = PathRef.encodeKnownRootsInPath(path) - val dec = PathRef.decodeKnownRootsInPath(enc) - assert(path.toString == dec) - contains.foreach(s => enc.containsSlice(s)) - containsNot.foreach(s => !enc.containsSlice(s)) - - path -> enc - } - - val file1 = tmpDir / "file1" - val file2 = workspaceDir / "file2" - val file3 = outDir / "file3" - - Seq( - "mapping" -> PathRef.knownRoots, - check(file1, Seq("ref:v0:", file1.toString), Seq("$WORKSPACE", "$MILL_OUT")), - check(file2, Seq("ref:v0:", "$WORKSPACE/file2"), Seq("$MILL_OUT")), - check(file3, Seq("ref:v0:", "$MILL_OUT/file3"), Seq("$WORKSPACE")) - ) + val outDir = workspaceDir / "out" + PathRef.mappedRoots.withMillDefaults(outPath = outDir, workspacePath = workspaceDir) { + def check(path: os.Path, contains: Seq[String], containsNot: Seq[String]) = { + val enc = PathRef.encodeKnownRootsInPath(path) + val dec = PathRef.decodeKnownRootsInPath(enc) + assert(path.toString == dec) + contains.foreach(s => enc.containsSlice(s)) + containsNot.foreach(s => !enc.containsSlice(s)) + + path -> enc } + + val file1 = tmpDir / "file1" + val file2 = workspaceDir / "file2" + val file3 = outDir / "file3" + + Seq( + "mapping" -> PathRef.mappedRoots.get, + check(file1, Seq("ref:v0:", file1.toString), Seq("$WORKSPACE", "$MILL_OUT")), + check(file2, Seq("ref:v0:", "$WORKSPACE/file2"), Seq("$MILL_OUT")), + check(file3, Seq("ref:v0:", "$MILL_OUT/file3"), Seq("$WORKSPACE")) + ) } } } diff --git a/core/constants/src/mill/constants/PathVars.java b/core/constants/src/mill/constants/PathVars.java new file mode 100644 index 000000000000..574f5c61c5a1 --- /dev/null +++ b/core/constants/src/mill/constants/PathVars.java @@ -0,0 +1,22 @@ +package mill.constants; + +/** + * Central place containing all the path variables that Mill uses in PathRef or os.Path. + */ +public interface PathVars { + + /** + * Output directory where Mill workers' state and Mill tasks output should be + * written to + */ + String MILL_OUT = "MILL_OUT"; + + /** + * The Mill project workspace root directory. + */ + String WORKSPACE = "WORKSPACE"; + + String HOME = "HOME"; + + String ROOT = "ROOT"; +} diff --git a/core/exec/src/mill/exec/GroupExecution.scala b/core/exec/src/mill/exec/GroupExecution.scala index afac154a4781..c87f847f037e 100644 --- a/core/exec/src/mill/exec/GroupExecution.scala +++ b/core/exec/src/mill/exec/GroupExecution.scala @@ -83,7 +83,7 @@ trait GroupExecution { executionContext: mill.api.TaskCtx.Fork.Api, exclusive: Boolean, upstreamPathRefs: Seq[PathRef] - ): GroupExecution.Results = PathRef.outPathOverride.withValue(Some(outPath)) { + ): GroupExecution.Results = { val inputsHash = { val externalInputsHash = MurmurHash3.orderedHash( diff --git a/libs/javalib/src/mill/javalib/TestModuleUtil.scala b/libs/javalib/src/mill/javalib/TestModuleUtil.scala index 74051569e36c..b164bb2b0345 100644 --- a/libs/javalib/src/mill/javalib/TestModuleUtil.scala +++ b/libs/javalib/src/mill/javalib/TestModuleUtil.scala @@ -5,6 +5,7 @@ import mill.api.daemon.internal.TestReporter import mill.util.Jvm import mill.api.internal.Util import mill.Task +import mill.constants.PathVars import sbt.testing.Status import java.time.format.DateTimeFormatter @@ -148,7 +149,7 @@ final class TestModuleUtil( mainArgs = Seq( testRunnerClasspathArg, argsFile.toString, - PathRef.outPathOverride.value.get.toString + PathRef.mappedRoots.toMap(PathVars.MILL_OUT).toString ), cwd = if (testSandboxWorkingDir) sandbox else forkWorkingDir, cpPassingJarPath = Option.when(useArgsFile)( diff --git a/libs/javalib/testrunner/src/mill/javalib/testrunner/TestRunnerMain0.scala b/libs/javalib/testrunner/src/mill/javalib/testrunner/TestRunnerMain0.scala index 30e978fb05db..87200b648fd8 100644 --- a/libs/javalib/testrunner/src/mill/javalib/testrunner/TestRunnerMain0.scala +++ b/libs/javalib/testrunner/src/mill/javalib/testrunner/TestRunnerMain0.scala @@ -8,7 +8,7 @@ import mill.api.daemon.internal.{TestReporter, internal} try { val millOutPath = os.Path(args(2)) val testArgs = - PathRef.outPathOverride.withValue(Some(millOutPath)) { + PathRef.mappedRoots.withMillDefaults(millOutPath) { upickle.read[TestArgs](os.read(os.Path(args(1)))) } testArgs.sysProps.foreach { case (k, v) => System.setProperty(k, v) } diff --git a/runner/daemon/src/mill/daemon/MillBuildBootstrap.scala b/runner/daemon/src/mill/daemon/MillBuildBootstrap.scala index d71866df4456..cc8d4af17060 100644 --- a/runner/daemon/src/mill/daemon/MillBuildBootstrap.scala +++ b/runner/daemon/src/mill/daemon/MillBuildBootstrap.scala @@ -70,7 +70,7 @@ class MillBuildBootstrap( val runnerState = evaluateRec(0) for ((frame, depth) <- runnerState.frames.zipWithIndex) { - PathRef.outPathOverride.withValue(Some(output)) { + PathRef.mappedRoots.withMillDefaults(output) { os.write.over( recOut(output, depth) / millRunnerState, upickle.write(frame.loggedData, indent = 4), diff --git a/runner/daemon/src/mill/daemon/MillDaemonMain.scala b/runner/daemon/src/mill/daemon/MillDaemonMain.scala index 1919cd680b7e..594e97996aa3 100644 --- a/runner/daemon/src/mill/daemon/MillDaemonMain.scala +++ b/runner/daemon/src/mill/daemon/MillDaemonMain.scala @@ -40,7 +40,7 @@ object MillDaemonMain { val args = Args(getClass.getName, args0).fold(err => throw IllegalArgumentException(err), identity) - PathRef.outPathOverride.withValue(Some(args.outDir)) { + PathRef.mappedRoots.withMillDefaults(args.outDir) { // temporarily disabling FFM use by coursier, which has issues with the way // Mill manages class loaders, throwing things like // UnsatisfiedLinkError: Native Library C:\Windows\System32\ole32.dll already loaded in another classloader diff --git a/runner/daemon/src/mill/daemon/MillMain0.scala b/runner/daemon/src/mill/daemon/MillMain0.scala index 8e6c9ce62a77..2cc80cdfc537 100644 --- a/runner/daemon/src/mill/daemon/MillMain0.scala +++ b/runner/daemon/src/mill/daemon/MillMain0.scala @@ -110,7 +110,7 @@ object MillMain0 { daemonDir: os.Path, outLock: Lock, outDir: os.Path - ): (Boolean, RunnerState) = PathRef.outPathOverride.withValue(Some(outDir)) { + ): (Boolean, RunnerState) = PathRef.mappedRoots.withMillDefaults(outPath = outDir) { mill.api.daemon.internal.MillScalaParser.current.withValue(MillScalaParserImpl) { os.SubProcess.env.withValue(env) { val parserResult = MillCliConfig.parse(args) diff --git a/testkit/src/mill/testkit/UnitTester.scala b/testkit/src/mill/testkit/UnitTester.scala index 678b63045cf2..c4d5d8106d4d 100644 --- a/testkit/src/mill/testkit/UnitTester.scala +++ b/testkit/src/mill/testkit/UnitTester.scala @@ -237,7 +237,7 @@ class UnitTester( def scoped[T](tester: UnitTester => T): T = { try { BuildCtx.workspaceRoot0.withValue(module.moduleDir) { - PathRef.outPathOverride.withValue(Some(outPath)) { + PathRef.mappedRoots.withMillDefaults(outPath) { tester(this) } } From 2f816169ed4890cf3ca1ddf1eabcdfc76a9d8892 Mon Sep 17 00:00:00 2001 From: Tobias Roeser Date: Mon, 27 Oct 2025 10:57:51 +0100 Subject: [PATCH 15/45] Addd more checks --- core/api/src/mill/api/PathRef.scala | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/core/api/src/mill/api/PathRef.scala b/core/api/src/mill/api/PathRef.scala index 40bd4eee3717..cebcac4976cf 100644 --- a/core/api/src/mill/api/PathRef.scala +++ b/core/api/src/mill/api/PathRef.scala @@ -249,9 +249,21 @@ object PathRef { def withMapping[T](mapping: MappedRoots => MappedRoots)(thunk: => T): T = { val newMapping = mapping(rootMapping.value) + var seenKeys = Set[String]() + var seenPaths = Set[os.Path]() newMapping.foreach { case m => require(!m.key.startsWith("$"), "Key must not start with a `$`.") require(m.key != PathVars.ROOT, s"Invalid key, '${PathVars.ROOT}' is a reserved key name.") + require( + !seenKeys.contains(m.key), + s"Key must be unique, but '${m.key}' was given multiple times." + ) + require( + !seenPaths.contains(m.path), + s"Paths must be unique, but '${m.path}' was given multiple times." + ) + seenKeys += m.key + seenPaths += m.path } rootMapping.withValue(newMapping)(thunk) } From 7d158930c2d51ae3fc5f6626986534724b97144e Mon Sep 17 00:00:00 2001 From: Tobias Roeser Date: Mon, 27 Oct 2025 11:02:40 +0100 Subject: [PATCH 16/45] Fix defaults --- core/api/src/mill/api/PathRef.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/api/src/mill/api/PathRef.scala b/core/api/src/mill/api/PathRef.scala index cebcac4976cf..c8572577a6b0 100644 --- a/core/api/src/mill/api/PathRef.scala +++ b/core/api/src/mill/api/PathRef.scala @@ -239,9 +239,9 @@ object PathRef { )(thunk: => T): T = withMapping( Seq( ("MILL_OUT", outPath), - ("$WORKSPACE", workspacePath), + ("WORKSPACE", workspacePath), // TODO: add coursier here - ("$HOME", homePath) + ("HOME", homePath) ) )(thunk) From a77ed17ce80df3fc1b7d888aac43e9993c20a8fe Mon Sep 17 00:00:00 2001 From: Tobias Roeser Date: Mon, 27 Oct 2025 11:17:26 +0100 Subject: [PATCH 17/45] Cleanup --- core/api/src/mill/api/PathRef.scala | 3 +- .../javalib/HelloJavaMinimalCacheTests.scala | 41 ------------------- 2 files changed, 1 insertion(+), 43 deletions(-) delete mode 100644 libs/javalib/test/src/mill/javalib/HelloJavaMinimalCacheTests.scala diff --git a/core/api/src/mill/api/PathRef.scala b/core/api/src/mill/api/PathRef.scala index c8572577a6b0..e22d519f0777 100644 --- a/core/api/src/mill/api/PathRef.scala +++ b/core/api/src/mill/api/PathRef.scala @@ -71,7 +71,7 @@ case class PathRef private[mill] ( renderToString(false) } - // Instead of using `path` we need to use `pathVal`, to make the hashcode stable as cache key + // Instead of using `path` we need to use `mappedPath` to make the hashcode stable as cache key override def hashCode(): Int = { var h = MurmurHash3.productSeed h = MurmurHash3.mix(h, "PathRef".hashCode) @@ -282,7 +282,6 @@ object PathRef { } private[api] def decodeKnownRootsInPath(encoded: String): String = { - pprint.err.log(encoded) if (encoded.startsWith("$")) { val offset = 1 // "$".length mappedRoots.get.collectFirst { diff --git a/libs/javalib/test/src/mill/javalib/HelloJavaMinimalCacheTests.scala b/libs/javalib/test/src/mill/javalib/HelloJavaMinimalCacheTests.scala deleted file mode 100644 index 4183fae29609..000000000000 --- a/libs/javalib/test/src/mill/javalib/HelloJavaMinimalCacheTests.scala +++ /dev/null @@ -1,41 +0,0 @@ -package mill.javalib - -import mill.* -import mill.api.{Discover, Task} -import mill.testkit.{TestRootModule, UnitTester} -import utest.* -import utest.framework.TestPath - -/** - * Reproduce cache-miss when a cache value for a PathRef-result should be present. - * This is an issue with out pathref-mangling to replace known root variables - */ -object HelloJavaMinimalCacheTests extends TestSuite { - - object HelloJava extends TestRootModule { - object core extends JavaModule - protected lazy val millDiscover = Discover[this.type] - } - - val resourcePath = os.Path(sys.env("MILL_TEST_RESOURCE_DIR").trim()) / "hello-java" - - def testEval() = UnitTester(HelloJava, resourcePath, debugEnabled = true) - def tests: Tests = Tests { - test("javacOptions") { - println("Test: " + summon[TestPath].value.mkString(".")) - testEval().scoped { eval => - - val Right(result1) = eval.apply(HelloJava.core.compileResources): @unchecked - val Right(result2) = eval.apply(HelloJava.core.compileResources): @unchecked - val Right(result3) = eval.apply(HelloJava.core.compileResources): @unchecked - - assert( - result1.value == result2.value, - result1.evalCount != 0, - result2.evalCount == 0, - result3.evalCount == 0 - ) - } - } - } -} From 486e22316d64b3daadf4cbfef1ace69f3d6b7d2c Mon Sep 17 00:00:00 2001 From: Tobias Roeser Date: Mon, 27 Oct 2025 11:53:49 +0100 Subject: [PATCH 18/45] Always use current mapping when serializing to json --- core/api/src/mill/api/PathRef.scala | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/core/api/src/mill/api/PathRef.scala b/core/api/src/mill/api/PathRef.scala index e22d519f0777..c1034341addd 100644 --- a/core/api/src/mill/api/PathRef.scala +++ b/core/api/src/mill/api/PathRef.scala @@ -54,7 +54,7 @@ case class PathRef private[mill] ( * See [[PathRef.mappedRoots]]. * If `false`, the path is local and absolute. */ - private def renderToString(map: Boolean): String = { + private def toStringPrefix: String = { val quick = if (this.quick) "qref:" else "ref:" val valid = revalidate match { @@ -63,12 +63,11 @@ case class PathRef private[mill] ( case PathRef.Revalidate.Always => "vn:" } val sig = String.format("%08x", this.sig: Integer) - val p = if (map) mappedPath else path.toString() - s"${quick}${valid}${sig}:${p}" + s"${quick}${valid}${sig}:" } override def toString: String = { - renderToString(false) + toStringPrefix + path.toString() } // Instead of using `path` we need to use `mappedPath` to make the hashcode stable as cache key @@ -299,7 +298,7 @@ object PathRef { implicit def jsonFormatter: RW[PathRef] = upickle.readwriter[String].bimap[PathRef]( p => { storeSerializedPaths(p) - p.renderToString(true) + p.toStringPrefix + encodeKnownRootsInPath(p.path) }, { case s"$prefix:$valid0:$hex:$pathVal" if prefix == "ref" || prefix == "qref" => From ab58247429b03fbbb07305ab5d9d4e15dafa12b0 Mon Sep 17 00:00:00 2001 From: Tobias Roeser Date: Mon, 27 Oct 2025 11:54:40 +0100 Subject: [PATCH 19/45] Ensure, we don't map any root paths in RPC communication --- .../src/mill/javalib/zinc/ZincWorkerMain.scala | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/libs/javalib/worker/src/mill/javalib/zinc/ZincWorkerMain.scala b/libs/javalib/worker/src/mill/javalib/zinc/ZincWorkerMain.scala index 322e229a949d..e887ce060e6d 100644 --- a/libs/javalib/worker/src/mill/javalib/zinc/ZincWorkerMain.scala +++ b/libs/javalib/worker/src/mill/javalib/zinc/ZincWorkerMain.scala @@ -1,6 +1,6 @@ package mill.javalib.zinc -import mill.api.SystemStreamsUtils +import mill.api.{PathRef, SystemStreamsUtils} import mill.api.daemon.{DummyInputStream, SystemStreams} import mill.client.lock.Locks import mill.rpc.MillRpcWireTransport @@ -63,7 +63,16 @@ object ZincWorkerMain { Using.Manager { use => val stdin = use(BufferedReader(InputStreamReader(connectionData.clientToServer))) val stdout = use(PrintStream(connectionData.serverToClient)) - val transport = MillRpcWireTransport(serverName, stdin, stdout, writeSynchronizer) + class Transport() + extends MillRpcWireTransport(serverName, stdin, stdout, writeSynchronizer) { + override def writeSerialized[A: upickle.Writer](message: A, log: String => Unit): Unit = { + // RPC communication is local and uncached, so we don't want to use any root mapping + PathRef.mappedRoots.withMapping(Seq()) { + super.writeSerialized(message, log) + } + } + } + val transport = Transport() val server = ZincWorkerRpcServer(worker, serverName, transport, setIdle, serverLog) // Make sure stdout and stderr is sent to the client From e97a96cf0eb9471c06149da351e824a8c27ce97d Mon Sep 17 00:00:00 2001 From: Tobias Roeser Date: Mon, 27 Oct 2025 14:01:47 +0100 Subject: [PATCH 20/45] Don't use placeholders in test runner testargs files --- libs/javalib/src/mill/javalib/TestModule.scala | 5 ++++- libs/javalib/src/mill/javalib/TestModuleUtil.scala | 12 +++++------- .../testrunner/entrypoint/TestRunnerMain.java | 3 +-- .../mill/javalib/testrunner/TestRunnerMain0.scala | 7 +------ 4 files changed, 11 insertions(+), 16 deletions(-) diff --git a/libs/javalib/src/mill/javalib/TestModule.scala b/libs/javalib/src/mill/javalib/TestModule.scala index 3925b9bd78bd..13ed06c63a5a 100644 --- a/libs/javalib/src/mill/javalib/TestModule.scala +++ b/libs/javalib/src/mill/javalib/TestModule.scala @@ -195,7 +195,10 @@ trait TestModule ) val argsFile = Task.dest / "testargs" - os.write(argsFile, upickle.write(testArgs)) + PathRef.mappedRoots.withMapping(Seq()) { + // Don't use placeholders, so we only have local absolute paths + os.write(argsFile, upickle.write(testArgs)) + } val testRunnerClasspathArg = jvmWorker().scalalibClasspath() diff --git a/libs/javalib/src/mill/javalib/TestModuleUtil.scala b/libs/javalib/src/mill/javalib/TestModuleUtil.scala index b164bb2b0345..4cfde31fcd8e 100644 --- a/libs/javalib/src/mill/javalib/TestModuleUtil.scala +++ b/libs/javalib/src/mill/javalib/TestModuleUtil.scala @@ -5,7 +5,6 @@ import mill.api.daemon.internal.TestReporter import mill.util.Jvm import mill.api.internal.Util import mill.Task -import mill.constants.PathVars import sbt.testing.Status import java.time.format.DateTimeFormatter @@ -136,7 +135,10 @@ final class TestModuleUtil( val argsFile = baseFolder / "testargs" val sandbox = baseFolder / "sandbox" - os.write(argsFile, upickle.write(testArgs), createFolders = true) + PathRef.mappedRoots.withMapping(Seq()) { + // Don't use placeholders, so we only have local absolute paths + os.write(argsFile, upickle.write(testArgs), createFolders = true) + } os.makeDir.all(sandbox) @@ -146,11 +148,7 @@ final class TestModuleUtil( classPath = (runClasspath ++ testrunnerEntrypointClasspath).map(_.path), jvmArgs = jvmArgs, env = (if (propagateEnv) Task.env else Map()) ++ forkEnv, - mainArgs = Seq( - testRunnerClasspathArg, - argsFile.toString, - PathRef.mappedRoots.toMap(PathVars.MILL_OUT).toString - ), + mainArgs = Seq(testRunnerClasspathArg, argsFile.toString), cwd = if (testSandboxWorkingDir) sandbox else forkWorkingDir, cpPassingJarPath = Option.when(useArgsFile)( os.temp(prefix = "run-", suffix = ".jar", deleteOnExit = false) diff --git a/libs/javalib/testrunner/entrypoint/src/mill/javalib/testrunner/entrypoint/TestRunnerMain.java b/libs/javalib/testrunner/entrypoint/src/mill/javalib/testrunner/entrypoint/TestRunnerMain.java index 8857ca5e7101..18e8d21283ec 100644 --- a/libs/javalib/testrunner/entrypoint/src/mill/javalib/testrunner/entrypoint/TestRunnerMain.java +++ b/libs/javalib/testrunner/entrypoint/src/mill/javalib/testrunner/entrypoint/TestRunnerMain.java @@ -16,8 +16,7 @@ public class TestRunnerMain { /** * - * @param args arg1: classpath, arg2 testArgs-file, arg2 Mill out path - * @throws Exception + * @param args arg1: classpath, arg2 testArgs-file */ public static void main(String[] args) throws Exception { URL[] testRunnerClasspath = Stream.of(args[0].split(",")) diff --git a/libs/javalib/testrunner/src/mill/javalib/testrunner/TestRunnerMain0.scala b/libs/javalib/testrunner/src/mill/javalib/testrunner/TestRunnerMain0.scala index 87200b648fd8..8c1b4722e78c 100644 --- a/libs/javalib/testrunner/src/mill/javalib/testrunner/TestRunnerMain0.scala +++ b/libs/javalib/testrunner/src/mill/javalib/testrunner/TestRunnerMain0.scala @@ -1,16 +1,11 @@ package mill.javalib.testrunner -import mill.api.PathRef import mill.api.daemon.internal.{TestReporter, internal} @internal object TestRunnerMain0 { def main0(args: Array[String], classLoader: ClassLoader): Unit = { try { - val millOutPath = os.Path(args(2)) - val testArgs = - PathRef.mappedRoots.withMillDefaults(millOutPath) { - upickle.read[TestArgs](os.read(os.Path(args(1)))) - } + val testArgs = upickle.read[TestArgs](os.read(os.Path(args(1)))) testArgs.sysProps.foreach { case (k, v) => System.setProperty(k, v) } val result = testArgs.globSelectors match { From ce8680a4d6cfd58416d1b3ac4c942661def6105a Mon Sep 17 00:00:00 2001 From: Tobias Roeser Date: Mon, 27 Oct 2025 17:48:32 +0100 Subject: [PATCH 21/45] Made test condition on Java version --- .../src/mill/scalalib/HelloWorldTests.scala | 204 ++++++++++-------- 1 file changed, 116 insertions(+), 88 deletions(-) diff --git a/libs/scalalib/test/src/mill/scalalib/HelloWorldTests.scala b/libs/scalalib/test/src/mill/scalalib/HelloWorldTests.scala index edfa402c6639..eb2a7e5f2d4a 100644 --- a/libs/scalalib/test/src/mill/scalalib/HelloWorldTests.scala +++ b/libs/scalalib/test/src/mill/scalalib/HelloWorldTests.scala @@ -159,101 +159,121 @@ object HelloWorldTests extends TestSuite { test("compile") { test("fromScratch") - UnitTester(HelloWorld, sourceRoot = resourcePath).scoped { eval => - val Right(result) = eval.apply(HelloWorld.core.compile): @unchecked - - val classesPath = eval.outPath / "core/compile.dest/classes" - val analysisFile = result.value.analysisFile - val outputFiles = os.walk(result.value.classes.path) - val expectedClassfiles = compileClassfiles.map(classesPath / _) - assert( - result.value.classes.path == classesPath, - os.exists(analysisFile), - outputFiles.nonEmpty, - outputFiles.forall(expectedClassfiles.contains), - result.evalCount > 0 - ) + if (scala.util.Properties.isJavaAtLeast(21)) + "Skipping on Java 21+ due to too old Scala version" + else { + val Right(result) = eval.apply(HelloWorld.core.compile): @unchecked + + val classesPath = eval.outPath / "core/compile.dest/classes" + val analysisFile = result.value.analysisFile + val outputFiles = os.walk(result.value.classes.path) + val expectedClassfiles = compileClassfiles.map(classesPath / _) + assert( + result.value.classes.path == classesPath, + os.exists(analysisFile), + outputFiles.nonEmpty, + outputFiles.forall(expectedClassfiles.contains), + result.evalCount > 0 + ) - // don't recompile if nothing changed - val Right(result2) = eval.apply(HelloWorld.core.compile): @unchecked + // don't recompile if nothing changed + val Right(result2) = eval.apply(HelloWorld.core.compile): @unchecked - assert(result2.evalCount == 0) + assert(result2.evalCount == 0) - // Make sure we *do not* end up compiling the compiler bridge, since - // it's using a pre-compiled bridge value - assert(!os.exists( - eval.outPath / "mill/scalalib/JvmWorkerModule/internalWorker.dest" / s"zinc-${zincVersion}" - )) + // Make sure we *do not* end up compiling the compiler bridge, since + // it's using a pre-compiled bridge value + assert(!os.exists( + eval.outPath / "mill/scalalib/JvmWorkerModule/internalWorker.dest" / s"zinc-${zincVersion}" + )) + } } test("nonPreCompiledBridge") - UnitTester( HelloWorldNonPrecompiledBridge, sourceRoot = resourcePath ).scoped { eval => - val Right(result) = eval.apply(HelloWorldNonPrecompiledBridge.core.compile): @unchecked + if (scala.util.Properties.isJavaAtLeast(21)) + "Skipping on Java 21+ due to too old Scala version" + else { + val Right(result) = eval.apply(HelloWorldNonPrecompiledBridge.core.compile): @unchecked - val classesPath = eval.outPath / "core/compile.dest/classes" + val classesPath = eval.outPath / "core/compile.dest/classes" - val analysisFile = result.value.analysisFile - val outputFiles = os.walk(result.value.classes.path) - val expectedClassfiles = compileClassfiles.map(classesPath / _) - assert( - result.value.classes.path == classesPath, - os.exists(analysisFile), - outputFiles.nonEmpty, - outputFiles.forall(expectedClassfiles.contains), - result.evalCount > 0 - ) + val analysisFile = result.value.analysisFile + val outputFiles = os.walk(result.value.classes.path) + val expectedClassfiles = compileClassfiles.map(classesPath / _) + assert( + result.value.classes.path == classesPath, + os.exists(analysisFile), + outputFiles.nonEmpty, + outputFiles.forall(expectedClassfiles.contains), + result.evalCount > 0 + ) - // don't recompile if nothing changed - val Right(result2) = eval.apply(HelloWorldNonPrecompiledBridge.core.compile): @unchecked + // don't recompile if nothing changed + val Right(result2) = eval.apply(HelloWorldNonPrecompiledBridge.core.compile): @unchecked - assert(result2.evalCount == 0) + assert(result2.evalCount == 0) - // Make sure we *do* end up compiling the compiler bridge, since it's - // *not* using a pre-compiled bridge value - assert(os.exists( - eval.outPath / "mill.javalib.JvmWorkerModule/internalWorker.dest" / s"zinc-${zincVersion}" - )) + // Make sure we *do* end up compiling the compiler bridge, since it's + // *not* using a pre-compiled bridge value + assert(os.exists( + eval.outPath / "mill.javalib.JvmWorkerModule/internalWorker.dest" / s"zinc-${zincVersion}" + )) + } } test("recompileOnChange") - UnitTester(HelloWorld, sourceRoot = resourcePath).scoped { eval => - val Right(result) = eval.apply(HelloWorld.core.compile): @unchecked - assert(result.evalCount > 0) + if (scala.util.Properties.isJavaAtLeast(21)) + "Skipping on Java 21+ due to too old Scala version" + else { + val Right(result) = eval.apply(HelloWorld.core.compile): @unchecked + assert(result.evalCount > 0) - os.write.append(HelloWorld.moduleDir / "core/src/Main.scala", "\n") + os.write.append(HelloWorld.moduleDir / "core/src/Main.scala", "\n") - val Right(result2) = eval.apply(HelloWorld.core.compile): @unchecked - assert(result2.evalCount > 0, result2.evalCount < result.evalCount) + val Right(result2) = eval.apply(HelloWorld.core.compile): @unchecked + assert(result2.evalCount > 0, result2.evalCount < result.evalCount) + } } test("failOnError") - UnitTester(HelloWorld, sourceRoot = resourcePath).scoped { eval => - os.write.append(HelloWorld.moduleDir / "core/src/Main.scala", "val x: ") + if (scala.util.Properties.isJavaAtLeast(21)) + "Skipping on Java 21+ due to too old Scala version" + else { + os.write.append(HelloWorld.moduleDir / "core/src/Main.scala", "val x: ") - val Left(ExecResult.Failure("Compilation failed")) = - eval.apply(HelloWorld.core.compile): @unchecked + val Left(ExecResult.Failure("Compilation failed")) = + eval.apply(HelloWorld.core.compile): @unchecked - val paths = ExecutionPaths.resolve(eval.outPath, HelloWorld.core.compile) + val paths = ExecutionPaths.resolve(eval.outPath, HelloWorld.core.compile) - assert( - os.walk(paths.dest / "classes").isEmpty, - !os.exists(paths.meta) - ) - // Works when fixed - os.write.over( - HelloWorld.moduleDir / "core/src/Main.scala", - os.read(HelloWorld.moduleDir / "core/src/Main.scala").dropRight( - "val x: ".length + assert( + os.walk(paths.dest / "classes").isEmpty, + !os.exists(paths.meta) + ) + // Works when fixed + os.write.over( + HelloWorld.moduleDir / "core/src/Main.scala", + os.read(HelloWorld.moduleDir / "core/src/Main.scala").dropRight( + "val x: ".length + ) ) - ) - val Right(_) = eval.apply(HelloWorld.core.compile): @unchecked + val Right(_) = eval.apply(HelloWorld.core.compile): @unchecked + } } test("passScalacOptions") - UnitTester( HelloWorldFatalWarnings, sourceRoot = resourcePath ).scoped { eval => - // compilation fails because of "-Xfatal-warnings" flag - val Left(ExecResult.Failure("Compilation failed")) = - eval.apply(HelloWorldFatalWarnings.core.compile): @unchecked + if (scala.util.Properties.isJavaAtLeast(21)) + "Skipping on Java 21+ due to too old Scala version" + else { + // compilation fails because of "-Xfatal-warnings" flag + val Left(ExecResult.Failure("Compilation failed")) = + eval.apply(HelloWorldFatalWarnings.core.compile): @unchecked + } } } @@ -266,39 +286,47 @@ object HelloWorldTests extends TestSuite { test("jar") { test("nonEmpty") - UnitTester(HelloWorldWithMain, resourcePath).scoped { eval => - val Right(result) = eval.apply(HelloWorldWithMain.core.jar): @unchecked + if (scala.util.Properties.isJavaAtLeast(21)) + "Skipping on Java 21+ due to too old Scala version" + else { + val Right(result) = eval.apply(HelloWorldWithMain.core.jar): @unchecked - assert( - os.exists(result.value.path), - result.evalCount > 0 - ) + assert( + os.exists(result.value.path), + result.evalCount > 0 + ) - Using.resource(new JarFile(result.value.path.toIO)) { jarFile => - val entries = jarFile.entries().asScala.map(_.getName).toSeq.sorted + Using.resource(new JarFile(result.value.path.toIO)) { jarFile => + val entries = jarFile.entries().asScala.map(_.getName).toSeq.sorted - val otherFiles = Seq( - "META-INF/", - "META-INF/MANIFEST.MF", - "reference.conf" - ) - val expectedFiles = (compileClassfiles.map(_.toString()) ++ otherFiles).sorted + val otherFiles = Seq( + "META-INF/", + "META-INF/MANIFEST.MF", + "reference.conf" + ) + val expectedFiles = (compileClassfiles.map(_.toString()) ++ otherFiles).sorted - assert( - entries.nonEmpty, - entries == expectedFiles - ) + assert( + entries.nonEmpty, + entries == expectedFiles + ) - val mainClass = jarMainClass(jarFile) - assert(mainClass.contains("Main")) + val mainClass = jarMainClass(jarFile) + assert(mainClass.contains("Main")) + } } } test("logOutputToFile") - UnitTester(HelloWorld, resourcePath).scoped { eval => - val outPath = eval.outPath - eval.apply(HelloWorld.core.compile) - - val logFile = outPath / "core/compile.log" - assert(os.exists(logFile)) + if (scala.util.Properties.isJavaAtLeast(21)) + "Skipping on Java 21+ due to too old Scala version" + else { + val outPath = eval.outPath + eval.apply(HelloWorld.core.compile) + + val logFile = outPath / "core/compile.log" + assert(os.exists(logFile)) + } } } } From 774457e53ad02a6ae9b1cf95a603f8bdc8c07849 Mon Sep 17 00:00:00 2001 From: Tobias Roeser Date: Mon, 27 Oct 2025 18:01:33 +0100 Subject: [PATCH 22/45] Readd pathref config in GroupExecution --- core/exec/src/mill/exec/GroupExecution.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/exec/src/mill/exec/GroupExecution.scala b/core/exec/src/mill/exec/GroupExecution.scala index c87f847f037e..033032d1f793 100644 --- a/core/exec/src/mill/exec/GroupExecution.scala +++ b/core/exec/src/mill/exec/GroupExecution.scala @@ -83,7 +83,7 @@ trait GroupExecution { executionContext: mill.api.TaskCtx.Fork.Api, exclusive: Boolean, upstreamPathRefs: Seq[PathRef] - ): GroupExecution.Results = { + ): GroupExecution.Results = PathRef.mappedRoots.withMillDefaults(outPath) { val inputsHash = { val externalInputsHash = MurmurHash3.orderedHash( From 5b7783e5b881700a969f2c8fbf42eb2b4eb6804c Mon Sep 17 00:00:00 2001 From: Tobias Roeser Date: Mon, 27 Oct 2025 18:44:06 +0100 Subject: [PATCH 23/45] Fixed test expectation --- example/depth/sandbox/2-test/build.mill | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/example/depth/sandbox/2-test/build.mill b/example/depth/sandbox/2-test/build.mill index 730f11298044..3288e086f056 100644 --- a/example/depth/sandbox/2-test/build.mill +++ b/example/depth/sandbox/2-test/build.mill @@ -47,8 +47,8 @@ object bar extends MyModule /** Usage > find . | grep generated.html -...$MILL_OUT/foo/test/testForked.dest/sandbox/generated.html -...$MILL_OUT/bar/test/testForked.dest/sandbox/generated.html +./out/foo/test/testForked.dest/sandbox/generated.html +./out/bar/test/testForked.dest/sandbox/generated.html > cat out/foo/test/testForked.dest/sandbox/generated.html

hello

@@ -81,7 +81,7 @@ object qux extends JavaModule { > find . | grep .html ... -...$MILL_OUT/qux/test/testForked.dest/sandbox/foo.html +./out/qux/test/testForked.dest/sandbox/foo.html > cat out/qux/test/testForked.dest/sandbox/foo.html

foo

From 6a69ac5c1939136388532be543b2e441caea5847 Mon Sep 17 00:00:00 2001 From: Tobias Roeser Date: Mon, 27 Oct 2025 21:34:18 +0100 Subject: [PATCH 24/45] Revert more eager expected test output changes --- .../builtins/1-builtin-commands/build.mill | 20 +++++++++---------- example/depth/sandbox/1-task/build.mill | 8 ++++---- .../fundamentals/libraries/1-oslib/build.mill | 4 ++-- 3 files changed, 16 insertions(+), 16 deletions(-) diff --git a/example/cli/builtins/1-builtin-commands/build.mill b/example/cli/builtins/1-builtin-commands/build.mill index 805c75ab8cfa..e33ca895b722 100644 --- a/example/cli/builtins/1-builtin-commands/build.mill +++ b/example/cli/builtins/1-builtin-commands/build.mill @@ -287,11 +287,11 @@ foo.compileClasspath /** Usage > ./mill visualize foo._ [ - "...$MILL_OUT/visualize.dest/out.dot", - "...$MILL_OUT/visualize.dest/out.json", - "...$MILL_OUT/visualize.dest/out.png", - "...$MILL_OUT/visualize.dest/out.svg", - "...$MILL_OUT/visualize.dest/out.txt" + ".../out/visualize.dest/out.dot", + ".../out/visualize.dest/out.json", + ".../out/visualize.dest/out.png", + ".../out/visualize.dest/out.svg", + ".../out/visualize.dest/out.txt" ] */ // @@ -342,11 +342,11 @@ graph ["rankdir"="LR"] /** Usage > ./mill visualizePlan foo.run [ - "...$MILL_OUT/visualizePlan.dest/out.dot", - "...$MILL_OUT/visualizePlan.dest/out.json", - "...$MILL_OUT/visualizePlan.dest/out.png", - "...$MILL_OUT/visualizePlan.dest/out.svg", - "...$MILL_OUT/visualizePlan.dest/out.txt" + ".../out/visualizePlan.dest/out.dot", + ".../out/visualizePlan.dest/out.json", + ".../out/visualizePlan.dest/out.png", + ".../out/visualizePlan.dest/out.svg", + ".../out/visualizePlan.dest/out.txt" ] */ // diff --git a/example/depth/sandbox/1-task/build.mill b/example/depth/sandbox/1-task/build.mill index 53c8b0002d68..7e6bbd50c3ac 100644 --- a/example/depth/sandbox/1-task/build.mill +++ b/example/depth/sandbox/1-task/build.mill @@ -14,7 +14,7 @@ object foo extends Module { /** Usage > ./mill foo.tDestTask -...$MILL_OUT/foo/tDestTask.dest +.../out/foo/tDestTask.dest */ // If you really need to reference paths outside of the `Task.dest`, you can do @@ -95,7 +95,7 @@ def osPwdTask = Task { println(os.pwd.toString) } /** Usage > ./mill osPwdTask -...$MILL_OUT/osPwdTask.dest +.../out/osPwdTask.dest */ // The redirection of `os.pwd` applies to `os.proc`, `os.call`, and `os.spawn` methods @@ -108,7 +108,7 @@ def osProcTask = Task { /** Usage > ./mill osProcTask -...$MILL_OUT/osProcTask.dest +.../out/osProcTask.dest */ // === Non-task `os.pwd` redirection @@ -122,5 +122,5 @@ def externalPwdTask = Task { println(externalPwd.toString) } /** Usage > ./mill externalPwdTask -...$MILL_OUT/mill-daemon/sandbox +.../out/mill-daemon/sandbox */ diff --git a/example/fundamentals/libraries/1-oslib/build.mill b/example/fundamentals/libraries/1-oslib/build.mill index 65ea587b53e8..98cbbaecb037 100644 --- a/example/fundamentals/libraries/1-oslib/build.mill +++ b/example/fundamentals/libraries/1-oslib/build.mill @@ -35,9 +35,9 @@ def command = Task { /** Usage > ./mill command # mac/linux -...$MILL_OUT/task1.dest/file.txt +.../out/task1.dest/file.txt hello -...$MILL_OUT/task2.dest/file.txt +.../out/task2.dest/file.txt world */ From 5de36b7be259f3591a8c3ea348c95a3ac906a29d Mon Sep 17 00:00:00 2001 From: Tobias Roeser Date: Mon, 27 Oct 2025 22:07:04 +0100 Subject: [PATCH 25/45] Ajust reading of json files --- .../src/BuildClasspathContentsTests.scala | 106 +++++++++--------- 1 file changed, 55 insertions(+), 51 deletions(-) diff --git a/integration/ide/build-classpath-contents/src/BuildClasspathContentsTests.scala b/integration/ide/build-classpath-contents/src/BuildClasspathContentsTests.scala index fad8eaa2ccec..a562e654689d 100644 --- a/integration/ide/build-classpath-contents/src/BuildClasspathContentsTests.scala +++ b/integration/ide/build-classpath-contents/src/BuildClasspathContentsTests.scala @@ -1,4 +1,4 @@ -import mill.api.BuildCtx +import mill.api.{BuildCtx, PathRef} import mill.testkit.UtestIntegrationTestSuite import utest.* @@ -6,56 +6,60 @@ object BuildClasspathContentsTests extends UtestIntegrationTestSuite { val tests: Tests = Tests { test("test") - integrationTest { tester => - val result1 = - tester.eval(("--meta-level", "1", "show", "compileClasspath"), stderr = os.Inherit) - val deserialized = upickle.read[Seq[mill.api.PathRef]](result1.out) - val millPublishedJars = deserialized - .map(_.path.last) - .filter(_.startsWith("mill-")) - .sorted - val millLocalClasspath = deserialized - .map(_.path) - .filter(_.startsWith(BuildCtx.workspaceRoot)) - .map(_.subRelativeTo(BuildCtx.workspaceRoot)) - .filter(!_.startsWith("out/integration")) - .filter(!_.startsWith("out/dist/localRepo.dest")) - .map(_.toString) - .sorted - if (sys.env("MILL_INTEGRATION_IS_PACKAGED_LAUNCHER") == "true") { - assertGoldenLiteral( - millPublishedJars, - List( - "mill-core-api-daemon_3-SNAPSHOT.jar", - "mill-core-api_3-SNAPSHOT.jar", - "mill-core-constants-SNAPSHOT.jar", - "mill-libs-androidlib-databinding_3-SNAPSHOT.jar", - "mill-libs-androidlib_3-SNAPSHOT.jar", - "mill-libs-daemon-client-SNAPSHOT.jar", - "mill-libs-daemon-server_3-SNAPSHOT.jar", - "mill-libs-javalib-api_3-SNAPSHOT.jar", - "mill-libs-javalib-testrunner-entrypoint-SNAPSHOT.jar", - "mill-libs-javalib-testrunner_3-SNAPSHOT.jar", - "mill-libs-javalib_3-SNAPSHOT.jar", - "mill-libs-javascriptlib_3-SNAPSHOT.jar", - "mill-libs-kotlinlib-api_3-SNAPSHOT.jar", - "mill-libs-kotlinlib-ksp2-api_3-SNAPSHOT.jar", - "mill-libs-kotlinlib_3-SNAPSHOT.jar", - "mill-libs-pythonlib_3-SNAPSHOT.jar", - "mill-libs-rpc_3-SNAPSHOT.jar", - "mill-libs-scalajslib-api_3-SNAPSHOT.jar", - "mill-libs-scalajslib_3-SNAPSHOT.jar", - "mill-libs-scalalib_3-SNAPSHOT.jar", - "mill-libs-scalanativelib-api_3-SNAPSHOT.jar", - "mill-libs-scalanativelib_3-SNAPSHOT.jar", - "mill-libs-script_3-SNAPSHOT.jar", - "mill-libs-util_3-SNAPSHOT.jar", - "mill-libs_3-SNAPSHOT.jar", - "mill-moduledefs_3-0.11.10.jar" - ) - ) - assert(millLocalClasspath == Nil) - } else { - sys.error("This test must be run in `packaged` mode, not `local`") + PathRef.mappedRoots.withMapping(Seq( + "HOME" -> os.home, + "WORKSPACE" -> tester.workspacePath + )) { + val result1 = + tester.eval(("--meta-level", "1", "show", "compileClasspath"), stderr = os.Inherit) + val deserialized = upickle.read[Seq[mill.api.PathRef]](result1.out) + val millPublishedJars = deserialized + .map(_.path.last) + .filter(_.startsWith("mill-")) + .sorted + val millLocalClasspath = deserialized + .map(_.path) + .filter(_.startsWith(BuildCtx.workspaceRoot)) + .map(_.subRelativeTo(BuildCtx.workspaceRoot)) + .filter(!_.startsWith("out/integration")) + .filter(!_.startsWith("out/dist/localRepo.dest")) + .map(_.toString) + .sorted + if (sys.env("MILL_INTEGRATION_IS_PACKAGED_LAUNCHER") == "true") { + assertGoldenLiteral( + millPublishedJars, + List( + "mill-core-api-daemon_3-SNAPSHOT.jar", + "mill-core-api_3-SNAPSHOT.jar", + "mill-core-constants-SNAPSHOT.jar", + "mill-libs-androidlib-databinding_3-SNAPSHOT.jar", + "mill-libs-androidlib_3-SNAPSHOT.jar", + "mill-libs-daemon-client-SNAPSHOT.jar", + "mill-libs-daemon-server_3-SNAPSHOT.jar", + "mill-libs-javalib-api_3-SNAPSHOT.jar", + "mill-libs-javalib-testrunner-entrypoint-SNAPSHOT.jar", + "mill-libs-javalib-testrunner_3-SNAPSHOT.jar", + "mill-libs-javalib_3-SNAPSHOT.jar", + "mill-libs-javascriptlib_3-SNAPSHOT.jar", + "mill-libs-kotlinlib-api_3-SNAPSHOT.jar", + "mill-libs-kotlinlib-ksp2-api_3-SNAPSHOT.jar", + "mill-libs-kotlinlib_3-SNAPSHOT.jar", + "mill-libs-pythonlib_3-SNAPSHOT.jar", + "mill-libs-rpc_3-SNAPSHOT.jar", + "mill-libs-scalajslib-api_3-SNAPSHOT.jar", + "mill-libs-scalajslib_3-SNAPSHOT.jar", + "mill-libs-scalalib_3-SNAPSHOT.jar", + "mill-libs-scalanativelib-api_3-SNAPSHOT.jar", + "mill-libs-scalanativelib_3-SNAPSHOT.jar", + "mill-libs-script_3-SNAPSHOT.jar", + "mill-libs-util_3-SNAPSHOT.jar", + "mill-libs_3-SNAPSHOT.jar", + "mill-moduledefs_3-0.11.10.jar" + ) + assert(millLocalClasspath == Nil) + } else { + sys.error("This test must be run in `packaged` mode, not `local`") + } } } } From 8c4856e6565781e7a8b79bd207eb161ebd70be49 Mon Sep 17 00:00:00 2001 From: Tobias Roeser Date: Mon, 27 Oct 2025 22:16:36 +0100 Subject: [PATCH 26/45] Fix tests --- example/kotlinlib/linting/4-kover/build.mill | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/example/kotlinlib/linting/4-kover/build.mill b/example/kotlinlib/linting/4-kover/build.mill index 37a393ff13cd..493e5786ded1 100644 --- a/example/kotlinlib/linting/4-kover/build.mill +++ b/example/kotlinlib/linting/4-kover/build.mill @@ -57,7 +57,7 @@ kover.xmlReport > ./mill show kover.htmlReport ... -...out/kover/htmlReport.dest/kover-report... +...$MILL_OUT/kover/htmlReport.dest/kover-report... > cat out/kover/htmlReport.dest/kover-report/index.html ... @@ -65,7 +65,7 @@ kover.xmlReport > ./mill show mill.kotlinlib.kover/htmlReportAll # collect reports from all modules ... -...out/mill.kotlinlib.kover.Kover/htmlReportAll.dest/kover-report... +...$MILL_OUT/mill.kotlinlib.kover.Kover/htmlReportAll.dest/kover-report... > cat out/mill.kotlinlib.kover.Kover/htmlReportAll.dest/kover-report/index.html ... From 660a95ce9b23be152e31e441ec8e0f2b5419aaf4 Mon Sep 17 00:00:00 2001 From: Tobias Roeser Date: Mon, 27 Oct 2025 23:07:22 +0100 Subject: [PATCH 27/45] Fix test --- .../multi-level-editing/src/MultiLevelBuildTests.scala | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/integration/invalidation/multi-level-editing/src/MultiLevelBuildTests.scala b/integration/invalidation/multi-level-editing/src/MultiLevelBuildTests.scala index 8fabf73fab97..c4dece2783d9 100644 --- a/integration/invalidation/multi-level-editing/src/MultiLevelBuildTests.scala +++ b/integration/invalidation/multi-level-editing/src/MultiLevelBuildTests.scala @@ -1,5 +1,6 @@ package mill.integration +import mill.api.PathRef import mill.testkit.{IntegrationTester, UtestIntegrationTestSuite} import mill.constants.OutFiles.* import mill.daemon.RunnerState @@ -48,7 +49,13 @@ trait MultiLevelBuildTests extends UtestIntegrationTestSuite { yield { val path = tester.workspacePath / "out" / Seq.fill(depth)(millBuild) / millRunnerState - if (os.exists(path)) upickle.read[RunnerState.Frame.Logged](os.read(path)) -> path + if (os.exists(path)) + PathRef.mappedRoots.withMillDefaults( + outPath = tester.workspacePath / "out", + workspacePath = tester.workspacePath + ) { + upickle.read[RunnerState.Frame.Logged](os.read(path)) -> path + } else RunnerState.Frame.Logged(Map(), Seq(), Seq(), None, Seq(), 0) -> path } } From b20be4697ae2aa2d43d9095d37bb961f35762130 Mon Sep 17 00:00:00 2001 From: Tobias Roeser Date: Tue, 28 Oct 2025 10:37:53 +0100 Subject: [PATCH 28/45] cleanup --- core/api/src/mill/api/PathRef.scala | 6 ------ 1 file changed, 6 deletions(-) diff --git a/core/api/src/mill/api/PathRef.scala b/core/api/src/mill/api/PathRef.scala index c1034341addd..f1282766319a 100644 --- a/core/api/src/mill/api/PathRef.scala +++ b/core/api/src/mill/api/PathRef.scala @@ -48,12 +48,6 @@ case class PathRef private[mill] ( def withRevalidate(revalidate: PathRef.Revalidate): PathRef = copy(revalidate = revalidate) def withRevalidateOnce: PathRef = copy(revalidate = PathRef.Revalidate.Once) - /** - * Renders a toString represntation. - * @param map if `true`, the rendered path may contain placeholders for configured root paths. - * See [[PathRef.mappedRoots]]. - * If `false`, the path is local and absolute. - */ private def toStringPrefix: String = { val quick = if (this.quick) "qref:" else "ref:" From f2139e9164de6597497f23d09ccb3656b8fb24fc Mon Sep 17 00:00:00 2001 From: Tobias Roeser Date: Tue, 28 Oct 2025 10:38:57 +0100 Subject: [PATCH 29/45] cleanup --- core/api/src/mill/api/MillTaskHash.scala | 5 ----- 1 file changed, 5 deletions(-) delete mode 100644 core/api/src/mill/api/MillTaskHash.scala diff --git a/core/api/src/mill/api/MillTaskHash.scala b/core/api/src/mill/api/MillTaskHash.scala deleted file mode 100644 index 32f59f21c607..000000000000 --- a/core/api/src/mill/api/MillTaskHash.scala +++ /dev/null @@ -1,5 +0,0 @@ -package mill.api - -trait MillTaskHash { - def millCacheHash: Int -} From ed1a763e66d9c436e52fab4ec66aa15099bd8610 Mon Sep 17 00:00:00 2001 From: Tobias Roeser Date: Tue, 28 Oct 2025 11:14:35 +0100 Subject: [PATCH 30/45] Moved new API to `MappedRoots` object --- core/api/src/mill/api/JsonFormatters.scala | 4 +- core/api/src/mill/api/MappedRoots.scala | 78 ++++++++++++++++ core/api/src/mill/api/PathRef.scala | 91 +++---------------- .../test/src/mill/api/MappedRootsTests.scala | 48 ++++++++++ core/api/test/src/mill/api/PathRefTests.scala | 31 +------ core/exec/src/mill/exec/GroupExecution.scala | 2 +- .../src/BuildClasspathContentsTests.scala | 4 +- .../src/MultiLevelBuildTests.scala | 4 +- .../javalib/src/mill/javalib/TestModule.scala | 8 +- .../src/mill/javalib/TestModuleUtil.scala | 4 +- .../mill/javalib/zinc/ZincWorkerMain.scala | 4 +- .../src/mill/daemon/MillBuildBootstrap.scala | 5 +- .../src/mill/daemon/MillDaemonMain.scala | 4 +- runner/daemon/src/mill/daemon/MillMain0.scala | 4 +- testkit/src/mill/testkit/UnitTester.scala | 4 +- 15 files changed, 161 insertions(+), 134 deletions(-) create mode 100644 core/api/src/mill/api/MappedRoots.scala create mode 100644 core/api/test/src/mill/api/MappedRootsTests.scala diff --git a/core/api/src/mill/api/JsonFormatters.scala b/core/api/src/mill/api/JsonFormatters.scala index d19e18880b4e..3caa447db858 100644 --- a/core/api/src/mill/api/JsonFormatters.scala +++ b/core/api/src/mill/api/JsonFormatters.scala @@ -23,8 +23,8 @@ trait JsonFormatters { implicit val pathReadWrite: RW[os.Path] = upickle.readwriter[String] .bimap[os.Path]( - p => PathRef.encodeKnownRootsInPath(p), - s => os.Path(PathRef.decodeKnownRootsInPath(s)) + p => MappedRoots.encodeKnownRootsInPath(p), + s => os.Path(MappedRoots.decodeKnownRootsInPath(s)) ) implicit val relPathRW: RW[os.RelPath] = upickle.readwriter[String] diff --git a/core/api/src/mill/api/MappedRoots.scala b/core/api/src/mill/api/MappedRoots.scala new file mode 100644 index 000000000000..7963edfd7caf --- /dev/null +++ b/core/api/src/mill/api/MappedRoots.scala @@ -0,0 +1,78 @@ +package mill.api + +import mill.constants.PathVars + +import scala.util.DynamicVariable + +trait MappedRoots { + + private type MappedRoots = Seq[(key: String, path: os.Path)] + + private val rootMapping: DynamicVariable[MappedRoots] = DynamicVariable(Seq()) + + def get: MappedRoots = rootMapping.value + + def toMap: Map[String, os.Path] = get.map(m => (m.key, m.path)).toMap + + def withMillDefaults[T]( + outPath: os.Path, + workspacePath: os.Path = BuildCtx.workspaceRoot, + homePath: os.Path = os.home + )(thunk: => T): T = withMapping( + Seq( + ("MILL_OUT", outPath), + ("WORKSPACE", workspacePath), + // TODO: add coursier here + ("HOME", homePath) + ) + )(thunk) + + def withMapping[T](mapping: MappedRoots)(thunk: => T): T = withMapping(_ => mapping)(thunk) + + def withMapping[T](mapping: MappedRoots => MappedRoots)(thunk: => T): T = { + val newMapping = mapping(rootMapping.value) + var seenKeys = Set[String]() + var seenPaths = Set[os.Path]() + newMapping.foreach { case m => + require(!m.key.startsWith("$"), "Key must not start with a `$`.") + require(m.key != PathVars.ROOT, s"Invalid key, '${PathVars.ROOT}' is a reserved key name.") + require( + !seenKeys.contains(m.key), + s"Key must be unique, but '${m.key}' was given multiple times." + ) + require( + !seenPaths.contains(m.path), + s"Paths must be unique, but '${m.path}' was given multiple times." + ) + seenKeys += m.key + seenPaths += m.path + } + rootMapping.withValue(newMapping)(thunk) + } + + def encodeKnownRootsInPath(p: os.Path): String = { + MappedRoots.get.collectFirst { + case rep if p.startsWith(rep.path) => + s"$$${rep.key}${ + if (p != rep.path) { + s"/${p.subRelativeTo(rep.path).toString()}" + } else "" + }" + }.getOrElse(p.toString) + } + + def decodeKnownRootsInPath(encoded: String): String = { + if (encoded.startsWith("$")) { + val offset = 1 // "$".length + MappedRoots.get.collectFirst { + case mapping if encoded.startsWith(mapping.key, offset) => + s"${mapping.path.toString}${encoded.substring(mapping.key.length + offset)}" + }.getOrElse(encoded) + } else { + encoded + } + } + +} + +object MappedRoots extends MappedRoots diff --git a/core/api/src/mill/api/PathRef.scala b/core/api/src/mill/api/PathRef.scala index f1282766319a..2b1007a1901d 100644 --- a/core/api/src/mill/api/PathRef.scala +++ b/core/api/src/mill/api/PathRef.scala @@ -2,7 +2,6 @@ package mill.api import mill.api.DummyOutputStream import mill.api.daemon.internal.PathRefApi -import mill.constants.PathVars import upickle.ReadWriter as RW import java.nio.file as jnio @@ -14,9 +13,9 @@ import scala.util.DynamicVariable import scala.util.hashing.MurmurHash3 /** - * A wrapper around `os.Path` that calculates it's hashcode based - * on the contents of the filesystem underneath it. Used to ensure filesystem - * changes can bust caches which are keyed off hashcodes. + * A wrapper around `os.Path` that calculates a `sig` (which ends up in the [[hashCode]]) + * based on the contents of the filesystem underneath it. + * Used to ensure filesystem changes can bust caches which are keyed off hashcodes. */ case class PathRef private[mill] ( path: os.Path, @@ -26,11 +25,15 @@ case class PathRef private[mill] ( ) extends PathRefApi { private[mill] def javaPath = path.toNIO - private[mill] val mappedPath: String = PathRef.encodeKnownRootsInPath(path) + /** + * The path with common mapped path roots replaced, to make it relocatable. + * See [[MappedRoots]]. + */ + private val mappedPath: String = MappedRoots.encodeKnownRootsInPath(path) /** * Apply the current contextual path mapping to this PathRef. - * Updates [[mappedPath]] but does not recalculate the sig`. + * Updates [[mappedPath]] but does not recalculate the [[sig]]. */ def remap: PathRef = PathRef(path, quick, sig, revalidate) @@ -216,88 +219,18 @@ object PathRef { } } - private[api] type MappedRoots = Seq[(key: String, path: os.Path)] - - object mappedRoots { - private[PathRef] val rootMapping: DynamicVariable[MappedRoots] = DynamicVariable(Seq()) - - def get: MappedRoots = rootMapping.value - - def toMap: Map[String, os.Path] = get.map(m => (m.key, m.path)).toMap - - def withMillDefaults[T]( - outPath: os.Path, - workspacePath: os.Path = BuildCtx.workspaceRoot, - homePath: os.Path = os.home - )(thunk: => T): T = withMapping( - Seq( - ("MILL_OUT", outPath), - ("WORKSPACE", workspacePath), - // TODO: add coursier here - ("HOME", homePath) - ) - )(thunk) - - def withMapping[T](mapping: MappedRoots)(thunk: => T): T = withMapping(_ => mapping)(thunk) - - def withMapping[T](mapping: MappedRoots => MappedRoots)(thunk: => T): T = { - val newMapping = mapping(rootMapping.value) - var seenKeys = Set[String]() - var seenPaths = Set[os.Path]() - newMapping.foreach { case m => - require(!m.key.startsWith("$"), "Key must not start with a `$`.") - require(m.key != PathVars.ROOT, s"Invalid key, '${PathVars.ROOT}' is a reserved key name.") - require( - !seenKeys.contains(m.key), - s"Key must be unique, but '${m.key}' was given multiple times." - ) - require( - !seenPaths.contains(m.path), - s"Paths must be unique, but '${m.path}' was given multiple times." - ) - seenKeys += m.key - seenPaths += m.path - } - rootMapping.withValue(newMapping)(thunk) - } - } - - private[api] def encodeKnownRootsInPath(p: os.Path): String = { - // TODO: Do we need to check for '$' and mask it ? - mappedRoots.get.collectFirst { - case rep if p.startsWith(rep.path) => - s"$$${rep.key}${ - if (p != rep.path) { - s"/${p.subRelativeTo(rep.path).toString()}" - } else "" - }" - }.getOrElse(p.toString) - } - - private[api] def decodeKnownRootsInPath(encoded: String): String = { - if (encoded.startsWith("$")) { - val offset = 1 // "$".length - mappedRoots.get.collectFirst { - case mapping if encoded.startsWith(mapping.key, offset) => - s"${mapping.path.toString}${encoded.substring(mapping.key.length + offset)}" - }.getOrElse(encoded) - } else { - encoded - } - } - /** * Default JSON formatter for [[PathRef]]. */ implicit def jsonFormatter: RW[PathRef] = upickle.readwriter[String].bimap[PathRef]( p => { storeSerializedPaths(p) - p.toStringPrefix + encodeKnownRootsInPath(p.path) + p.toStringPrefix + MappedRoots.encodeKnownRootsInPath(p.path) }, { case s"$prefix:$valid0:$hex:$pathVal" if prefix == "ref" || prefix == "qref" => - val path = os.Path(decodeKnownRootsInPath(pathVal)) + val path = os.Path(MappedRoots.decodeKnownRootsInPath(pathVal)) val quick = prefix match { case "qref" => true case "ref" => false @@ -316,7 +249,7 @@ object PathRef { pr case s => mill.api.BuildCtx.withFilesystemCheckerDisabled( - PathRef(os.Path(decodeKnownRootsInPath(s), currentOverrideModulePath.value)) + PathRef(os.Path(MappedRoots.decodeKnownRootsInPath(s), currentOverrideModulePath.value)) ) } ) diff --git a/core/api/test/src/mill/api/MappedRootsTests.scala b/core/api/test/src/mill/api/MappedRootsTests.scala new file mode 100644 index 000000000000..63ac9a391fff --- /dev/null +++ b/core/api/test/src/mill/api/MappedRootsTests.scala @@ -0,0 +1,48 @@ +package mill.api + +import utest.* + +import java.nio.file.Files +import mill.api.{MappedRoots => MR} + +object MappedRootsTests extends TestSuite { + val tests: Tests = Tests { + test("encode") { + withTmpDir { tmpDir => + val workspaceDir = tmpDir / "workspace" + val outDir = workspaceDir / "out" + MR.withMillDefaults(outPath = outDir, workspacePath = workspaceDir) { + + def check(path: os.Path, encContains: Seq[String], containsNot: Seq[String]) = { + val enc = MR.encodeKnownRootsInPath(path) + val dec = MR.decodeKnownRootsInPath(enc) + assert(path.toString == dec) + encContains.foreach(s => assert(enc.containsSlice(s))) + containsNot.foreach(s => assert(!enc.containsSlice(s))) + + path -> enc + } + + val file1 = tmpDir / "file1" + val file2 = workspaceDir / "file2" + val file3 = outDir / "file3" + + Seq( + "mapping" -> MR.get, + check(file1, Seq(file1.toString), Seq("$WORKSPACE", "$MILL_OUT")), + check(file2, Seq("$WORKSPACE/file2"), Seq("$MILL_OUT")), + check(file3, Seq("$MILL_OUT/file3"), Seq("$WORKSPACE")) + ) + } + } + } + } + + private def withTmpDir[T](body: os.Path => T): T = { + val tmpDir = os.Path(Files.createTempDirectory("")) + val res = body(tmpDir) + os.remove.all(tmpDir) + res + } + +} diff --git a/core/api/test/src/mill/api/PathRefTests.scala b/core/api/test/src/mill/api/PathRefTests.scala index 4fc47074dfb1..28f14c6efae6 100644 --- a/core/api/test/src/mill/api/PathRefTests.scala +++ b/core/api/test/src/mill/api/PathRefTests.scala @@ -19,6 +19,7 @@ object PathRefTests extends TestSuite { val sig2 = PathRef(file, quick).sig assert(sig1 != sig2) } + test("qref") - check(quick = true) test("ref") - check(quick = false) } @@ -82,6 +83,7 @@ object PathRefTests extends TestSuite { val sig2 = PathRef(tmpDir, quick).sig assert(sig1 == sig2) } + test("qref") - check(quick = true) test("ref") - check(quick = false) } @@ -111,35 +113,6 @@ object PathRefTests extends TestSuite { test("qref") - check(quick = true) test("ref") - check(quick = false) } - - test("encode") { - withTmpDir { tmpDir => - val workspaceDir = tmpDir / "workspace" - val outDir = workspaceDir / "out" - PathRef.mappedRoots.withMillDefaults(outPath = outDir, workspacePath = workspaceDir) { - def check(path: os.Path, contains: Seq[String], containsNot: Seq[String]) = { - val enc = PathRef.encodeKnownRootsInPath(path) - val dec = PathRef.decodeKnownRootsInPath(enc) - assert(path.toString == dec) - contains.foreach(s => enc.containsSlice(s)) - containsNot.foreach(s => !enc.containsSlice(s)) - - path -> enc - } - - val file1 = tmpDir / "file1" - val file2 = workspaceDir / "file2" - val file3 = outDir / "file3" - - Seq( - "mapping" -> PathRef.mappedRoots.get, - check(file1, Seq("ref:v0:", file1.toString), Seq("$WORKSPACE", "$MILL_OUT")), - check(file2, Seq("ref:v0:", "$WORKSPACE/file2"), Seq("$MILL_OUT")), - check(file3, Seq("ref:v0:", "$MILL_OUT/file3"), Seq("$WORKSPACE")) - ) - } - } - } } private def withTmpDir[T](body: os.Path => T): T = { diff --git a/core/exec/src/mill/exec/GroupExecution.scala b/core/exec/src/mill/exec/GroupExecution.scala index 033032d1f793..a33be63527d3 100644 --- a/core/exec/src/mill/exec/GroupExecution.scala +++ b/core/exec/src/mill/exec/GroupExecution.scala @@ -83,7 +83,7 @@ trait GroupExecution { executionContext: mill.api.TaskCtx.Fork.Api, exclusive: Boolean, upstreamPathRefs: Seq[PathRef] - ): GroupExecution.Results = PathRef.mappedRoots.withMillDefaults(outPath) { + ): GroupExecution.Results = MappedRoots.withMillDefaults(outPath) { val inputsHash = { val externalInputsHash = MurmurHash3.orderedHash( diff --git a/integration/ide/build-classpath-contents/src/BuildClasspathContentsTests.scala b/integration/ide/build-classpath-contents/src/BuildClasspathContentsTests.scala index a562e654689d..25fc5be89931 100644 --- a/integration/ide/build-classpath-contents/src/BuildClasspathContentsTests.scala +++ b/integration/ide/build-classpath-contents/src/BuildClasspathContentsTests.scala @@ -1,4 +1,4 @@ -import mill.api.{BuildCtx, PathRef} +import mill.api.{BuildCtx, MappedRoots, PathRef} import mill.testkit.UtestIntegrationTestSuite import utest.* @@ -6,7 +6,7 @@ object BuildClasspathContentsTests extends UtestIntegrationTestSuite { val tests: Tests = Tests { test("test") - integrationTest { tester => - PathRef.mappedRoots.withMapping(Seq( + MappedRoots.withMapping(Seq( "HOME" -> os.home, "WORKSPACE" -> tester.workspacePath )) { diff --git a/integration/invalidation/multi-level-editing/src/MultiLevelBuildTests.scala b/integration/invalidation/multi-level-editing/src/MultiLevelBuildTests.scala index c4dece2783d9..4625dba1a362 100644 --- a/integration/invalidation/multi-level-editing/src/MultiLevelBuildTests.scala +++ b/integration/invalidation/multi-level-editing/src/MultiLevelBuildTests.scala @@ -1,6 +1,6 @@ package mill.integration -import mill.api.PathRef +import mill.api.MappedRoots import mill.testkit.{IntegrationTester, UtestIntegrationTestSuite} import mill.constants.OutFiles.* import mill.daemon.RunnerState @@ -50,7 +50,7 @@ trait MultiLevelBuildTests extends UtestIntegrationTestSuite { val path = tester.workspacePath / "out" / Seq.fill(depth)(millBuild) / millRunnerState if (os.exists(path)) - PathRef.mappedRoots.withMillDefaults( + MappedRoots.withMillDefaults( outPath = tester.workspacePath / "out", workspacePath = tester.workspacePath ) { diff --git a/libs/javalib/src/mill/javalib/TestModule.scala b/libs/javalib/src/mill/javalib/TestModule.scala index 13ed06c63a5a..1f58c779bdc3 100644 --- a/libs/javalib/src/mill/javalib/TestModule.scala +++ b/libs/javalib/src/mill/javalib/TestModule.scala @@ -1,14 +1,10 @@ package mill.javalib import mill.T -import mill.api.Result +import mill.api.{DefaultTaskModule, MappedRoots, PathRef, Result, Task, TaskCtx} import mill.api.daemon.internal.TestModuleApi import mill.api.daemon.internal.TestReporter import mill.api.daemon.internal.bsp.{BspBuildTarget, BspModuleApi} -import mill.api.PathRef -import mill.api.Task -import mill.api.TaskCtx -import mill.api.DefaultTaskModule import mill.javalib.bsp.BspModule import mill.api.JsonFormatters.given import mill.constants.EnvVars @@ -195,7 +191,7 @@ trait TestModule ) val argsFile = Task.dest / "testargs" - PathRef.mappedRoots.withMapping(Seq()) { + MappedRoots.withMapping(Seq()) { // Don't use placeholders, so we only have local absolute paths os.write(argsFile, upickle.write(testArgs)) } diff --git a/libs/javalib/src/mill/javalib/TestModuleUtil.scala b/libs/javalib/src/mill/javalib/TestModuleUtil.scala index 4cfde31fcd8e..98b376b99689 100644 --- a/libs/javalib/src/mill/javalib/TestModuleUtil.scala +++ b/libs/javalib/src/mill/javalib/TestModuleUtil.scala @@ -1,6 +1,6 @@ package mill.javalib -import mill.api.{BuildCtx, Logger, PathRef, Result, TaskCtx} +import mill.api.{BuildCtx, Logger, MappedRoots, PathRef, Result, TaskCtx} import mill.api.daemon.internal.TestReporter import mill.util.Jvm import mill.api.internal.Util @@ -135,7 +135,7 @@ final class TestModuleUtil( val argsFile = baseFolder / "testargs" val sandbox = baseFolder / "sandbox" - PathRef.mappedRoots.withMapping(Seq()) { + MappedRoots.withMapping(Seq()) { // Don't use placeholders, so we only have local absolute paths os.write(argsFile, upickle.write(testArgs), createFolders = true) } diff --git a/libs/javalib/worker/src/mill/javalib/zinc/ZincWorkerMain.scala b/libs/javalib/worker/src/mill/javalib/zinc/ZincWorkerMain.scala index e887ce060e6d..ce7237fd42f7 100644 --- a/libs/javalib/worker/src/mill/javalib/zinc/ZincWorkerMain.scala +++ b/libs/javalib/worker/src/mill/javalib/zinc/ZincWorkerMain.scala @@ -1,6 +1,6 @@ package mill.javalib.zinc -import mill.api.{PathRef, SystemStreamsUtils} +import mill.api.{MappedRoots, SystemStreamsUtils} import mill.api.daemon.{DummyInputStream, SystemStreams} import mill.client.lock.Locks import mill.rpc.MillRpcWireTransport @@ -67,7 +67,7 @@ object ZincWorkerMain { extends MillRpcWireTransport(serverName, stdin, stdout, writeSynchronizer) { override def writeSerialized[A: upickle.Writer](message: A, log: String => Unit): Unit = { // RPC communication is local and uncached, so we don't want to use any root mapping - PathRef.mappedRoots.withMapping(Seq()) { + MappedRoots.withMapping(Seq()) { super.writeSerialized(message, log) } } diff --git a/runner/daemon/src/mill/daemon/MillBuildBootstrap.scala b/runner/daemon/src/mill/daemon/MillBuildBootstrap.scala index cc8d4af17060..e18fe8f01844 100644 --- a/runner/daemon/src/mill/daemon/MillBuildBootstrap.scala +++ b/runner/daemon/src/mill/daemon/MillBuildBootstrap.scala @@ -7,12 +7,11 @@ import mill.api.daemon.internal.{ PathRefApi, RootModuleApi } -import mill.api.{Logger, Result, SystemStreams, Val} +import mill.api.{BuildCtx, Logger, MappedRoots, PathRef, Result, SelectMode, SystemStreams, Val} import mill.constants.CodeGenConstants.* import mill.constants.OutFiles.{millBuild, millRunnerState} import mill.api.daemon.Watchable import mill.api.internal.RootModule -import mill.api.{BuildCtx, PathRef, SelectMode} import mill.internal.PrefixLogger import mill.meta.MillBuildRootModule import mill.meta.CliImports @@ -70,7 +69,7 @@ class MillBuildBootstrap( val runnerState = evaluateRec(0) for ((frame, depth) <- runnerState.frames.zipWithIndex) { - PathRef.mappedRoots.withMillDefaults(output) { + MappedRoots.withMillDefaults(output) { os.write.over( recOut(output, depth) / millRunnerState, upickle.write(frame.loggedData, indent = 4), diff --git a/runner/daemon/src/mill/daemon/MillDaemonMain.scala b/runner/daemon/src/mill/daemon/MillDaemonMain.scala index 594e97996aa3..b2573c6c1857 100644 --- a/runner/daemon/src/mill/daemon/MillDaemonMain.scala +++ b/runner/daemon/src/mill/daemon/MillDaemonMain.scala @@ -1,7 +1,7 @@ package mill.daemon import mill.api.{BuildCtx, SystemStreams} -import mill.api.{PathRef, SystemStreams} +import mill.api.{MappedRoots, SystemStreams} import mill.client.ClientUtil import mill.client.lock.{Lock, Locks} import mill.constants.OutFolderMode @@ -40,7 +40,7 @@ object MillDaemonMain { val args = Args(getClass.getName, args0).fold(err => throw IllegalArgumentException(err), identity) - PathRef.mappedRoots.withMillDefaults(args.outDir) { + MappedRoots.withMillDefaults(args.outDir) { // temporarily disabling FFM use by coursier, which has issues with the way // Mill manages class loaders, throwing things like // UnsatisfiedLinkError: Native Library C:\Windows\System32\ole32.dll already loaded in another classloader diff --git a/runner/daemon/src/mill/daemon/MillMain0.scala b/runner/daemon/src/mill/daemon/MillMain0.scala index 2cc80cdfc537..6688cca76943 100644 --- a/runner/daemon/src/mill/daemon/MillMain0.scala +++ b/runner/daemon/src/mill/daemon/MillMain0.scala @@ -3,7 +3,7 @@ package mill.daemon import ch.epfl.scala.bsp4j.BuildClient import mill.api.daemon.internal.bsp.BspServerHandle import mill.api.daemon.internal.{CompileProblemReporter, EvaluatorApi} -import mill.api.{BuildCtx, Logger, MillException, PathRef, Result, SystemStreams} +import mill.api.{BuildCtx, Logger, MappedRoots, MillException, Result, SystemStreams} import mill.bsp.BSP import mill.client.lock.{DoubleLock, Lock} import mill.constants.{DaemonFiles, OutFiles} @@ -110,7 +110,7 @@ object MillMain0 { daemonDir: os.Path, outLock: Lock, outDir: os.Path - ): (Boolean, RunnerState) = PathRef.mappedRoots.withMillDefaults(outPath = outDir) { + ): (Boolean, RunnerState) = MappedRoots.withMillDefaults(outPath = outDir) { mill.api.daemon.internal.MillScalaParser.current.withValue(MillScalaParserImpl) { os.SubProcess.env.withValue(env) { val parserResult = MillCliConfig.parse(args) diff --git a/testkit/src/mill/testkit/UnitTester.scala b/testkit/src/mill/testkit/UnitTester.scala index c4d5d8106d4d..2d84265755c5 100644 --- a/testkit/src/mill/testkit/UnitTester.scala +++ b/testkit/src/mill/testkit/UnitTester.scala @@ -6,7 +6,7 @@ import mill.api.{ DummyInputStream, Evaluator, ExecResult, - PathRef, + MappedRoots, Result, SelectMode, SystemStreams, @@ -237,7 +237,7 @@ class UnitTester( def scoped[T](tester: UnitTester => T): T = { try { BuildCtx.workspaceRoot0.withValue(module.moduleDir) { - PathRef.mappedRoots.withMillDefaults(outPath) { + MappedRoots.withMillDefaults(outPath) { tester(this) } } From 7da1ec4f0db7867c8e658e1113fba95b57c591be Mon Sep 17 00:00:00 2001 From: Tobias Roeser Date: Tue, 28 Oct 2025 11:37:12 +0100 Subject: [PATCH 31/45] Renamings --- core/api/src/mill/api/MappedRoots.scala | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/core/api/src/mill/api/MappedRoots.scala b/core/api/src/mill/api/MappedRoots.scala index 7963edfd7caf..5e47c86481fc 100644 --- a/core/api/src/mill/api/MappedRoots.scala +++ b/core/api/src/mill/api/MappedRoots.scala @@ -4,9 +4,11 @@ import mill.constants.PathVars import scala.util.DynamicVariable -trait MappedRoots { +type MappedRoots = Seq[(key: String, path: os.Path)] - private type MappedRoots = Seq[(key: String, path: os.Path)] +object MappedRoots extends MappedRootsImpl + +trait MappedRootsImpl { private val rootMapping: DynamicVariable[MappedRoots] = DynamicVariable(Seq()) @@ -74,5 +76,3 @@ trait MappedRoots { } } - -object MappedRoots extends MappedRoots From 0ac506a0a0d9c407023667d057d38f397a3bff7e Mon Sep 17 00:00:00 2001 From: Tobias Roeser Date: Tue, 28 Oct 2025 13:13:50 +0100 Subject: [PATCH 32/45] Ensure `MappedRoots.withMillDefaults` is used with named parameters --- core/api/src/mill/api/MappedRoots.scala | 3 +++ core/exec/src/mill/exec/GroupExecution.scala | 2 +- runner/daemon/src/mill/daemon/MillBuildBootstrap.scala | 2 +- runner/daemon/src/mill/daemon/MillDaemonMain.scala | 2 +- testkit/src/mill/testkit/UnitTester.scala | 2 +- 5 files changed, 7 insertions(+), 4 deletions(-) diff --git a/core/api/src/mill/api/MappedRoots.scala b/core/api/src/mill/api/MappedRoots.scala index 5e47c86481fc..c44151522f5a 100644 --- a/core/api/src/mill/api/MappedRoots.scala +++ b/core/api/src/mill/api/MappedRoots.scala @@ -1,7 +1,9 @@ package mill.api +import mill.api.internal.NamedParameterOnlyDummy import mill.constants.PathVars +import scala.annotation.unused import scala.util.DynamicVariable type MappedRoots = Seq[(key: String, path: os.Path)] @@ -17,6 +19,7 @@ trait MappedRootsImpl { def toMap: Map[String, os.Path] = get.map(m => (m.key, m.path)).toMap def withMillDefaults[T]( + @unused t: NamedParameterOnlyDummy = new NamedParameterOnlyDummy, outPath: os.Path, workspacePath: os.Path = BuildCtx.workspaceRoot, homePath: os.Path = os.home diff --git a/core/exec/src/mill/exec/GroupExecution.scala b/core/exec/src/mill/exec/GroupExecution.scala index a33be63527d3..e6890962534b 100644 --- a/core/exec/src/mill/exec/GroupExecution.scala +++ b/core/exec/src/mill/exec/GroupExecution.scala @@ -83,7 +83,7 @@ trait GroupExecution { executionContext: mill.api.TaskCtx.Fork.Api, exclusive: Boolean, upstreamPathRefs: Seq[PathRef] - ): GroupExecution.Results = MappedRoots.withMillDefaults(outPath) { + ): GroupExecution.Results = MappedRoots.withMillDefaults(outPath = outPath) { val inputsHash = { val externalInputsHash = MurmurHash3.orderedHash( diff --git a/runner/daemon/src/mill/daemon/MillBuildBootstrap.scala b/runner/daemon/src/mill/daemon/MillBuildBootstrap.scala index e18fe8f01844..31897ef4c9c3 100644 --- a/runner/daemon/src/mill/daemon/MillBuildBootstrap.scala +++ b/runner/daemon/src/mill/daemon/MillBuildBootstrap.scala @@ -69,7 +69,7 @@ class MillBuildBootstrap( val runnerState = evaluateRec(0) for ((frame, depth) <- runnerState.frames.zipWithIndex) { - MappedRoots.withMillDefaults(output) { + MappedRoots.withMillDefaults(outPath = output) { os.write.over( recOut(output, depth) / millRunnerState, upickle.write(frame.loggedData, indent = 4), diff --git a/runner/daemon/src/mill/daemon/MillDaemonMain.scala b/runner/daemon/src/mill/daemon/MillDaemonMain.scala index b2573c6c1857..2f2efdff7779 100644 --- a/runner/daemon/src/mill/daemon/MillDaemonMain.scala +++ b/runner/daemon/src/mill/daemon/MillDaemonMain.scala @@ -40,7 +40,7 @@ object MillDaemonMain { val args = Args(getClass.getName, args0).fold(err => throw IllegalArgumentException(err), identity) - MappedRoots.withMillDefaults(args.outDir) { + MappedRoots.withMillDefaults(outPath = args.outDir) { // temporarily disabling FFM use by coursier, which has issues with the way // Mill manages class loaders, throwing things like // UnsatisfiedLinkError: Native Library C:\Windows\System32\ole32.dll already loaded in another classloader diff --git a/testkit/src/mill/testkit/UnitTester.scala b/testkit/src/mill/testkit/UnitTester.scala index 2d84265755c5..8b9e8b3d6dc9 100644 --- a/testkit/src/mill/testkit/UnitTester.scala +++ b/testkit/src/mill/testkit/UnitTester.scala @@ -237,7 +237,7 @@ class UnitTester( def scoped[T](tester: UnitTester => T): T = { try { BuildCtx.workspaceRoot0.withValue(module.moduleDir) { - MappedRoots.withMillDefaults(outPath) { + MappedRoots.withMillDefaults(outPath = outPath) { tester(this) } } From 7e5691ce237fc473d36cbfa23130b8a839d83d1b Mon Sep 17 00:00:00 2001 From: Tobias Roeser Date: Tue, 28 Oct 2025 13:20:41 +0100 Subject: [PATCH 33/45] cleanup --- core/api/test/src/mill/api/PathRefTests.scala | 33 +++++++++---------- 1 file changed, 15 insertions(+), 18 deletions(-) diff --git a/core/api/test/src/mill/api/PathRefTests.scala b/core/api/test/src/mill/api/PathRefTests.scala index 28f14c6efae6..5c4f9790cd8c 100644 --- a/core/api/test/src/mill/api/PathRefTests.scala +++ b/core/api/test/src/mill/api/PathRefTests.scala @@ -89,25 +89,22 @@ object PathRefTests extends TestSuite { } test("json") { - def check(quick: Boolean) = withTmpDir { outDir => - withTmpDir { tmpDir => - val file = tmpDir / "foo.txt" - os.write(file, "hello") - val pr = PathRef(file, quick) - val prFile = - pr.path.toString().replace(outDir.toString(), "$MILL_OUT").replace("\\", "\\\\") - val json = upickle.write(pr) - if (quick) { - assert(json.startsWith(""""qref:v0:""")) - assert(json.endsWith(s""":${prFile}"""")) - } else { - val hash = if (Properties.isWin) "86df6a6a" else "4c7ef487" - val expected = s""""ref:v0:${hash}:${prFile}"""" - assert(json == expected) - } - val pr1 = upickle.read[PathRef](json) - assert(pr == pr1) + def check(quick: Boolean) = withTmpDir { tmpDir => + val file = tmpDir / "foo.txt" + os.write(file, "hello") + val pr = PathRef(file, quick) + val prFile = pr.path.toString().replace("\\", "\\\\") + val json = upickle.write(pr) + if (quick) { + assert(json.startsWith(""""qref:v0:""")) + assert(json.endsWith(s""":${prFile}"""")) + } else { + val hash = if (Properties.isWin) "86df6a6a" else "4c7ef487" + val expected = s""""ref:v0:${hash}:${prFile}"""" + assert(json == expected) } + val pr1 = upickle.read[PathRef](json) + assert(pr == pr1) } test("qref") - check(quick = true) From 078eba60f69cbd8e02c43852c53477a349b4d841 Mon Sep 17 00:00:00 2001 From: Tobias Roeser Date: Mon, 3 Nov 2025 09:40:07 +0100 Subject: [PATCH 34/45] Use named parameters --- core/exec/src/mill/exec/Execution.scala | 36 ++++++++++++------------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/core/exec/src/mill/exec/Execution.scala b/core/exec/src/mill/exec/Execution.scala index 5caa217abb4d..40219e333df4 100644 --- a/core/exec/src/mill/exec/Execution.scala +++ b/core/exec/src/mill/exec/Execution.scala @@ -54,24 +54,24 @@ private[mill] case class Execution( offline: Boolean, enableTicker: Boolean ) = this( - baseLogger, - new JsonArrayLogger.Profile(os.Path(outPath) / millProfile), - os.Path(workspace), - os.Path(outPath), - os.Path(externalOutPath), - rootModule, - classLoaderSigHash, - classLoaderIdentityHash, - workerCache, - env, - failFast, - ec, - codeSignatures, - systemExit, - exclusiveSystemStreams, - getEvaluator, - offline, - enableTicker + baseLogger = baseLogger, + profileLogger = new JsonArrayLogger.Profile(os.Path(outPath) / millProfile), + workspace = os.Path(workspace), + outPath = os.Path(outPath), + externalOutPath = os.Path(externalOutPath), + rootModule = rootModule, + classLoaderSigHash = classLoaderSigHash, + classLoaderIdentityHash = classLoaderIdentityHash, + workerCache = workerCache, + env = env, + failFast = failFast, + ec = ec, + codeSignatures = codeSignatures, + systemExit = systemExit, + exclusiveSystemStreams = exclusiveSystemStreams, + getEvaluator = getEvaluator, + offline = offline, + enableTicker = enableTicker ) def withBaseLogger(newBaseLogger: Logger) = this.copy(baseLogger = newBaseLogger) From d3ee20a0f4d9de46c9d36e06fec02ce3a112bc41 Mon Sep 17 00:00:00 2001 From: Tobias Roeser Date: Mon, 3 Nov 2025 11:44:45 +0100 Subject: [PATCH 35/45] Fix merge error --- .../src/BuildClasspathContentsTests.scala | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/integration/ide/build-classpath-contents/src/BuildClasspathContentsTests.scala b/integration/ide/build-classpath-contents/src/BuildClasspathContentsTests.scala index 25fc5be89931..14c7e1484118 100644 --- a/integration/ide/build-classpath-contents/src/BuildClasspathContentsTests.scala +++ b/integration/ide/build-classpath-contents/src/BuildClasspathContentsTests.scala @@ -56,10 +56,11 @@ object BuildClasspathContentsTests extends UtestIntegrationTestSuite { "mill-libs_3-SNAPSHOT.jar", "mill-moduledefs_3-0.11.10.jar" ) - assert(millLocalClasspath == Nil) - } else { - sys.error("This test must be run in `packaged` mode, not `local`") - } + ) + assert(millLocalClasspath == Nil) + } else { + sys.error("This test must be run in `packaged` mode, not `local`") + } } } } From 45a7b97f75840240178fb45a147e89a498e14cbf Mon Sep 17 00:00:00 2001 From: Tobias Roeser Date: Mon, 3 Nov 2025 10:59:52 +0100 Subject: [PATCH 36/45] Add MappedRoots.requireMappedPaths API --- core/api/src/mill/api/MappedRoots.scala | 13 +++++++++++++ .../daemon/src/mill/daemon/MillBuildBootstrap.scala | 8 ++++++++ 2 files changed, 21 insertions(+) diff --git a/core/api/src/mill/api/MappedRoots.scala b/core/api/src/mill/api/MappedRoots.scala index c44151522f5a..bc9689788d0d 100644 --- a/core/api/src/mill/api/MappedRoots.scala +++ b/core/api/src/mill/api/MappedRoots.scala @@ -78,4 +78,17 @@ trait MappedRootsImpl { } } + /** + * Use this to assert at runtime, that a root path with the given `key` is defined. + * @throws NoSuchElementException when no path was mapped under the given `key`. + */ + def requireMappedPaths(key: String*): Unit = { + val map = toMap + for { + singleKey <- key + } { + if (!map.contains(singleKey)) throw new NoSuchElementException(s"No root path mapping defined for '${key}'") + } + } + } diff --git a/runner/daemon/src/mill/daemon/MillBuildBootstrap.scala b/runner/daemon/src/mill/daemon/MillBuildBootstrap.scala index 31897ef4c9c3..ae62bb26c953 100644 --- a/runner/daemon/src/mill/daemon/MillBuildBootstrap.scala +++ b/runner/daemon/src/mill/daemon/MillBuildBootstrap.scala @@ -86,6 +86,14 @@ class MillBuildBootstrap( } def evaluateRec(depth: Int): RunnerState = { + + // We need relocatable PathRef for meta-builds for a stable classpathSig + MappedRoots.requireMappedPaths( + mill.constants.PathVars.WORKSPACE, + mill.constants.PathVars.HOME, + mill.constants.PathVars.MILL_OUT + ) + logger.withChromeProfile(s"meta-level $depth") { // println(s"+evaluateRec($depth) " + recRoot(projectRoot, depth)) val currentRoot = recRoot(projectRoot, depth) From cc98220ca1a2c3adc797bf5d947547df5da46673 Mon Sep 17 00:00:00 2001 From: Tobias Roeser Date: Tue, 4 Nov 2025 03:59:48 +0100 Subject: [PATCH 37/45] Fixed expected outputs of new tests --- example/javalib/script/10-resources/build.mill | 2 +- example/kotlinlib/script/10-resources/build.mill | 2 +- example/scalalib/script/10-resources/build.mill | 2 +- example/springboot/java/1-web-initializr/build.mill | 2 +- example/springboot/kotlin/1-web-initializr/build.mill | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/example/javalib/script/10-resources/build.mill b/example/javalib/script/10-resources/build.mill index e511c0b7ae96..a197381daf17 100644 --- a/example/javalib/script/10-resources/build.mill +++ b/example/javalib/script/10-resources/build.mill @@ -13,7 +13,7 @@ Hello World Resource File /** Usage > ./mill show Foo.java:assembly -".../out/Foo.java/assembly.dest/out.jar" +"...$MILL_OUT/Foo.java/assembly.dest/out.jar" > out/Foo.java/assembly.dest/out.jar Hello World Resource File diff --git a/example/kotlinlib/script/10-resources/build.mill b/example/kotlinlib/script/10-resources/build.mill index 6985db0a0b9b..e5104f1f1955 100644 --- a/example/kotlinlib/script/10-resources/build.mill +++ b/example/kotlinlib/script/10-resources/build.mill @@ -13,7 +13,7 @@ Hello World Resource File /** Usage > ./mill show Foo.kt:assembly -".../out/Foo.kt/assembly.dest/out.jar" +"...$MILL_OUT/Foo.kt/assembly.dest/out.jar" > out/Foo.kt/assembly.dest/out.jar Hello World Resource File diff --git a/example/scalalib/script/10-resources/build.mill b/example/scalalib/script/10-resources/build.mill index 664f45838f8e..ae6cb540c380 100644 --- a/example/scalalib/script/10-resources/build.mill +++ b/example/scalalib/script/10-resources/build.mill @@ -23,7 +23,7 @@ Hello World Resource File /** Usage > ./mill show Foo.scala:assembly -".../out/Foo.scala/assembly.dest/out.jar" +"...$MILL_OUT/Foo.scala/assembly.dest/out.jar" > out/Foo.scala/assembly.dest/out.jar Hello World Resource File diff --git a/example/springboot/java/1-web-initializr/build.mill b/example/springboot/java/1-web-initializr/build.mill index c22f48d4b9b5..729995098fdb 100644 --- a/example/springboot/java/1-web-initializr/build.mill +++ b/example/springboot/java/1-web-initializr/build.mill @@ -50,6 +50,6 @@ object `package` extends MavenModule { > ./mill clean runBackground > ./mill show assembly -.../out/assembly.dest/out.jar... +...$MILL_OUT/assembly.dest/out.jar... */ diff --git a/example/springboot/kotlin/1-web-initializr/build.mill b/example/springboot/kotlin/1-web-initializr/build.mill index 62785513d86e..3c31ae30bd24 100644 --- a/example/springboot/kotlin/1-web-initializr/build.mill +++ b/example/springboot/kotlin/1-web-initializr/build.mill @@ -59,6 +59,6 @@ object `package` extends KotlinMavenModule { > ./mill clean runBackground > ./mill show assembly -.../out/assembly.dest/out.jar... +...$MILL_OUT/assembly.dest/out.jar... */ From d69ce4c5ee2fa18115f83e70043d760de07d0974 Mon Sep 17 00:00:00 2001 From: Tobias Roeser Date: Tue, 4 Nov 2025 07:15:58 +0100 Subject: [PATCH 38/45] Use named parameter --- .../src/mill/daemon/MillBuildBootstrap.scala | 34 +++++++++---------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/runner/daemon/src/mill/daemon/MillBuildBootstrap.scala b/runner/daemon/src/mill/daemon/MillBuildBootstrap.scala index ae62bb26c953..17686369b88f 100644 --- a/runner/daemon/src/mill/daemon/MillBuildBootstrap.scala +++ b/runner/daemon/src/mill/daemon/MillBuildBootstrap.scala @@ -262,24 +262,24 @@ class MillBuildBootstrap( case Result.Success((buildFileApi)) => Using.resource(makeEvaluator( - projectRoot, - output, - keepGoing, - env, - logger, - ec, - allowPositionalCommandArgs, - systemExit, - streams0, - selectiveExecution, - offline, - newWorkerCache, - nestedState.frames.headOption.map(_.codeSignatures).getOrElse(Map.empty), - buildFileApi.rootModule, + projectRoot = projectRoot, + output = output, + keepGoing = keepGoing, + env = env, + logger = logger, + ec = ec, + allowPositionalCommandArgs = allowPositionalCommandArgs, + systemExit = systemExit, + streams0 = streams0, + selectiveExecution = selectiveExecution, + offline = offline, + workerCache = newWorkerCache, + codeSignatures = nestedState.frames.headOption.map(_.codeSignatures).getOrElse(Map.empty), + rootModule = buildFileApi.rootModule, // We want to use the grandparent buildHash, rather than the parent // buildHash, because the parent build changes are instead detected // by analyzing the scriptImportGraph in a more fine-grained manner. - nestedState + millClassloaderSigHash = nestedState .frames .dropRight(1) .headOption @@ -287,13 +287,13 @@ class MillBuildBootstrap( .getOrElse(millBootClasspathPathRefs) .map(p => (os.Path(p.javaPath), p.sig)) .hashCode(), - nestedState + millClassloaderIdentityHash = nestedState .frames .headOption .flatMap(_.classLoaderOpt) .map(_.hashCode()) .getOrElse(0), - depth, + depth = depth, actualBuildFileName = nestedState.buildFile, enableTicker = enableTicker )) { evaluator => From 65733a5c842edb97f577c2b01ba1b6b7cc510bcc Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Tue, 4 Nov 2025 06:27:47 +0000 Subject: [PATCH 39/45] [autofix.ci] apply automated fixes --- core/api/src/mill/api/MappedRoots.scala | 3 ++- runner/daemon/src/mill/daemon/MillBuildBootstrap.scala | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/core/api/src/mill/api/MappedRoots.scala b/core/api/src/mill/api/MappedRoots.scala index bc9689788d0d..96c23ae89ce3 100644 --- a/core/api/src/mill/api/MappedRoots.scala +++ b/core/api/src/mill/api/MappedRoots.scala @@ -87,7 +87,8 @@ trait MappedRootsImpl { for { singleKey <- key } { - if (!map.contains(singleKey)) throw new NoSuchElementException(s"No root path mapping defined for '${key}'") + if (!map.contains(singleKey)) + throw new NoSuchElementException(s"No root path mapping defined for '${key}'") } } diff --git a/runner/daemon/src/mill/daemon/MillBuildBootstrap.scala b/runner/daemon/src/mill/daemon/MillBuildBootstrap.scala index 17686369b88f..be8186daeab9 100644 --- a/runner/daemon/src/mill/daemon/MillBuildBootstrap.scala +++ b/runner/daemon/src/mill/daemon/MillBuildBootstrap.scala @@ -274,7 +274,8 @@ class MillBuildBootstrap( selectiveExecution = selectiveExecution, offline = offline, workerCache = newWorkerCache, - codeSignatures = nestedState.frames.headOption.map(_.codeSignatures).getOrElse(Map.empty), + codeSignatures = + nestedState.frames.headOption.map(_.codeSignatures).getOrElse(Map.empty), rootModule = buildFileApi.rootModule, // We want to use the grandparent buildHash, rather than the parent // buildHash, because the parent build changes are instead detected From 4235a3171387edab9fcd4cedb67112ab03327600 Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Fri, 7 Nov 2025 08:29:32 +0000 Subject: [PATCH 40/45] [autofix.ci] apply automated fixes (attempt 2/3) --- libs/javalib/src/mill/javalib/TestModuleUtil.scala | 2 +- runner/daemon/src/mill/daemon/MillDaemonMain.scala | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/libs/javalib/src/mill/javalib/TestModuleUtil.scala b/libs/javalib/src/mill/javalib/TestModuleUtil.scala index 98b376b99689..f8446fd09c5c 100644 --- a/libs/javalib/src/mill/javalib/TestModuleUtil.scala +++ b/libs/javalib/src/mill/javalib/TestModuleUtil.scala @@ -1,6 +1,6 @@ package mill.javalib -import mill.api.{BuildCtx, Logger, MappedRoots, PathRef, Result, TaskCtx} +import mill.api.{Logger, MappedRoots, PathRef, Result, TaskCtx} import mill.api.daemon.internal.TestReporter import mill.util.Jvm import mill.api.internal.Util diff --git a/runner/daemon/src/mill/daemon/MillDaemonMain.scala b/runner/daemon/src/mill/daemon/MillDaemonMain.scala index 2f2efdff7779..00ea81c6e782 100644 --- a/runner/daemon/src/mill/daemon/MillDaemonMain.scala +++ b/runner/daemon/src/mill/daemon/MillDaemonMain.scala @@ -1,8 +1,6 @@ package mill.daemon -import mill.api.{BuildCtx, SystemStreams} import mill.api.{MappedRoots, SystemStreams} -import mill.client.ClientUtil import mill.client.lock.{Lock, Locks} import mill.constants.OutFolderMode import mill.server.Server From fe658416063f6788094a7200a1d417679210d2eb Mon Sep 17 00:00:00 2001 From: Tobias Roeser Date: Fri, 7 Nov 2025 11:11:07 +0100 Subject: [PATCH 41/45] . --- core/exec/src/mill/exec/GroupExecution.scala | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/core/exec/src/mill/exec/GroupExecution.scala b/core/exec/src/mill/exec/GroupExecution.scala index e6890962534b..54f40eb81634 100644 --- a/core/exec/src/mill/exec/GroupExecution.scala +++ b/core/exec/src/mill/exec/GroupExecution.scala @@ -242,7 +242,7 @@ trait GroupExecution { newEvaluated = newEvaluated.toSeq, cached = if (labelled.isInstanceOf[Task.Input[?]]) null else false, inputsHash = inputsHash, - previousInputsHash = cached.map(_.inputHash).getOrElse(-1), + previousInputsHash = cached.map(_.inputsHash).getOrElse(-1), valueHashChanged = !cached.map(_.valueHash).contains(valueHash), serializedPaths = serializedPaths ) @@ -453,7 +453,11 @@ trait GroupExecution { inputsHash: Int, labelled: Task.Named[?], paths: ExecutionPaths - ): Option[(inputHash: Int, valOpt: Option[(Val, Seq[PathRef])], valueHash: Int)] = { + ): Option[( + inputsHash: Int, + valOpt: Option[(Val, Seq[PathRef])], + valueHash: Int + )] = { for { cached <- try Some(upickle.read[Cached](paths.meta.toIO, trace = false)) @@ -461,8 +465,8 @@ trait GroupExecution { case NonFatal(_) => None } } yield ( - cached.inputsHash, - for { + inputsHash = cached.inputsHash, + valOpt = for { _ <- Option.when(cached.inputsHash == inputsHash)(()) reader <- labelled.readWriterOpt (parsed, serializedPaths) <- @@ -478,7 +482,7 @@ trait GroupExecution { case NonFatal(_) => None } } yield (Val(parsed), serializedPaths), - cached.valueHash + valueHash = cached.valueHash ) } From 4f5187b68ac1282a3c2b7677ae40d612072f5cd2 Mon Sep 17 00:00:00 2001 From: Tobias Roeser Date: Fri, 7 Nov 2025 13:15:31 +0100 Subject: [PATCH 42/45] Fix expected test output --- example/kotlinlib/publishing/9-repackage-config/build.mill | 2 +- example/scalalib/publishing/9-repackage-config/build.mill | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/example/kotlinlib/publishing/9-repackage-config/build.mill b/example/kotlinlib/publishing/9-repackage-config/build.mill index 83a97c99ff14..96627654f8d8 100644 --- a/example/kotlinlib/publishing/9-repackage-config/build.mill +++ b/example/kotlinlib/publishing/9-repackage-config/build.mill @@ -47,7 +47,7 @@ Qux.value: 31337 ...Test run bar.BarTests finished: 0 failed, 0 ignored, 1 total, ...s > ./mill show foo.repackagedJar -".../out/foo/repackagedJar.dest/out.jar" +"...$MILL_OUT/foo/repackagedJar.dest/out.jar" > ./out/foo/repackagedJar.dest/out.jar Foo.value:

hello

diff --git a/example/scalalib/publishing/9-repackage-config/build.mill b/example/scalalib/publishing/9-repackage-config/build.mill index 48cda4ce64b7..30e22ab17008 100644 --- a/example/scalalib/publishing/9-repackage-config/build.mill +++ b/example/scalalib/publishing/9-repackage-config/build.mill @@ -59,7 +59,7 @@ Qux.value: 31337 ...Test run bar.BarTests finished: 0 failed, 0 ignored, 1 total, ...s > ./mill show foo.repackagedJar -".../out/foo/repackagedJar.dest/out.jar" +"...$MILL_OUT/foo/repackagedJar.dest/out.jar" > ./out/foo/repackagedJar.dest/out.jar Foo.value:

hello

From ae976d3b42be4c87134d4cf5e380a75817e56975 Mon Sep 17 00:00:00 2001 From: Tobias Roeser Date: Fri, 7 Nov 2025 13:52:32 +0100 Subject: [PATCH 43/45] Fix RPC server communication, (after git branch merge) again --- core/api/src/mill/api/PathRef.scala | 3 +-- .../NoMappedRootsMillRcpWireTransport.scala | 25 +++++++++++++++++++ .../javalib/worker/SubprocessZincApi.scala | 14 +++++------ .../mill/javalib/zinc/ZincWorkerMain.scala | 16 +++--------- .../src/mill/rpc/MillRpcWireTransport.scala | 1 + .../src/mill/daemon/MillBuildBootstrap.scala | 7 ++---- 6 files changed, 40 insertions(+), 26 deletions(-) create mode 100644 libs/javalib/worker/src/mill/javalib/worker/NoMappedRootsMillRcpWireTransport.scala diff --git a/core/api/src/mill/api/PathRef.scala b/core/api/src/mill/api/PathRef.scala index 2b1007a1901d..1078eab5c773 100644 --- a/core/api/src/mill/api/PathRef.scala +++ b/core/api/src/mill/api/PathRef.scala @@ -2,7 +2,6 @@ package mill.api import mill.api.DummyOutputStream import mill.api.daemon.internal.PathRefApi -import upickle.ReadWriter as RW import java.nio.file as jnio import java.security.{DigestOutputStream, MessageDigest} @@ -222,7 +221,7 @@ object PathRef { /** * Default JSON formatter for [[PathRef]]. */ - implicit def jsonFormatter: RW[PathRef] = upickle.readwriter[String].bimap[PathRef]( + implicit def jsonFormatter: upickle.ReadWriter[PathRef] = upickle.readwriter[String].bimap[PathRef]( p => { storeSerializedPaths(p) p.toStringPrefix + MappedRoots.encodeKnownRootsInPath(p.path) diff --git a/libs/javalib/worker/src/mill/javalib/worker/NoMappedRootsMillRcpWireTransport.scala b/libs/javalib/worker/src/mill/javalib/worker/NoMappedRootsMillRcpWireTransport.scala new file mode 100644 index 000000000000..ce3f418714d0 --- /dev/null +++ b/libs/javalib/worker/src/mill/javalib/worker/NoMappedRootsMillRcpWireTransport.scala @@ -0,0 +1,25 @@ +package mill.javalib.worker + +import mill.api.MappedRoots +import mill.rpc.MillRpcWireTransport + +import java.io.{BufferedReader, PrintStream} + +class NoMappedRootsMillRcpWireTransport( + name: String, + serverToClient: BufferedReader, + clientToServer: PrintStream, + writeSynchronizer: AnyRef +) extends MillRpcWireTransport( + name = name, + serverToClient = serverToClient, + clientToServer = clientToServer, + writeSynchronizer = writeSynchronizer + ) { + override def writeSerialized[A: upickle.Writer](message: A, log: String => Unit): Unit = { + // RPC communication is local and uncached, so we don't want to use any root mapping + MappedRoots.withMapping(Seq()) { + super.writeSerialized(message, log) + } + } +} diff --git a/libs/javalib/worker/src/mill/javalib/worker/SubprocessZincApi.scala b/libs/javalib/worker/src/mill/javalib/worker/SubprocessZincApi.scala index 28bdc91d9443..00ed45e16fd1 100644 --- a/libs/javalib/worker/src/mill/javalib/worker/SubprocessZincApi.scala +++ b/libs/javalib/worker/src/mill/javalib/worker/SubprocessZincApi.scala @@ -1,4 +1,5 @@ package mill.javalib.worker + import mill.api.* import mill.api.daemon.internal.CompileProblemReporter import mill.client.{LaunchedServer, ServerLauncher} @@ -93,13 +94,12 @@ class SubprocessZincApi( (in, out) => { val serverToClient = use(BufferedReader(InputStreamReader(in))) val clientToServer = use(PrintStream(out)) - val wireTransport = - MillRpcWireTransport( - debugName, - serverToClient, - clientToServer, - writeSynchronizer = clientToServer - ) + val wireTransport = NoMappedRootsMillRcpWireTransport( + name = debugName, + serverToClient = serverToClient, + clientToServer = clientToServer, + writeSynchronizer = clientToServer + ) val init = ZincWorkerRpcServer.Initialize(compilerBridgeWorkspace = compilerBridge.workspace) diff --git a/libs/javalib/worker/src/mill/javalib/zinc/ZincWorkerMain.scala b/libs/javalib/worker/src/mill/javalib/zinc/ZincWorkerMain.scala index ce7237fd42f7..9b5beb209bb3 100644 --- a/libs/javalib/worker/src/mill/javalib/zinc/ZincWorkerMain.scala +++ b/libs/javalib/worker/src/mill/javalib/zinc/ZincWorkerMain.scala @@ -1,9 +1,9 @@ package mill.javalib.zinc -import mill.api.{MappedRoots, SystemStreamsUtils} import mill.api.daemon.{DummyInputStream, SystemStreams} +import mill.api.SystemStreamsUtils import mill.client.lock.Locks -import mill.rpc.MillRpcWireTransport +import mill.javalib.worker.NoMappedRootsMillRcpWireTransport import mill.server.Server import mill.server.Server.ConnectionData import pprint.{TPrint, TPrintColors} @@ -63,16 +63,8 @@ object ZincWorkerMain { Using.Manager { use => val stdin = use(BufferedReader(InputStreamReader(connectionData.clientToServer))) val stdout = use(PrintStream(connectionData.serverToClient)) - class Transport() - extends MillRpcWireTransport(serverName, stdin, stdout, writeSynchronizer) { - override def writeSerialized[A: upickle.Writer](message: A, log: String => Unit): Unit = { - // RPC communication is local and uncached, so we don't want to use any root mapping - MappedRoots.withMapping(Seq()) { - super.writeSerialized(message, log) - } - } - } - val transport = Transport() + val transport = + NoMappedRootsMillRcpWireTransport(serverName, stdin, stdout, writeSynchronizer) val server = ZincWorkerRpcServer(worker, serverName, transport, setIdle, serverLog) // Make sure stdout and stderr is sent to the client diff --git a/libs/rpc/src/mill/rpc/MillRpcWireTransport.scala b/libs/rpc/src/mill/rpc/MillRpcWireTransport.scala index abb6d3a31924..d859c4347604 100644 --- a/libs/rpc/src/mill/rpc/MillRpcWireTransport.scala +++ b/libs/rpc/src/mill/rpc/MillRpcWireTransport.scala @@ -5,6 +5,7 @@ import upickle.{Reader, Writer} import java.io.{BufferedReader, PrintStream} import scala.annotation.tailrec + class MillRpcWireTransport( val name: String, serverToClient: BufferedReader, diff --git a/runner/daemon/src/mill/daemon/MillBuildBootstrap.scala b/runner/daemon/src/mill/daemon/MillBuildBootstrap.scala index be8186daeab9..10e668f3739f 100644 --- a/runner/daemon/src/mill/daemon/MillBuildBootstrap.scala +++ b/runner/daemon/src/mill/daemon/MillBuildBootstrap.scala @@ -12,6 +12,7 @@ import mill.constants.CodeGenConstants.* import mill.constants.OutFiles.{millBuild, millRunnerState} import mill.api.daemon.Watchable import mill.api.internal.RootModule +import mill.constants.PathVars import mill.internal.PrefixLogger import mill.meta.MillBuildRootModule import mill.meta.CliImports @@ -88,11 +89,7 @@ class MillBuildBootstrap( def evaluateRec(depth: Int): RunnerState = { // We need relocatable PathRef for meta-builds for a stable classpathSig - MappedRoots.requireMappedPaths( - mill.constants.PathVars.WORKSPACE, - mill.constants.PathVars.HOME, - mill.constants.PathVars.MILL_OUT - ) + MappedRoots.requireMappedPaths(PathVars.WORKSPACE, PathVars.HOME, PathVars.MILL_OUT) logger.withChromeProfile(s"meta-level $depth") { // println(s"+evaluateRec($depth) " + recRoot(projectRoot, depth)) From 83e83359ef94deca8f273139fa6bcaab37d155db Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Fri, 7 Nov 2025 13:04:00 +0000 Subject: [PATCH 44/45] [autofix.ci] apply automated fixes --- core/api/src/mill/api/PathRef.scala | 63 ++++++++++--------- .../javalib/worker/SubprocessZincApi.scala | 2 +- 2 files changed, 33 insertions(+), 32 deletions(-) diff --git a/core/api/src/mill/api/PathRef.scala b/core/api/src/mill/api/PathRef.scala index 1078eab5c773..29d72057ea09 100644 --- a/core/api/src/mill/api/PathRef.scala +++ b/core/api/src/mill/api/PathRef.scala @@ -221,37 +221,38 @@ object PathRef { /** * Default JSON formatter for [[PathRef]]. */ - implicit def jsonFormatter: upickle.ReadWriter[PathRef] = upickle.readwriter[String].bimap[PathRef]( - p => { - storeSerializedPaths(p) - p.toStringPrefix + MappedRoots.encodeKnownRootsInPath(p.path) - }, - { - case s"$prefix:$valid0:$hex:$pathVal" if prefix == "ref" || prefix == "qref" => - - val path = os.Path(MappedRoots.decodeKnownRootsInPath(pathVal)) - val quick = prefix match { - case "qref" => true - case "ref" => false - } - val validOrig = valid0 match { - case "v0" => Revalidate.Never - case "v1" => Revalidate.Once - case "vn" => Revalidate.Always - } - // Parsing to a long and casting to an int is the only way to make - // round-trip handling of negative numbers work =( - val sig = java.lang.Long.parseLong(hex, 16).toInt - val pr = PathRef(path, quick, sig, revalidate = validOrig) - validatedPaths.value.revalidateIfNeededOrThrow(pr) - storeSerializedPaths(pr) - pr - case s => - mill.api.BuildCtx.withFilesystemCheckerDisabled( - PathRef(os.Path(MappedRoots.decodeKnownRootsInPath(s), currentOverrideModulePath.value)) - ) - } - ) + implicit def jsonFormatter: upickle.ReadWriter[PathRef] = + upickle.readwriter[String].bimap[PathRef]( + p => { + storeSerializedPaths(p) + p.toStringPrefix + MappedRoots.encodeKnownRootsInPath(p.path) + }, + { + case s"$prefix:$valid0:$hex:$pathVal" if prefix == "ref" || prefix == "qref" => + + val path = os.Path(MappedRoots.decodeKnownRootsInPath(pathVal)) + val quick = prefix match { + case "qref" => true + case "ref" => false + } + val validOrig = valid0 match { + case "v0" => Revalidate.Never + case "v1" => Revalidate.Once + case "vn" => Revalidate.Always + } + // Parsing to a long and casting to an int is the only way to make + // round-trip handling of negative numbers work =( + val sig = java.lang.Long.parseLong(hex, 16).toInt + val pr = PathRef(path, quick, sig, revalidate = validOrig) + validatedPaths.value.revalidateIfNeededOrThrow(pr) + storeSerializedPaths(pr) + pr + case s => + mill.api.BuildCtx.withFilesystemCheckerDisabled( + PathRef(os.Path(MappedRoots.decodeKnownRootsInPath(s), currentOverrideModulePath.value)) + ) + } + ) private[mill] val currentOverrideModulePath = DynamicVariable[os.Path](null) // scalafix:off; we want to hide the unapply method diff --git a/libs/javalib/worker/src/mill/javalib/worker/SubprocessZincApi.scala b/libs/javalib/worker/src/mill/javalib/worker/SubprocessZincApi.scala index 00ed45e16fd1..f3c67fb83759 100644 --- a/libs/javalib/worker/src/mill/javalib/worker/SubprocessZincApi.scala +++ b/libs/javalib/worker/src/mill/javalib/worker/SubprocessZincApi.scala @@ -7,7 +7,7 @@ import mill.javalib.api.internal.* import mill.javalib.internal.{RpcProblemMessage, ZincCompilerBridgeProvider} import mill.javalib.zinc.ZincWorkerRpcServer.ReporterMode import mill.javalib.zinc.{ZincApi, ZincWorker, ZincWorkerRpcServer} -import mill.rpc.{MillRpcChannel, MillRpcClient, MillRpcWireTransport} +import mill.rpc.{MillRpcChannel, MillRpcClient} import mill.util.CachedFactoryWithInitData import java.io.* From 3e02a5707939e54110aa6f3217a1624fc19041ef Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Sun, 23 Nov 2025 17:27:48 +0000 Subject: [PATCH 45/45] [autofix.ci] apply automated fixes --- runner/daemon/src/mill/daemon/MillBuildBootstrap.scala | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/runner/daemon/src/mill/daemon/MillBuildBootstrap.scala b/runner/daemon/src/mill/daemon/MillBuildBootstrap.scala index 6a375a8290a5..be9efab70845 100644 --- a/runner/daemon/src/mill/daemon/MillBuildBootstrap.scala +++ b/runner/daemon/src/mill/daemon/MillBuildBootstrap.scala @@ -260,7 +260,8 @@ class MillBuildBootstrap( selectiveExecution = selectiveExecution, offline = offline, workerCache = newWorkerCache, - codeSignatures = nestedState.frames.headOption.map(_.codeSignatures).getOrElse(Map.empty), + codeSignatures = + nestedState.frames.headOption.map(_.codeSignatures).getOrElse(Map.empty), rootModule = buildFileApi.rootModule, // We want to use the grandparent buildHash, rather than the parent // buildHash, because the parent build changes are instead detected