diff --git a/.github/workflows/run-mill-action.yml b/.github/workflows/run-mill-action.yml index 4e3b6c6744f..f77fd86ad93 100644 --- a/.github/workflows/run-mill-action.yml +++ b/.github/workflows/run-mill-action.yml @@ -85,7 +85,7 @@ jobs: if: inputs.millargs != '' && !startsWith(inputs.os, 'windows') - name: Run Mill (on Windows) '${{ inputs.millargs }}' - run: cmd /C %GITHUB_WORKSPACE%\ci\mill.bat -i -j3 -k ${{ inputs.millargs }} + run: cmd /C %GITHUB_WORKSPACE%\mill.bat -i -j3 -k ${{ inputs.millargs }} if: inputs.millargs != '' && startsWith(inputs.os, 'windows') - name: Run Mill (on Windows) Worker Cleanup diff --git a/build.mill b/build.mill index 3e120c0ba48..9e0199980f1 100644 --- a/build.mill +++ b/build.mill @@ -576,6 +576,9 @@ trait MillStableScalaModule extends MillPublishScalaModule with Mima { ), ProblemFilter.exclude[Problem]( "mill.scalalib.RunModule#RunnerImpl.*" + ), + ProblemFilter.exclude[Problem]( + "mill.util.PromptLogger#*" ) ) def mimaPreviousVersions: T[Seq[String]] = Settings.mimaBaseVersions @@ -689,6 +692,9 @@ implicit object DepSegment extends Cross.ToSegments[Dep]({ dep => */ object dummy extends Cross[DependencyFetchDummy](dummyDeps) trait DependencyFetchDummy extends ScalaModule with Cross.Module[Dep] { + // these cross module names cause problems on windows, and anyway they + // are not necessary in order to load the project into IntelliJ anyway + def skipIdea = true def scalaVersion = Deps.scalaVersion def compileIvyDeps = Agg(crossValue) } diff --git a/dist/package.mill b/dist/package.mill index 0d371823a53..b6e11ad33f3 100644 --- a/dist/package.mill +++ b/dist/package.mill @@ -224,8 +224,9 @@ object `package` extends RootModule with build.MillPublishJavaModule { ) } + val batExt = if (scala.util.Properties.isWin) ".bat" else "" val DefaultLocalMillReleasePath = - s"target/mill-release${if (scala.util.Properties.isWin) ".bat" else ""}" + s"target/mill-release$batExt" /** * Build and install Mill locally. @@ -240,7 +241,7 @@ object `package` extends RootModule with build.MillPublishJavaModule { def installLocalCache() = Task.Command { val path = installLocalTask( - Task.Anon((os.home / ".cache" / "mill" / "download" / build.millVersion()).toString()) + Task.Anon((os.home / ".cache" / "mill" / "download" / (build.millVersion() + batExt)).toString()) )() Task.log.outputStream.println(path.toString()) PathRef(path) @@ -256,24 +257,30 @@ object `package` extends RootModule with build.MillPublishJavaModule { targetFile } - def millBootstrap = Task.Sources(Task.workspace / "mill") + def millBootstrap = Task.Source(Task.workspace / "mill") + def millBootstrapBat = Task.Source(Task.workspace / "mill.bat") - def bootstrapLauncher = Task { - val outputPath = Task.dest / "mill" + def prepareBootstrapLauncher(bootstrap: os.Path, dest: os.Path, buildVersion: String) = { + val outputPath = dest / "mill" val millBootstrapGrepPrefix = "(\n *DEFAULT_MILL_VERSION=)" - val millDownloadUrlPrefix = "(\n *MILL_DOWNLOAD_URL=)" os.write( outputPath, - os.read(millBootstrap().head.path) + os.read(bootstrap) .replaceAll( millBootstrapGrepPrefix + "[^\\n]+", - "$1" + build.millVersion() + "$1" + buildVersion ) ) os.perms.set(outputPath, "rwxrwxrwx") PathRef(outputPath) } + def bootstrapLauncher = Task { + prepareBootstrapLauncher(millBootstrap().path, Task.dest, build.millVersion()) + } + def bootstrapLauncherBat = Task { + prepareBootstrapLauncher(millBootstrapBat().path, Task.dest, build.millVersion()) + } def examplePathsWithArtifactName:Task[Seq[(os.Path,String)]] = Task.Anon{ for { @@ -292,6 +299,7 @@ object `package` extends RootModule with build.MillPublishJavaModule { os.copy(examplePath, Task.dest / exampleStr, createFolders = true) os.write(Task.dest / exampleStr / ".mill-version", build.millLastTag()) os.copy(bootstrapLauncher().path, Task.dest / exampleStr / "mill") + os.copy(bootstrapLauncherBat().path, Task.dest / exampleStr / "mill.bat") val zip = Task.dest / s"$exampleStr.zip" os.proc("zip", "-r", zip, exampleStr).call(cwd = Task.dest) PathRef(zip) @@ -319,7 +327,8 @@ object `package` extends RootModule with build.MillPublishJavaModule { val zips = examples ++ Seq( (build.dist.assembly().path, label + "-assembly"), - (bootstrapLauncher().path, label) + (bootstrapLauncher().path, label), + (bootstrapLauncherBat().path, label + ".bat") ) for ((zip, name) <- zips) { diff --git a/docs/modules/ROOT/pages/cli/installation-ide.adoc b/docs/modules/ROOT/pages/cli/installation-ide.adoc index f2c271c0e61..a5f7df6f88f 100644 --- a/docs/modules/ROOT/pages/cli/installation-ide.adoc +++ b/docs/modules/ROOT/pages/cli/installation-ide.adoc @@ -14,13 +14,18 @@ globally. [#_bootstrap_scripts] == Bootstrap Scripts -Although the Mill example projects come with their own `./mill` bootstrap script, +Although the Mill example projects come with their own `./mill` and `./mill.bat` bootstrap script, you can also download it manually: [source,bash,subs="verbatim,attributes"] ---- +# Mac/Linux curl -L {mill-github-url}/releases/download/{mill-last-tag}/{mill-last-tag} > mill && chmod +x mill echo {mill-last-tag} > .mill-version + +# Windows +curl -L {mill-github-url}/releases/download/{mill-last-tag}/{mill-last-tag}.bat -o mill.bat +echo {mill-last-tag} > .mill-version ---- Downloading a `mill` bootstrap script to the root of your project repository helps make it easier for diff --git a/example/scalalib/basic/1-simple/build.mill b/example/scalalib/basic/1-simple/build.mill index 057b73ff159..d6e61d0f97d 100644 --- a/example/scalalib/basic/1-simple/build.mill +++ b/example/scalalib/basic/1-simple/build.mill @@ -23,7 +23,8 @@ object foo extends ScalaModule { // You can download this example project using the *download* link above // if you want to try out the commands below yourself, or browse the full sources // of the example (including supporting files) via the *browse* link. The only requirement is -// that you have some version of the JVM installed; the `./mill` script takes +// that you have some version of the JVM installed; the `./mill` script +// (`./mill.bat` on windows) takes // care of any further dependencies that need to be downloaded. All examples // in this documentation site are executable and are continually exercised as // part of Mill's CI workflows, and they range from the simple hello-world diff --git a/main/client/src/mill/main/client/ProxyStream.java b/main/client/src/mill/main/client/ProxyStream.java index 60bdde07f64..9f4fd653486 100644 --- a/main/client/src/mill/main/client/ProxyStream.java +++ b/main/client/src/mill/main/client/ProxyStream.java @@ -120,7 +120,9 @@ public Pumper(InputStream src, OutputStream destOut, OutputStream destErr) { public void preRead(InputStream src) {} - public void preWrite(byte[] buffer, int length) {} + public void write(OutputStream dest, byte[] buffer, int length) throws IOException { + dest.write(buffer, 0, length); + } public void run() { @@ -152,13 +154,12 @@ public void run() { if (delta != -1) { synchronized (synchronizer) { - this.preWrite(buffer, offset); switch (stream) { case ProxyStream.OUT: - destOut.write(buffer, 0, offset); + this.write(destOut, buffer, offset); break; case ProxyStream.ERR: - destErr.write(buffer, 0, offset); + this.write(destErr, buffer, offset); break; } } diff --git a/main/util/src/mill/util/PromptLogger.scala b/main/util/src/mill/util/PromptLogger.scala index bb1b1ad843a..4459d33dd39 100644 --- a/main/util/src/mill/util/PromptLogger.scala +++ b/main/util/src/mill/util/PromptLogger.scala @@ -2,13 +2,7 @@ package mill.util import mill.api.SystemStreams import mill.main.client.ProxyStream -import mill.util.PromptLoggerUtil.{ - Status, - clearScreenToEndBytes, - defaultTermHeight, - defaultTermWidth, - renderPrompt -} +import mill.util.PromptLoggerUtil.{Status, defaultTermHeight, defaultTermWidth, renderPrompt} import pprint.Util.literalize import java.io._ @@ -90,8 +84,8 @@ private[mill] class PromptLogger( ) def refreshPrompt(ending: Boolean = false): Unit = synchronized { - promptLineState.updatePrompt(ending) - streamManager.refreshPrompt() + val updated = promptLineState.updatePrompt(ending) + if (updated) streamManager.refreshPrompt() } if (enableTicker && autoUpdate) promptUpdaterThread.start() @@ -289,16 +283,21 @@ private[mill] object PromptLogger { } } - override def preWrite(buf: Array[Byte], end: Int): Unit = { - // Before any write, make sure we clear the terminal of any prompt that was - // written earlier and not yet cleared, so the following output can be written - // to a clean section of the terminal - + override def write(dest: OutputStream, buf: Array[Byte], end: Int): Unit = { lastCharWritten = buf(end - 1).toChar if (interactive() && !paused() && promptShown) { - systemStreams0.err.write(clearScreenToEndBytes) promptShown = false } + + // Clear each line as they are drawn, rather than relying on clearing + // the entire screen before each batch of writes, to try and reduce the + // amount of terminal flickering in slow terminals (e.g. windows) + // https://stackoverflow.com/questions/71452837/how-to-reduce-flicker-in-terminal-re-drawing + dest.write( + new String(buf, 0, end) + .replaceAll("(\r\n|\n)", AnsiNav.clearLine(0) + "$1") + .getBytes + ) } } @@ -338,7 +337,7 @@ private[mill] object PromptLogger { def getCurrentPrompt() = currentPromptBytes - def updatePrompt(ending: Boolean = false): Unit = { + def updatePrompt(ending: Boolean = false): Boolean = { val now = currentTimeMillis() for (k <- statuses.keySet) { val removedTime = statuses(k).beginTransitionTime @@ -367,8 +366,9 @@ private[mill] object PromptLogger { ending = ending ) + val oldPromptBytes = currentPromptBytes currentPromptBytes = renderPromptWrapped(currentPromptLines, interactive, ending).getBytes - + !java.util.Arrays.equals(oldPromptBytes, currentPromptBytes) } def clearStatuses(): Unit = { statuses.clear() } diff --git a/main/util/src/mill/util/PromptLoggerUtil.scala b/main/util/src/mill/util/PromptLoggerUtil.scala index 7d09586cd31..92fbd3b592c 100644 --- a/main/util/src/mill/util/PromptLoggerUtil.scala +++ b/main/util/src/mill/util/PromptLoggerUtil.scala @@ -171,7 +171,9 @@ private object PromptLoggerUtil { if (ending) "\n" else AnsiNav.left(9999) + AnsiNav.up(currentPromptLines.length - 1) - AnsiNav.clearScreen(0) + currentPromptLines.mkString("\n") + backUp + currentPromptLines.map(_ + AnsiNav.clearLine(0)).mkString("\n") + + AnsiNav.clearScreen(0) + + backUp } } diff --git a/main/util/test/src/mill/util/PromptLoggerTests.scala b/main/util/test/src/mill/util/PromptLoggerTests.scala index 9f79125a9f2..1d34702a88f 100644 --- a/main/util/test/src/mill/util/PromptLoggerTests.scala +++ b/main/util/test/src/mill/util/PromptLoggerTests.scala @@ -46,274 +46,259 @@ object PromptLoggerTests extends TestSuite { pumper.run() val term = new TestTerminal(width) term.writeAll(finalBaos.toString) - val lines = term.grid + val lines = term.grid.map(_.stripSuffix("\r")) assert(lines == expected) } val tests = Tests { test("nonInteractive") - retry(3) { - // These tests seem flaky on windows but not sure why - if (!Util.windowsPlatform) { - var now = 0L - - val (baos, promptLogger, prefixLogger) = setup(() => now, os.temp()) - - promptLogger.setPromptHeaderPrefix("123/456") - promptLogger.setPromptLine(Seq("1"), "/456", "my-task") - - now += 10000 - - prefixLogger.outputStream.println("HELLO") - - promptLogger.refreshPrompt() - - prefixLogger.outputStream.println("WORLD") - - promptLogger.removePromptLine(Seq("1")) - - now += 10000 - promptLogger.refreshPrompt() - now += 10000 - promptLogger.close() - - check(promptLogger, baos, width = 999 /*log file has no line wrapping*/ )( - "============================== TITLE ==============================", - "===================================================================", - // Make sure that the first time a prefix is reported, - // we print the verbose prefix along with the ticker string - "[1/456] my-task", - // Further `println`s come with the prefix - "[1] HELLO", - // Calling `refreshPrompt()` prints the header with the given `globalTicker` without - // the double space prefix (since it's non-interactive and we don't need space for a cursor), - // the time elapsed, the reported title and ticker, the list of active tickers, followed by the - // footer - "[123/456] ============================== TITLE ============================== 10s", - "[1] my-task 10s", - "=================================================================================", - "[1] WORLD", - // Calling `refreshPrompt()` after closing the ticker shows the prompt without - // the ticker in the list, with an updated time elapsed - "[123/456] ============================== TITLE ============================== 20s", - "=================================================================================", - // Closing the prompt prints the prompt one last time with an updated time elapsed - "[123/456] ============================== TITLE ============================== 30s", - "=================================================================================", - "" - ) - } + var now = 0L + + val (baos, promptLogger, prefixLogger) = setup(() => now, os.temp()) + + promptLogger.setPromptHeaderPrefix("123/456") + promptLogger.setPromptLine(Seq("1"), "/456", "my-task") + + now += 10000 + + prefixLogger.outputStream.println("HELLO") + + promptLogger.refreshPrompt() + + prefixLogger.outputStream.println("WORLD") + + promptLogger.removePromptLine(Seq("1")) + + now += 10000 + promptLogger.refreshPrompt() + now += 10000 + promptLogger.close() + + check(promptLogger, baos, width = 999 /*log file has no line wrapping*/ )( + "============================== TITLE ==============================", + "===================================================================", + // Make sure that the first time a prefix is reported, + // we print the verbose prefix along with the ticker string + "[1/456] my-task", + // Further `println`s come with the prefix + "[1] HELLO", + // Calling `refreshPrompt()` prints the header with the given `globalTicker` without + // the double space prefix (since it's non-interactive and we don't need space for a cursor), + // the time elapsed, the reported title and ticker, the list of active tickers, followed by the + // footer + "[123/456] ============================== TITLE ============================== 10s", + "[1] my-task 10s", + "=================================================================================", + "[1] WORLD", + // Calling `refreshPrompt()` after closing the ticker shows the prompt without + // the ticker in the list, with an updated time elapsed + "[123/456] ============================== TITLE ============================== 20s", + "=================================================================================", + // Closing the prompt prints the prompt one last time with an updated time elapsed + "[123/456] ============================== TITLE ============================== 30s", + "=================================================================================", + "" + ) + } test("interactive") - retry(3) { - if (!Util.windowsPlatform) { - var now = 0L - val (baos, promptLogger, prefixLogger) = setup(() => now, os.temp("80 40")) - - promptLogger.setPromptHeaderPrefix("123/456") - promptLogger.refreshPrompt() - check(promptLogger, baos)( - " [123/456] ============================== TITLE ==============================" - ) - promptLogger.setPromptLine(Seq("1"), "/456", "my-task") - - now += 10000 - - prefixLogger.outputStream.println("HELLO") - - promptLogger.refreshPrompt() // Need to call `refreshPrompt()` for prompt to change - // First time we log with the prefix `[1]`, make sure we print out the title line - // `[1/456] my-task` so the viewer knows what `[1]` refers to - check(promptLogger, baos)( - // Leading newline because we don't have an actual terminal prompt for the initial - // "up" movement to cancel out the initial "\n" - "", - "[1/456] my-task", - "[1] HELLO", - " [123/456] ============================ TITLE ============================ 10s", - "[1] my-task 10s" - ) - - prefixLogger.outputStream.println("WORLD") - // Prompt doesn't change, no need to call `refreshPrompt()` for it to be - // re-rendered below the latest prefixed output. Subsequent log line with `[1]` - // prefix does not re-render title line `[1/456] ...` - check(promptLogger, baos)( - "", - "[1/456] my-task", - "[1] HELLO", - "[1] WORLD", - " [123/456] ============================ TITLE ============================ 10s", - "[1] my-task 10s" - ) - - // Adding new ticker entries doesn't appear immediately, - // Only after some time has passed do we start displaying the new ticker entry, - // to ensure it is meaningful to read and not just something that will flash and disappear - val newPrefixLogger2 = new PrefixLogger(promptLogger, Seq("2")) - newPrefixLogger2.setPromptLine(Seq("2"), "/456", "my-task-new") - newPrefixLogger2.errorStream.println("I AM COW") - newPrefixLogger2.errorStream.println("HEAR ME MOO") - - // For short-lived ticker entries that are removed quickly, they never - // appear in the prompt at all even though they can run and generate logs - val newPrefixLogger3 = new PrefixLogger(promptLogger, Seq("3")) - newPrefixLogger3.setPromptLine(Seq("3"), "/456", "my-task-short-lived") - newPrefixLogger3.errorStream.println("hello short lived") - newPrefixLogger3.errorStream.println("goodbye short lived") - - // my-task-new does not appear yet because it is too new - promptLogger.refreshPrompt() - check(promptLogger, baos)( - "", - "[1/456] my-task", - "[1] HELLO", - "[1] WORLD", - "[2/456] my-task-new", - "[2] I AM COW", - "[2] HEAR ME MOO", - "[3/456] my-task-short-lived", - "[3] hello short lived", - "[3] goodbye short lived", - " [123/456] ============================ TITLE ============================ 10s", - "[1] my-task 10s" - ) - - newPrefixLogger3.removePromptLine(Seq("3")) - - now += 1000 - - // my-task-new appears by now, but my-task-short-lived has already ended and never appears - promptLogger.refreshPrompt() - check(promptLogger, baos)( - "", - "[1/456] my-task", - "[1] HELLO", - "[1] WORLD", - "[2/456] my-task-new", - "[2] I AM COW", - "[2] HEAR ME MOO", - "[3/456] my-task-short-lived", - "[3] hello short lived", - "[3] goodbye short lived", - " [123/456] ============================ TITLE ============================ 11s", - "[1] my-task 11s", - "[2] my-task-new 1s" - ) - - promptLogger.removePromptLine(Seq("1")) - - now += 10 - - // Even after ending my-task, it remains on the ticker for a moment before being removed - promptLogger.refreshPrompt() - check(promptLogger, baos)( - "", - "[1/456] my-task", - "[1] HELLO", - "[1] WORLD", - "[2/456] my-task-new", - "[2] I AM COW", - "[2] HEAR ME MOO", - "[3/456] my-task-short-lived", - "[3] hello short lived", - "[3] goodbye short lived", - " [123/456] ============================ TITLE ============================ 11s", - "[1] my-task 11s", - "[2] my-task-new 1s" - ) - - now += 1000 - - // When my-task disappears from the ticker, it leaves a blank line for a - // moment to preserve the height of the prompt - promptLogger.refreshPrompt() - check(promptLogger, baos)( - "", - "[1/456] my-task", - "[1] HELLO", - "[1] WORLD", - "[2/456] my-task-new", - "[2] I AM COW", - "[2] HEAR ME MOO", - "[3/456] my-task-short-lived", - "[3] hello short lived", - "[3] goodbye short lived", - " [123/456] ============================ TITLE ============================ 12s", - "[2] my-task-new 2s", - "" - ) - - now += 10000 - - // Only after more time does the prompt shrink back - promptLogger.refreshPrompt() - check(promptLogger, baos)( - "", - "[1/456] my-task", - "[1] HELLO", - "[1] WORLD", - "[2/456] my-task-new", - "[2] I AM COW", - "[2] HEAR ME MOO", - "[3/456] my-task-short-lived", - "[3] hello short lived", - "[3] goodbye short lived", - " [123/456] ============================ TITLE ============================ 22s", - "[2] my-task-new 12s" - ) - now += 10000 - promptLogger.close() - check(promptLogger, baos)( - "", - "[1/456] my-task", - "[1] HELLO", - "[1] WORLD", - "[2/456] my-task-new", - "[2] I AM COW", - "[2] HEAR ME MOO", - "[3/456] my-task-short-lived", - "[3] hello short lived", - "[3] goodbye short lived", - "[123/456] ============================= TITLE ============================= 32s", - "" - ) - } + + var now = 0L + val (baos, promptLogger, prefixLogger) = setup(() => now, os.temp("80 40")) + + promptLogger.setPromptHeaderPrefix("123/456") + promptLogger.refreshPrompt() + check(promptLogger, baos)( + " [123/456] ============================== TITLE ==============================" + ) + promptLogger.setPromptLine(Seq("1"), "/456", "my-task") + + now += 10000 + + prefixLogger.outputStream.println("HELLO") + + promptLogger.refreshPrompt() // Need to call `refreshPrompt()` for prompt to change + // First time we log with the prefix `[1]`, make sure we print out the title line + // `[1/456] my-task` so the viewer knows what `[1]` refers to + check(promptLogger, baos)( + "[1/456] my-task", + "[1] HELLO", + " [123/456] ============================ TITLE ============================ 10s", + "[1] my-task 10s" + ) + + prefixLogger.outputStream.println("WORLD") + // Prompt doesn't change, no need to call `refreshPrompt()` for it to be + // re-rendered below the latest prefixed output. Subsequent log line with `[1]` + // prefix does not re-render title line `[1/456] ...` + check(promptLogger, baos)( + "[1/456] my-task", + "[1] HELLO", + "[1] WORLD", + " [123/456] ============================ TITLE ============================ 10s", + "[1] my-task 10s" + ) + + // Adding new ticker entries doesn't appear immediately, + // Only after some time has passed do we start displaying the new ticker entry, + // to ensure it is meaningful to read and not just something that will flash and disappear + val newPrefixLogger2 = new PrefixLogger(promptLogger, Seq("2")) + newPrefixLogger2.setPromptLine(Seq("2"), "/456", "my-task-new") + newPrefixLogger2.errorStream.println("I AM COW") + newPrefixLogger2.errorStream.println("HEAR ME MOO") + + // For short-lived ticker entries that are removed quickly, they never + // appear in the prompt at all even though they can run and generate logs + val newPrefixLogger3 = new PrefixLogger(promptLogger, Seq("3")) + newPrefixLogger3.setPromptLine(Seq("3"), "/456", "my-task-short-lived") + newPrefixLogger3.errorStream.println("hello short lived") + newPrefixLogger3.errorStream.println("goodbye short lived") + + // my-task-new does not appear yet because it is too new + promptLogger.refreshPrompt() + check(promptLogger, baos)( + "[1/456] my-task", + "[1] HELLO", + "[1] WORLD", + "[2/456] my-task-new", + "[2] I AM COW", + "[2] HEAR ME MOO", + "[3/456] my-task-short-lived", + "[3] hello short lived", + "[3] goodbye short lived", + " [123/456] ============================ TITLE ============================ 10s", + "[1] my-task 10s" + ) + + newPrefixLogger3.removePromptLine(Seq("3")) + + now += 1000 + + // my-task-new appears by now, but my-task-short-lived has already ended and never appears + promptLogger.refreshPrompt() + check(promptLogger, baos)( + "[1/456] my-task", + "[1] HELLO", + "[1] WORLD", + "[2/456] my-task-new", + "[2] I AM COW", + "[2] HEAR ME MOO", + "[3/456] my-task-short-lived", + "[3] hello short lived", + "[3] goodbye short lived", + " [123/456] ============================ TITLE ============================ 11s", + "[1] my-task 11s", + "[2] my-task-new 1s" + ) + + promptLogger.removePromptLine(Seq("1")) + + now += 10 + + // Even after ending my-task, it remains on the ticker for a moment before being removed + promptLogger.refreshPrompt() + check(promptLogger, baos)( + "[1/456] my-task", + "[1] HELLO", + "[1] WORLD", + "[2/456] my-task-new", + "[2] I AM COW", + "[2] HEAR ME MOO", + "[3/456] my-task-short-lived", + "[3] hello short lived", + "[3] goodbye short lived", + " [123/456] ============================ TITLE ============================ 11s", + "[1] my-task 11s", + "[2] my-task-new 1s" + ) + + now += 1000 + + // When my-task disappears from the ticker, it leaves a blank line for a + // moment to preserve the height of the prompt + promptLogger.refreshPrompt() + check(promptLogger, baos)( + "[1/456] my-task", + "[1] HELLO", + "[1] WORLD", + "[2/456] my-task-new", + "[2] I AM COW", + "[2] HEAR ME MOO", + "[3/456] my-task-short-lived", + "[3] hello short lived", + "[3] goodbye short lived", + " [123/456] ============================ TITLE ============================ 12s", + "[2] my-task-new 2s", + "" + ) + + now += 10000 + + // Only after more time does the prompt shrink back + promptLogger.refreshPrompt() + check(promptLogger, baos)( + "[1/456] my-task", + "[1] HELLO", + "[1] WORLD", + "[2/456] my-task-new", + "[2] I AM COW", + "[2] HEAR ME MOO", + "[3/456] my-task-short-lived", + "[3] hello short lived", + "[3] goodbye short lived", + " [123/456] ============================ TITLE ============================ 22s", + "[2] my-task-new 12s" + ) + now += 10000 + promptLogger.close() + check(promptLogger, baos)( + "[1/456] my-task", + "[1] HELLO", + "[1] WORLD", + "[2/456] my-task-new", + "[2] I AM COW", + "[2] HEAR ME MOO", + "[3/456] my-task-short-lived", + "[3] hello short lived", + "[3] goodbye short lived", + "[123/456] ============================= TITLE ============================= 32s", + "" + ) } test("detail") { - if (!Util.windowsPlatform) { - // Make sure that when we have multiple sequential tasks being run on different threads, - // we still end up showing some kind of task in progress in the ticker, even though the - // tasks on each thread are short-lived enough they would not normally get shown if run - // alone. - @volatile var now = 0L - val (baos, promptLogger, prefixLogger) = setup(() => now, os.temp("80 40")) - - promptLogger.setPromptHeaderPrefix("123/456") - promptLogger.refreshPrompt() - - promptLogger.setPromptLine(Seq("1"), "/456", "my-task") - prefixLogger.ticker("detail") - now += 1000 - promptLogger.refreshPrompt() - check(promptLogger, baos)( - " [123/456] ============================= TITLE ============================ 1s", - "[1] my-task 1s detail" - ) - prefixLogger.ticker("detail-too-long-gets-truncated-abcdefghijklmnopqrstuvwxyz1234567890") - promptLogger.refreshPrompt() - check(promptLogger, baos)( - " [123/456] ============================= TITLE ============================ 1s", - "[1] my-task 1s detail-too-long-gets-truncated...fghijklmnopqrstuvwxyz1234567890" - ) - promptLogger.removePromptLine(Seq("1")) - now += 10000 - promptLogger.refreshPrompt() - check(promptLogger, baos)( - " [123/456] ============================ TITLE ============================ 11s" - ) - } + // Make sure that when we have multiple sequential tasks being run on different threads, + // we still end up showing some kind of task in progress in the ticker, even though the + // tasks on each thread are short-lived enough they would not normally get shown if run + // alone. + @volatile var now = 0L + val (baos, promptLogger, prefixLogger) = setup(() => now, os.temp("80 40")) + + promptLogger.setPromptHeaderPrefix("123/456") + promptLogger.refreshPrompt() + + promptLogger.setPromptLine(Seq("1"), "/456", "my-task") + prefixLogger.ticker("detail") + now += 1000 + promptLogger.refreshPrompt() + check(promptLogger, baos)( + " [123/456] ============================= TITLE ============================ 1s", + "[1] my-task 1s detail" + ) + prefixLogger.ticker("detail-too-long-gets-truncated-abcdefghijklmnopqrstuvwxyz1234567890") + promptLogger.refreshPrompt() + check(promptLogger, baos)( + " [123/456] ============================= TITLE ============================ 1s", + "[1] my-task 1s detail-too-long-gets-truncated...fghijklmnopqrstuvwxyz1234567890" + ) + promptLogger.removePromptLine(Seq("1")) + now += 10000 + promptLogger.refreshPrompt() + check(promptLogger, baos)( + " [123/456] ============================ TITLE ============================ 11s" + ) } } } diff --git a/mill b/mill index 649c1426e54..d087ac04d54 100755 --- a/mill +++ b/mill @@ -1,51 +1,222 @@ #!/usr/bin/env sh # This is a wrapper script, that automatically download mill from GitHub release pages -# You can give the required mill version with MILL_VERSION env variable +# You can give the required mill version with --mill-version parameter # If no version is given, it falls back to the value of DEFAULT_MILL_VERSION +# +# Original Project page: https://github.com/lefou/millw +# Script Version: 0.4.12 +# +# If you want to improve this script, please also contribute your changes back! +# +# Licensed under the Apache License, Version 2.0 set -e if [ -z "${DEFAULT_MILL_VERSION}" ] ; then - DEFAULT_MILL_VERSION=0.12.2 + DEFAULT_MILL_VERSION="0.11.4" fi -if [ -z "$MILL_VERSION" ] ; then + +if [ -z "${GITHUB_RELEASE_CDN}" ] ; then + GITHUB_RELEASE_CDN="" +fi + + +MILL_REPO_URL="https://github.com/com-lihaoyi/mill" + +if [ -z "${CURL_CMD}" ] ; then + CURL_CMD=curl +fi + +# Explicit commandline argument takes precedence over all other methods +if [ "$1" = "--mill-version" ] ; then + shift + if [ "x$1" != "x" ] ; then + MILL_VERSION="$1" + shift + else + echo "You specified --mill-version without a version." 1>&2 + echo "Please provide a version that matches one provided on" 1>&2 + echo "${MILL_REPO_URL}/releases" 1>&2 + false + fi +fi + +# Please note, that if a MILL_VERSION is already set in the environment, +# We reuse it's value and skip searching for a value. + +# If not already set, read .mill-version file +if [ -z "${MILL_VERSION}" ] ; then if [ -f ".mill-version" ] ; then MILL_VERSION="$(tr '\r' '\n' < .mill-version | head -n 1 2> /dev/null)" elif [ -f ".config/mill-version" ] ; then MILL_VERSION="$(tr '\r' '\n' < .config/mill-version | head -n 1 2> /dev/null)" - elif [ -f "mill" ] && [ "$0" != "mill" ] ; then - MILL_VERSION=$(grep -F "DEFAULT_MILL_VERSION=" "mill" | head -n 1 | cut -d= -f2) - else - MILL_VERSION=$DEFAULT_MILL_VERSION fi fi -if [ "x${XDG_CACHE_HOME}" != "x" ] ; then - MILL_DOWNLOAD_PATH="${XDG_CACHE_HOME}/mill/download" -else - MILL_DOWNLOAD_PATH="${HOME}/.cache/mill/download" +MILL_USER_CACHE_DIR="${XDG_CACHE_HOME:-${HOME}/.cache}/mill" + +if [ -z "${MILL_DOWNLOAD_PATH}" ] ; then + MILL_DOWNLOAD_PATH="${MILL_USER_CACHE_DIR}/download" +fi + +# If not already set, try to fetch newest from Github +if [ -z "${MILL_VERSION}" ] ; then + # TODO: try to load latest version from release page + echo "No mill version specified." 1>&2 + echo "You should provide a version via '.mill-version' file or --mill-version option." 1>&2 + + mkdir -p "${MILL_DOWNLOAD_PATH}" + LANG=C touch -d '1 hour ago' "${MILL_DOWNLOAD_PATH}/.expire_latest" 2>/dev/null || ( + # we might be on OSX or BSD which don't have -d option for touch + # but probably a -A [-][[hh]mm]SS + touch "${MILL_DOWNLOAD_PATH}/.expire_latest"; touch -A -010000 "${MILL_DOWNLOAD_PATH}/.expire_latest" + ) || ( + # in case we still failed, we retry the first touch command with the intention + # to show the (previously suppressed) error message + LANG=C touch -d '1 hour ago' "${MILL_DOWNLOAD_PATH}/.expire_latest" + ) + + # POSIX shell variant of bash's -nt operator, see https://unix.stackexchange.com/a/449744/6993 + # if [ "${MILL_DOWNLOAD_PATH}/.latest" -nt "${MILL_DOWNLOAD_PATH}/.expire_latest" ] ; then + if [ -n "$(find -L "${MILL_DOWNLOAD_PATH}/.latest" -prune -newer "${MILL_DOWNLOAD_PATH}/.expire_latest")" ]; then + # we know a current latest version + MILL_VERSION=$(head -n 1 "${MILL_DOWNLOAD_PATH}"/.latest 2> /dev/null) + fi + + if [ -z "${MILL_VERSION}" ] ; then + # we don't know a current latest version + echo "Retrieving latest mill version ..." 1>&2 + LANG=C ${CURL_CMD} -s -i -f -I ${MILL_REPO_URL}/releases/latest 2> /dev/null | grep --ignore-case Location: | sed s'/^.*tag\///' | tr -d '\r\n' > "${MILL_DOWNLOAD_PATH}/.latest" + MILL_VERSION=$(head -n 1 "${MILL_DOWNLOAD_PATH}"/.latest 2> /dev/null) + fi + + if [ -z "${MILL_VERSION}" ] ; then + # Last resort + MILL_VERSION="${DEFAULT_MILL_VERSION}" + echo "Falling back to hardcoded mill version ${MILL_VERSION}" 1>&2 + else + echo "Using mill version ${MILL_VERSION}" 1>&2 + fi fi -MILL_EXEC_PATH="${MILL_DOWNLOAD_PATH}/${MILL_VERSION}" -version_remainder="$MILL_VERSION" -MILL_MAJOR_VERSION="${version_remainder%%.*}"; version_remainder="${version_remainder#*.}" -MILL_MINOR_VERSION="${version_remainder%%.*}"; version_remainder="${version_remainder#*.}" +MILL="${MILL_DOWNLOAD_PATH}/${MILL_VERSION}" -if [ ! -s "$MILL_EXEC_PATH" ] ; then - mkdir -p "$MILL_DOWNLOAD_PATH" - if [ "$MILL_MAJOR_VERSION" -gt 0 ] || [ "$MILL_MINOR_VERSION" -ge 5 ] ; then - ASSEMBLY="-assembly" +try_to_use_system_mill() { + if [ "$(uname)" != "Linux" ]; then + return 0 + fi + + MILL_IN_PATH="$(command -v mill || true)" + + if [ -z "${MILL_IN_PATH}" ]; then + return 0 + fi + + SYSTEM_MILL_FIRST_TWO_BYTES=$(head --bytes=2 "${MILL_IN_PATH}") + if [ "${SYSTEM_MILL_FIRST_TWO_BYTES}" = "#!" ]; then + # MILL_IN_PATH is (very likely) a shell script and not the mill + # executable, ignore it. + return 0 + fi + + SYSTEM_MILL_PATH=$(readlink -e "${MILL_IN_PATH}") + SYSTEM_MILL_SIZE=$(stat --format=%s "${SYSTEM_MILL_PATH}") + SYSTEM_MILL_MTIME=$(stat --format=%y "${SYSTEM_MILL_PATH}") + + if [ ! -d "${MILL_USER_CACHE_DIR}" ]; then + mkdir -p "${MILL_USER_CACHE_DIR}" + fi + + SYSTEM_MILL_INFO_FILE="${MILL_USER_CACHE_DIR}/system-mill-info" + if [ -f "${SYSTEM_MILL_INFO_FILE}" ]; then + parseSystemMillInfo() { + LINE_NUMBER="${1}" + # Select the line number of the SYSTEM_MILL_INFO_FILE, cut the + # variable definition in that line in two halves and return + # the value, and finally remove the quotes. + sed -n "${LINE_NUMBER}p" "${SYSTEM_MILL_INFO_FILE}" |\ + cut -d= -f2 |\ + sed 's/"\(.*\)"/\1/' + } + + CACHED_SYSTEM_MILL_PATH=$(parseSystemMillInfo 1) + CACHED_SYSTEM_MILL_VERSION=$(parseSystemMillInfo 2) + CACHED_SYSTEM_MILL_SIZE=$(parseSystemMillInfo 3) + CACHED_SYSTEM_MILL_MTIME=$(parseSystemMillInfo 4) + + if [ "${SYSTEM_MILL_PATH}" = "${CACHED_SYSTEM_MILL_PATH}" ] \ + && [ "${SYSTEM_MILL_SIZE}" = "${CACHED_SYSTEM_MILL_SIZE}" ] \ + && [ "${SYSTEM_MILL_MTIME}" = "${CACHED_SYSTEM_MILL_MTIME}" ]; then + if [ "${CACHED_SYSTEM_MILL_VERSION}" = "${MILL_VERSION}" ]; then + MILL="${SYSTEM_MILL_PATH}" + return 0 + else + return 0 + fi + fi + fi + + SYSTEM_MILL_VERSION=$(${SYSTEM_MILL_PATH} --version | head -n1 | sed -n 's/^Mill.*version \(.*\)/\1/p') + + cat < "${SYSTEM_MILL_INFO_FILE}" +CACHED_SYSTEM_MILL_PATH="${SYSTEM_MILL_PATH}" +CACHED_SYSTEM_MILL_VERSION="${SYSTEM_MILL_VERSION}" +CACHED_SYSTEM_MILL_SIZE="${SYSTEM_MILL_SIZE}" +CACHED_SYSTEM_MILL_MTIME="${SYSTEM_MILL_MTIME}" +EOF + + if [ "${SYSTEM_MILL_VERSION}" = "${MILL_VERSION}" ]; then + MILL="${SYSTEM_MILL_PATH}" + fi +} +try_to_use_system_mill + +# If not already downloaded, download it +if [ ! -s "${MILL}" ] ; then + + # support old non-XDG download dir + MILL_OLD_DOWNLOAD_PATH="${HOME}/.mill/download" + OLD_MILL="${MILL_OLD_DOWNLOAD_PATH}/${MILL_VERSION}" + if [ -x "${OLD_MILL}" ] ; then + MILL="${OLD_MILL}" + else + case $MILL_VERSION in + 0.0.* | 0.1.* | 0.2.* | 0.3.* | 0.4.* ) + DOWNLOAD_SUFFIX="" + DOWNLOAD_FROM_MAVEN=0 + ;; + 0.5.* | 0.6.* | 0.7.* | 0.8.* | 0.9.* | 0.10.* | 0.11.0-M* ) + DOWNLOAD_SUFFIX="-assembly" + DOWNLOAD_FROM_MAVEN=0 + ;; + *) + DOWNLOAD_SUFFIX="-assembly" + DOWNLOAD_FROM_MAVEN=1 + ;; + esac + + DOWNLOAD_FILE=$(mktemp mill.XXXXXX) + + if [ "$DOWNLOAD_FROM_MAVEN" = "1" ] ; then + DOWNLOAD_URL="https://repo1.maven.org/maven2/com/lihaoyi/mill-dist/${MILL_VERSION}/mill-dist-${MILL_VERSION}.jar" + else + MILL_VERSION_TAG=$(echo "$MILL_VERSION" | sed -E 's/([^-]+)(-M[0-9]+)?(-.*)?/\1\2/') + DOWNLOAD_URL="${GITHUB_RELEASE_CDN}${MILL_REPO_URL}/releases/download/${MILL_VERSION_TAG}/${MILL_VERSION}${DOWNLOAD_SUFFIX}" + unset MILL_VERSION_TAG + fi + + # TODO: handle command not found + echo "Downloading mill ${MILL_VERSION} from ${DOWNLOAD_URL} ..." 1>&2 + ${CURL_CMD} -f -L -o "${DOWNLOAD_FILE}" "${DOWNLOAD_URL}" + chmod +x "${DOWNLOAD_FILE}" + mkdir -p "${MILL_DOWNLOAD_PATH}" + mv "${DOWNLOAD_FILE}" "${MILL}" + + unset DOWNLOAD_FILE + unset DOWNLOAD_SUFFIX fi - DOWNLOAD_FILE=$MILL_EXEC_PATH-tmp-download - MILL_VERSION_TAG=$(echo $MILL_VERSION | sed -E 's/([^-]+)(-M[0-9]+)?(-.*)?/\1\2/') - MILL_DOWNLOAD_URL="https://repo1.maven.org/maven2/com/lihaoyi/mill-dist/$MILL_VERSION/mill-dist-$MILL_VERSION.jar" - curl --fail -L -o "$DOWNLOAD_FILE" "$MILL_DOWNLOAD_URL" - chmod +x "$DOWNLOAD_FILE" - mv "$DOWNLOAD_FILE" "$MILL_EXEC_PATH" - unset DOWNLOAD_FILE - unset MILL_DOWNLOAD_URL fi if [ -z "$MILL_MAIN_CLI" ] ; then @@ -53,15 +224,18 @@ if [ -z "$MILL_MAIN_CLI" ] ; then fi MILL_FIRST_ARG="" - - # first arg is a long flag for "--interactive" or starts with "-i" -if [ "$1" = "--bsp" ] || [ "${1#"-i"}" != "$1" ] || [ "$1" = "--interactive" ] || [ "$1" = "--no-server" ] || [ "$1" = "--repl" ] || [ "$1" = "--help" ] ; then +if [ "$1" = "--bsp" ] || [ "$1" = "-i" ] || [ "$1" = "--interactive" ] || [ "$1" = "--no-server" ] || [ "$1" = "--repl" ] || [ "$1" = "--help" ] ; then # Need to preserve the first position of those listed options MILL_FIRST_ARG=$1 shift fi unset MILL_DOWNLOAD_PATH +unset MILL_OLD_DOWNLOAD_PATH +unset OLD_MILL unset MILL_VERSION +unset MILL_REPO_URL -exec $MILL_EXEC_PATH $MILL_FIRST_ARG -D "mill.main.cli=${MILL_MAIN_CLI}" "$@" +# We don't quote MILL_FIRST_ARG on purpose, so we can expand the empty value without quotes +# shellcheck disable=SC2086 +exec "${MILL}" $MILL_FIRST_ARG -D "mill.main.cli=${MILL_MAIN_CLI}" "$@" \ No newline at end of file diff --git a/ci/mill.bat b/mill.bat old mode 100755 new mode 100644 similarity index 95% rename from ci/mill.bat rename to mill.bat index c81a35d9374..6957369ba43 --- a/ci/mill.bat +++ b/mill.bat @@ -1,220 +1,220 @@ -@echo off - -rem This is a wrapper script, that automatically download mill from GitHub release pages -rem You can give the required mill version with --mill-version parameter -rem If no version is given, it falls back to the value of DEFAULT_MILL_VERSION -rem -rem Project page: https://github.com/lefou/millw -rem Script Version: 0.4.12 -rem -rem If you want to improve this script, please also contribute your changes back! -rem -rem Licensed under the Apache License, Version 2.0 - -rem setlocal seems to be unavailable on Windows 95/98/ME -rem but I don't think we need to support them in 2019 -setlocal enabledelayedexpansion - -if [!DEFAULT_MILL_VERSION!]==[] ( - set "DEFAULT_MILL_VERSION=0.11.4" -) - -if [!GITHUB_RELEASE_CDN!]==[] ( - set "GITHUB_RELEASE_CDN=" -) - -if [!MILL_MAIN_CLI!]==[] ( - set "MILL_MAIN_CLI=%~f0" -) - -set "MILL_REPO_URL=https://github.com/com-lihaoyi/mill" - -rem %~1% removes surrounding quotes -if [%~1%]==[--mill-version] ( - if not [%~2%]==[] ( - set MILL_VERSION=%~2% - rem shift command doesn't work within parentheses - set "STRIP_VERSION_PARAMS=true" - ) else ( - echo You specified --mill-version without a version. 1>&2 - echo Please provide a version that matches one provided on 1>&2 - echo %MILL_REPO_URL%/releases 1>&2 - exit /b 1 - ) -) - -if not defined STRIP_VERSION_PARAMS GOTO AfterStripVersionParams -rem strip the: --mill-version {version} -shift -shift -:AfterStripVersionParams - -if [!MILL_VERSION!]==[] ( - if exist .mill-version ( - set /p MILL_VERSION=<.mill-version - ) else ( - if exist .config\mill-version ( - set /p MILL_VERSION=<.config\mill-version - ) - ) -) - -if [!MILL_VERSION!]==[] ( - set MILL_VERSION=%DEFAULT_MILL_VERSION% -) - -if [!MILL_DOWNLOAD_PATH!]==[] ( - set MILL_DOWNLOAD_PATH=%USERPROFILE%\.mill\download -) - -rem without bat file extension, cmd doesn't seem to be able to run it -set MILL=%MILL_DOWNLOAD_PATH%\!MILL_VERSION!.bat - -if not exist "%MILL%" ( - set VERSION_PREFIX=%MILL_VERSION:~0,4% - rem Since 0.5.0 - set DOWNLOAD_SUFFIX=-assembly - rem Since 0.11.0 - set DOWNLOAD_FROM_MAVEN=1 - if [!VERSION_PREFIX!]==[0.0.] ( - set DOWNLOAD_SUFFIX= - set DOWNLOAD_FROM_MAVEN=0 - ) - if [!VERSION_PREFIX!]==[0.1.] ( - set DOWNLOAD_SUFFIX= - set DOWNLOAD_FROM_MAVEN=0 - ) - if [!VERSION_PREFIX!]==[0.2.] ( - set DOWNLOAD_SUFFIX= - set DOWNLOAD_FROM_MAVEN=0 - ) - if [!VERSION_PREFIX!]==[0.3.] ( - set DOWNLOAD_SUFFIX= - set DOWNLOAD_FROM_MAVEN=0 - ) - if [!VERSION_PREFIX!]==[0.4.] ( - set DOWNLOAD_SUFFIX= - set DOWNLOAD_FROM_MAVEN=0 - ) - if [!VERSION_PREFIX!]==[0.5.] ( - set DOWNLOAD_FROM_MAVEN=0 - ) - if [!VERSION_PREFIX!]==[0.6.] ( - set DOWNLOAD_FROM_MAVEN=0 - ) - if [!VERSION_PREFIX!]==[0.7.] ( - set DOWNLOAD_FROM_MAVEN=0 - ) - if [!VERSION_PREFIX!]==[0.8.] ( - set DOWNLOAD_FROM_MAVEN=0 - ) - if [!VERSION_PREFIX!]==[0.9.] ( - set DOWNLOAD_FROM_MAVEN=0 - ) - set VERSION_PREFIX=%MILL_VERSION:~0,5% - if [!VERSION_PREFIX!]==[0.10.] ( - set DOWNLOAD_FROM_MAVEN=0 - ) - set VERSION_PREFIX=%MILL_VERSION:~0,8% - if [!VERSION_PREFIX!]==[0.11.0-M] ( - set DOWNLOAD_FROM_MAVEN=0 - ) - set VERSION_PREFIX= - - for /F "delims=- tokens=1" %%A in ("!MILL_VERSION!") do set MILL_VERSION_BASE=%%A - for /F "delims=- tokens=2" %%A in ("!MILL_VERSION!") do set MILL_VERSION_MILESTONE=%%A - set VERSION_MILESTONE_START=!MILL_VERSION_MILESTONE:~0,1! - if [!VERSION_MILESTONE_START!]==[M] ( - set MILL_VERSION_TAG="!MILL_VERSION_BASE!-!MILL_VERSION_MILESTONE!" - ) else ( - set MILL_VERSION_TAG=!MILL_VERSION_BASE! - ) - - rem there seems to be no way to generate a unique temporary file path (on native Windows) - set DOWNLOAD_FILE=%MILL%.tmp - - if [!DOWNLOAD_FROM_MAVEN!]==[1] ( - set DOWNLOAD_URL=https://repo1.maven.org/maven2/com/lihaoyi/mill-dist/!MILL_VERSION!/mill-dist-!MILL_VERSION!.jar - ) else ( - set DOWNLOAD_URL=!GITHUB_RELEASE_CDN!%MILL_REPO_URL%/releases/download/!MILL_VERSION_TAG!/!MILL_VERSION!!DOWNLOAD_SUFFIX! - ) - - echo Downloading mill %MILL_VERSION% from !DOWNLOAD_URL! ... 1>&2 - - if not exist "%MILL_DOWNLOAD_PATH%" mkdir "%MILL_DOWNLOAD_PATH%" - rem curl is bundled with recent Windows 10 - rem but I don't think we can expect all the users to have it in 2019 - where /Q curl - if %ERRORLEVEL% EQU 0 ( - curl -f -L "!DOWNLOAD_URL!" -o "!DOWNLOAD_FILE!" - ) else ( - rem bitsadmin seems to be available on Windows 7 - rem without /dynamic, github returns 403 - rem bitsadmin is sometimes needlessly slow but it looks better with /priority foreground - bitsadmin /transfer millDownloadJob /dynamic /priority foreground "!DOWNLOAD_URL!" "!DOWNLOAD_FILE!" - ) - if not exist "!DOWNLOAD_FILE!" ( - echo Could not download mill %MILL_VERSION% 1>&2 - exit /b 1 - ) - - move /y "!DOWNLOAD_FILE!" "%MILL%" - - set DOWNLOAD_FILE= - set DOWNLOAD_SUFFIX= -) - -set MILL_DOWNLOAD_PATH= -set MILL_VERSION= -set MILL_REPO_URL= - -rem Need to preserve the first position of those listed options -set MILL_FIRST_ARG= -if [%~1%]==[--bsp] ( - set MILL_FIRST_ARG=%1% -) else ( - if [%~1%]==[-i] ( - set MILL_FIRST_ARG=%1% - ) else ( - if [%~1%]==[--interactive] ( - set MILL_FIRST_ARG=%1% - ) else ( - if [%~1%]==[--no-server] ( - set MILL_FIRST_ARG=%1% - ) else ( - if [%~1%]==[--repl] ( - set MILL_FIRST_ARG=%1% - ) else ( - if [%~1%]==[--help] ( - set MILL_FIRST_ARG=%1% - ) - ) - ) - ) - ) -) - -set "MILL_PARAMS=%*%" - -if not [!MILL_FIRST_ARG!]==[] ( - if defined STRIP_VERSION_PARAMS ( - for /f "tokens=1-3*" %%a in ("%*") do ( - set "MILL_PARAMS=%%d" - ) - ) else ( - for /f "tokens=1*" %%a in ("%*") do ( - set "MILL_PARAMS=%%b" - ) - ) -) else ( - if defined STRIP_VERSION_PARAMS ( - for /f "tokens=1-2*" %%a in ("%*") do ( - rem strip %%a - It's the "--mill-version" option. - rem strip %%b - it's the version number that comes after the option. - rem keep %%c - It's the remaining options. - set "MILL_PARAMS=%%c" - ) - ) -) - -"%MILL%" %MILL_FIRST_ARG% -D "mill.main.cli=%MILL_MAIN_CLI%" %MILL_PARAMS% +@echo off + +rem This is a wrapper script, that automatically download mill from GitHub release pages +rem You can give the required mill version with --mill-version parameter +rem If no version is given, it falls back to the value of DEFAULT_MILL_VERSION +rem +rem Original Project page: https://github.com/lefou/millw +rem Script Version: 0.4.12 +rem +rem If you want to improve this script, please also contribute your changes back! +rem +rem Licensed under the Apache License, Version 2.0 + +rem setlocal seems to be unavailable on Windows 95/98/ME +rem but I don't think we need to support them in 2019 +setlocal enabledelayedexpansion + +if [!DEFAULT_MILL_VERSION!]==[] ( + set "DEFAULT_MILL_VERSION=0.11.4" +) + +if [!GITHUB_RELEASE_CDN!]==[] ( + set "GITHUB_RELEASE_CDN=" +) + +if [!MILL_MAIN_CLI!]==[] ( + set "MILL_MAIN_CLI=%~f0" +) + +set "MILL_REPO_URL=https://github.com/com-lihaoyi/mill" + +rem %~1% removes surrounding quotes +if [%~1%]==[--mill-version] ( + if not [%~2%]==[] ( + set MILL_VERSION=%~2% + rem shift command doesn't work within parentheses + set "STRIP_VERSION_PARAMS=true" + ) else ( + echo You specified --mill-version without a version. 1>&2 + echo Please provide a version that matches one provided on 1>&2 + echo %MILL_REPO_URL%/releases 1>&2 + exit /b 1 + ) +) + +if not defined STRIP_VERSION_PARAMS GOTO AfterStripVersionParams +rem strip the: --mill-version {version} +shift +shift +:AfterStripVersionParams + +if [!MILL_VERSION!]==[] ( + if exist .mill-version ( + set /p MILL_VERSION=<.mill-version + ) else ( + if exist .config\mill-version ( + set /p MILL_VERSION=<.config\mill-version + ) + ) +) + +if [!MILL_VERSION!]==[] ( + set MILL_VERSION=%DEFAULT_MILL_VERSION% +) + +if [!MILL_DOWNLOAD_PATH!]==[] ( + set MILL_DOWNLOAD_PATH=%USERPROFILE%\.mill\download +) + +rem without bat file extension, cmd doesn't seem to be able to run it +set MILL=%MILL_DOWNLOAD_PATH%\!MILL_VERSION!.bat + +if not exist "%MILL%" ( + set VERSION_PREFIX=%MILL_VERSION:~0,4% + rem Since 0.5.0 + set DOWNLOAD_SUFFIX=-assembly + rem Since 0.11.0 + set DOWNLOAD_FROM_MAVEN=1 + if [!VERSION_PREFIX!]==[0.0.] ( + set DOWNLOAD_SUFFIX= + set DOWNLOAD_FROM_MAVEN=0 + ) + if [!VERSION_PREFIX!]==[0.1.] ( + set DOWNLOAD_SUFFIX= + set DOWNLOAD_FROM_MAVEN=0 + ) + if [!VERSION_PREFIX!]==[0.2.] ( + set DOWNLOAD_SUFFIX= + set DOWNLOAD_FROM_MAVEN=0 + ) + if [!VERSION_PREFIX!]==[0.3.] ( + set DOWNLOAD_SUFFIX= + set DOWNLOAD_FROM_MAVEN=0 + ) + if [!VERSION_PREFIX!]==[0.4.] ( + set DOWNLOAD_SUFFIX= + set DOWNLOAD_FROM_MAVEN=0 + ) + if [!VERSION_PREFIX!]==[0.5.] ( + set DOWNLOAD_FROM_MAVEN=0 + ) + if [!VERSION_PREFIX!]==[0.6.] ( + set DOWNLOAD_FROM_MAVEN=0 + ) + if [!VERSION_PREFIX!]==[0.7.] ( + set DOWNLOAD_FROM_MAVEN=0 + ) + if [!VERSION_PREFIX!]==[0.8.] ( + set DOWNLOAD_FROM_MAVEN=0 + ) + if [!VERSION_PREFIX!]==[0.9.] ( + set DOWNLOAD_FROM_MAVEN=0 + ) + set VERSION_PREFIX=%MILL_VERSION:~0,5% + if [!VERSION_PREFIX!]==[0.10.] ( + set DOWNLOAD_FROM_MAVEN=0 + ) + set VERSION_PREFIX=%MILL_VERSION:~0,8% + if [!VERSION_PREFIX!]==[0.11.0-M] ( + set DOWNLOAD_FROM_MAVEN=0 + ) + set VERSION_PREFIX= + + for /F "delims=- tokens=1" %%A in ("!MILL_VERSION!") do set MILL_VERSION_BASE=%%A + for /F "delims=- tokens=2" %%A in ("!MILL_VERSION!") do set MILL_VERSION_MILESTONE=%%A + set VERSION_MILESTONE_START=!MILL_VERSION_MILESTONE:~0,1! + if [!VERSION_MILESTONE_START!]==[M] ( + set MILL_VERSION_TAG="!MILL_VERSION_BASE!-!MILL_VERSION_MILESTONE!" + ) else ( + set MILL_VERSION_TAG=!MILL_VERSION_BASE! + ) + + rem there seems to be no way to generate a unique temporary file path (on native Windows) + set DOWNLOAD_FILE=%MILL%.tmp + + if [!DOWNLOAD_FROM_MAVEN!]==[1] ( + set DOWNLOAD_URL=https://repo1.maven.org/maven2/com/lihaoyi/mill-dist/!MILL_VERSION!/mill-dist-!MILL_VERSION!.jar + ) else ( + set DOWNLOAD_URL=!GITHUB_RELEASE_CDN!%MILL_REPO_URL%/releases/download/!MILL_VERSION_TAG!/!MILL_VERSION!!DOWNLOAD_SUFFIX! + ) + + echo Downloading mill %MILL_VERSION% from !DOWNLOAD_URL! ... 1>&2 + + if not exist "%MILL_DOWNLOAD_PATH%" mkdir "%MILL_DOWNLOAD_PATH%" + rem curl is bundled with recent Windows 10 + rem but I don't think we can expect all the users to have it in 2019 + where /Q curl + if %ERRORLEVEL% EQU 0 ( + curl -f -L "!DOWNLOAD_URL!" -o "!DOWNLOAD_FILE!" + ) else ( + rem bitsadmin seems to be available on Windows 7 + rem without /dynamic, github returns 403 + rem bitsadmin is sometimes needlessly slow but it looks better with /priority foreground + bitsadmin /transfer millDownloadJob /dynamic /priority foreground "!DOWNLOAD_URL!" "!DOWNLOAD_FILE!" + ) + if not exist "!DOWNLOAD_FILE!" ( + echo Could not download mill %MILL_VERSION% 1>&2 + exit /b 1 + ) + + move /y "!DOWNLOAD_FILE!" "%MILL%" + + set DOWNLOAD_FILE= + set DOWNLOAD_SUFFIX= +) + +set MILL_DOWNLOAD_PATH= +set MILL_VERSION= +set MILL_REPO_URL= + +rem Need to preserve the first position of those listed options +set MILL_FIRST_ARG= +if [%~1%]==[--bsp] ( + set MILL_FIRST_ARG=%1% +) else ( + if [%~1%]==[-i] ( + set MILL_FIRST_ARG=%1% + ) else ( + if [%~1%]==[--interactive] ( + set MILL_FIRST_ARG=%1% + ) else ( + if [%~1%]==[--no-server] ( + set MILL_FIRST_ARG=%1% + ) else ( + if [%~1%]==[--repl] ( + set MILL_FIRST_ARG=%1% + ) else ( + if [%~1%]==[--help] ( + set MILL_FIRST_ARG=%1% + ) + ) + ) + ) + ) +) + +set "MILL_PARAMS=%*%" + +if not [!MILL_FIRST_ARG!]==[] ( + if defined STRIP_VERSION_PARAMS ( + for /f "tokens=1-3*" %%a in ("%*") do ( + set "MILL_PARAMS=%%d" + ) + ) else ( + for /f "tokens=1*" %%a in ("%*") do ( + set "MILL_PARAMS=%%b" + ) + ) +) else ( + if defined STRIP_VERSION_PARAMS ( + for /f "tokens=1-2*" %%a in ("%*") do ( + rem strip %%a - It's the "--mill-version" option. + rem strip %%b - it's the version number that comes after the option. + rem keep %%c - It's the remaining options. + set "MILL_PARAMS=%%c" + ) + ) +) + +"%MILL%" %MILL_FIRST_ARG% -D "mill.main.cli=%MILL_MAIN_CLI%" %MILL_PARAMS% \ No newline at end of file diff --git a/runner/client/src/mill/runner/client/MillProcessLauncher.java b/runner/client/src/mill/runner/client/MillProcessLauncher.java index 9f36f86a6fe..9ebc3979b72 100644 --- a/runner/client/src/mill/runner/client/MillProcessLauncher.java +++ b/runner/client/src/mill/runner/client/MillProcessLauncher.java @@ -2,6 +2,7 @@ import static mill.main.client.OutFiles.*; +import io.github.alexarchambault.windowsansi.WindowsAnsi; import java.io.File; import java.io.IOException; import java.io.InputStream; @@ -213,15 +214,27 @@ static int getTerminalDim(String s, boolean inheritError) throws Exception { static void writeTerminalDims(boolean tputExists, Path serverDir) throws Exception { String str; - if (!tputExists) str = "0 0"; - else { - try { - if (java.lang.System.console() == null) str = "0 0"; - else str = getTerminalDim("cols", true) + " " + getTerminalDim("lines", true); - } catch (Exception e) { - str = "0 0"; + + try { + if (java.lang.System.console() == null) str = "0 0"; + else { + if (isWin()) { + + WindowsAnsi.Size size = WindowsAnsi.terminalSize(); + int width = size.getWidth(); + int height = size.getHeight(); + str = width + " " + height; + } else if (!tputExists) { + // Hardcoded size of a quarter screen terminal on 13" windows laptop + str = "78 24"; + } else { + str = getTerminalDim("cols", true) + " " + getTerminalDim("lines", true); + } } + } catch (Exception e) { + str = "0 0"; } + Files.write(serverDir.resolve(ServerFiles.terminfo), str.getBytes()); } diff --git a/runner/package.mill b/runner/package.mill index 9dc3f21f7c7..e91aad22e45 100644 --- a/runner/package.mill +++ b/runner/package.mill @@ -7,6 +7,7 @@ object `package` extends RootModule with build.MillPublishScalaModule { object client extends build.MillPublishJavaModule { def buildInfoPackageName = "mill.runner.client" def moduleDeps = Seq(build.main.client) + def ivyDeps = Agg(build.Deps.windowsAnsi) } def moduleDeps = Seq(