summaryrefslogtreecommitdiff
path: root/scalanativelib/src/ScalaNativeModule.scala
diff options
context:
space:
mode:
Diffstat (limited to 'scalanativelib/src/ScalaNativeModule.scala')
-rw-r--r--scalanativelib/src/ScalaNativeModule.scala294
1 files changed, 294 insertions, 0 deletions
diff --git a/scalanativelib/src/ScalaNativeModule.scala b/scalanativelib/src/ScalaNativeModule.scala
new file mode 100644
index 00000000..289ba759
--- /dev/null
+++ b/scalanativelib/src/ScalaNativeModule.scala
@@ -0,0 +1,294 @@
+package mill
+package scalanativelib
+
+import java.net.URLClassLoader
+
+import coursier.Cache
+import coursier.maven.MavenRepository
+import mill.define.{Target, Task}
+import mill.api.Result
+import mill.modules.Jvm
+import mill.scalalib.{Dep, DepSyntax, Lib, SbtModule, ScalaModule, TestModule, TestRunner}
+import mill.api.Loose.Agg
+import sbt.testing.{AnnotatedFingerprint, SubclassFingerprint}
+import sbt.testing.Fingerprint
+import upickle.default.{ReadWriter => RW, macroRW}
+import mill.scalanativelib.api._
+
+
+trait ScalaNativeModule extends ScalaModule { outer =>
+ def scalaNativeVersion: T[String]
+ override def platformSuffix = s"_native${scalaNativeBinaryVersion()}"
+ override def artifactSuffix: T[String] = s"${platformSuffix()}_${artifactScalaVersion()}"
+
+ trait Tests extends TestScalaNativeModule {
+ override def zincWorker = outer.zincWorker
+ override def scalaOrganization = outer.scalaOrganization()
+ override def scalaVersion = outer.scalaVersion()
+ override def scalaNativeVersion = outer.scalaNativeVersion()
+ override def releaseMode = outer.releaseMode()
+ override def logLevel = outer.logLevel()
+ override def moduleDeps = Seq(outer)
+ }
+
+ def scalaNativeBinaryVersion = T{ scalaNativeVersion().split('.').take(2).mkString(".") }
+
+ // This allows compilation and testing versus SNAPSHOT versions of scala-native
+ def scalaNativeToolsVersion = T{
+ if (scalaNativeVersion().endsWith("-SNAPSHOT"))
+ scalaNativeVersion()
+ else
+ scalaNativeBinaryVersion()
+ }
+
+ def scalaNativeWorker = T.task{ ScalaNativeWorkerApi.scalaNativeWorker().impl(bridgeFullClassPath()) }
+
+ def scalaNativeWorkerClasspath = T {
+ val workerKey = "MILL_SCALANATIVE_WORKER_" + scalaNativeBinaryVersion().replace('.', '_').replace('-', '_')
+ val workerPath = sys.props(workerKey)
+ if (workerPath != null)
+ Result.Success(Agg(workerPath.split(',').map(p => PathRef(os.Path(p), quick = true)): _*))
+ else
+ Lib.resolveDependencies(
+ Seq(Cache.ivy2Local, MavenRepository("https://repo1.maven.org/maven2")),
+ Lib.depToDependency(_, "2.12.4", ""),
+ Seq(ivy"com.lihaoyi::mill-scalanativelib-worker-${scalaNativeBinaryVersion()}:${sys.props("MILL_VERSION")}")
+ )
+ }
+
+ def toolsIvyDeps = T{
+ Seq(
+ ivy"org.scala-native:tools_2.12:${scalaNativeVersion()}",
+ ivy"org.scala-native:util_2.12:${scalaNativeVersion()}",
+ ivy"org.scala-native:nir_2.12:${scalaNativeVersion()}"
+ )
+ }
+
+ override def transitiveIvyDeps: T[Agg[Dep]] = T{
+ ivyDeps() ++ nativeIvyDeps() ++ Task.traverse(moduleDeps)(_.transitiveIvyDeps)().flatten
+ }
+
+ def nativeLibIvy = T{ ivy"org.scala-native::nativelib_native${scalaNativeToolsVersion()}:${scalaNativeVersion()}" }
+
+ def nativeIvyDeps = T{
+ Seq(nativeLibIvy()) ++
+ Seq(
+ ivy"org.scala-native::javalib_native${scalaNativeToolsVersion()}:${scalaNativeVersion()}",
+ ivy"org.scala-native::auxlib_native${scalaNativeToolsVersion()}:${scalaNativeVersion()}",
+ ivy"org.scala-native::scalalib_native${scalaNativeToolsVersion()}:${scalaNativeVersion()}"
+ )
+ }
+
+ def bridgeFullClassPath = T {
+ Lib.resolveDependencies(
+ Seq(Cache.ivy2Local, MavenRepository("https://repo1.maven.org/maven2")),
+ Lib.depToDependency(_, scalaVersion(), platformSuffix()),
+ toolsIvyDeps()
+ ).map(t => (scalaNativeWorkerClasspath().toSeq ++ t.toSeq).map(_.path))
+ }
+
+ override def scalacPluginIvyDeps = super.scalacPluginIvyDeps() ++
+ Agg(ivy"org.scala-native:nscplugin_${scalaVersion()}:${scalaNativeVersion()}")
+
+ def logLevel: Target[NativeLogLevel] = T{ NativeLogLevel.Info }
+
+ def releaseMode: Target[ReleaseMode] = T { ReleaseMode.Debug }
+
+ def nativeWorkdir = T{ T.ctx().dest }
+
+ // Location of the clang compiler
+ def nativeClang = T{ scalaNativeWorker().discoverClang }
+
+ // Location of the clang++ compiler
+ def nativeClangPP = T{ scalaNativeWorker().discoverClangPP }
+
+ // GC choice, either "none", "boehm" or "immix"
+ def nativeGC = T{
+ Option(System.getenv.get("SCALANATIVE_GC"))
+ .getOrElse(scalaNativeWorker().defaultGarbageCollector)
+ }
+
+ def nativeTarget = T{ scalaNativeWorker().discoverTarget(nativeClang(), nativeWorkdir()) }
+
+ // Options that are passed to clang during compilation
+ def nativeCompileOptions = T{ scalaNativeWorker().discoverCompileOptions }
+
+ // Options that are passed to clang during linking
+ def nativeLinkingOptions = T{ scalaNativeWorker().discoverLinkingOptions }
+
+ // Whether to link `@stub` methods, or ignore them
+ def nativeLinkStubs = T { false }
+
+
+ def nativeLibJar = T{
+ resolveDeps(T.task{Agg(nativeLibIvy())})()
+ .filter{p => p.toString.contains("scala-native") && p.toString.contains("nativelib")}
+ .toList
+ .head
+ }
+
+ def nativeConfig = T.task {
+ val classpath = runClasspath().map(_.path).filter(_.toIO.exists).toList
+
+ scalaNativeWorker().config(
+ nativeLibJar().path,
+ finalMainClass(),
+ classpath,
+ nativeWorkdir(),
+ nativeClang(),
+ nativeClangPP(),
+ nativeTarget(),
+ nativeCompileOptions(),
+ nativeLinkingOptions(),
+ nativeGC(),
+ nativeLinkStubs(),
+ releaseMode(),
+ logLevel())
+ }
+
+ // Generates native binary
+ def nativeLink = T{ scalaNativeWorker().nativeLink(nativeConfig(), (T.ctx().dest / 'out)) }
+
+ // Runs the native binary
+ override def run(args: String*) = T.command{
+ Jvm.baseInteractiveSubprocess(
+ Vector(nativeLink().toString) ++ args,
+ forkEnv(),
+ workingDir = ammonite.ops.pwd)
+ }
+}
+
+
+trait TestScalaNativeModule extends ScalaNativeModule with TestModule { testOuter =>
+ case class TestDefinition(framework: String, clazz: Class[_], fingerprint: Fingerprint) {
+ def name = clazz.getName.reverse.dropWhile(_ == '$').reverse
+ }
+
+ override def testLocal(args: String*) = T.command { test(args:_*) }
+
+ override def test(args: String*) = T.command{
+ val outputPath = T.ctx().dest / "out.json"
+
+ // The test frameworks run under the JVM and communicate with the native binary over a socket
+ // therefore the test framework is loaded from a JVM classloader
+ val testClassloader =
+ new URLClassLoader(testClasspathJvm().map(_.path.toIO.toURI.toURL).toArray,
+ this.getClass.getClassLoader)
+ val frameworkInstances = TestRunner.frameworks(testFrameworks())(testClassloader)
+ val testBinary = testRunnerNative.nativeLink().toIO
+ val envVars = forkEnv()
+
+ val nativeFrameworks = (cl: ClassLoader) =>
+ frameworkInstances.zipWithIndex.map { case (f, id) =>
+ scalaNativeWorker().newScalaNativeFrameWork(f, id, testBinary, logLevel(), envVars)
+ }
+
+ val (doneMsg, results) = TestRunner.runTests(
+ nativeFrameworks,
+ testClasspathJvm().map(_.path),
+ Agg(compile().classes.path),
+ args
+ )
+
+ TestModule.handleResults(doneMsg, results)
+ }
+
+ private val supportedTestFrameworks = Set("utest", "scalatest")
+
+ // get the JVM classpath entries for supported test frameworks
+ def testFrameworksJvmClasspath = T{
+ Lib.resolveDependencies(
+ repositories,
+ Lib.depToDependency(_, scalaVersion(), ""),
+ transitiveIvyDeps().filter(d => d.cross.isBinary && supportedTestFrameworks(d.dep.module.name))
+ )
+ }
+
+ def testClasspathJvm = T{
+ localClasspath() ++
+ transitiveLocalClasspath() ++
+ unmanagedClasspath() ++
+ testFrameworksJvmClasspath()
+ }
+
+ // creates a specific binary used for running tests - has a different (generated) main class
+ // which knows the names of all the tests and references to invoke them
+ object testRunnerNative extends ScalaNativeModule {
+ override def zincWorker = testOuter.zincWorker
+ override def scalaOrganization = testOuter.scalaOrganization()
+ override def scalaVersion = testOuter.scalaVersion()
+ override def scalaNativeVersion = testOuter.scalaNativeVersion()
+ override def moduleDeps = Seq(testOuter)
+ override def releaseMode = testOuter.releaseMode()
+ override def logLevel = testOuter.logLevel()
+ override def nativeLinkStubs = true
+
+ override def ivyDeps = testOuter.ivyDeps() ++ Agg(
+ ivy"org.scala-native::test-interface_native${scalaNativeToolsVersion()}:${scalaNativeVersion()}"
+ )
+
+ override def mainClass = Some("scala.scalanative.testinterface.TestMain")
+
+ override def generatedSources = T {
+ val outDir = T.ctx().dest
+ ammonite.ops.write.over(outDir / "TestMain.scala", makeTestMain())
+ Seq(PathRef(outDir))
+ }
+ }
+
+ // generate a main class for the tests
+ def makeTestMain = T{
+ val frameworkInstances = TestRunner.frameworks(testFrameworks()) _
+
+ val testClasses =
+ Jvm.inprocess(testClasspathJvm().map(_.path), classLoaderOverrideSbtTesting = true, isolated = true, closeContextClassLoaderWhenDone = true,
+ cl => {
+ frameworkInstances(cl).flatMap { framework =>
+ val df = Lib.discoverTests(cl, framework, Agg(compile().classes.path))
+ df.map(d => TestDefinition(framework.getClass.getName, d._1, d._2))
+ }
+ }
+ )
+
+ val frameworks = testClasses.map(_.framework).distinct
+
+ val frameworksList =
+ if (frameworks.nonEmpty) frameworks.mkString("List(new _root_.", ", new _root_.", ")")
+ else {
+ throw new Exception(
+ "Cannot find any tests; make sure you defined the test framework correctly, " +
+ "and extend whatever trait or annotation necessary to mark your test suites"
+ )
+ }
+
+
+ val testsMap = makeTestsMap(testClasses)
+
+ s"""package scala.scalanative.testinterface
+ |object TestMain extends TestMainBase {
+ | override val frameworks = $frameworksList
+ | override val tests = Map[String, AnyRef]($testsMap)
+ | def main(args: Array[String]): Unit =
+ | testMain(args)
+ |}""".stripMargin
+ }
+
+ private def makeTestsMap(tests: Seq[TestDefinition]): String = {
+ tests
+ .map { t =>
+ val isModule = t.fingerprint match {
+ case af: AnnotatedFingerprint => af.isModule
+ case sf: SubclassFingerprint => sf.isModule
+ }
+
+ val inst =
+ if (isModule) s"_root_.${t.name}" else s"new _root_.${t.name}"
+ s""""${t.name}" -> $inst"""
+ }
+ .mkString(", ")
+ }
+}
+
+
+trait SbtNativeModule extends ScalaNativeModule with SbtModule
+