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/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 83bbc3b14cfe..53b3a8b734c6 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
+ private[mill] val pathVal: String = PathRef.encodeKnownRootsInPath(path)
+
def recomputeSig(): Int = PathRef.apply(path, quick).sig
def validate(): Boolean = recomputeSig() == sig
@@ -38,16 +41,33 @@ 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()
+ }
+
+ // 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 {
@@ -189,18 +209,60 @@ object PathRef {
}
}
+ private[mill] val outPathOverride: DynamicVariable[Option[os.Path]] = DynamicVariable(None)
+
+ private[api] type KnownRoots = Seq[(replacement: String, root: os.Path)]
+
+ 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)
+ )
+ }
+
+ 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()}"
+ } else ""
+ }"
+ }.getOrElse(p.toString)
+ }
+
+ private[api] def decodeKnownRootsInPath(encoded: String): String = {
+ 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
+ }
+ }
+
/**
* Default JSON formatter for [[PathRef]].
*/
implicit def jsonFormatter: RW[PathRef] = upickle.readwriter[String].bimap[PathRef](
p => {
storeSerializedPaths(p)
- p.toString()
+ 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
@@ -219,7 +281,7 @@ object PathRef {
pr
case s =>
mill.api.BuildCtx.withFilesystemCheckerDisabled(
- PathRef(os.Path(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 dd8717a6949d..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)
-
- Files.setPosixFilePermissions(file.wrapped, PosixFilePermissions.fromString("rwxrw----"))
- val rwxSig = PathRef(file, quick).sig
-
- assert(rwSig != rwxSig)
+ 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)
+ }
}
else "Test Skipped on non-POSIX host"
@@ -61,47 +73,86 @@ 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)
}
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(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)
+ }
}
- 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(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"))
+ )
+ }
+ }
+ }
+ }
}
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 e32b009a491c..3fb1a11fedfb 100644
--- a/core/exec/src/mill/exec/GroupExecution.scala
+++ b/core/exec/src/mill/exec/GroupExecution.scala
@@ -60,7 +60,7 @@ trait GroupExecution {
executionContext: mill.api.TaskCtx.Fork.Api,
exclusive: Boolean,
upstreamPathRefs: Seq[PathRef]
- ): GroupExecution.Results = {
+ ): GroupExecution.Results = PathRef.outPathOverride.withValue(Some(outPath)) {
val externalInputsHash = MurmurHash3.orderedHash(
group.flatMap(_.inputs).filter(!group.contains(_))
@@ -104,7 +104,7 @@ trait GroupExecution {
case single if labelled.ctx.enclosingModule.buildOverrides.contains(single) =>
val jsonData = labelled.ctx.enclosingModule.buildOverrides(single)
- import collection.JavaConverters._
+ import scala.jdk.CollectionConverters.*
def rec(x: ujson.Value): ujson.Value = x match {
case ujson.Str(s) => mill.constants.Util.interpolateEnvVars(s, envWithPwd.asJava)
case ujson.Arr(xs) => ujson.Arr(xs.map(rec))
@@ -214,8 +214,8 @@ trait GroupExecution {
newEvaluated.toSeq,
cached = if (labelled.isInstanceOf[Task.Input[?]]) null else false,
inputsHash,
- cached.map(_._1).getOrElse(-1),
- !cached.map(_._3).contains(valueHash),
+ cached.map(_.inputHash).getOrElse(-1),
+ !cached.map(_.valueHash).contains(valueHash),
serializedPaths
)
}
@@ -424,7 +424,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))
@@ -589,7 +589,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[?] =>
diff --git a/example/androidlib/java/1-hello-world/build.mill b/example/androidlib/java/1-hello-world/build.mill
index 69b38d78ff0d..bf76cc245dc1 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 88709cab8447..f2af0425ddba 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 8ed9a401f4b5..133fdf6a0707 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 669fee9f566a..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 fa1c543c6b10..7c0c088e7535 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 d8a42f9762e9..25dbc5ff25c4 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 18ce8f4ce9a7..9d7aa546dcb9 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 4ac5ba3b98a4..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 7e6bbd50c3ac..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 70f409653e32..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 fda9104f287d..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:
> ./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:
diff --git a/example/extending/imports/2-mvn-deps-scala/build.mill b/example/extending/imports/2-mvn-deps-scala/build.mill
index da924f68e671..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:
> ./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:
diff --git a/example/extending/metabuild/4-meta-build/build.mill b/example/extending/metabuild/4-meta-build/build.mill
index b8b0a56cfd43..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 b1da6f0571a3..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 006a4749c8a0..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 0cb186843ed6..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 98cbbaecb037..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 fcac91f6de98..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 4b6a508fd561..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 a512437f7652..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 570dd57b59ce..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 995538cfca06..64c063437618 100644
--- a/example/javalib/basic/1-script/build.mill
+++ b/example/javalib/basic/1-script/build.mill
@@ -16,7 +16,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 f23038a5b082..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 d9031d13d04f..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 150c9f8be48a..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 70d599571def..e234033c7ede 100644
--- a/example/javalib/publishing/9-repackage-config/build.mill
+++ b/example/javalib/publishing/9-repackage-config/build.mill
@@ -44,7 +44,7 @@ Bar.value: world
Qux.value: 31337
> ./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 339ef9c07355..75e43bdc6d18 100644
--- a/example/kotlinlib/basic/1-script/build.mill
+++ b/example/kotlinlib/basic/1-script/build.mill
@@ -16,7 +16,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 7cc8bd8d94a9..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 ed47d4eafa03..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 21ee94f61468..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 a399d898f067..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 ef6454532025..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 75f91333158e..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 0c2349d15cd5..440a4cf25c66 100644
--- a/example/scalalib/basic/1-script/build.mill
+++ b/example/scalalib/basic/1-script/build.mill
@@ -37,7 +37,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 b9ea519a6e6e..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 15bd3b362567..7c28a908cafc 100644
--- a/example/scalalib/basic/3-simple/build.mill
+++ b/example/scalalib/basic/3-simple/build.mill
@@ -105,7 +105,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 3732d0a1cdc4..5221ad12b210 100644
--- a/example/scalalib/basic/6-programmatic/build.mill
+++ b/example/scalalib/basic/6-programmatic/build.mill
@@ -82,7 +82,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 21fb61260b21..ab338f056185 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 94f1f27fb1d2..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 6103b6ff4cea..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 f6a43a8451e8..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 73668b29a008..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 b9eae71f61c6..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 f85403e81e10..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 7bbec86eb8fc..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/libs/javalib/api/src/mill/javalib/api/internal/JvmWorkerApi.scala b/libs/javalib/api/src/mill/javalib/api/internal/JvmWorkerApi.scala
index 8b4d41d536a9..776ecef30c15 100644
--- a/libs/javalib/api/src/mill/javalib/api/internal/JvmWorkerApi.scala
+++ b/libs/javalib/api/src/mill/javalib/api/internal/JvmWorkerApi.scala
@@ -48,8 +48,8 @@ trait JvmWorkerApi extends PublicJvmWorkerApi {
compileJava(
ZincCompileJava(
upstreamCompileOutput = upstreamCompileOutput,
- sources = sources,
- compileClasspath = compileClasspath,
+ sources = sources.map(LocalPath.apply),
+ compileClasspath = compileClasspath.map(LocalPath.apply),
javacOptions = jOpts.compiler,
incrementalCompilation = incrementalCompilation
),
@@ -81,14 +81,14 @@ trait JvmWorkerApi extends PublicJvmWorkerApi {
compileMixed(
ZincCompileMixed(
upstreamCompileOutput = upstreamCompileOutput,
- sources = sources,
- compileClasspath = compileClasspath,
+ sources = sources.map(LocalPath.apply),
+ compileClasspath = compileClasspath.map(LocalPath.apply),
javacOptions = jOpts.compiler,
scalaVersion = scalaVersion,
scalaOrganization = scalaOrganization,
scalacOptions = scalacOptions,
- compilerClasspath = compilerClasspath,
- scalacPluginClasspath = scalacPluginClasspath,
+ compilerClasspath = compilerClasspath.map(LocalPathRef.apply),
+ scalacPluginClasspath = scalacPluginClasspath.map(LocalPathRef.apply),
incrementalCompilation = incrementalCompilation,
auxiliaryClassFileExtensions = auxiliaryClassFileExtensions
),
@@ -112,8 +112,8 @@ trait JvmWorkerApi extends PublicJvmWorkerApi {
ZincScaladocJar(
scalaVersion = scalaVersion,
scalaOrganization = scalaOrganization,
- compilerClasspath = compilerClasspath,
- scalacPluginClasspath = scalacPluginClasspath,
+ compilerClasspath = compilerClasspath.map(LocalPathRef.apply),
+ scalacPluginClasspath = scalacPluginClasspath.map(LocalPathRef.apply),
args = args
),
javaHome = javaHome
diff --git a/libs/javalib/api/src/mill/javalib/api/internal/zinc_operations.scala b/libs/javalib/api/src/mill/javalib/api/internal/zinc_operations.scala
index be2e95bd51e7..9c05c36eee5a 100644
--- a/libs/javalib/api/src/mill/javalib/api/internal/zinc_operations.scala
+++ b/libs/javalib/api/src/mill/javalib/api/internal/zinc_operations.scala
@@ -3,12 +3,45 @@ package mill.javalib.api.internal
import mill.api.PathRef
import mill.javalib.api.CompilationResult
import mill.api.JsonFormatters.*
+import mill.api.PathRef.Revalidate
+import upickle.ReadWriter
+
+case class LocalPath private (path: String) derives upickle.ReadWriter {
+ def toOsPath: os.Path = os.Path(path)
+ def toIO: java.io.File = java.io.File(path)
+ def toNIO: java.nio.file.Path = java.nio.file.Paths.get(path)
+ override def toString(): String = path
+}
+object LocalPath {
+ implicit def apply(path: os.Path): LocalPath = LocalPath(path.toString)
+}
+
+case class LocalPathRef(path: LocalPath, quick: Boolean, sig: Int, revalidate: PathRef.Revalidate)
+ derives upickle.ReadWriter {
+ def toPathRef: PathRef = PathRef(path.toOsPath, quick, sig, revalidate)
+}
+object LocalPathRef {
+ implicit def apply(pr: PathRef): LocalPathRef =
+ LocalPathRef(pr.path, pr.quick, pr.sig, pr.revalidate)
+ implicit def rwRevalidate: upickle.ReadWriter[Revalidate] = upickle.readwriter[String].bimap(
+ {
+ case Revalidate.Never => "Never"
+ case Revalidate.Once => "Once"
+ case Revalidate.Always => "Always"
+ },
+ {
+ case "Never" => Revalidate.Never
+ case "Once" => Revalidate.Once
+ case "Always" => Revalidate.Always
+ }
+ )
+}
/** Compiles Java-only sources. */
case class ZincCompileJava(
upstreamCompileOutput: Seq[CompilationResult],
- sources: Seq[os.Path],
- compileClasspath: Seq[os.Path],
+ sources: Seq[LocalPath],
+ compileClasspath: Seq[LocalPath],
javacOptions: JavaCompilerOptions,
incrementalCompilation: Boolean
) derives upickle.ReadWriter
@@ -16,14 +49,14 @@ case class ZincCompileJava(
/** Compiles Java and Scala sources. */
case class ZincCompileMixed(
upstreamCompileOutput: Seq[CompilationResult],
- sources: Seq[os.Path],
- compileClasspath: Seq[os.Path],
+ sources: Seq[LocalPath],
+ compileClasspath: Seq[LocalPath],
javacOptions: JavaCompilerOptions,
scalaVersion: String,
scalaOrganization: String,
scalacOptions: Seq[String],
- compilerClasspath: Seq[PathRef],
- scalacPluginClasspath: Seq[PathRef],
+ compilerClasspath: Seq[LocalPathRef],
+ scalacPluginClasspath: Seq[LocalPathRef],
incrementalCompilation: Boolean,
auxiliaryClassFileExtensions: Seq[String]
) derives upickle.ReadWriter
@@ -32,7 +65,7 @@ case class ZincCompileMixed(
case class ZincScaladocJar(
scalaVersion: String,
scalaOrganization: String,
- compilerClasspath: Seq[PathRef],
- scalacPluginClasspath: Seq[PathRef],
+ compilerClasspath: Seq[LocalPathRef],
+ scalacPluginClasspath: Seq[LocalPathRef],
args: Seq[String]
) derives upickle.ReadWriter
diff --git a/libs/javalib/src/mill/javalib/TestModuleUtil.scala b/libs/javalib/src/mill/javalib/TestModuleUtil.scala
index 553312dbd871..a371aefab5e9 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, Logger, PathRef, Result, TaskCtx}
import mill.api.daemon.internal.TestReporter
import mill.util.Jvm
import mill.api.internal.Util
@@ -13,10 +12,7 @@ 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.testrunner.{GetTestTasksMain, TestArgs, TestResult, TestRunnerUtils}
import os.Path
@@ -162,7 +158,11 @@ 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/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
+ )
+ }
+ }
+ }
+}
diff --git a/libs/javalib/test/src/mill/javalib/HelloJavaTests.scala b/libs/javalib/test/src/mill/javalib/HelloJavaTests.scala
index 58224f0532dc..80d8070ad779 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"),
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 {
diff --git a/libs/javalib/worker/src/mill/javalib/zinc/ZincWorker.scala b/libs/javalib/worker/src/mill/javalib/zinc/ZincWorker.scala
index fc73d9ec45cf..04d220ce31a3 100644
--- a/libs/javalib/worker/src/mill/javalib/zinc/ZincWorker.scala
+++ b/libs/javalib/worker/src/mill/javalib/zinc/ZincWorker.scala
@@ -1,16 +1,10 @@
package mill.javalib.zinc
-import mill.api.JsonFormatters.*
import mill.api.PathRef
import mill.api.daemon.internal.CompileProblemReporter
import mill.api.daemon.{Logger, Result}
import mill.client.lock.*
-import mill.javalib.api.internal.{
- JavaCompilerOptions,
- ZincCompileJava,
- ZincCompileMixed,
- ZincScaladocJar
-}
+import mill.javalib.api.internal.{JavaCompilerOptions, LocalPath, LocalPathRef, ZincCompileJava, ZincCompileMixed, ZincScaladocJar}
import mill.javalib.api.{CompilationResult, JvmWorkerUtil, Versions}
import mill.javalib.internal.ZincCompilerBridgeProvider
import mill.javalib.internal.ZincCompilerBridgeProvider.AcquireResult
@@ -91,22 +85,22 @@ class ZincWorker(
): ScalaCompilerCached = {
import key.*
- val combinedCompilerJars = combinedCompilerClasspath.iterator.map(_.path.toIO).toArray
+ val combinedCompilerJars = combinedCompilerClasspath.iterator.map(_.path.toOsPath.toIO).toArray
val compiledCompilerBridge = compileBridgeIfNeeded(
scalaVersion,
scalaOrganization,
- compilerClasspath.map(_.path),
+ compilerClasspath.map(_.path.toOsPath),
compilerBridge
)
- val classLoader = classloaderCache.get(key.combinedCompilerClasspath)
+ val classLoader = classloaderCache.get(key.combinedCompilerClasspath.map(_.toPathRef))
val scalaInstance = new inc.ScalaInstance(
version = key.scalaVersion,
loader = classLoader,
loaderCompilerOnly = classLoader,
loaderLibraryOnly = ClasspathUtil.rootLoader,
libraryJars = Array(libraryJarNameGrep(
- compilerClasspath,
+ compilerClasspath.map(_.toPathRef),
// if Dotty or Scala 3.0 - 3.7, use the 2.13 version of the standard library
if (JvmWorkerUtil.enforceScala213Library(key.scalaVersion)) "2.13."
// otherwise use the library matching the Scala version
@@ -124,7 +118,7 @@ class ZincWorker(
}
override def teardown(key: ScalaCompilerCacheKey, value: ScalaCompilerCached): Unit = {
- classloaderCache.release(key.combinedCompilerClasspath)
+ classloaderCache.release(key.combinedCompilerClasspath.map(_.toPathRef))
}
}
@@ -216,7 +210,7 @@ class ZincWorker(
) { compilers =>
compileInternal(
upstreamCompileOutput = upstreamCompileOutput,
- sources = sources,
+ sources = sources.map(_.toOsPath),
compileClasspath = compileClasspath,
javacOptions = javacOptions,
scalacOptions = scalacOptions,
@@ -315,8 +309,8 @@ class ZincWorker(
private def withScalaCompilers[T](
scalaVersion: String,
scalaOrganization: String,
- compilerClasspath: Seq[PathRef],
- scalacPluginClasspath: Seq[PathRef],
+ compilerClasspath: Seq[LocalPathRef],
+ scalacPluginClasspath: Seq[LocalPathRef],
javacOptions: JavaCompilerOptions,
compilerBridge: ZincCompilerBridgeProvider
)(f: Compilers => T) = {
@@ -334,8 +328,8 @@ class ZincWorker(
private def compileInternal(
upstreamCompileOutput: Seq[CompilationResult],
- sources: Seq[os.Path],
- compileClasspath: Seq[os.Path],
+ sources: Seq[LocalPath],
+ compileClasspath: Seq[LocalPath],
javacOptions: JavaCompilerOptions,
scalacOptions: Seq[String],
compilers: Compilers,
@@ -349,7 +343,7 @@ class ZincWorker(
deps: ZincWorker.InvocationDependencies
): Result[CompilationResult] = {
- os.makeDir.all(ctx.dest)
+ os.makeDir.all(ctx.dest.toOsPath)
val classesDir = ctx.dest / "classes"
@@ -405,7 +399,7 @@ class ZincWorker(
val lookup = MockedLookup(analysisMap)
- val store = fileAnalysisStore(ctx.dest / zincCache)
+ val store = fileAnalysisStore(ctx.dest.toOsPath / zincCache)
// Fix jdk classes marked as binary dependencies, see https://github.com/com-lihaoyi/mill/pull/1904
val converter = MappedFileConverter.empty
@@ -504,7 +498,7 @@ class ZincWorker(
newResult.setup()
)
)
- Result.Success(CompilationResult(ctx.dest / zincCache, PathRef(classesDir)))
+ Result.Success(CompilationResult(ctx.dest.toOsPath / zincCache, PathRef(classesDir)))
} catch {
case e: CompileFailed =>
Result.Failure(e.toString)
@@ -576,6 +570,7 @@ class ZincWorker(
} finally doubleLock.close()
}
}
+
object ZincWorker {
/**
@@ -593,7 +588,7 @@ object ZincWorker {
/** The invocation context, always comes from the Mill's process. */
case class InvocationContext(
env: Map[String, String],
- dest: os.Path,
+ dest: LocalPath,
logDebugEnabled: Boolean,
logPromptColored: Boolean,
zincLogDebug: Boolean
@@ -601,12 +596,12 @@ object ZincWorker {
private case class ScalaCompilerCacheKey(
scalaVersion: String,
- compilerClasspath: Seq[PathRef],
- scalacPluginClasspath: Seq[PathRef],
+ compilerClasspath: Seq[LocalPathRef],
+ scalacPluginClasspath: Seq[LocalPathRef],
scalaOrganization: String,
javacOptions: JavaCompilerOptions
) {
- val combinedCompilerClasspath: Seq[PathRef] = compilerClasspath ++ scalacPluginClasspath
+ val combinedCompilerClasspath: Seq[LocalPathRef] = compilerClasspath ++ scalacPluginClasspath
}
private case class ScalaCompilerCached(classLoader: URLClassLoader, compilers: Compilers)
diff --git a/libs/javalib/worker/src/mill/javalib/zinc/ZincWorkerRpcServer.scala b/libs/javalib/worker/src/mill/javalib/zinc/ZincWorkerRpcServer.scala
index 8dc1fc919a20..18d2355422c8 100644
--- a/libs/javalib/worker/src/mill/javalib/zinc/ZincWorkerRpcServer.scala
+++ b/libs/javalib/worker/src/mill/javalib/zinc/ZincWorkerRpcServer.scala
@@ -4,7 +4,7 @@ import mill.api.JsonFormatters.*
import mill.api.daemon.{Logger, Result}
import mill.api.daemon.internal.CompileProblemReporter
import mill.javalib.api.CompilationResult
-import mill.javalib.api.internal.{ZincCompileJava, ZincCompileMixed, ZincScaladocJar}
+import mill.javalib.api.internal.{LocalPath, ZincCompileJava, ZincCompileMixed, ZincScaladocJar}
import mill.javalib.internal.{RpcCompileProblemReporterMessage, ZincCompilerBridgeProvider}
import mill.rpc.*
import mill.server.Server
@@ -36,6 +36,7 @@ class ZincWorkerRpcServer(
serverToClient: MillRpcChannel[ServerToClient]
): MillRpcChannel[ClientToServer] = setIdle.doWork {
val result = Timed {
+
// This is an ugly hack. `ConsoleOut` is sealed, but we need to provide a way to send these logs to the Mill server
// over RPC, so we hijack `PrintStream` by overriding the methods that `ConsoleOut` uses.
//
@@ -52,7 +53,7 @@ class ZincWorkerRpcServer(
def makeCompilerBridge(clientRequestId: MillRpcRequestId) =
ZincCompilerBridgeProvider(
- workspace = initialize.compilerBridgeWorkspace,
+ workspace = initialize.compilerBridgeWorkspace.toOsPath,
logInfo = log.info,
acquire = (scalaVersion, scalaOrganization) =>
serverToClient(
@@ -139,13 +140,14 @@ class ZincWorkerRpcServer(
result.result
}
}
+
object ZincWorkerRpcServer {
/**
* @param compilerBridgeWorkspace The workspace to use for the compiler bridge.
*/
case class Initialize(
- compilerBridgeWorkspace: os.Path
+ compilerBridgeWorkspace: LocalPath,
) derives ReadWriter
sealed trait ReporterMode derives ReadWriter {
diff --git a/runner/daemon/src/mill/daemon/MillBuildBootstrap.scala b/runner/daemon/src/mill/daemon/MillBuildBootstrap.scala
index 2f3662f78013..8e1752f02f4d 100644
--- a/runner/daemon/src/mill/daemon/MillBuildBootstrap.scala
+++ b/runner/daemon/src/mill/daemon/MillBuildBootstrap.scala
@@ -71,11 +71,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(
diff --git a/runner/daemon/src/mill/daemon/MillDaemonMain.scala b/runner/daemon/src/mill/daemon/MillDaemonMain.scala
index 0fe2bf0eaac0..78589957d848 100644
--- a/runner/daemon/src/mill/daemon/MillDaemonMain.scala
+++ b/runner/daemon/src/mill/daemon/MillDaemonMain.scala
@@ -1,28 +1,29 @@
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())
}
@@ -38,29 +39,32 @@ object MillDaemonMain {
val args =
Args(getClass.getName, args0).fold(err => throw IllegalArgumentException(err), identity)
- if (Properties.isWin)
- // 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
- sys.props("coursier.windows.disable-ffm") = "true"
+ PathRef.outPathOverride.withValue(Some(args.outDir)) {
+ if (Properties.isWin)
+ // 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
+ sys.props("coursier.windows.disable-ffm") = "true"
- // Take into account proxy-related Java properties
- coursier.Resolve.proxySetup()
+ // Take into account proxy-related Java properties
+ coursier.Resolve.proxySetup()
- 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)
- new MillDaemonMain(
- daemonDir = args.daemonDir,
- acceptTimeout = acceptTimeout,
- Locks.files(args.daemonDir.toString),
- outMode = args.outMode
- ).run()
+ new MillDaemonMain(
+ daemonDir = args.daemonDir,
+ acceptTimeout = acceptTimeout,
+ Locks.files(args.daemonDir.toString),
+ outMode = args.outMode,
+ outDir = args.outDir
+ ).run()
- System.exit(ClientUtil.ExitServerCodeWhenIdle())
+ System.exit(ClientUtil.ExitServerCodeWhenIdle())
+ }
}
}
}
@@ -68,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 stateCache0 = RunnerState.empty
- val out: os.Path = os.Path(OutFiles.outFor(outMode), BuildCtx.workspaceRoot)
-
- val outLock = MillMain0.doubleLock(out)
+ val outLock = MillMain0.doubleLock(outDir)
def main0(
args: Array[String],
@@ -103,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 031cac415cde..256ab7a3f66d 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.internal.{
Colors,
JsonArrayLogger,
@@ -65,12 +64,12 @@ object MillMain0 {
private def withStreams[T](
bspMode: Boolean,
- streams: SystemStreams
- )(thunk: SystemStreams => T): T =
+ 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
@@ -93,10 +92,13 @@ object MillMain0 {
errFileStream.close()
outFileStream.close()
}
- } else
+ } else {
+ BuildCtx.workspaceRoot / os.RelPath(OutFiles.outFor(OutFolderMode.REGULAR))
mill.api.SystemStreamsUtils.withStreams(streams) {
thunk(streams)
}
+ }
+ }
def main0(
args: Array[String],
@@ -109,403 +111,406 @@ object MillMain0 {
initialSystemProperties: Map[String, String],
systemExit: Server.StopServer,
daemonDir: os.Path,
- outLock: Lock
- ): (Boolean, RunnerState) =
- mill.api.daemon.internal.MillScalaParser.current.withValue(MillScalaParserImpl) {
- os.SubProcess.env.withValue(env) {
- val parserResult = MillCliConfig.parse(args)
- // Detect when we're running in BSP mode as early as possible,
- // and ensure we don't log to the default stdout or use the default
- // stdin, meant to be used for BSP JSONRPC communication, where those
- // logs would be lost.
- // 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 =>
- parserResult match {
- // Cannot parse args
- case Result.Failure(msg) =>
- streams.err.println(msg)
- (false, RunnerState.empty)
-
- case Result.Success(config) if config.help.value =>
- streams.out.println(MillCliConfig.longUsageText)
- (true, RunnerState.empty)
-
- case Result.Success(config) if config.helpAdvanced.value =>
- streams.out.println(MillCliConfig.helpAdvancedUsageText)
- (true, RunnerState.empty)
-
- case Result.Success(config) if config.showVersion.value =>
- def prop(k: String) = System.getProperty(k, s"")
-
- val javaVersion = prop("java.version")
- val javaVendor = prop("java.vendor")
- val javaHome = prop("java.home")
- val fileEncoding = prop("file.encoding")
- val osName = prop("os.name")
- val osVersion = prop("os.version")
- val osArch = prop("os.arch")
- streams.out.println(
- s"""Mill Build Tool version ${BuildInfo.millVersion}
- |Java version: $javaVersion, vendor: $javaVendor, runtime: $javaHome
- |Default locale: ${Locale.getDefault()}, platform encoding: $fileEncoding
- |OS name: "$osName", version: $osVersion, arch: $osArch""".stripMargin
- )
- (true, RunnerState.empty)
-
- case Result.Success(config) if config.noDaemonEnabled > 1 =>
- streams.err.println(
- "Only one of -i/--interactive, --no-daemon or --bsp may be given"
- )
- (false, RunnerState.empty)
-
- // Check non-negative --meta-level option
- case Result.Success(config) if config.metaLevel.exists(_ < 0) =>
- streams.err.println("--meta-level cannot be negative")
- (false, RunnerState.empty)
-
- case Result.Success(config) =>
- val noColorViaEnv = env.get("NO_COLOR").exists(_.nonEmpty)
- val colored = config.color.getOrElse(mainInteractive && !noColorViaEnv)
- val colors =
- if (colored) mill.internal.Colors.Default else mill.internal.Colors.BlackWhite
-
- checkMillVersionFromFile(BuildCtx.workspaceRoot, streams.err)
-
- val maybeThreadCount =
- parseThreadCount(config.threadCountRaw, Runtime.getRuntime.availableProcessors())
-
- // 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)
-
- val viaEmulatedExternalCommand = Option.when(
- !config.bsp.value &&
- (config.leftoverArgs.value.headOption.contains("mill.bsp.BSP/install") ||
- config.leftoverArgs.value.headOption.contains("mill.bsp/install"))
- ) {
- config.leftoverArgs.value.tail match {
- case Seq() => defaultJobCount
- case Seq("--jobs", value) =>
- val asIntOpt = value.toIntOption
- asIntOpt.getOrElse {
+ 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)
+ // Detect when we're running in BSP mode as early as possible,
+ // and ensure we don't log to the default stdout or use the default
+ // stdin, meant to be used for BSP JSONRPC communication, where those
+ // logs would be lost.
+ // 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, outDir) { streams =>
+ parserResult match {
+ // Cannot parse args
+ case Result.Failure(msg) =>
+ streams.err.println(msg)
+ (false, RunnerState.empty)
+
+ case Result.Success(config) if config.help.value =>
+ streams.out.println(MillCliConfig.longUsageText)
+ (true, RunnerState.empty)
+
+ case Result.Success(config) if config.helpAdvanced.value =>
+ streams.out.println(MillCliConfig.helpAdvancedUsageText)
+ (true, RunnerState.empty)
+
+ case Result.Success(config) if config.showVersion.value =>
+ def prop(k: String) = System.getProperty(k, s"")
+
+ val javaVersion = prop("java.version")
+ val javaVendor = prop("java.vendor")
+ val javaHome = prop("java.home")
+ val fileEncoding = prop("file.encoding")
+ val osName = prop("os.name")
+ val osVersion = prop("os.version")
+ val osArch = prop("os.arch")
+ streams.out.println(
+ s"""Mill Build Tool version ${BuildInfo.millVersion}
+ |Java version: $javaVersion, vendor: $javaVendor, runtime: $javaHome
+ |Default locale: ${Locale.getDefault()}, platform encoding: $fileEncoding
+ |OS name: "$osName", version: $osVersion, arch: $osArch""".stripMargin
+ )
+ (true, RunnerState.empty)
+
+ case Result.Success(config) if config.noDaemonEnabled > 1 =>
+ streams.err.println(
+ "Only one of -i/--interactive, --no-daemon or --bsp may be given"
+ )
+ (false, RunnerState.empty)
+
+ // Check non-negative --meta-level option
+ case Result.Success(config) if config.metaLevel.exists(_ < 0) =>
+ streams.err.println("--meta-level cannot be negative")
+ (false, RunnerState.empty)
+
+ case Result.Success(config) =>
+ val noColorViaEnv = env.get("NO_COLOR").exists(_.nonEmpty)
+ val colored = config.color.getOrElse(mainInteractive && !noColorViaEnv)
+ val colors =
+ if (colored) mill.internal.Colors.Default else mill.internal.Colors.BlackWhite
+
+ checkMillVersionFromFile(BuildCtx.workspaceRoot, streams.err)
+
+ val maybeThreadCount =
+ parseThreadCount(config.threadCountRaw, Runtime.getRuntime.availableProcessors())
+
+ // 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 bspInstallModeJobCountOpt = {
+ def defaultJobCount =
+ maybeThreadCount.toOption.getOrElse(BSP.defaultJobCount)
+
+ val viaEmulatedExternalCommand = Option.when(
+ !config.bsp.value &&
+ (config.leftoverArgs.value.headOption.contains("mill.bsp.BSP/install") ||
+ config.leftoverArgs.value.headOption.contains("mill.bsp/install"))
+ ) {
+ config.leftoverArgs.value.tail match {
+ case Seq() => defaultJobCount
+ case Seq("--jobs", value) =>
+ val asIntOpt = value.toIntOption
+ asIntOpt.getOrElse {
+ streams.err.println(
+ s"Warning: ignoring --jobs value passed to ${config.leftoverArgs.value.head}"
+ )
+ defaultJobCount
+ }
+ case _ =>
streams.err.println(
- s"Warning: ignoring --jobs value passed to ${config.leftoverArgs.value.head}"
+ s"Warning: ignoring leftover arguments passed to ${config.leftoverArgs.value.head}"
)
defaultJobCount
- }
- case _ =>
- streams.err.println(
- s"Warning: ignoring leftover arguments passed to ${config.leftoverArgs.value.head}"
- )
- defaultJobCount
+ }
}
- }
- viaEmulatedExternalCommand.orElse {
- Option.when(config.bspInstall.value)(defaultJobCount)
+ viaEmulatedExternalCommand.orElse {
+ Option.when(config.bspInstall.value)(defaultJobCount)
+ }
}
- }
- val enableTicker = config.ticker
- .orElse(config.enableTicker)
- .orElse(Option.when(config.tabComplete.value)(false))
- .orElse(Option.when(config.disableTicker.value)(false))
- .getOrElse(true)
-
- val (success, nextStateCache) = {
- if (bspInstallModeJobCountOpt.isDefined) {
- BSP.install(bspInstallModeJobCountOpt.get, config.debugLog.value, streams.err)
- (true, stateCache)
- } else if (
- !bspMode && !config.jshell.value && !config.repl.value && config.leftoverArgs.value.isEmpty
- ) {
- println(MillCliConfig.shortUsageText)
-
- (true, stateCache)
-
- } else if (maybeThreadCount.errorOpt.isDefined) {
- streams.err.println(maybeThreadCount.errorOpt.get)
- (false, stateCache)
-
- } else {
- val userSpecifiedProperties =
- userSpecifiedProperties0 ++ config.extraSystemProperties
-
- val threadCount = maybeThreadCount.toOption.get
-
- def createEc(): Option[ThreadPoolExecutor] =
- 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,
- prevState: Option[RunnerState],
- tasksAndParams: Seq[String],
- streams: SystemStreams,
- millActiveCommandMessage: String,
- loggerOpt: Option[Logger] = None,
- reporter: EvaluatorApi => Int => Option[CompileProblemReporter] =
- _ => _ => None,
- extraEnv: Seq[(String, String)] = Nil,
- metaLevelOverride: Option[Int] = None
- ) = MillDaemonServer.withOutLock(
- config.noBuildLock.value,
- config.noWaitForBuildLock.value,
- out,
- millActiveCommandMessage,
- streams,
- outLock
- ) {
- def proceed(logger: Logger): Watching.Result[RunnerState] = {
- // Enter key pressed, removing mill-selective-execution.json to
- // ensure all tasks re-run even though no inputs may have changed
-
- mill.api.SystemStreamsUtils.withStreams(logger.streams) {
- mill.api.FilesystemCheckerEnabled.withValue(
- !config.noFilesystemChecker.value
- ) {
- tailManager.withOutErr(logger.streams.out, logger.streams.err) {
- new MillBuildBootstrap(
- projectRoot = BuildCtx.workspaceRoot,
- output = out,
- // 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,
- imports = config.imports,
- env = env ++ extraEnv,
- ec = ec,
- tasksAndParams = tasksAndParams,
- prevRunnerState = prevState.getOrElse(stateCache),
- logger = logger,
- requestedMetaLevel = config.metaLevel.orElse(metaLevelOverride),
- allowPositionalCommandArgs = config.allowPositional.value,
- systemExit = systemExit,
- streams0 = streams,
- selectiveExecution = config.watch.value,
- offline = config.offline.value,
- reporter = reporter,
- skipSelectiveExecution = skipSelectiveExecution,
- enableTicker = enableTicker
- ).evaluate()
+ val enableTicker = config.ticker
+ .orElse(config.enableTicker)
+ .orElse(Option.when(config.tabComplete.value)(false))
+ .orElse(Option.when(config.disableTicker.value)(false))
+ .getOrElse(true)
+
+ val (success, nextStateCache) = {
+ if (bspInstallModeJobCountOpt.isDefined) {
+ BSP.install(bspInstallModeJobCountOpt.get, config.debugLog.value, streams.err)
+ (true, stateCache)
+ } else if (
+ !bspMode && !config.jshell.value && !config.repl.value && config.leftoverArgs.value.isEmpty
+ ) {
+ println(MillCliConfig.shortUsageText)
+
+ (true, stateCache)
+
+ } else if (maybeThreadCount.errorOpt.isDefined) {
+ streams.err.println(maybeThreadCount.errorOpt.get)
+ (false, stateCache)
+
+ } else {
+ val userSpecifiedProperties =
+ userSpecifiedProperties0 ++ config.extraSystemProperties
+
+ val threadCount = maybeThreadCount.toOption.get
+
+ def createEc(): Option[ThreadPoolExecutor] =
+ if (threadCount == 1) None
+ else Some(mill.exec.ExecutionContexts.createExecutor(threadCount))
+
+ Using.resources(new TailManager(daemonDir), createEc()) { (tailManager, ec) =>
+ def runMillBootstrap(
+ skipSelectiveExecution: Boolean,
+ prevState: Option[RunnerState],
+ tasksAndParams: Seq[String],
+ streams: SystemStreams,
+ millActiveCommandMessage: String,
+ loggerOpt: Option[Logger] = None,
+ reporter: EvaluatorApi => Int => Option[CompileProblemReporter] =
+ _ => _ => None,
+ extraEnv: Seq[(String, String)] = Nil,
+ metaLevelOverride: Option[Int] = None
+ ) = MillDaemonServer.withOutLock(
+ config.noBuildLock.value,
+ config.noWaitForBuildLock.value,
+ outDir,
+ millActiveCommandMessage,
+ streams,
+ outLock
+ ) {
+ def proceed(logger: Logger): Watching.Result[RunnerState] = {
+ // Enter key pressed, removing mill-selective-execution.json to
+ // ensure all tasks re-run even though no inputs may have changed
+
+ mill.api.SystemStreamsUtils.withStreams(logger.streams) {
+ mill.api.FilesystemCheckerEnabled.withValue(
+ !config.noFilesystemChecker.value
+ ) {
+ tailManager.withOutErr(logger.streams.out, logger.streams.err) {
+ new MillBuildBootstrap(
+ projectRoot = BuildCtx.workspaceRoot,
+ 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,
+ imports = config.imports,
+ env = env ++ extraEnv,
+ ec = ec,
+ tasksAndParams = tasksAndParams,
+ prevRunnerState = prevState.getOrElse(stateCache),
+ logger = logger,
+ requestedMetaLevel = config.metaLevel.orElse(metaLevelOverride),
+ allowPositionalCommandArgs = config.allowPositional.value,
+ systemExit = systemExit,
+ streams0 = streams,
+ selectiveExecution = config.watch.value,
+ offline = config.offline.value,
+ reporter = reporter,
+ skipSelectiveExecution = skipSelectiveExecution,
+ enableTicker = enableTicker
+ ).evaluate()
+ }
}
}
}
- }
- loggerOpt match {
- case Some(logger) =>
- proceed(logger)
- case None =>
- Using.resource(getLogger(
- streams,
- config,
- enableTicker = enableTicker,
- daemonDir,
- colored = colored,
- colors = colors,
- out = out
- )) { logger =>
+ loggerOpt match {
+ case Some(logger) =>
proceed(logger)
- }
+ case None =>
+ Using.resource(getLogger(
+ streams,
+ config,
+ enableTicker = enableTicker,
+ daemonDir,
+ colored = colored,
+ colors = colors,
+ out = outDir
+ )) { logger =>
+ proceed(logger)
+ }
+ }
}
- }
- if (config.jshell.value) {
- val bootstrapped = runMillBootstrap(
- skipSelectiveExecution = false,
- Some(stateCache),
- Seq("jshell") ++ config.leftoverArgs.value,
- streams,
- "jshell",
- metaLevelOverride = Some(1)
- )
-
- (true, bootstrapped.result)
- } else if (config.repl.value) {
- val bootstrapped = runMillBootstrap(
- skipSelectiveExecution = false,
- Some(stateCache),
- Seq("console") ++ config.leftoverArgs.value,
- streams,
- "repl",
- metaLevelOverride = Some(1)
- )
-
- (true, bootstrapped.result)
- } else if (config.tabComplete.value) {
- val bootstrapped = runMillBootstrap(
- skipSelectiveExecution = false,
- Some(stateCache),
- Seq(
- "mill.tabcomplete.TabCompleteModule/complete"
- ) ++ config.leftoverArgs.value,
- streams,
- "tab-completion"
- )
-
- (true, bootstrapped.result)
- } else if (bspMode) {
- val bspLogger = getBspLogger(streams, config)
- var prevRunnerStateOpt = Option.empty[RunnerState]
- val (bspServerHandle, buildClient) =
- startBspServer(streams0, outLock, bspLogger)
- var keepGoing = true
- var errored = false
- val initCommandLogger = new PrefixLogger(bspLogger, Seq("init"))
- val watchLogger = new PrefixLogger(bspLogger, Seq("watch"))
- while (keepGoing) {
- val watchRes = runMillBootstrap(
- false,
- prevRunnerStateOpt,
- Seq("version"),
- initCommandLogger.streams,
- "BSP:initialize",
- loggerOpt = Some(initCommandLogger),
- reporter = ev => {
- val bspIdByModule = mill.bsp.worker.BspEvaluators(
- BuildCtx.workspaceRoot,
- Seq(ev),
- _ => (),
- Nil
- ).bspIdByModule
- mill.bsp.worker.Utils.getBspLoggedReporterPool(
- "",
- bspIdByModule,
- buildClient
- )
- }
+ if (config.jshell.value) {
+ val bootstrapped = runMillBootstrap(
+ skipSelectiveExecution = false,
+ Some(stateCache),
+ Seq("jshell") ++ config.leftoverArgs.value,
+ streams,
+ "jshell",
+ metaLevelOverride = Some(1)
)
- for (err <- watchRes.error)
- bspLogger.streams.err.println(err)
-
- prevRunnerStateOpt = Some(watchRes.result)
+ (true, bootstrapped.result)
+ } else if (config.repl.value) {
+ val bootstrapped = runMillBootstrap(
+ skipSelectiveExecution = false,
+ Some(stateCache),
+ Seq("console") ++ config.leftoverArgs.value,
+ streams,
+ "repl",
+ metaLevelOverride = Some(1)
+ )
- val sessionResultFuture = bspServerHandle.startSession(
- watchRes.result.frames.flatMap(_.evaluator),
- errored = watchRes.error.nonEmpty,
- watched = watchRes.watched
+ (true, bootstrapped.result)
+ } else if (config.tabComplete.value) {
+ val bootstrapped = runMillBootstrap(
+ skipSelectiveExecution = false,
+ Some(stateCache),
+ Seq(
+ "mill.tabcomplete.TabCompleteModule/complete"
+ ) ++ config.leftoverArgs.value,
+ streams,
+ "tab-completion"
)
- val res =
- if (config.bspWatch)
- Watching.watchAndWait(
- watchRes.watched,
- Watching.WatchArgs(
- setIdle = setIdle,
- colors = mill.internal.Colors.BlackWhite,
- useNotify = config.watchViaFsNotify,
- daemonDir = daemonDir
- ),
- () => sessionResultFuture.value,
- "",
- watchLogger.info(_)
- )
- else {
- watchLogger.info("Watching of build sources disabled")
- Some {
- try Success(Await.result(sessionResultFuture, Duration.Inf))
- catch {
- case NonFatal(ex) =>
- Failure(ex)
+ (true, bootstrapped.result)
+ } else if (bspMode) {
+ val bspLogger = getBspLogger(streams, config)
+ var prevRunnerStateOpt = Option.empty[RunnerState]
+ val (bspServerHandle, buildClient) =
+ startBspServer(streams0, outLock, bspLogger, outDir)
+ var keepGoing = true
+ var errored = false
+ val initCommandLogger = new PrefixLogger(bspLogger, Seq("init"))
+ val watchLogger = new PrefixLogger(bspLogger, Seq("watch"))
+ while (keepGoing) {
+ val watchRes = runMillBootstrap(
+ false,
+ prevRunnerStateOpt,
+ Seq("version"),
+ initCommandLogger.streams,
+ "BSP:initialize",
+ loggerOpt = Some(initCommandLogger),
+ reporter = ev => {
+ val bspIdByModule = mill.bsp.worker.BspEvaluators(
+ BuildCtx.workspaceRoot,
+ Seq(ev),
+ _ => (),
+ Nil
+ ).bspIdByModule
+ mill.bsp.worker.Utils.getBspLoggedReporterPool(
+ "",
+ bspIdByModule,
+ buildClient
+ )
+ }
+ )
+
+ for (err <- watchRes.error)
+ bspLogger.streams.err.println(err)
+
+ prevRunnerStateOpt = Some(watchRes.result)
+
+ val sessionResultFuture = bspServerHandle.startSession(
+ watchRes.result.frames.flatMap(_.evaluator),
+ errored = watchRes.error.nonEmpty,
+ watched = watchRes.watched
+ )
+
+ val res =
+ if (config.bspWatch)
+ Watching.watchAndWait(
+ watchRes.watched,
+ Watching.WatchArgs(
+ setIdle = setIdle,
+ colors = mill.internal.Colors.BlackWhite,
+ useNotify = config.watchViaFsNotify,
+ daemonDir = daemonDir
+ ),
+ () => sessionResultFuture.value,
+ "",
+ watchLogger.info(_)
+ )
+ else {
+ watchLogger.info("Watching of build sources disabled")
+ Some {
+ try Success(Await.result(sessionResultFuture, Duration.Inf))
+ catch {
+ case NonFatal(ex) =>
+ Failure(ex)
+ }
}
}
- }
- // Suspend any BSP request until the next call to startSession
- // (that is, until we've attempted to re-compile the build)
- bspServerHandle.resetSession()
-
- res match {
- case None =>
- // Some watched meta-build files changed
- case Some(Failure(ex)) =>
- streams.err.println("BSP server threw an exception, exiting")
- ex.printStackTrace(streams.err)
- errored = true
- keepGoing = false
- case Some(Success(BspServerResult.ReloadWorkspace)) =>
- // reload asked by client
- case Some(Success(BspServerResult.Shutdown)) =>
- streams.err.println("BSP shutdown asked by client, exiting")
- // shutdown asked by client
- keepGoing = false
- // should make the lsp4j-managed BSP server exit
- streams.in.close()
+ // Suspend any BSP request until the next call to startSession
+ // (that is, until we've attempted to re-compile the build)
+ bspServerHandle.resetSession()
+
+ res match {
+ case None =>
+ // Some watched meta-build files changed
+ case Some(Failure(ex)) =>
+ streams.err.println("BSP server threw an exception, exiting")
+ ex.printStackTrace(streams.err)
+ errored = true
+ keepGoing = false
+ case Some(Success(BspServerResult.ReloadWorkspace)) =>
+ // reload asked by client
+ case Some(Success(BspServerResult.Shutdown)) =>
+ streams.err.println("BSP shutdown asked by client, exiting")
+ // shutdown asked by client
+ keepGoing = false
+ // should make the lsp4j-managed BSP server exit
+ streams.in.close()
+ }
}
- }
- streams.err.println("Exiting BSP runner loop")
-
- (!errored, RunnerState(None, Nil, None))
- } else if (
- config.leftoverArgs.value == Seq("mill.idea.GenIdea/idea") ||
- config.leftoverArgs.value == Seq("mill.idea.GenIdea/") ||
- config.leftoverArgs.value == Seq("mill.idea/")
- ) {
- val runnerState =
- runMillBootstrap(false, None, Seq("version"), streams, "BSP:initialize")
- new mill.idea.GenIdeaImpl(
- runnerState.result.frames.flatMap(_.evaluator)
- ).run()
- (true, RunnerState(None, Nil, None))
- } else if (
- config.leftoverArgs.value == Seq("mill.eclipse.GenEclipse/eclipse") ||
- config.leftoverArgs.value == Seq("mill.eclipse.GenEclipse/") ||
- config.leftoverArgs.value == Seq("mill.eclipse/")
- ) {
- val runnerState =
- runMillBootstrap(false, None, Seq("version"), streams, "BSP:initialize")
- new mill.eclipse.GenEclipseImpl(
- runnerState.result.frames.flatMap(_.evaluator)
- ).run()
- (true, RunnerState(None, Nil, None))
- } else {
- Watching.watchLoop(
- ringBell = config.ringBell.value,
- watch = Option.when(config.watch.value)(Watching.WatchArgs(
- setIdle = setIdle,
- colors,
- useNotify = config.watchViaFsNotify,
- daemonDir = daemonDir
- )),
- streams = streams,
- evaluate =
- (skipSelectiveExecution: Boolean, prevState: Option[RunnerState]) => {
- adjustJvmProperties(userSpecifiedProperties, initialSystemProperties)
- runMillBootstrap(
- skipSelectiveExecution,
- prevState,
- config.leftoverArgs.value,
- streams,
- config.leftoverArgs.value.mkString(" ")
- )
- }
- )
+ streams.err.println("Exiting BSP runner loop")
+
+ (!errored, RunnerState(None, Nil, None))
+ } else if (
+ config.leftoverArgs.value == Seq("mill.idea.GenIdea/idea") ||
+ config.leftoverArgs.value == Seq("mill.idea.GenIdea/") ||
+ config.leftoverArgs.value == Seq("mill.idea/")
+ ) {
+ val runnerState =
+ runMillBootstrap(false, None, Seq("version"), streams, "BSP:initialize")
+ new mill.idea.GenIdeaImpl(
+ runnerState.result.frames.flatMap(_.evaluator)
+ ).run()
+ (true, RunnerState(None, Nil, None))
+ } else if (
+ config.leftoverArgs.value == Seq("mill.eclipse.GenEclipse/eclipse") ||
+ config.leftoverArgs.value == Seq("mill.eclipse.GenEclipse/") ||
+ config.leftoverArgs.value == Seq("mill.eclipse/")
+ ) {
+ val runnerState =
+ runMillBootstrap(false, None, Seq("version"), streams, "BSP:initialize")
+ new mill.eclipse.GenEclipseImpl(
+ runnerState.result.frames.flatMap(_.evaluator)
+ ).run()
+ (true, RunnerState(None, Nil, None))
+ } else {
+ Watching.watchLoop(
+ ringBell = config.ringBell.value,
+ watch = Option.when(config.watch.value)(Watching.WatchArgs(
+ setIdle = setIdle,
+ colors,
+ useNotify = config.watchViaFsNotify,
+ daemonDir = daemonDir
+ )),
+ streams = streams,
+ evaluate =
+ (skipSelectiveExecution: Boolean, prevState: Option[RunnerState]) => {
+ adjustJvmProperties(userSpecifiedProperties, initialSystemProperties)
+ runMillBootstrap(
+ skipSelectiveExecution,
+ prevState,
+ config.leftoverArgs.value,
+ streams,
+ config.leftoverArgs.value.mkString(" ")
+ )
+ }
+ )
+ }
}
}
}
- }
- if (config.ringBell.value) {
- if (success) println("\u0007")
- else {
- println("\u0007")
- Thread.sleep(250)
- println("\u0007")
+ if (config.ringBell.value) {
+ if (success) println("\u0007")
+ else {
+ println("\u0007")
+ Thread.sleep(250)
+ println("\u0007")
+ }
}
- }
- (success, nextStateCache)
+ (success, nextStateCache)
+ }
}
}
}
}
+ }
/**
* Starts the BSP server
@@ -515,13 +520,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 =
@@ -532,7 +537,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 65b0fa749f16..e9cfc3deefd2 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 a1b7544bcf97..d0741475f4e1 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)
@@ -242,11 +245,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,
@@ -284,6 +282,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 f0354fe67bec..71838fd96495 100644
--- a/runner/server/src/mill/server/MillDaemonServer.scala
+++ b/runner/server/src/mill/server/MillDaemonServer.scala
@@ -22,6 +22,7 @@ abstract class MillDaemonServer[State](
daemonDir: os.Path,
acceptTimeout: FiniteDuration,
locks: Locks,
+ outDir: os.Path,
testLogEvenWhenServerIdWrong: Boolean = false
) extends ProxyStreamServer(Server.Args(
daemonDir = daemonDir,
@@ -31,7 +32,6 @@ abstract class MillDaemonServer[State](
bufferSize = 4 * 1024
)) {
def outLock: mill.client.lock.Lock
- def out: os.Path
private var stateCache: State = stateCache0
@@ -71,7 +71,7 @@ abstract class MillDaemonServer[State](
MillDaemonServer.withOutLock(
noBuildLock = false,
noWaitForBuildLock = false,
- out = out,
+ 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 265472b582f8..6a5acfd20e60 100644
--- a/runner/server/test/src/mill/server/ClientServerTests.scala
+++ b/runner/server/test/src/mill/server/ClientServerTests.scala
@@ -26,17 +26,16 @@ 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
)
with Runnable {
override def outLock = mill.client.lock.Lock.memory()
- override def out = os.temp.dir()
-
def stateCache0 = None
override def serverLog0(s: String) = {
diff --git a/testkit/src/mill/testkit/UnitTester.scala b/testkit/src/mill/testkit/UnitTester.scala
index f74e1d130549..a491b7790c85 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()
}
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..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 2eeb3eff3cf9..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 2818d48fb406..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