diff --git a/build.mill b/build.mill
index 821f7dc3260..f5376616b8d 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.29-M1"
val coursierJvm = ivy"io.get-coursier::coursier-jvm:$coursierVersion"
val cask = ivy"com.lihaoyi::cask:0.9.4"
@@ -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/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/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..d3a2ae2b2d2 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;
@@ -14,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 {
@@ -248,15 +250,72 @@ static int getTerminalDim(String s, boolean inheritError) throws Exception {
private static AtomicReference memoizedTerminalDims = new AtomicReference();
+ 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()) {
+ 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..867719dc40d 100644
--- a/runner/package.mill
+++ b/runner/package.mill
@@ -10,15 +10,25 @@ 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.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(
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(