Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use system calls to get terminal size on Linux / Mac #4497

Draft
wants to merge 7 commits into
base: 0.12.x
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions build.mill
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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"
Expand Down
30 changes: 20 additions & 10 deletions main/client/src/mill/main/client/Util.java
Original file line number Diff line number Diff line change
Expand Up @@ -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.
* <p>
Expand All @@ -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 {
Expand Down
2 changes: 1 addition & 1 deletion main/package.mill
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
67 changes: 63 additions & 4 deletions runner/client/src/mill/runner/client/MillProcessLauncher.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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 {

Expand Down Expand Up @@ -248,15 +250,72 @@ static int getTerminalDim(String s, boolean inheritError) throws Exception {

private static AtomicReference<String> 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;
Expand Down
18 changes: 14 additions & 4 deletions runner/package.mill
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
20 changes: 20 additions & 0 deletions scalalib/src/mill/scalalib/JsonFormatters.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
Loading