Skip to content

Make build.mill Scala version configurable #5366

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

Open
wants to merge 8 commits into
base: main
Choose a base branch
from
Open
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
15 changes: 15 additions & 0 deletions core/constants/src/mill/constants/CacheFiles.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package mill.constants;

public class CacheFiles {
/** Prefix for all cache files. */
public static final String prefix = "mill-";

public static String filename(String name) {
return prefix + name;
}

public static final String javaHome = "java-home";

/** Caches the classpath for the mill runner. */
public static final String resolveRunner = "resolve-runner";
}
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
package mill.integration

import mill.constants.CacheFiles
import mill.testkit.{IntegrationTester, UtestIntegrationTestSuite}
import utest.*

object MillScalaVersionTests extends UtestIntegrationTestSuite {
private def writeBuildMill(tester: IntegrationTester, scalaVersion: Option[String]): Unit = {
val scalaVersionLine = scalaVersion.fold("")(v => s"//| mill-scala-version: $v")
os.write.over(
tester.workspacePath / "build.mill",
s"""$scalaVersionLine
|package build
|
|// empty file
|""".stripMargin
)
}

private def readResolveRunner(tester: IntegrationTester) =
os.read.lines(tester.workspacePath / "out" / CacheFiles.filename(CacheFiles.resolveRunner))

private def scalaLibraryFor(version: String) =
s"org/scala-lang/scala3-library_3/$version/scala3-library_3-$version.jar"

private def scalaCompilerFor(version: String) =
s"org/scala-lang/scala3-compiler_3/$version/scala3-compiler_3-$version.jar"

private val ScalaVersion = "3.7.2-RC1"

val tests: Tests = Tests {

Check failure on line 31 in integration/feature/mill-scala-version/src/MillScalaVersionTests.scala

View workflow job for this annotation

GitHub Actions / linux (11, 'integration.{failure,feature,ide}.__.packaged.daemon', false, false) / run

MillScalaVersionTests.withDirectiveAndThenWithout

!lines.exists(_.contains(scalaCompilerFor(ScalaVersion))) lines: IndexedSeq[String] = ArraySeq("mill:SNAPSHOT,scala:3.7.2-RC1", "/home/runner/.ivy2/local/com.lihaoyi/mill-runner-daemon_3/SNAPSHOT/jars/mill-runner-daemon_3.jar", "/home/runner/.cache/coursier/v1/https/repo1.maven.org/maven2/org/scala-lang/scala3-compiler_3/3.7.2-RC1/scala3-compiler_3-3.7.2-RC1.jar", "/home/runner/.cache/coursier/v1/https/repo1.maven.org/maven2/com/lihaoyi/sourcecode_3/0.4.3-M5/sourcecode_3-0.4.3-M5.jar", "/home/runner/.cache/coursier/v1/https/repo1.maven.org/maven2/com/lihaoyi/os-lib_3/0.11.5-M9/os-lib_3-0.11.5-M9.jar", "/home/runner/.cache/coursier/v1/https/repo1.maven.org/maven2/com/lihaoyi/os-lib-watch_3/0.11.5-M9/os-lib-watch_3-0.11.5-M9.jar", "/home/runner/.cache/coursier/v1/https/repo1.maven.org/maven2/com/lihaoyi/mainargs_3/0.7.6/mainargs_3-0.7.6.jar", "/home/runner/.cache/coursier/v1/https/repo1.maven.org/maven2/com/lihaoyi/upickle_3/4.2.1/upickle_3-4.2.1.jar", "/home/runner/.cache/coursier/v1/https/repo1.maven.org/maven2/com/lihaoyi/pprint_3/0.9.0/pprint_3-0.9.0.jar", "/home/runner/.cache/coursier/v1/https/repo1.maven.org/maven2/com/lihaoyi/fansi_3/0.5.0/fansi_3-0.5.0.jar", "/home/runner/.cache/coursier/v1/https/repo1.maven.org/maven2/org/scala-lang/scala3-library_3/3.7.2-RC1/scala3-library_3-3.7.2-RC1.jar", "/home/runner/.cache/coursier/v1/https/repo1.maven.org/maven2/com/lihaoyi/mill-moduledefs_3/0.11.9/mill-moduledefs_3-0.11.9.jar", "/home/runner/.ivy2/local/com.lihaoyi/mill-runner-idea_3/SNAPSHOT/jars/mill-runner-idea_3.jar", "/home/runner/.ivy2/local/com.lihaoyi/mill-runner-bsp_3/SNAPSHOT/jars/mill-runner-bsp_3.jar", "/home/runner/.ivy2/local/com.lihaoyi/mill-runner-bsp-worker_3/SNAPSHOT/jars/mill-runner-bsp-worker_3.jar", "/home/runner/.ivy2/local/com.lihaoyi/mill-core-eval_3/SNAPSHOT/jars/mill-core-eval_3.jar", "/home/runner/.ivy2/local/com.lihaoyi/mill-runner-server_3/SNAPSHOT/jars/mill-runner-server_3.jar", "/home/runner/.ivy2/local/com.lihaoyi/mill-runner-launcher_3/SNAPSHOT/jars/mill-runner-launcher_3.jar", "/home/runner/.ivy2/local/com.lihaoyi/mill-runner-meta_3/SNAPSHOT/jars/mill-runner-meta_3.jar", "/home/runner/.cache/coursier/v1/https/repo1.maven.org/maven2/org/scala-lang/scala3-interfaces/3.7.2-RC1/scala3-interfaces-3.7.2-RC1.jar", "/home/runner/.cache/coursier/v1/https/repo1.maven.org/maven2/org/scala-lang/tasty-core_3/3.7.2-RC1/tasty-core_3-3.7.2-RC1.jar", "/home/runner/.cache/coursier/v1/https/repo1.maven.org/maven2/org/scala-lang/modules/scala-asm/9.8.0-scala-1/scala-asm-9.8.0-scala-1.jar", "/home/runner/.cache/coursier/v1/https/repo1.maven.org/maven2/org/scala-sbt/compiler-interface/1.10.7/compiler-interface-1.10.7.jar", "/home/runner/.cache/coursier/v1/https/repo1.maven.org/maven2/org/jline/jline-reader/3.29.0/jline-reader-3.29.0.jar", "/home/runner/.cache/coursier/v1/https/repo1.maven.org/maven2/org/jline/jline-terminal/3.29.0/jline-terminal-3.29.0.jar", "/home/runner/.cache/coursier/v1/https/repo1.maven.org/maven2/org/jline/jline-terminal-jni/3.29.0/jline-terminal-jni-3.29.0.jar", "/home/runner/.cache/coursier/v1/https/repo1.maven.org/maven2/com/lihaoyi/geny_3/1.1.1/geny_3-1.1.1.jar", "/home/runner/.cache/coursier/v1/https/repo1.maven.org/maven2/com/lihaoyi/os-zip/0.11.5-M9/os-zip-0.11.5-M9.jar", "/home/runner/.cache/coursier/v1/https/repo1.maven.org/maven2/net/java/dev/jna/jna/5.17.0/jna-5.17.0.jar", "/home/runner/.cache/coursier/v1/https/repo1.maven.org/maven2/org/scala-lang/modules/scala-collection-compat_3/2.12.0/scala-collection-compat_3-2.12.0.jar", "/home/runner/.cache/coursier/v1/https/repo1.maven.org/maven2/com/lihaoyi/ujson_3/4.2.1/ujson_3-4.2.1.jar", "/home/runner/.cache/coursier/v1/https/repo1.maven.org/maven2/com/lihaoyi/upack_3/4.2.1/upack_3-4.2.1.jar", "/home/runner/.cache/coursier/v1/https/repo1.maven.org/maven2/com/lihaoyi/upickle-implicits_3/4.2.1/upickle-implicits_3-4.2.1.jar", "/home/runner/.cache/coursier/v1/https/repo1.maven.org/maven2/org/scala-lang/scala-library/2.13.16/scala-library-2.13.16.jar", "/home/runner/.cache/coursier/v1/https/repo1.maven.org/maven2/org/scala-lang/modules/s
test("noDirective") - integrationTest { tester =>
import tester.*

writeBuildMill(tester, scalaVersion = None)
val res = eval("version")
assert(res.isSuccess)

val lines = readResolveRunner(tester)
assert(!lines.exists(_.contains(scalaCompilerFor(ScalaVersion))))
assert(!lines.exists(_.contains(scalaLibraryFor(ScalaVersion))))
}

test("withDirective") - integrationTest { tester =>
import tester.*

writeBuildMill(tester, scalaVersion = Some(ScalaVersion))
val res = eval("version")
assert(res.isSuccess)

val lines = readResolveRunner(tester)
assert(lines.exists(_.contains(scalaCompilerFor(ScalaVersion))))
assert(lines.exists(_.contains(scalaLibraryFor(ScalaVersion))))
}

test("withDirectiveAndThenWithout") - integrationTest { tester =>
import tester.*

{
writeBuildMill(tester, scalaVersion = Some(ScalaVersion))
val res = eval("version")
assert(res.isSuccess)

val lines = readResolveRunner(tester)
assert(lines.exists(_.contains(scalaCompilerFor(ScalaVersion))))
assert(lines.exists(_.contains(scalaLibraryFor(ScalaVersion))))
}

{
// This should recompile the build with the new version.
writeBuildMill(tester, scalaVersion = None)
val res = eval("version")
assert(res.isSuccess)

val lines = readResolveRunner(tester)
assert(!lines.exists(_.contains(scalaCompilerFor(ScalaVersion))))
assert(!lines.exists(_.contains(scalaLibraryFor(ScalaVersion))))
}
}

}
}
41 changes: 31 additions & 10 deletions runner/launcher/src/mill/launcher/CoursierClient.scala
Original file line number Diff line number Diff line change
Expand Up @@ -12,32 +12,53 @@ import scala.concurrent.duration.Duration
import mill.coursierutil.TestOverridesRepo

object CoursierClient {
def resolveMillDaemon() = {

/**
* Resolves the classpath for the mill daemon.
*
* @param scalaVersion the version of the Scala to use. If not specified, the version that mill uses itself will be
* used.
*/
def resolveMillDaemon(scalaVersion: Option[String]): Array[String] = {
val repositories = Await.result(Resolve().finalRepositories.future(), Duration.Inf)
val coursierCache0 = FileCache[Task]()
.withLogger(coursier.cache.loggers.RefreshLogger.create())

val artifactsResultOrError = {

val artifactsResult = {
val resolve = Resolve()
.withCache(coursierCache0)
.withDependencies(Seq(Dependency(
Module(Organization("com.lihaoyi"), ModuleName("mill-runner-daemon_3"), Map()),
VersionConstraint(mill.client.BuildInfo.millVersion)
)))
.withDependencies(Seq(
Dependency(
Module(
Organization("com.lihaoyi"),
ModuleName("mill-runner-daemon_3"),
attributes = Map.empty
),
VersionConstraint(mill.client.BuildInfo.millVersion)
)
) ++ scalaVersion.map { version =>
Dependency(
Module(
Organization("org.scala-lang"),
ModuleName("scala3-compiler_3"),
attributes = Map.empty
),
VersionConstraint(version)
)
})
.withRepositories(Seq(TestOverridesRepo) ++ repositories)

resolve.either() match {
case Left(err) => sys.error(err.toString)
case Right(v) =>
case Right(resolution) =>
Artifacts(coursierCache0)
.withResolution(v)
.withResolution(resolution)
.eitherResult()
.right.get
}
}

artifactsResultOrError.artifacts.map(_._2.toString).toArray
artifactsResult.artifacts.iterator.map { case (_, file) => file.toString }.toArray
}

def resolveJavaHome(id: String): java.io.File = {
Expand Down
43 changes: 35 additions & 8 deletions runner/launcher/src/mill/launcher/MillProcessLauncher.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,8 @@
import java.util.function.Supplier;
import java.util.stream.Stream;
import mill.client.ClientUtil;
import mill.constants.BuildInfo;
import mill.constants.CodeGenConstants;
import mill.constants.DaemonFiles;
import mill.constants.EnvVars;
import mill.constants.*;
import scala.Option$;

public class MillProcessLauncher {

Expand Down Expand Up @@ -120,10 +118,12 @@ static List<String> loadMillConfig(String key) throws Exception {
Object conf = mill.launcher.ConfigReader.readYaml(
buildFile, buildFile.getFileName().toString());
if (!(conf instanceof Map)) return new String[] {};
@SuppressWarnings("unchecked")
Map<String, Object> conf2 = (Map<String, Object>) conf;

if (!conf2.containsKey(key)) return new String[] {};
if (conf2.get(key) instanceof List) {
@SuppressWarnings("unchecked")
List<String> list = (List<String>) conf2.get(key);
String[] arr = new String[list.size()];
for (int i = 0; i < arr.length; i++) {
Expand Down Expand Up @@ -152,7 +152,15 @@ static List<String> millOpts() throws Exception {
}

static String millJvmVersion() throws Exception {
List<String> res = loadMillConfig("mill-jvm-version");
return loadMillConfigSingleValue("mill-jvm-version");
}

static String millScalaVersion() throws Exception {
return loadMillConfigSingleValue("mill-scala-version");
}

static String loadMillConfigSingleValue(String key) throws Exception {
List<String> res = loadMillConfig(key);
if (res.isEmpty()) return null;
else return res.get(0);
}
Expand Down Expand Up @@ -183,7 +191,7 @@ static String javaHome() throws Exception {
if (jvmId != null) {
final String jvmIdFinal = jvmId;
javaHome = cachedComputedValue0(
"java-home",
CacheFiles.javaHome,
jvmId,
() -> new String[] {CoursierClient.resolveJavaHome(jvmIdFinal).getAbsolutePath()},
// Make sure we check to see if the saved java home exists before using
Expand Down Expand Up @@ -228,10 +236,16 @@ static List<String> millLaunchJvmCommand() throws Exception {
// extra opts
vmOptions.addAll(millJvmOpts());

var maybeScalaVersion = millScalaVersion();
vmOptions.add("-XX:+HeapDumpOnOutOfMemoryError");
vmOptions.add("-cp");
var classPathCacheKey = "mill:" + BuildInfo.millVersion + ",scala:"
+ (maybeScalaVersion == null ? "default" : maybeScalaVersion);
String[] runnerClasspath = cachedComputedValue0(
"resolve-runner", BuildInfo.millVersion, () -> CoursierClient.resolveMillDaemon(), arr -> {
CacheFiles.resolveRunner,
classPathCacheKey,
() -> CoursierClient.resolveMillDaemon(Option$.MODULE$.apply(maybeScalaVersion)),
arr -> {
for (String s : arr) {
if (!Files.exists(Paths.get(s))) return false;
}
Expand All @@ -246,10 +260,23 @@ static String[] cachedComputedValue(String name, String key, Supplier<String[]>
return cachedComputedValue0(name, key, block, arr -> true);
}

/**
* Loads a value from the cache, or computes it if it's not in the cache, or re-computes it if it's
* in the cache but invalid.
* <p>
* The cache is stored in the `out/mill-{name}` file and contains only a single key.
*
* @param name name of the cached value
* @param key key of the value in the cache. If the cache exists but the key doesn't match, the value
* will be re-computed.
* @param block block to compute the value
* @param validate function to validate the value. If the value is in cache but invalid, it will be
* re-computed.
*/
static String[] cachedComputedValue0(
String name, String key, Supplier<String[]> block, Function<String[], Boolean> validate) {
try {
Path cacheFile = Paths.get(".").resolve(out).resolve("mill-" + name);
Path cacheFile = Paths.get(".").resolve(out).resolve(CacheFiles.filename(name));
String[] value = null;
if (Files.exists(cacheFile)) {
String[] savedInfo = Files.readString(cacheFile).split("\n");
Expand Down
18 changes: 18 additions & 0 deletions website/docs/modules/ROOT/pages/cli/build-header.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ For example, a build header may look something like:
//| mill-opts: ["--jobs=0.5C"]
//| mill-jvm-version: temurin:11
//| mill-jvm-opts: ["-XX:NonProfiledCodeHeapSize=250m", "-XX:ReservedCodeCacheSize=500m"]
//| mill-scala-version: 3.7.2-RC1
//| # newlines are allowed too
//|
//| repositories:
Expand Down Expand Up @@ -125,3 +126,20 @@ _build.mill_

Missing environment variables are converted to the empty string.

== mill-scala-version

Mill allows you to specify the Scala version you want to use for your build
via a `mill-scala-version` key in the build header:

_build.mill_

[source]
----
//| mill-scala-version: 3.7.2-RC1
----

This allows you to use newer versions of Scala than the version used by Mill
itself, as long as the version you use is backwards compatible.

If `mill-scala-version` is not provided, Mill uses the version of Scala
used by Mill itself.
Loading