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:

hello

world

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

hello

world

diff --git a/example/extending/imports/2-mvn-deps-scala/build.mill b/example/extending/imports/2-mvn-deps-scala/build.mill index 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:

hello

world

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

hello

world

diff --git a/example/extending/metabuild/4-meta-build/build.mill b/example/extending/metabuild/4-meta-build/build.mill index 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