From 3548d0acd25ae326a681594b3a3a08f99f36d0f6 Mon Sep 17 00:00:00 2001 From: Alex Archambault Date: Thu, 6 Feb 2025 20:49:03 +0100 Subject: [PATCH 1/6] Use system calls to get terminal size on Linux / Mac --- build.mill | 2 +- main/package.mill | 2 +- .../runner/client/MillProcessLauncher.java | 26 ++++++++++++++++--- runner/package.mill | 2 +- 4 files changed, 25 insertions(+), 7 deletions(-) diff --git a/build.mill b/build.mill index 821f7dc3260..9707629eca7 100644 --- a/build.mill +++ b/build.mill @@ -177,7 +177,7 @@ object Deps { val semanticDbJava = ivy"com.sourcegraph:semanticdb-java:0.10.3" val sourcecode = ivy"com.lihaoyi::sourcecode:0.4.2" val upickle = ivy"com.lihaoyi::upickle:3.3.1" - val windowsAnsi = ivy"io.github.alexarchambault.windows-ansi:windows-ansi:0.0.6" + val nativeTerminal = ivy"io.github.alexarchambault.native-terminal:native-terminal:0.0.7" val zinc = ivy"org.scala-sbt::zinc:1.10.7" // keep in sync with doc/antora/antory.yml val bsp4j = ivy"ch.epfl.scala:bsp4j:2.2.0-M2" diff --git a/main/package.mill b/main/package.mill index 00a02c4e347..05a41ef102f 100644 --- a/main/package.mill +++ b/main/package.mill @@ -11,7 +11,7 @@ object `package` extends RootModule with build.MillStableScalaModule with BuildI def moduleDeps = Seq(eval, resolve, client) def ivyDeps = Agg( - build.Deps.windowsAnsi, + build.Deps.nativeTerminal, build.Deps.coursierInterface, build.Deps.mainargs, build.Deps.requests, diff --git a/runner/client/src/mill/runner/client/MillProcessLauncher.java b/runner/client/src/mill/runner/client/MillProcessLauncher.java index 9eff9a105b2..efc7eb4fedb 100644 --- a/runner/client/src/mill/runner/client/MillProcessLauncher.java +++ b/runner/client/src/mill/runner/client/MillProcessLauncher.java @@ -2,7 +2,8 @@ import static mill.main.client.OutFiles.*; -import io.github.alexarchambault.windowsansi.WindowsAnsi; +import io.github.alexarchambault.nativeterm.NativeTerminal; +import io.github.alexarchambault.nativeterm.TerminalSize; import java.io.File; import java.io.IOException; import java.io.InputStream; @@ -248,15 +249,32 @@ static int getTerminalDim(String s, boolean inheritError) throws Exception { private static AtomicReference memoizedTerminalDims = new AtomicReference(); + private final static boolean canUseNativeTerminal; + + static { + boolean canUse; + if (mill.main.client.Util.hasConsole()) + try { + NativeTerminal.getSize(); + canUse = true; + } catch (Throwable ex) { + canUse = false; + } + else + canUse = false; + + canUseNativeTerminal = canUse; + } + static void writeTerminalDims(boolean tputExists, Path serverDir) throws Exception { String str; try { - if (java.lang.System.console() == null) str = "0 0"; + if (!mill.main.client.Util.hasConsole()) str = "0 0"; else { - if (isWin()) { + if (canUseNativeTerminal) { - WindowsAnsi.Size size = WindowsAnsi.terminalSize(); + TerminalSize size = NativeTerminal.getSize(); int width = size.getWidth(); int height = size.getHeight(); str = width + " " + height; diff --git a/runner/package.mill b/runner/package.mill index 645765218fd..83f2a1b286b 100644 --- a/runner/package.mill +++ b/runner/package.mill @@ -10,7 +10,7 @@ object `package` extends RootModule with build.MillPublishScalaModule { def buildInfoPackageName = "mill.runner.client" def moduleDeps = Seq(build.main.client) def ivyDeps = Agg( - build.Deps.windowsAnsi, + build.Deps.nativeTerminal, build.Deps.coursier, build.Deps.coursierJvm, build.Deps.logback From 3932da7a5a43ba681a2c01e6133d6d77743ecc32 Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Thu, 6 Feb 2025 20:09:20 +0000 Subject: [PATCH 2/6] [autofix.ci] apply automated fixes --- .../client/src/mill/runner/client/MillProcessLauncher.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/runner/client/src/mill/runner/client/MillProcessLauncher.java b/runner/client/src/mill/runner/client/MillProcessLauncher.java index efc7eb4fedb..cdc65cf82e5 100644 --- a/runner/client/src/mill/runner/client/MillProcessLauncher.java +++ b/runner/client/src/mill/runner/client/MillProcessLauncher.java @@ -249,7 +249,7 @@ static int getTerminalDim(String s, boolean inheritError) throws Exception { private static AtomicReference memoizedTerminalDims = new AtomicReference(); - private final static boolean canUseNativeTerminal; + private static final boolean canUseNativeTerminal; static { boolean canUse; @@ -260,8 +260,7 @@ static int getTerminalDim(String s, boolean inheritError) throws Exception { } catch (Throwable ex) { canUse = false; } - else - canUse = false; + else canUse = false; canUseNativeTerminal = canUse; } From 068a0f74ebd4e63c695ec0d21332776329244587 Mon Sep 17 00:00:00 2001 From: Alex Archambault Date: Fri, 7 Feb 2025 18:18:23 +0100 Subject: [PATCH 3/6] Load jansi native lib ourselves to speed things up --- build.mill | 4 +- main/client/src/mill/main/client/Util.java | 30 ++++++++---- .../runner/client/MillProcessLauncher.java | 46 ++++++++++++++++++- runner/package.mill | 16 +++++-- 4 files changed, 79 insertions(+), 17 deletions(-) diff --git a/build.mill b/build.mill index 9707629eca7..711963cd1b1 100644 --- a/build.mill +++ b/build.mill @@ -117,9 +117,9 @@ object Deps { val asmTree = ivy"org.ow2.asm:asm-tree:9.7.1" val bloopConfig = ivy"ch.epfl.scala::bloop-config:1.5.5" - val coursierVersion = "2.1.24" + val coursierVersion = "2.1.25-M1" val coursier = ivy"io.get-coursier::coursier:$coursierVersion" - val coursierInterface = ivy"io.get-coursier:interface:1.0.27" + val coursierInterface = ivy"io.get-coursier:interface:1.0.28-M1" val coursierJvm = ivy"io.get-coursier::coursier-jvm:$coursierVersion" val cask = ivy"com.lihaoyi::cask:0.9.4" diff --git a/main/client/src/mill/main/client/Util.java b/main/client/src/mill/main/client/Util.java index 30c169f905f..1f831f20bc3 100644 --- a/main/client/src/mill/main/client/Util.java +++ b/main/client/src/mill/main/client/Util.java @@ -41,6 +41,25 @@ public static final int ExitServerCodeWhenVersionMismatch() { !System.getProperty("java.specification.version").startsWith("1."); private static Charset utf8 = Charset.forName("UTF-8"); + private static final boolean hasConsole0; + + static { + + Console console = System.console(); + + boolean foundConsole; + if (console != null) { + try { + Method method = console.getClass().getMethod("isTerminal"); + foundConsole = (Boolean) method.invoke(console); + } catch (InvocationTargetException | NoSuchMethodException | IllegalAccessException ignored) { + foundConsole = true; + } + } else foundConsole = false; + + hasConsole0 = foundConsole; + } + /** * Determines if we have an interactive console attached to the application. *

@@ -54,16 +73,7 @@ public static final int ExitServerCodeWhenVersionMismatch() { * both JDK versions before 22 and later. */ public static boolean hasConsole() { - Console console = System.console(); - - if (console != null) { - try { - Method method = console.getClass().getMethod("isTerminal"); - return (Boolean) method.invoke(console); - } catch (InvocationTargetException | NoSuchMethodException | IllegalAccessException ignored) { - return true; - } - } else return false; + return hasConsole0; } public static String[] parseArgs(InputStream argStream) throws IOException { diff --git a/runner/client/src/mill/runner/client/MillProcessLauncher.java b/runner/client/src/mill/runner/client/MillProcessLauncher.java index cdc65cf82e5..f8af221a5b0 100644 --- a/runner/client/src/mill/runner/client/MillProcessLauncher.java +++ b/runner/client/src/mill/runner/client/MillProcessLauncher.java @@ -15,6 +15,7 @@ import mill.main.client.EnvVars; import mill.main.client.ServerFiles; import mill.main.client.Util; +import org.fusesource.jansi.internal.OSInfo; public class MillProcessLauncher { @@ -252,15 +253,56 @@ static int getTerminalDim(String s, boolean inheritError) throws Exception { private static final boolean canUseNativeTerminal; static { + // Load jansi native library + // We pre-compute the library location in the coursier archive cache, so that + // we don't need to load the whole of coursier upfront if the native library file + // is already in cache. + // Loading the native library on our own allows to speed things compared to what jansi would + // have done on its own (reading the library in its resources and writing it in a temporary + // location upon every new Mill run) + String jansiVersion = mill.runner.client.Versions.jansiVersion; + File archiveCacheLocation; + try { + archiveCacheLocation = coursier.paths.CachePath.defaultArchiveCacheDirectory(); + } catch (IOException ex) { + throw new RuntimeException(ex); + } + String jansiLibPathInArchive = "org/fusesource/jansi/internal/native/" + OSInfo.getNativeLibFolderPathForCurrentOS() + "/" + System.mapLibraryName("jansi").replace(".dylib", ".jnilib"); + File jansiLib = new File( + archiveCacheLocation, + "https/repo1.maven.org/maven2/org/fusesource/jansi/jansi/" + jansiVersion + "/jansi-" + jansiVersion + ".jar/" + jansiLibPathInArchive + ); + if (!jansiLib.exists()) { + coursierapi.ArchiveCache archiveCache = coursierapi.ArchiveCache.create(); + File jansiDir = archiveCache.get(coursierapi.Artifact.of("https://repo1.maven.org/maven2/org/fusesource/jansi/jansi/" + jansiVersion + "/jansi-" + jansiVersion + ".jar")); + jansiLib = new File(jansiDir, jansiLibPathInArchive); // just in case, should be the same value as before + } + System.load(jansiLib.getAbsolutePath()); + + // Tell jansi not to attempt to load a native library on its own + Class cls = org.fusesource.jansi.internal.JansiLoader.class; + java.lang.reflect.Field fld; + try { + fld = cls.getDeclaredField("loaded"); + } catch (NoSuchFieldException ex) { + throw new RuntimeException(ex); + } + fld.setAccessible(true); + try { + fld.set(null, Boolean.TRUE); + } catch (IllegalAccessException ex) { + throw new RuntimeException(ex); + } + boolean canUse; - if (mill.main.client.Util.hasConsole()) + if (mill.main.client.Util.hasConsole()) { try { NativeTerminal.getSize(); canUse = true; } catch (Throwable ex) { canUse = false; } - else canUse = false; + } else canUse = false; canUseNativeTerminal = canUse; } diff --git a/runner/package.mill b/runner/package.mill index 83f2a1b286b..867719dc40d 100644 --- a/runner/package.mill +++ b/runner/package.mill @@ -12,13 +12,23 @@ object `package` extends RootModule with build.MillPublishScalaModule { def ivyDeps = Agg( build.Deps.nativeTerminal, build.Deps.coursier, + build.Deps.coursierInterface, build.Deps.coursierJvm, build.Deps.logback ) def buildInfoObjectName = "Versions" - def buildInfoMembers = Seq( - BuildInfo.Value("coursierJvmIndexVersion", build.Deps.coursierJvmIndexVersion) - ) + def buildInfoMembers = Task { + val jansiVersion = compileClasspath().map(_.path.last) + .find(name => name.startsWith("jansi-") && name.endsWith(".jar")) + .map(_.stripPrefix("jansi-").stripSuffix(".jar")) + .getOrElse { + sys.error("Cannot get jansi version from compile class path") + } + Seq( + BuildInfo.Value("coursierJvmIndexVersion", build.Deps.coursierJvmIndexVersion), + BuildInfo.Value("jansiVersion", jansiVersion) + ) + } } def moduleDeps = Seq( From e033ef525eb71562f21fb72db9a1541cac71e776 Mon Sep 17 00:00:00 2001 From: Alex Archambault Date: Mon, 17 Feb 2025 12:28:57 +0100 Subject: [PATCH 4/6] Fix coursier interface version --- build.mill | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.mill b/build.mill index 711963cd1b1..f5376616b8d 100644 --- a/build.mill +++ b/build.mill @@ -119,7 +119,7 @@ object Deps { val coursierVersion = "2.1.25-M1" val coursier = ivy"io.get-coursier::coursier:$coursierVersion" - val coursierInterface = ivy"io.get-coursier:interface:1.0.28-M1" + val coursierInterface = ivy"io.get-coursier:interface:1.0.29-M1" val coursierJvm = ivy"io.get-coursier::coursier-jvm:$coursierVersion" val cask = ivy"com.lihaoyi::cask:0.9.4" From 253cb6bd8024c1703756b268c09ae0113c9b026a Mon Sep 17 00:00:00 2001 From: Alex Archambault Date: Mon, 17 Feb 2025 22:26:42 +0100 Subject: [PATCH 5/6] Fix --- runner/client/src/mill/runner/client/MillProcessLauncher.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/runner/client/src/mill/runner/client/MillProcessLauncher.java b/runner/client/src/mill/runner/client/MillProcessLauncher.java index f8af221a5b0..d3a2ae2b2d2 100644 --- a/runner/client/src/mill/runner/client/MillProcessLauncher.java +++ b/runner/client/src/mill/runner/client/MillProcessLauncher.java @@ -260,7 +260,7 @@ static int getTerminalDim(String s, boolean inheritError) throws Exception { // Loading the native library on our own allows to speed things compared to what jansi would // have done on its own (reading the library in its resources and writing it in a temporary // location upon every new Mill run) - String jansiVersion = mill.runner.client.Versions.jansiVersion; + String jansiVersion = mill.runner.client.Versions.jansiVersion(); File archiveCacheLocation; try { archiveCacheLocation = coursier.paths.CachePath.defaultArchiveCacheDirectory(); From 39f2c8611cae49540591ffa4a21a8025b5fb4bf2 Mon Sep 17 00:00:00 2001 From: Alex Archambault Date: Mon, 17 Feb 2025 22:42:44 +0100 Subject: [PATCH 6/6] Add missing coursier codecs --- .../src/mill/scalalib/JsonFormatters.scala | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/scalalib/src/mill/scalalib/JsonFormatters.scala b/scalalib/src/mill/scalalib/JsonFormatters.scala index 3b2d879e6e0..e32c5a00754 100644 --- a/scalalib/src/mill/scalalib/JsonFormatters.scala +++ b/scalalib/src/mill/scalalib/JsonFormatters.scala @@ -7,6 +7,26 @@ trait JsonFormatters { implicit lazy val extensionFormat: RW[coursier.core.Extension] = upickle.default.macroRW implicit lazy val modFormat: RW[coursier.Module] = upickle.default.macroRW + implicit lazy val versionConstraintFormat: RW[coursier.version.VersionConstraint] = + implicitly[RW[String]].bimap( + _.asString, + coursier.version.VersionConstraint(_) + ) + implicit lazy val versionIntervalFormat0: RW[coursier.version.VersionInterval] = + upickle.default.macroRW + implicit lazy val versionFormat0: RW[coursier.version.Version] = + implicitly[RW[String]].bimap( + _.asString, + coursier.version.Version(_) + ) + implicit lazy val variantSelectorFormat: RW[coursier.core.VariantSelector] = + RW.merge( + upickle.default.macroRW[coursier.core.VariantSelector.ConfigurationBased] + ) + implicit lazy val variantFormat: RW[coursier.core.Variant] = + RW.merge( + upickle.default.macroRW[coursier.core.Variant.Configuration] + ) implicit lazy val bomDepFormat: RW[coursier.core.BomDependency] = upickle.default.macroRW implicit lazy val overridesFormat: RW[coursier.core.Overrides] = implicitly[RW[coursier.core.DependencyManagement.Map]].bimap(