From 5d7a3ea8cdd91f1aee5cbbbbd081295bc780ee80 Mon Sep 17 00:00:00 2001 From: Lorenzo Gabriele <lorenzolespaul@gmail.com> Date: Sat, 2 Mar 2024 11:57:19 +0100 Subject: [PATCH] Support Scala Native 0.5.0-RC1 (#3054) Scala Native `0.5.0-RC1` was just released: https://github.com/scala-native/scala-native/releases/tag/v0.5.0-RC1 Pull Request: https://github.com/com-lihaoyi/mill/pull/3054 --- build.sc | 23 ++- .../src/mill/contrib/bloop/BloopTests.scala | 2 +- .../scalanativelib/ScalaNativeModule.scala | 12 +- .../src/utest/tests/ArgsParserTests.scala | 4 +- .../test/src/utest/tests/MainTests.scala | 6 +- .../HelloNativeWorldTests.scala | 33 +++-- .../scalanativelib/ScalaTestsErrorTests.scala | 2 +- .../scalanative/build/MillUtils.scala} | 0 .../worker/ScalaNativeWorkerImpl.scala | 135 ++++++++++++++++++ 9 files changed, 192 insertions(+), 25 deletions(-) rename scalanativelib/worker/0.4/src/{mill/scalanativelib/worker/scala/scalanative/Utils.scala => scala/scalanative/build/MillUtils.scala} (100%) create mode 100644 scalanativelib/worker/0.5/src/mill/scalanativelib/worker/ScalaNativeWorkerImpl.scala diff --git a/build.sc b/build.sc index 3b65aedf441..2e0c46af17b 100644 --- a/build.sc +++ b/build.sc @@ -86,6 +86,14 @@ object Deps { val scalanativeTestRunner = ivy"org.scala-native::test-runner:${scalanativeVersion}" } + object Scalanative_0_5 { + val scalanativeVersion = "0.5.0-RC1" + val scalanativeTools = ivy"org.scala-native::tools:${scalanativeVersion}" + val scalanativeUtil = ivy"org.scala-native::util:${scalanativeVersion}" + val scalanativeNir = ivy"org.scala-native::nir:${scalanativeVersion}" + val scalanativeTestRunner = ivy"org.scala-native::test-runner:${scalanativeVersion}" + } + trait Play { def playVersion: String def playBinVersion: String = playVersion.split("[.]").take(2).mkString(".") @@ -395,7 +403,8 @@ trait MillBaseTestsModule extends MillJavaModule with TestModule { s"-DTEST_SCALA_3_2_VERSION=${Deps.testScala32Version}", s"-DTEST_SCALA_3_3_VERSION=${Deps.testScala33Version}", s"-DTEST_SCALAJS_VERSION=${Deps.Scalajs_1.scalaJsVersion}", - s"-DTEST_SCALANATIVE_VERSION=${Deps.Scalanative_0_4.scalanativeVersion}", + s"-DTEST_SCALANATIVE_0_4_VERSION=${Deps.Scalanative_0_4.scalanativeVersion}", + s"-DTEST_SCALANATIVE_0_5_VERSION=${Deps.Scalanative_0_5.scalanativeVersion}", s"-DTEST_UTEST_VERSION=${Deps.utest.dep.version}", s"-DTEST_SCALATEST_VERSION=${Deps.TestDeps.scalaTest.dep.version}", s"-DTEST_TEST_INTERFACE_VERSION=${Deps.sbtTestInterface.dep.version}", @@ -948,13 +957,13 @@ object contrib extends Module { object scalanativelib extends MillStableScalaModule { def moduleDeps = Seq(scalalib, scalanativelib.`worker-api`) - def testTransitiveDeps = super.testTransitiveDeps() ++ Seq(worker("0.4").testDep()) + def testTransitiveDeps = super.testTransitiveDeps() ++ Seq(worker("0.4").testDep(), worker("0.5").testDep()) object `worker-api` extends MillPublishScalaModule { def ivyDeps = Agg(Deps.sbtTestInterface) } - object worker extends Cross[WorkerModule]("0.4") + object worker extends Cross[WorkerModule]("0.4", "0.5") trait WorkerModule extends MillPublishScalaModule with Cross.Module[String] { def scalaNativeWorkerVersion = crossValue @@ -962,6 +971,14 @@ object scalanativelib extends MillStableScalaModule { def testDepPaths = T { Seq(compile().classes) } def moduleDeps = Seq(scalanativelib.`worker-api`) def ivyDeps = scalaNativeWorkerVersion match { + case "0.5" => + Agg( + Deps.osLib, + Deps.Scalanative_0_5.scalanativeTools, + Deps.Scalanative_0_5.scalanativeUtil, + Deps.Scalanative_0_5.scalanativeNir, + Deps.Scalanative_0_5.scalanativeTestRunner + ) case "0.4" => Agg( Deps.osLib, diff --git a/contrib/bloop/test/src/mill/contrib/bloop/BloopTests.scala b/contrib/bloop/test/src/mill/contrib/bloop/BloopTests.scala index 6a00aa86bcf..e5eb1428cc0 100644 --- a/contrib/bloop/test/src/mill/contrib/bloop/BloopTests.scala +++ b/contrib/bloop/test/src/mill/contrib/bloop/BloopTests.scala @@ -63,7 +63,7 @@ object BloopTests extends TestSuite { val sv = sys.props.getOrElse("TEST_SCALA_2_13_VERSION", ???) override def skipBloop: Boolean = isWin override def scalaVersion = sv - override def scalaNativeVersion = sys.props.getOrElse("TEST_SCALANATIVE_VERSION", ???) + override def scalaNativeVersion = sys.props.getOrElse("TEST_SCALANATIVE_0_4_VERSION", ???) override def releaseMode = T(ReleaseMode.Debug) } diff --git a/scalanativelib/src/mill/scalanativelib/ScalaNativeModule.scala b/scalanativelib/src/mill/scalanativelib/ScalaNativeModule.scala index faaa6a2fd67..02fa7c6da26 100644 --- a/scalanativelib/src/mill/scalanativelib/ScalaNativeModule.scala +++ b/scalanativelib/src/mill/scalanativelib/ScalaNativeModule.scala @@ -65,11 +65,15 @@ trait ScalaNativeModule extends ScalaModule { outer => } def nativeIvyDeps: T[Agg[Dep]] = T { - val scalaVersionSpecific = + val scalaVersionSpecific = { + val version = + if (scalaNativeVersion().startsWith("0.4")) scalaNativeVersion() + else s"${scalaVersion()}+${scalaNativeVersion()}" + if (ZincWorkerUtil.isScala3(scalaVersion())) - Agg(ivy"org.scala-native::scala3lib::${scalaNativeVersion()}") - else - Agg(ivy"org.scala-native::scalalib::${scalaNativeVersion()}") + Agg(ivy"org.scala-native::scala3lib::$version") + else Agg(ivy"org.scala-native::scalalib::$version") + } Agg( ivy"org.scala-native::nativelib::${scalaNativeVersion()}", diff --git a/scalanativelib/test/resources/hello-native-world/test/src/utest/tests/ArgsParserTests.scala b/scalanativelib/test/resources/hello-native-world/test/src/utest/tests/ArgsParserTests.scala index 7929f94783f..f1f43a188bf 100644 --- a/scalanativelib/test/resources/hello-native-world/test/src/utest/tests/ArgsParserTests.scala +++ b/scalanativelib/test/resources/hello-native-world/test/src/utest/tests/ArgsParserTests.scala @@ -6,14 +6,14 @@ import utest._ object ArgsParserTests extends TestSuite { def tests: Tests = Tests { - 'one - { + "one" - { val result = ArgsParser.parse("hello:world") assert( result.length == 2, result == Seq("hello", "world") ) } - 'two - { // we fail this test to check testing in scala.js + "two" - { // we fail this test to check testing in Scala Native val result = ArgsParser.parse("hello:world") assert( result.length == 80 diff --git a/scalanativelib/test/resources/hello-native-world/test/src/utest/tests/MainTests.scala b/scalanativelib/test/resources/hello-native-world/test/src/utest/tests/MainTests.scala index d0498f314e0..eb1b1b19415 100644 --- a/scalanativelib/test/resources/hello-native-world/test/src/utest/tests/MainTests.scala +++ b/scalanativelib/test/resources/hello-native-world/test/src/utest/tests/MainTests.scala @@ -6,13 +6,13 @@ import utest._ object MainTests extends TestSuite { val tests: Tests = Tests { - 'vmName - { - 'containNative - { + "vmName" - { + "containNative" - { assert( Main.vmName.contains("Native") ) } - 'containScala - { + "containScala" - { assert( Main.vmName.contains("Scala") ) diff --git a/scalanativelib/test/src/mill/scalanativelib/HelloNativeWorldTests.scala b/scalanativelib/test/src/mill/scalanativelib/HelloNativeWorldTests.scala index 618320d60af..74ce743a57e 100644 --- a/scalanativelib/test/src/mill/scalanativelib/HelloNativeWorldTests.scala +++ b/scalanativelib/test/src/mill/scalanativelib/HelloNativeWorldTests.scala @@ -30,18 +30,25 @@ object HelloNativeWorldTests extends TestSuite { override def mainClass = Some("hello.Main") } - val scala213 = "2.13.6" - val scalaNative04 = "0.4.2" + val scala213 = sys.props.getOrElse("TEST_SCALA_2_13_VERSION", ???) + val scala31 = sys.props.getOrElse("TEST_SCALA_3_1_VERSION", ???) + val scala33 = sys.props.getOrElse("TEST_SCALA_3_3_VERSION", ???) + val scalaNative04Old = "0.4.2" + val scalaNative04 = sys.props.getOrElse("TEST_SCALANATIVE_0_4_VERSION", ???) + val scalaNative05 = sys.props.getOrElse("TEST_SCALANATIVE_0_5_VERSION", ???) + val utestVersion = sys.props.getOrElse("TEST_UTEST_VERSION", ???) object HelloNativeWorld extends TestUtil.BaseModule { implicit object ReleaseModeToSegments extends Cross.ToSegments[ReleaseMode](v => List(v.toString)) val matrix = for { - scala <- Seq("3.2.1", "3.1.3", scala213, "2.12.13", "2.11.12") - scalaNative <- Seq(scalaNative04, "0.4.9") + scala <- Seq(scala33, scala31, scala213, "2.12.13", "2.11.12") + scalaNative <- Seq(scalaNative04Old, scalaNative04, scalaNative05) mode <- List(ReleaseMode.Debug, ReleaseMode.ReleaseFast) - if !(ZincWorkerUtil.isScala3(scala) && scalaNative == scalaNative04) + if !(ZincWorkerUtil.isScala3(scala) && scalaNative == scalaNative04Old) + if !(scala.startsWith("2.11") && scalaNative != scalaNative04Old) + if !(scala.startsWith("2.12") && scalaNative == scalaNative05) } yield (scala, scalaNative, mode) object helloNativeWorld extends Cross[RootModule](matrix) @@ -64,7 +71,7 @@ object HelloNativeWorldTests extends TestSuite { object test extends ScalaNativeTests with TestModule.Utest { override def sources = T.sources { millSourcePath / "src" / "utest" } override def ivyDeps = super.ivyDeps() ++ Agg( - ivy"com.lihaoyi::utest::0.7.6" + ivy"com.lihaoyi::utest::$utestVersion" ) } } @@ -129,7 +136,7 @@ object HelloNativeWorldTests extends TestSuite { val Right((result, evalCount)) = helloWorldEvaluator(HelloNativeWorld.helloNativeWorld( scala213, - scalaNative04, + scalaNative04Old, ReleaseMode.Debug ).jar) val jar = result.path @@ -155,7 +162,7 @@ object HelloNativeWorldTests extends TestSuite { } "artifactId_040" - testArtifactId( scala213, - scalaNative04, + scalaNative04Old, ReleaseMode.Debug, "hello-native-world_native0.4_2.13" ) @@ -202,14 +209,18 @@ object HelloNativeWorldTests extends TestSuite { testAllMatrix( (scala, scalaNative, releaseMode) => checkUtest(scala, scalaNative, releaseMode, cached), - skipScala = ZincWorkerUtil.isScala3 // Remove this once utest is released for Scala 3 + skipScalaNative = v => + v == scalaNative04Old || + v.startsWith("0.5.") // Remove this once utest is released for Scala Native 0.5 ) } "testCached" - { val cached = true testAllMatrix( (scala, scalaNative, releaseMode) => checkUtest(scala, scalaNative, releaseMode, cached), - skipScala = ZincWorkerUtil.isScala3 // Remove this once utest is released for Scala 3 + skipScalaNative = v => + v == scalaNative04Old || + v.startsWith("0.5.") // Remove this once utest is released for Scala Native 0.5 ) } @@ -262,7 +273,7 @@ object HelloNativeWorldTests extends TestSuite { ) val scalaNativeVersionSpecific = - if (scalaNativeVersion == scalaNative04) Set.empty + if (scalaNativeVersion == scalaNative04Old) Set.empty else Set("Main.nir", "ArgsParser.nir") common ++ scalaVersionSpecific ++ scalaNativeVersionSpecific diff --git a/scalanativelib/test/src/mill/scalanativelib/ScalaTestsErrorTests.scala b/scalanativelib/test/src/mill/scalanativelib/ScalaTestsErrorTests.scala index fcc13540fcd..37336c2d7da 100644 --- a/scalanativelib/test/src/mill/scalanativelib/ScalaTestsErrorTests.scala +++ b/scalanativelib/test/src/mill/scalanativelib/ScalaTestsErrorTests.scala @@ -10,7 +10,7 @@ object ScalaTestsErrorTests extends TestSuite { object ScalaTestsError extends TestUtil.BaseModule { object scalaTestsError extends ScalaNativeModule { def scalaVersion = sys.props.getOrElse("TEST_SCALA_3_3_VERSION", ???) - def scalaNativeVersion = sys.props.getOrElse("TEST_SCALANATIVE_VERSION", ???) + def scalaNativeVersion = sys.props.getOrElse("TEST_SCALANATIVE_0_4_VERSION", ???) object test extends ScalaTests with TestModule.Utest object testDisabledError extends ScalaTests with TestModule.Utest { override def hierarchyChecks(): Unit = {} diff --git a/scalanativelib/worker/0.4/src/mill/scalanativelib/worker/scala/scalanative/Utils.scala b/scalanativelib/worker/0.4/src/scala/scalanative/build/MillUtils.scala similarity index 100% rename from scalanativelib/worker/0.4/src/mill/scalanativelib/worker/scala/scalanative/Utils.scala rename to scalanativelib/worker/0.4/src/scala/scalanative/build/MillUtils.scala diff --git a/scalanativelib/worker/0.5/src/mill/scalanativelib/worker/ScalaNativeWorkerImpl.scala b/scalanativelib/worker/0.5/src/mill/scalanativelib/worker/ScalaNativeWorkerImpl.scala new file mode 100644 index 00000000000..891c90bde69 --- /dev/null +++ b/scalanativelib/worker/0.5/src/mill/scalanativelib/worker/ScalaNativeWorkerImpl.scala @@ -0,0 +1,135 @@ +package mill.scalanativelib.worker + +import java.io.File +import java.lang.System.{err, out} + +import mill.scalanativelib.worker.api._ +import scala.scalanative.util.Scope +import scala.scalanative.build.{ + Build, + BuildTarget => ScalaNativeBuildTarget, + Config, + Discover, + GC, + Logger, + LTO, + Mode, + NativeConfig => ScalaNativeNativeConfig +} +import scala.scalanative.testinterface.adapter.TestAdapter + +import scala.concurrent.Await +import scala.concurrent.duration.Duration +import scala.concurrent.ExecutionContext.Implicits.global +import java.nio.file.Files + +class ScalaNativeWorkerImpl extends mill.scalanativelib.worker.api.ScalaNativeWorkerApi { + implicit val scope: Scope = Scope.forever + + def logger(level: NativeLogLevel): Logger = + Logger( + traceFn = msg => if (level.value >= NativeLogLevel.Trace.value) err.println(s"[trace] $msg"), + debugFn = msg => if (level.value >= NativeLogLevel.Debug.value) out.println(s"[debug] $msg"), + infoFn = msg => if (level.value >= NativeLogLevel.Info.value) out.println(s"[info] $msg"), + warnFn = msg => if (level.value >= NativeLogLevel.Warn.value) out.println(s"[warn] $msg"), + errorFn = msg => if (level.value >= NativeLogLevel.Error.value) err.println(s"[error] $msg") + ) + + def discoverClang(): File = Discover.clang().toFile + def discoverClangPP(): File = Discover.clangpp().toFile + def discoverCompileOptions(): Seq[String] = Discover.compileOptions() + def discoverLinkingOptions(): Seq[String] = Discover.linkingOptions() + def defaultGarbageCollector(): String = GC.default.name + + def config( + mainClass: Either[String, String], + classpath: Seq[File], + nativeWorkdir: File, + nativeClang: File, + nativeClangPP: File, + nativeTarget: Option[String], + nativeCompileOptions: Seq[String], + nativeLinkingOptions: Seq[String], + nativeGC: String, + nativeLinkStubs: Boolean, + nativeLTO: String, + releaseMode: String, + nativeOptimize: Boolean, + nativeEmbedResources: Boolean, + nativeIncrementalCompilation: Boolean, + nativeDump: Boolean, + logLevel: NativeLogLevel, + buildTarget: BuildTarget + ): Either[String, Config] = { + val nativeConfig = + ScalaNativeNativeConfig.empty + .withClang(nativeClang.toPath) + .withClangPP(nativeClangPP.toPath) + .withTargetTriple(nativeTarget) + .withCompileOptions(nativeCompileOptions) + .withLinkingOptions(nativeLinkingOptions) + .withGC(GC(nativeGC)) + .withLinkStubs(nativeLinkStubs) + .withMode(Mode(releaseMode)) + .withOptimize(nativeOptimize) + .withLTO(LTO(nativeLTO)) + .withDump(nativeDump) + .withBuildTarget(buildTarget match { + case BuildTarget.Application => ScalaNativeBuildTarget.application + case BuildTarget.LibraryDynamic => ScalaNativeBuildTarget.libraryDynamic + case BuildTarget.LibraryStatic => ScalaNativeBuildTarget.libraryStatic + }) + .withEmbedResources(nativeEmbedResources) + .withIncrementalCompilation(nativeIncrementalCompilation) + .withBaseName("out") + + val config = Config.empty + .withClassPath(classpath.map(_.toPath)) + .withBaseDir(nativeWorkdir.toPath) + .withCompilerConfig(nativeConfig) + .withLogger(logger(logLevel)) + + if (buildTarget == BuildTarget.Application) { + mainClass match { + case Left(error) => + Left(error) + case Right(mainClass) => + Right(config.withMainClass(Some(mainClass))) + } + } else Right(config) + } + + def nativeLink(nativeConfig: Object, outDirectory: File): File = { + val config = nativeConfig.asInstanceOf[Config] + + val result = Await.result(Build.buildCached(config), Duration.Inf) + + val resultInOutDirectory = + Files.move(result, outDirectory.toPath().resolve(result.getFileName())) + + resultInOutDirectory.toFile() + } + + def getFramework( + testBinary: File, + envVars: Map[String, String], + logLevel: NativeLogLevel, + frameworkName: String + ): (() => Unit, sbt.testing.Framework) = { + val config = TestAdapter.Config() + .withBinaryFile(testBinary) + .withEnvVars(envVars) + .withLogger(logger(logLevel)) + + val adapter = new TestAdapter(config) + + ( + () => adapter.close(), + adapter + .loadFrameworks(List(List(frameworkName))) + .flatten + .headOption + .getOrElse(throw new RuntimeException("Failed to get framework")) + ) + } +}