diff --git a/build.sbt b/build.sbt
index 7b37cb1819..574864a9c4 100644
--- a/build.sbt
+++ b/build.sbt
@@ -1,7 +1,7 @@
+import java.io.File.pathSeparator
import scala.util.Try
import scalanative.tools.OptimizerReporter
-import scalanative.sbtplugin.ScalaNativePluginInternal.nativeOptimizerReporter
-import java.io.File.pathSeparator
+import scalanative.sbtplugin.ScalaNativePluginInternal._
val toolScalaVersion = "2.10.6"
@@ -272,27 +272,6 @@ lazy val nativelib =
.in(file("nativelib"))
.settings(libSettings)
.settings(mavenPublishSettings)
- .settings(compile in Compile := {
- val clang = findClang(nativeClang.value)
- val clangpp = findClangPP(nativeClangPP.value)
-
- val source = baseDirectory.value
- val compileSuccess =
- IO.withTemporaryDirectory { tmp =>
- IO.copyDirectory(baseDirectory.value, tmp)
- scala.scalanative.sbtplugin.ScalaNativePluginInternal
- .compileCSources(clang,
- clangpp,
- tmp,
- nativeGC.value,
- streams.value.log)
- }
- if (compileSuccess) {
- (compile in Compile).value
- } else {
- sys.error("Compilation failed")
- }
- })
lazy val javalib =
project
diff --git a/docs/user/sbt.rst b/docs/user/sbt.rst
index 474afb3e5d..9055c763cf 100644
--- a/docs/user/sbt.rst
+++ b/docs/user/sbt.rst
@@ -35,33 +35,24 @@ and now you can write your first application in ``./src/main/scala/HelloWorld.sc
now simply run ``sbt run`` to get everything compiled and have the expected output!
-Sbt settings
+Sbt settings and tasks
----------------------
-===== ======================== ================ =================================================== =========================================================
-Since Name Type Default Value Description
-===== ======================== ================ =================================================== =========================================================
-0.1 ``nativeClang`` ``Option[File]`` ``None`` Path to ``clang`` command
-0.1 ``nativeClangPP`` ``Option[File]`` ``None`` Path to ``clang++`` command
-0.1 ``nativeCompileOptions`` ``Seq[String]`` ``Seq("-O0")`` Extra options passed to clang verbatim during compilation
-0.1 ``nativeLinkingOptions`` ``Seq[String]`` ``Seq("-I/usr/local/include", "-L/usr/local/lib")`` Extra options passed to clang verbatim during linking
-0.1 ``nativeMode`` ``String`` ``"debug"`` Either ``"debug"`` or ``"release"`` (2)
-0.2 ``nativeGC`` ``String`` ``"boehm"`` Either ``"none"`` or ``"boehm"`` (3)
-===== ======================== ================ =================================================== =========================================================
-
-Sbt tasks
-----------------------
-
-===== ======================== ============ ====================================================
-Since Name Type Description
-===== ======================== ============ ====================================================
-0.1 ``compile`` ``Analysis`` Compile Scala code to NIR
-0.1 ``run`` ``Unit`` Compile, link and run the generated binary
-0.1 ``package`` ``File`` Similar to standard package with addition of NIR
-0.1 ``publish`` ``Unit`` Similar to standard publish with addition of NIR (1)
-0.1 ``nativeLink`` ``File`` Link NIR and generate native binary
-===== ======================== ============ ====================================================
-
+===== ======================== =============== =========================================================
+Since Name Type Description
+===== ======================== =============== =========================================================
+0.1 ``compile`` ``Analysis`` Compile Scala code to NIR
+0.1 ``run`` ``Unit`` Compile, link and run the generated binary
+0.1 ``package`` ``File`` Similar to standard package with addition of NIR
+0.1 ``publish`` ``Unit`` Similar to standard publish with addition of NIR (1)
+0.1 ``nativeLink`` ``File`` Link NIR and generate native binary
+0.1 ``nativeClang`` ``File`` Path to ``clang`` command
+0.1 ``nativeClangPP`` ``File`` Path to ``clang++`` command
+0.1 ``nativeCompileOptions`` ``Seq[String]`` Extra options passed to clang verbatim during compilation
+0.1 ``nativeLinkingOptions`` ``Seq[String]`` Extra options passed to clang verbatim during linking
+0.1 ``nativeMode`` ``String`` Either ``"debug"`` or ``"release"`` (2)
+0.2 ``nativeGC`` ``String`` Either ``"none"`` or ``"boehm"`` (3)
+===== ======================== =============== =========================================================
1. See `Publishing`_ and `Cross compilation`_ for details.
2. See `Compilation modes`_ for details.
@@ -72,7 +63,7 @@ Compilation modes
Scala Native supports two distinct linking modes:
-1. **debug.**
+1. **debug.** (default)
Default mode. Optimized for shortest compilation time. Runs fewer
optimizations and is much more suited for iterative development workflow.
@@ -87,7 +78,7 @@ Scala Native supports two distinct linking modes:
Garbage collectors
------------------
-1. **boehm.**
+1. **boehm.** (default)
Conservative generational garbage collector. More information is available
at the `project's page `_.
diff --git a/sbt-scala-native/src/main/scala/scala/scalanative/sbtplugin/ScalaNativePlugin.scala b/sbt-scala-native/src/main/scala/scala/scalanative/sbtplugin/ScalaNativePlugin.scala
index 77e3b895df..7454e40b43 100644
--- a/sbt-scala-native/src/main/scala/scala/scalanative/sbtplugin/ScalaNativePlugin.scala
+++ b/sbt-scala-native/src/main/scala/scala/scalanative/sbtplugin/ScalaNativePlugin.scala
@@ -13,39 +13,35 @@ object ScalaNativePlugin extends AutoPlugin {
object AutoImport extends NativeCross {
- def findClang(default: Option[File]): File =
- default.getOrElse(
- discover("clang", clangVersions)
- )
-
- def findClangPP(default: Option[File]): File =
- default.getOrElse(
- discover("clang++", clangVersions)
- )
-
val ScalaNativeCrossVersion = sbtplugin.ScalaNativeCrossVersion
val nativeVersion = nir.Versions.current
val nativeClang =
- settingKey[Option[File]]("Location of the clang compiler.")
+ taskKey[File]("Location of the clang compiler.")
val nativeClangPP =
- settingKey[Option[File]]("Location of the clang++ compiler.")
+ taskKey[File]("Location of the clang++ compiler.")
val nativeCompileOptions =
- settingKey[Seq[String]](
+ taskKey[Seq[String]](
"Additional options are passed to clang during compilation.")
val nativeLinkingOptions =
- settingKey[Seq[String]](
+ taskKey[Seq[String]](
"Additional options that are pased to clang during linking.")
val nativeLink =
taskKey[File]("Generates native binary without running it.")
- val nativeSharedLibrary = settingKey[Boolean](
- "Will create a shared library instead of a program with a main method.")
+ val nativeExternalDependencies =
+ taskKey[Seq[String]]("List all external dependencies at link time.")
+
+ val nativeAvailableDependencies =
+ taskKey[Seq[String]]("List all symbols available at link time")
+
+ val nativeMissingDependencies =
+ taskKey[Seq[String]]("List all symbols not available at link time")
val nativeMode =
settingKey[String]("Compilation mode, either \"debug\" or \"release\".")
diff --git a/sbt-scala-native/src/main/scala/scala/scalanative/sbtplugin/ScalaNativePluginInternal.scala b/sbt-scala-native/src/main/scala/scala/scalanative/sbtplugin/ScalaNativePluginInternal.scala
index ee77f4ce98..8ef8f69df8 100644
--- a/sbt-scala-native/src/main/scala/scala/scalanative/sbtplugin/ScalaNativePluginInternal.scala
+++ b/sbt-scala-native/src/main/scala/scala/scalanative/sbtplugin/ScalaNativePluginInternal.scala
@@ -22,252 +22,57 @@ import java.io.File
object ScalaNativePluginInternal {
- val nativeNativelib =
- taskKey[File]("Unpack and precompile native lib.")
-
- val nativeTarget = taskKey[String]("Target triple.")
-
- val nativeLinkerReporter = settingKey[tools.LinkerReporter](
- "A reporter that gets notified whenever a linking event happens.")
-
- val nativeOptimizerReporter = settingKey[tools.OptimizerReporter](
- "A reporter that gets notified whenever an optimizer event happens.")
-
- val nativeExternalDependencies =
- taskKey[Seq[String]]("List all external dependencies at link time.")
-
- val nativeAvailableDependencies =
- taskKey[Seq[String]]("List all symbols available at link time")
-
- val nativeMissingDependencies =
- taskKey[Seq[String]]("List all symbols not available at link time")
-
- private lazy val includes = {
- val includedir =
- Try(Process("llvm-config --includedir").lines_!.toSeq)
- .getOrElse(Seq.empty)
- ("/usr/local/include" +: includedir).map(s => s"-I$s")
- }
-
- private lazy val libs = {
- val libdir =
- Try(Process("llvm-config --libdir").lines_!.toSeq).getOrElse(Seq.empty)
- ("/usr/local/lib" +: libdir).map(s => s"-L$s")
- }
-
- private def abs(file: File): String =
- file.getAbsolutePath
-
- def discover(binaryName: String,
- binaryVersions: Seq[(String, String)]): File = {
-
- val docInstallUrl =
- "http://scala-native.readthedocs.io/en/latest/user/setup.html#installing-llvm-clang-and-boehm-gc"
-
- val envName =
- if (binaryName == "clang") "CLANG"
- else if (binaryName == "clang++") "CLANGPP"
- else binaryName
-
- sys.env.get(s"${envName}_PATH") match {
- case Some(path) => file(path)
- case None => {
- val binaryNames = binaryVersions.flatMap {
- case (major, minor) =>
- Seq(s"$binaryName$major$minor", s"$binaryName-$major.$minor")
- } :+ binaryName
-
- Process("which" +: binaryNames).lines_!
- .map(file(_))
- .headOption
- .getOrElse {
- throw new MessageOnlyException(
- s"no ${binaryNames.mkString(", ")} found in $$PATH. Install clang ($docInstallUrl)")
- }
- }
- }
- }
-
- private def reportLinkingErrors(unresolved: Seq[nir.Global],
- logger: Logger): Nothing = {
- unresolved.map(_.show).sorted.foreach { signature =>
- logger.error(s"cannot link: $signature")
- }
-
- throw new MessageOnlyException("unable to link")
- }
-
- implicit class RichLogger(logger: Logger) {
- def time[T](msg: String)(f: => T): T = {
- import java.lang.System.nanoTime
- val start = nanoTime()
- val res = f
- val end = nanoTime()
- logger.info(s"$msg (${(end - start) / 1000000} ms)")
- res
- }
-
- def running(command: Seq[String]): Unit =
- logger.debug("running" + nl + command.mkString(nl + "\t"))
- }
-
- /** Compiles application nir to llvm ir. */
- private def compileNir(config: tools.Config,
- logger: Logger,
- linkerReporter: tools.LinkerReporter,
- optimizerReporter: tools.OptimizerReporter,
- cwd: File): Seq[nir.Attr.Link] = {
-
- val driver = tools.OptimizerDriver(config)
- val (unresolved, links, raw, dyns) = logger.time("Linking") {
- tools.link(config, driver, linkerReporter)
- }
-
- if (unresolved.nonEmpty) { reportLinkingErrors(unresolved, logger) }
-
- val classCount = raw.count {
- case _: nir.Defn.Class | _: nir.Defn.Module | _: nir.Defn.Trait => true
- case _ => false
- }
- val methodCount = raw.count(_.isInstanceOf[nir.Defn.Define])
- logger.info(s"Discovered ${classCount} classes and ${methodCount} methods")
-
- val optimized = logger.time("Optimizing") {
- tools.optimize(config, driver, raw, dyns, optimizerReporter)
- }
-
- logger.time("Generating intermediate code") {
- tools.codegen(config, optimized)
- }
- logger.info(s"Produced ${(cwd ** "*.ll").get.length} files")
-
- links
- }
-
- /** Compiles *.c[pp] in `cwd`. */
- def compileCSources(clang: File,
- clangpp: File,
- cwd: File,
- gc: String,
- logger: Logger): Boolean = {
+ val nativeTarget =
+ taskKey[String]("Target triple.")
- val cpaths = filterGCSources(gc, (cwd ** "*.c").get, cwd).map(abs)
- val cpppaths = filterGCSources(gc, (cwd ** "*.cpp").get, cwd).map(abs)
- val paths = cpaths ++ cpppaths
+ val nativeLinkerReporter =
+ settingKey[tools.LinkerReporter](
+ "A reporter that gets notified whenever a linking event happens.")
- paths.par
- .map { path =>
- val isCppSource = path.endsWith(".cpp")
+ val nativeOptimizerReporter =
+ settingKey[tools.OptimizerReporter](
+ "A reporter that gets notified whenever an optimizer event happens.")
- val compiler = abs(if (isCppSource) clangpp else clang)
- val flags = if (isCppSource) Seq("-std=c++11") else Seq()
+ val nativeOptimizerDriver =
+ taskKey[tools.OptimizerDriver]("Pass manager for the optimizer.")
- val compilec = (compiler +: "-O2" +: (includes :+ "-c" :+ path :+ "-o" :+ path + ".o")) ++ flags
+ val nativeWorkdir =
+ taskKey[File]("Working directory for intermediate build files.")
- logger.running(compilec)
- Process(compilec, cwd) ! logger
- }
- .seq
- .forall(_ == 0)
- }
+ val nativeConfig =
+ taskKey[tools.Config]("Aggregate config object that's used for tools.")
- /** Detect target platform. */
- private def compileTargetProbe(clang: File,
- cwd: File,
- logger: Logger): Boolean = {
- val targetcfile = cwd / "target.c"
- val targetllfile = cwd / "target.ll"
- val targetfile = cwd / "target"
- val compilec = Seq(abs(clang), "-S", "-emit-llvm", abs(targetcfile))
- IO.write(targetcfile, "int probe;")
- logger.running(compilec)
- val exit = Process(compilec, cwd) ! logger
- if (exit != 0) {
- return false
- }
+ val nativeLogger =
+ taskKey[Logger]("Logger, that's used by sbt-scala-native.")
- val targetvalue = IO
- .readLines(targetllfile)
- .collectFirst {
- case line if line.startsWith("target triple") =>
- line.split("\"").apply(1)
- }
- .getOrElse {
- return false
- }
- IO.write(targetfile, targetvalue)
- true
- }
+ val nativeLinkNIR =
+ taskKey[tools.LinkerResult]("Link NIR using Scala Native linker.")
- /** Compiles application and runtime llvm ir file to binary using clang. */
- private def compileLl(clangpp: File,
- target: File,
- nativelib: File,
- appll: Seq[File],
- binary: File,
- compileTarget: String,
- applinks: Seq[String],
- compileOpts: Seq[String],
- linkingOpts: Seq[String],
- gc: String,
- logger: Logger): Unit =
- logger.time("Compiling to native code") {
- val outpath = abs(binary)
- val apppaths = appll.par
- .map { appll =>
- val apppath = abs(appll)
- val outpath = apppath + ".o"
- val compile = Seq(abs(clangpp), "-c", apppath, "-o", apppath + ".o") ++ compileOpts
- logger.running(compile)
- Process(compile, target) ! logger
- outpath
- }
- .seq
- .toSeq
+ val nativeOptimizeNIR =
+ taskKey[Seq[nir.Defn]]("Optimize NIR produced after linking.")
- val opaths = (nativelib ** "*.o").get.map(abs)
- val paths = apppaths ++ opaths
- val links = {
- val os = Option(sys props "os.name").getOrElse("")
- val arch = compileTarget.split("-").head
+ val nativeGenerateLL =
+ taskKey[Seq[File]]("Generate LLVM IR based on the optimized NIR.")
- // we need re2 to link the re2 c wrapper (cre2.h)
- val regex = Seq("re2")
+ val nativeCompileLL =
+ taskKey[Seq[File]]("Compile LLVM IR to native object files.")
- val librt = os match {
- case "Linux" => Seq("rt")
- case _ => Seq.empty
- }
- val libunwind = os match {
- case "Mac OS X" => Seq.empty
- case _ => Seq("unwind", "unwind-" + arch)
- }
- librt ++ libunwind ++ applinks ++ garbageCollector(gc).links ++ regex
- }
- val linkopts = links.map("-l" + _) ++ linkingOpts
- val targetopt = Seq("-target", compileTarget)
- val flags = Seq("-o", outpath) ++ linkopts ++ targetopt
- val compile = abs(clangpp) +: (flags ++ paths)
+ val nativeCompileLib =
+ taskKey[File]("Unpack and precompile native lib.")
- logger.running(compile)
- Process(compile, target) ! logger
- }
+ val nativeLinkLL =
+ taskKey[File]("Link native object files into the final binary")
private def externalDependenciesTask[T](compileTask: TaskKey[T]) =
nativeExternalDependencies := ResourceScope { implicit scope =>
val forceCompile = compileTask.value
+ val classDir = classDirectory.value
+ val globals = linker.Path(VirtualDirectory.real(classDir)).globals
- val classes = classDirectory.value
- val progDir = VirtualDirectory.real(classes)
- val prog = linker.Path(progDir)
+ val config = tools.Config.empty.withPaths(Seq(classDir))
+ val result = (linker.Linker(config)).link(globals.toSeq)
- val config =
- tools.Config.empty.withPaths(Seq(prog)).withTargetDirectory(progDir)
-
- val (unresolved, _, _, _) =
- (linker.Linker(config)).link(prog.globals.toSeq)
-
- unresolved.map(_.show).sorted
+ result.unresolved.map(_.show).sorted
}
private def availableDependenciesTask[T](compileTask: TaskKey[T]) =
@@ -296,8 +101,6 @@ object ScalaNativePluginInternal {
inConfig(Compile)(nativeMissingDependenciesTask) ++
inConfig(Test)(nativeMissingDependenciesTask)
- val clangVersions = Seq(("3", "8"), ("3", "7"))
-
lazy val unscopedSettings = Seq(
libraryDependencies ++= Seq(
"org.scala-native" %%% "nativelib" % nativeVersion,
@@ -313,21 +116,58 @@ object ScalaNativePluginInternal {
},
addCompilerPlugin(
"org.scala-native" % "nscplugin" % nativeVersion cross CrossVersion.full),
- nativeSharedLibrary := false,
- nativeClang := None,
- nativeClangPP := None,
+ nativeClang := {
+ val clang = discover("clang", clangVersions)
+ checkThatClangIsRecentEnough(clang)
+ clang
+ },
+ nativeClangPP := {
+ val clang = discover("clang++", clangVersions)
+ checkThatClangIsRecentEnough(clang)
+ clang
+ },
nativeCompileOptions := {
- Seq("-Qunused-arguments") ++
+ val includes = {
+ val includedir =
+ Try(Process("llvm-config --includedir").lines_!.toSeq)
+ .getOrElse(Seq.empty)
+ ("/usr/local/include" +: includedir).map(s => s"-I$s")
+ }
+ includes :+ "-Qunused-arguments" :+
(mode(nativeMode.value) match {
- case tools.Mode.Debug => Seq("-O0")
- case tools.Mode.Release => Seq("-O2")
+ case tools.Mode.Debug => "-O0"
+ case tools.Mode.Release => "-O2"
})
},
nativeLinkingOptions := {
- includes ++ libs ++ maybeInjectShared(nativeSharedLibrary.value)
+ val libs = {
+ val libdir =
+ Try(Process("llvm-config --libdir").lines_!.toSeq)
+ .getOrElse(Seq.empty)
+ ("/usr/local/lib" +: libdir).map(s => s"-L$s")
+ }
+ libs
},
nativeTarget := {
- IO.read(nativeNativelib.value / "target")
+ val logger = nativeLogger.value
+ val cwd = nativeWorkdir.value
+ val clang = nativeClang.value
+ val targetcfile = cwd / "target.c"
+ val targetllfile = cwd / "target.ll"
+ val compilec = Seq(abs(clang), "-S", "-emit-llvm", abs(targetcfile))
+ def fail =
+ throw new MessageOnlyException("Failed to detect native target.")
+
+ IO.write(targetcfile, "int probe;")
+ logger.running(compilec)
+ val exit = Process(compilec, cwd) ! logger
+ if (exit != 0) fail
+ IO.readLines(targetllfile)
+ .collectFirst {
+ case line if line.startsWith("target triple") =>
+ line.split("\"").apply(1)
+ }
+ .getOrElse(fail)
},
nativeMode := "debug",
artifactPath in nativeLink := {
@@ -335,108 +175,200 @@ object ScalaNativePluginInternal {
},
nativeLinkerReporter := tools.LinkerReporter.empty,
nativeOptimizerReporter := tools.OptimizerReporter.empty,
+ nativeOptimizerDriver := tools.OptimizerDriver(nativeConfig.value),
+ nativeWorkdir := {
+ val workdir = (Keys.crossTarget in Compile).value / "native"
+ IO.delete(workdir)
+ IO.createDirectory(workdir)
+ workdir
+ },
+ nativeLogger := streams.value.log,
nativeGC := "boehm",
- nativeNativelib := {
- val nativelib = (crossTarget in Compile).value / "nativelib"
-
- val clang = findClang(nativeClang.value)
- val clangpp = findClangPP(nativeClangPP.value)
-
- val jar = (fullClasspath in Compile).value
- .map(entry => abs(entry.data))
- .collectFirst {
- case p if p.contains("scala-native") && p.contains("nativelib") =>
- file(p)
- }
- .get
- val logger = streams.value.log
-
+ nativeCompileLib := {
+ val cwd = nativeWorkdir.value
+ val logger = nativeLogger.value
+ val gc = nativeGC.value
+ val clang = nativeClang.value
+ val clangpp = nativeClangPP.value
+ val classpath = (fullClasspath in Compile).value
+ val opts = nativeCompileOptions.value
+
+ val lib = cwd / "lib"
+ val jar =
+ classpath
+ .map(entry => abs(entry.data))
+ .collectFirst {
+ case p if p.contains("scala-native") && p.contains("nativelib") =>
+ file(p)
+ }
+ .get
val jarhash = Hash(jar).toSeq
- val jarhashfile = nativelib / "jarhash"
+ val jarhashfile = lib / "jarhash"
def bootstrapped =
- nativelib.exists &&
+ lib.exists &&
jarhashfile.exists &&
jarhash == IO.readBytes(jarhashfile).toSeq
if (!bootstrapped) {
- IO.delete(nativelib)
- IO.unzip(jar, nativelib)
+ IO.delete(lib)
+ IO.unzip(jar, lib)
IO.write(jarhashfile, Hash(jar))
- val compiledC =
- compileCSources(clang, clangpp, nativelib, nativeGC.value, logger)
-
- val detectedTarget = compileTargetProbe(clang, nativelib, logger)
-
- if (!compiledC || !detectedTarget) {
- throw new MessageOnlyException("failed to unpack nativelib")
+ val cpaths = filterGCSources(gc, (cwd ** "*.c").get, cwd).map(abs)
+ val cpppaths = filterGCSources(gc, (cwd ** "*.cpp").get, cwd).map(abs)
+ val paths = cpaths ++ cpppaths
+
+ paths.par.foreach {
+ path =>
+ val isCppSource = path.endsWith(".cpp")
+
+ val compiler = abs(if (isCppSource) clangpp else clang)
+ val flags = Seq("-O2") ++ (if (isCppSource) Seq("-std=c++11")
+ else Seq()) ++ opts
+ val compilec = Seq(compiler) ++ flags ++ Seq("-c",
+ path,
+ "-o",
+ path + ".o")
+
+ logger.running(compilec)
+ val result = Process(compilec, cwd) ! logger
+ if (result != 0) {
+ println("Failed to compile native library runtime code.")
+ }
}
}
- nativelib
+ lib
},
- nativeLink := ResourceScope { implicit scope =>
- val logger = streams.value.log
+ nativeConfig := {
+ val mainClass = (selectMainClass in Compile).value.getOrElse(
+ throw new MessageOnlyException("No main class detected.")
+ )
+ val classpath = (fullClasspath in Compile).value.map(_.data)
+ val entry = nir.Global.Top(mainClass.toString + "$")
+ val cwd = nativeWorkdir.value
+
+ tools.Config.empty
+ .withEntry(entry)
+ .withPaths(classpath)
+ .withWorkdir(cwd)
+ .withTarget(nativeTarget.value)
+ .withMode(mode(nativeMode.value))
+ },
+ nativeLinkNIR := {
+ val logger = nativeLogger.value
+ val driver = nativeOptimizerDriver.value
+ val config = nativeConfig.value
+ val reporter = nativeLinkerReporter.value
+ val result = logger.time("Linking") {
+ tools.link(config, driver, reporter)
+ }
+ if (result.unresolved.nonEmpty) {
+ result.unresolved.map(_.show).sorted.foreach { signature =>
+ logger.error(s"cannot link: $signature")
+ throw new MessageOnlyException("unable to link")
+ }
+ }
+ val classCount = result.defns.count {
+ case _: nir.Defn.Class | _: nir.Defn.Module | _: nir.Defn.Trait => true
+ case _ => false
+ }
+ val methodCount = result.defns.count(_.isInstanceOf[nir.Defn.Define])
+ logger.info(
+ s"Discovered ${classCount} classes and ${methodCount} methods")
+ result
+ },
+ nativeOptimizeNIR := {
+ val logger = nativeLogger.value
+ val result = nativeLinkNIR.value
+ val config = nativeConfig.value
+ val reporter = nativeOptimizerReporter.value
+ val driver = nativeOptimizerDriver.value
+ logger.time("Optimizing") {
+ tools.optimize(config, driver, result.defns, result.dyns, reporter)
+ }
+ },
+ nativeGenerateLL := {
+ val logger = nativeLogger.value
+ val config = nativeConfig.value
+ val optimized = nativeOptimizeNIR.value
+ val cwd = nativeWorkdir.value
+ logger.time("Generating intermediate code") {
+ tools.codegen(config, optimized)
+ }
+ logger.info(s"Produced ${(cwd ** "*.ll").get.length} files")
+ (cwd ** "*.ll").get.toSeq
+ },
+ nativeCompileLL := {
+ val logger = nativeLogger.value
+ val generated = nativeGenerateLL.value
+ val clangpp = nativeClangPP.value
+ val cwd = nativeWorkdir.value
+ val compileOpts = nativeCompileOptions.value
+ logger.time("Compiling to native code") {
+ generated.par
+ .map { ll =>
+ val apppath = abs(ll)
+ val outpath = apppath + ".o"
+ val compile = Seq(abs(clangpp), "-c", apppath, "-o", outpath) ++ compileOpts
+ logger.running(compile)
+ Process(compile, cwd) ! logger
+ new File(outpath)
+ }
+ .seq
+ .toSeq
+ }
+ },
+ nativeLinkLL := {
+ val linked = nativeLinkNIR.value
+ val logger = nativeLogger.value
+ val apppaths = nativeCompileLL.value
+ val nativelib = nativeCompileLib.value
+ val cwd = nativeWorkdir.value
+ val target = nativeTarget.value
+ val gc = nativeGC.value
+ val linkingOpts = nativeLinkingOptions.value
+ val clangpp = nativeClangPP.value
+ val outpath = (artifactPath in nativeLink).value
+ val opaths = (nativelib ** "*.o").get.map(abs)
+ val paths = apppaths.map(abs) ++ opaths
+ val links: Seq[String] = {
+ val os = Option(sys props "os.name").getOrElse("")
+ val arch = target.split("-").head
+ // we need re2 to link the re2 c wrapper (cre2.h)
+ val regex = Seq("re2")
+ val librt = os match {
+ case "Linux" => Seq("rt")
+ case _ => Seq.empty
+ }
+ val libunwind = os match {
+ case "Mac OS X" => Seq.empty
+ case _ => Seq("unwind", "unwind-" + arch)
+ }
+ librt ++ libunwind ++ linked.links
+ .map(_.name) ++ garbageCollector(gc).links ++ regex
+ }
+ val linkopts = links.map("-l" + _) ++ linkingOpts
+ val targetopt = Seq("-target", target)
+ val flags = Seq("-o", abs(outpath)) ++ linkopts ++ targetopt
+ val compile = abs(clangpp) +: (flags ++ paths)
- logger.time("Total") {
- val nativelib = nativeNativelib.value
- val clang = findClang(nativeClang.value)
- val clangpp = findClangPP(nativeClangPP.value)
-
- val compileOpts = nativeCompileOptions.value
- val linkingOpts = nativeLinkingOptions.value
- checkThatClangIsRecentEnough(clang)
-
- val mainClass = (selectMainClass in Compile).value.getOrElse(
- throw new MessageOnlyException("No main class detected.")
- )
- val entry = nir.Global.Top(mainClass.toString + "$")
- val classpath = (fullClasspath in Compile).value.map(_.data)
- val crossTarget = (Keys.crossTarget in Compile).value
- val llTarget = crossTarget / "ll"
- val binary = (artifactPath in nativeLink).value
-
- IO.delete(llTarget)
- IO.createDirectory(llTarget)
-
- val linkerReporter = nativeLinkerReporter.value
- val optimizerReporter = nativeOptimizerReporter.value
- val sharedLibrary = nativeSharedLibrary.value
-
- val config = tools.Config.empty
- .withEntry(entry)
- .withPaths(classpath.map(p =>
- tools.LinkerPath(VirtualDirectory.real(p))))
- .withTargetDirectory(VirtualDirectory.real(llTarget))
- .withInjectMain(!nativeSharedLibrary.value)
- .withTarget(nativeTarget.value)
- .withMode(mode(nativeMode.value))
-
- val nirFiles = (Keys.target.value ** "*.nir").get.toSet
- val configFile = (streams.value.cacheDirectory / "native-config")
- val inputFiles = nirFiles + configFile
-
- val links =
- compileNir(config,
- logger,
- linkerReporter,
- optimizerReporter,
- llTarget)
- val appll = (llTarget ** "*.ll").get.toSeq
- compileLl(clangpp,
- llTarget,
- nativelib,
- appll,
- binary,
- nativeTarget.value,
- links.map(_.name),
- compileOpts,
- linkingOpts,
- nativeGC.value,
- logger)
-
- binary
+ logger.time("Linking native code") {
+ logger.running(compile)
+ Process(compile, cwd) ! logger
}
+ outpath
+ },
+ nativeLink := {
+ // We explicitly mention all of the steps in the pipeline
+ // although only the last one is strictly necessary.
+ (compile in Compile).value
+ nativeLinkNIR.value
+ nativeOptimizeNIR.value
+ nativeGenerateLL.value
+ nativeCompileLL.value
+ nativeCompileLib.value
+ nativeLinkLL.value
},
run := {
val env = (envVars in run).value.toSeq
@@ -455,18 +387,45 @@ object ScalaNativePluginInternal {
}
)
- private def writeConfigHash(file: File, config: Any*): Unit = {
- val force = config.## // Force evaluation of lazy structures
- IO.write(file, Hash(config.toString))
- }
-
val scalaNativeEcosystemSettings = Seq(
crossVersion := ScalaNativeCrossVersion.binary,
crossPlatform := NativePlatform
)
- private def maybeInjectShared(lib: Boolean): Seq[String] =
- if (lib) Seq("-shared") else Seq.empty
+ private def abs(file: File): String =
+ file.getAbsolutePath
+
+ private def discover(binaryName: String,
+ binaryVersions: Seq[(String, String)]): File = {
+ val docSetup =
+ "http://www.scala-native.org/en/latest/user/setup.html"
+
+ val envName =
+ if (binaryName == "clang") "CLANG"
+ else if (binaryName == "clang++") "CLANGPP"
+ else binaryName
+
+ sys.env.get(s"${envName}_PATH") match {
+ case Some(path) => file(path)
+ case None => {
+ val binaryNames = binaryVersions.flatMap {
+ case (major, minor) =>
+ Seq(s"$binaryName$major$minor", s"$binaryName-$major.$minor")
+ } :+ binaryName
+
+ Process("which" +: binaryNames).lines_!
+ .map(file(_))
+ .headOption
+ .getOrElse {
+ throw new MessageOnlyException(
+ s"no ${binaryNames.mkString(", ")} found in $$PATH. Install clang ($docSetup)")
+ }
+ }
+ }
+ }
+
+ private val clangVersions =
+ Seq(("4", "0"), ("3", "9"), ("3", "8"), ("3", "7"))
/**
* Tests whether the clang compiler is recent enough.
@@ -534,7 +493,7 @@ object ScalaNativePluginInternal {
nativelib: File) = {
val gc = garbageCollector(gcName)
// Directory in nativelib containing the garbage collectors
- val garbageCollectorsDir = "gc"
+ val garbageCollectorsDir = "lib/gc"
val specificDir = s"$garbageCollectorsDir/${gc.dir}"
def isOtherGC(path: String, nativelib: File) = {
@@ -544,4 +503,19 @@ object ScalaNativePluginInternal {
files.filterNot(f => isOtherGC(f.getPath().toString, nativelib))
}
+
+ private implicit class RichLogger(logger: Logger) {
+ def time[T](msg: String)(f: => T): T = {
+ import java.lang.System.nanoTime
+ val start = nanoTime()
+ val res = f
+ val end = nanoTime()
+ logger.info(s"$msg (${(end - start) / 1000000} ms)")
+ res
+ }
+
+ def running(command: Seq[String]): Unit =
+ logger.debug("running" + nl + command.mkString(nl + "\t"))
+ }
+
}
diff --git a/scripted-tests/run/external-dependencies/build.sbt b/scripted-tests/run/external-dependencies/build.sbt
index c5381bad4c..2a411cb058 100644
--- a/scripted-tests/run/external-dependencies/build.sbt
+++ b/scripted-tests/run/external-dependencies/build.sbt
@@ -2,4 +2,74 @@ enablePlugins(ScalaNativePlugin)
scalaVersion := "2.11.8"
-Check.setup
+lazy val check =
+ TaskKey[Unit]("check", "make sure we list external dependencies correctly")
+
+check := {
+ val external = (nativeExternalDependencies in Compile).value.toSet
+ val missing = (nativeMissingDependencies in Compile).value.toSet
+
+ // not supported
+ val applets = Set(
+ "@java.applet.Applet",
+ "@java.applet.Applet::destroy_unit",
+ "@java.applet.Applet::init",
+ "@java.applet.Applet::init_unit",
+ "@java.applet.Applet::start_unit",
+ "@java.applet.Applet::stop_unit"
+ )
+ val awt = Set(
+ "@java.awt.Component",
+ "@java.awt.Component::addMouseListener_trait.java.awt.event.MouseListener_unit",
+ "@java.awt.Component::getHeight_i32",
+ "@java.awt.Component::getWidth_i32",
+ "@java.awt.Component::paint_class.java.awt.Graphics_unit",
+ "@java.awt.Component::repaint_unit",
+ "@java.awt.Container",
+ "@java.awt.Container::paint_class.java.awt.Graphics_unit",
+ "@java.awt.Graphics",
+ "@java.awt.Graphics::drawRect_i32_i32_i32_i32_unit",
+ "@java.awt.Graphics::drawString_class.java.lang.String_i32_i32_unit",
+ "@java.awt.event.MouseEvent",
+ "@java.awt.event.MouseListener",
+ "@java.awt.event.MouseListener::mouseClicked_class.java.awt.event.MouseEvent_unit",
+ "@java.awt.event.MouseListener::mouseEntered_class.java.awt.event.MouseEvent_unit",
+ "@java.awt.event.MouseListener::mouseExited_class.java.awt.event.MouseEvent_unit",
+ "@java.awt.event.MouseListener::mousePressed_class.java.awt.event.MouseEvent_unit",
+ "@java.awt.event.MouseListener::mouseReleased_class.java.awt.event.MouseEvent_unit"
+ )
+
+ assert((awt -- missing).isEmpty)
+ assert(((applets ++ awt) -- external).isEmpty)
+
+ // implemented
+ val rest = Set(
+ "@java.io.PrintStream",
+ "@java.io.PrintStream::println_class.java.lang.String_unit",
+ "@java.lang.Object",
+ "@java.lang.String",
+ "@java.lang.StringBuffer",
+ "@java.lang.StringBuffer::append_class.java.lang.String_class.java.lang.StringBuffer",
+ "@java.lang.StringBuffer::init",
+ "@java.lang.StringBuffer::toString_class.java.lang.String",
+ "@java.lang.System$",
+ "@java.lang.System$::out_class.java.io.PrintStream",
+ "@scala.LowPriorityImplicits",
+ "@scala.LowPriorityImplicits::wrapIntArray_class.ssnr.IntArray_class.scala.collection.mutable.WrappedArray",
+ "@scala.Predef$",
+ "@scala.Predef$::Set_module.scala.collection.immutable.Set$",
+ "@scala.collection.GenTraversable",
+ "@scala.collection.Seq",
+ "@scala.collection.generic.GenericCompanion",
+ "@scala.collection.generic.GenericCompanion::apply_trait.scala.collection.Seq_trait.scala.collection.GenTraversable",
+ "@scala.collection.immutable.Set",
+ "@scala.collection.immutable.Set$",
+ "@scala.collection.mutable.WrappedArray",
+ "@scala.scalanative.runtime.IntArray",
+ "@scala.scalanative.runtime.IntArray$",
+ "@scala.scalanative.runtime.IntArray$::alloc_i32_class.ssnr.IntArray",
+ "@scala.scalanative.runtime.IntArray::update_i32_i32_unit"
+ )
+
+ assert((rest -- external).isEmpty)
+}
diff --git a/scripted-tests/run/external-dependencies/project/Check.scala b/scripted-tests/run/external-dependencies/project/Check.scala
deleted file mode 100644
index 509d6b8b1a..0000000000
--- a/scripted-tests/run/external-dependencies/project/Check.scala
+++ /dev/null
@@ -1,83 +0,0 @@
-import sbt._
-import Keys._
-
-import scala.scalanative.sbtplugin.ScalaNativePluginInternal._
-
-object Check {
- lazy val check =
- TaskKey[Unit]("check", "make sure we list external dependencies correctly")
-
- val setup = Seq(
- check := {
- val deps = (nativeExternalDependencies in Compile).value.toSet
-
- val missing = (nativeMissingDependencies in Compile).value.toSet
-
- // most propably not implemented
- val applets = Set(
- "@java.applet.Applet",
- "@java.applet.Applet::destroy_unit",
- "@java.applet.Applet::init",
- "@java.applet.Applet::init_unit",
- "@java.applet.Applet::start_unit",
- "@java.applet.Applet::stop_unit"
- )
-
- val awt = Set(
- "@java.awt.Component",
- "@java.awt.Component::addMouseListener_trait.java.awt.event.MouseListener_unit",
- "@java.awt.Component::getHeight_i32",
- "@java.awt.Component::getWidth_i32",
- "@java.awt.Component::paint_class.java.awt.Graphics_unit",
- "@java.awt.Component::repaint_unit",
- "@java.awt.Container",
- "@java.awt.Container::paint_class.java.awt.Graphics_unit",
- "@java.awt.Graphics",
- "@java.awt.Graphics::drawRect_i32_i32_i32_i32_unit",
- "@java.awt.Graphics::drawString_class.java.lang.String_i32_i32_unit",
- "@java.awt.event.MouseEvent",
- "@java.awt.event.MouseListener",
- "@java.awt.event.MouseListener::mouseClicked_class.java.awt.event.MouseEvent_unit",
- "@java.awt.event.MouseListener::mouseEntered_class.java.awt.event.MouseEvent_unit",
- "@java.awt.event.MouseListener::mouseExited_class.java.awt.event.MouseEvent_unit",
- "@java.awt.event.MouseListener::mousePressed_class.java.awt.event.MouseEvent_unit",
- "@java.awt.event.MouseListener::mouseReleased_class.java.awt.event.MouseEvent_unit"
- )
-
- assert((awt -- missing).isEmpty)
-
- assert(((applets ++ awt) -- deps).isEmpty)
-
- // most propably implemented
- val rest = Set(
- "@java.io.PrintStream",
- "@java.io.PrintStream::println_class.java.lang.String_unit",
- "@java.lang.Object",
- "@java.lang.String",
- "@java.lang.StringBuffer",
- "@java.lang.StringBuffer::append_class.java.lang.String_class.java.lang.StringBuffer",
- "@java.lang.StringBuffer::init",
- "@java.lang.StringBuffer::toString_class.java.lang.String",
- "@java.lang.System$",
- "@java.lang.System$::out_class.java.io.PrintStream",
- "@scala.LowPriorityImplicits",
- "@scala.LowPriorityImplicits::wrapIntArray_class.ssnr.IntArray_class.scala.collection.mutable.WrappedArray",
- "@scala.Predef$",
- "@scala.Predef$::Set_module.scala.collection.immutable.Set$",
- "@scala.collection.GenTraversable",
- "@scala.collection.Seq",
- "@scala.collection.generic.GenericCompanion",
- "@scala.collection.generic.GenericCompanion::apply_trait.scala.collection.Seq_trait.scala.collection.GenTraversable",
- "@scala.collection.immutable.Set",
- "@scala.collection.immutable.Set$",
- "@scala.collection.mutable.WrappedArray",
- "@scala.scalanative.runtime.IntArray",
- "@scala.scalanative.runtime.IntArray$",
- "@scala.scalanative.runtime.IntArray$::alloc_i32_class.ssnr.IntArray",
- "@scala.scalanative.runtime.IntArray::update_i32_i32_unit"
- )
-
- assert((rest -- deps).isEmpty)
- }
- )
-}
diff --git a/tools/src/main/scala/scala/scalanative/codegen/CodeGen.scala b/tools/src/main/scala/scala/scalanative/codegen/CodeGen.scala
index 3f0fe87cb6..60742e3b04 100644
--- a/tools/src/main/scala/scala/scalanative/codegen/CodeGen.scala
+++ b/tools/src/main/scala/scala/scalanative/codegen/CodeGen.scala
@@ -5,7 +5,7 @@ import java.{lang => jl}
import java.nio.ByteBuffer
import java.nio.file.Paths
import scala.collection.mutable
-import scalanative.util.{ShowBuilder, unsupported}
+import scalanative.util.{Scope, ShowBuilder, unsupported}
import scalanative.io.{VirtualDirectory, withScratchBuffer}
import scalanative.optimizer.analysis.ControlFlow.{Graph => CFG, Block, Edge}
import scalanative.nir._
@@ -13,62 +13,60 @@ import scalanative.nir._
object CodeGen {
/** Generate code for given assembly. */
- def apply(config: tools.Config, assembly: Seq[Defn]): Unit = {
- val env = assembly.map(defn => defn.name -> defn).toMap
-
- def debug(): Unit = {
- val batches = mutable.Map.empty[String, mutable.Buffer[Defn]]
- assembly.foreach { defn =>
- val top = defn.name.top.id
- val key =
- if (top.startsWith("__")) top
- else if (top == "main") "__main"
- else {
- val pkg = top.split("\\.").init.mkString(".")
- if (pkg == "") "__empty"
- else pkg
+ def apply(config: tools.Config, assembly: Seq[Defn]): Unit =
+ Scope { implicit in =>
+ val env = assembly.map(defn => defn.name -> defn).toMap
+ val workdir = VirtualDirectory.real(config.workdir)
+
+ def debug(): Unit = {
+ val batches = mutable.Map.empty[String, mutable.Buffer[Defn]]
+ assembly.foreach { defn =>
+ val top = defn.name.top.id
+ val key =
+ if (top.startsWith("__")) top
+ else if (top == "main") "__main"
+ else {
+ val pkg = top.split("\\.").init.mkString(".")
+ if (pkg == "") "__empty"
+ else pkg
+ }
+ if (!batches.contains(key)) {
+ batches(key) = mutable.UnrolledBuffer.empty[Defn]
}
- if (!batches.contains(key)) {
- batches(key) = mutable.UnrolledBuffer.empty[Defn]
+ batches(key) += defn
+ }
+ batches.par.foreach {
+ case (k, defns) =>
+ val impl =
+ new Impl(config.target, env, defns, workdir)
+ val outpath = k + ".ll"
+ withScratchBuffer { buffer =>
+ impl.gen(buffer)
+ buffer.flip
+ workdir.write(Paths.get(outpath), buffer)
+ }
}
- batches(key) += defn
- }
- batches.par.foreach {
- case (k, defns) =>
- val impl =
- new Impl(config.target, env, defns, config.targetDirectory)
- val outpath = k + ".ll"
- withScratchBuffer { buffer =>
- impl.gen(buffer)
- buffer.flip
- config.targetDirectory.write(Paths.get(outpath), buffer)
- }
}
- }
- def release(): Unit = {
- withScratchBuffer { buffer =>
- val defns = assembly
- val impl = new Impl(config.target, env, defns, config.targetDirectory)
- val outpath = "out.ll"
+ def release(): Unit = {
withScratchBuffer { buffer =>
+ val impl = new Impl(config.target, env, assembly, workdir)
impl.gen(buffer)
buffer.flip
- config.targetDirectory.write(Paths.get(outpath), buffer)
+ workdir.write(Paths.get("out.ll"), buffer)
}
}
- }
- config.mode match {
- case tools.Mode.Debug => debug()
- case tools.Mode.Release => release()
+ config.mode match {
+ case tools.Mode.Debug => debug()
+ case tools.Mode.Release => release()
+ }
}
- }
private final class Impl(target: String,
env: Map[Global, Defn],
defns: Seq[Defn],
- targetDirectory: VirtualDirectory) {
+ workdir: VirtualDirectory) {
import Impl._
var currentBlockName: Local = _
diff --git a/tools/src/main/scala/scala/scalanative/linker/Linker.scala b/tools/src/main/scala/scala/scalanative/linker/Linker.scala
index dfececa36f..b2d0fcf20b 100644
--- a/tools/src/main/scala/scala/scalanative/linker/Linker.scala
+++ b/tools/src/main/scala/scala/scalanative/linker/Linker.scala
@@ -2,17 +2,17 @@ package scala.scalanative
package linker
import scala.collection.mutable
-import nir._
-import nir.serialization._
-import util.Scope
+import scalanative.nir._
+import scalanative.nir.serialization._
+import scalanative.io.VirtualDirectory
+import scalanative.util.Scope
import ReflectiveProxy._
sealed trait Linker {
/** Link the whole world under closed world assumption. */
- def link(entries: Seq[Global])
- : (Seq[Global], Seq[Attr.Link], Seq[Defn], Seq[String])
+ def link(entries: Seq[Global]): Result
}
object Linker {
@@ -26,15 +26,7 @@ object Linker {
extends Linker {
import reporter._
- private def load(global: Global)
- : Option[(Seq[Dep], Seq[Attr.Link], Seq[String], Defn)] =
- config.paths.collectFirst {
- case path if path.contains(global) =>
- path.load(global)
- }.flatten
-
- def link(entries: Seq[Global])
- : (Seq[Global], Seq[Attr.Link], Seq[Defn], Seq[String]) = {
+ def link(entries: Seq[Global]): Result = Scope { implicit in =>
val resolved = mutable.Set.empty[Global]
val unresolved = mutable.Set.empty[Global]
val links = mutable.Set.empty[Attr.Link]
@@ -45,6 +37,13 @@ object Linker {
val signatures = mutable.Set.empty[String]
val dyndefns = mutable.Set.empty[Global]
+ val paths = config.paths.map(p => Path(VirtualDirectory.real(p)))
+ def load(global: Global) =
+ paths.collectFirst {
+ case path if path.contains(global) =>
+ path.load(global)
+ }.flatten
+
def processDirect =
while (direct.nonEmpty) {
val workitem = direct.pop()
@@ -135,10 +134,10 @@ object Linker {
onComplete()
- (unresolved.toSeq,
- links.toSeq,
- defnss.sortBy(_.name.toString),
- signatures.toSeq)
+ Result(unresolved.toSeq,
+ links.toSeq,
+ defnss.sortBy(_.name.toString),
+ signatures.toSeq)
}
}
}
diff --git a/tools/src/main/scala/scala/scalanative/linker/Result.scala b/tools/src/main/scala/scala/scalanative/linker/Result.scala
new file mode 100644
index 0000000000..413cb1eef4
--- /dev/null
+++ b/tools/src/main/scala/scala/scalanative/linker/Result.scala
@@ -0,0 +1,61 @@
+package scala.scalanative
+package linker
+
+import nir._
+
+trait Result {
+
+ /** Sequence of globals that could not be resolved. */
+ def unresolved: Seq[Global]
+
+ /** Sequence of external c libraries to link with. */
+ def links: Seq[Attr.Link]
+
+ /** Sequence of definitions that were discovered during linking. */
+ def defns: Seq[nir.Defn]
+
+ /** Sequence of signatures of dynamic methods that were discovered during linking. */
+ def dyns: Seq[String]
+
+ /** Create a copy of the result with given unresolved sequence. */
+ def withUnresolved(value: Seq[Global]): Result
+
+ /** Create a copy of the result with given links sequence. */
+ def withLinks(value: Seq[Attr.Link]): Result
+
+ /** Create a copy of the result with given defns sequence. */
+ def withDefns(value: Seq[nir.Defn]): Result
+
+ /** Create a copy of the result with given dyns sequence. */
+ def withDyns(value: Seq[String]): Result
+}
+
+object Result {
+
+ /** Default, empty linker result. */
+ val empty: Result = Impl(Seq.empty, Seq.empty, Seq.empty, Seq.empty)
+
+ private[linker] final case class Impl(unresolved: Seq[Global],
+ links: Seq[Attr.Link],
+ defns: Seq[nir.Defn],
+ dyns: Seq[String])
+ extends Result {
+ def withUnresolved(value: Seq[Global]): Result =
+ copy(unresolved = value)
+
+ def withLinks(value: Seq[Attr.Link]): Result =
+ copy(links = value)
+
+ def withDefns(value: Seq[nir.Defn]): Result =
+ copy(defns = value)
+
+ def withDyns(value: Seq[String]): Result =
+ copy(dyns = value)
+ }
+
+ private[linker] def apply(unresolved: Seq[Global],
+ links: Seq[Attr.Link],
+ defns: Seq[nir.Defn],
+ dyns: Seq[String]): Result =
+ Impl(unresolved, links, defns, dyns)
+}
diff --git a/tools/src/main/scala/scala/scalanative/optimizer/Driver.scala b/tools/src/main/scala/scala/scalanative/optimizer/Driver.scala
index 8d6cd2334f..8ae70d64cc 100644
--- a/tools/src/main/scala/scala/scalanative/optimizer/Driver.scala
+++ b/tools/src/main/scala/scala/scalanative/optimizer/Driver.scala
@@ -8,14 +8,8 @@ sealed trait Driver {
/** Companion of all the passes in the driver's pipeline. */
def passes: Seq[AnyPassCompanion]
- /** Take all passes including the given one. */
- def takeUpTo(pass: AnyPassCompanion): Driver
-
- /** Take all passes including the given one. */
- def takeBefore(pass: AnyPassCompanion): Driver
-
- /** Append a pass to the pipeline. */
- def append(pass: AnyPassCompanion): Driver
+ /** Create a copy with given passes. */
+ def withPasses(passes: Seq[AnyPassCompanion]): Driver
}
object Driver {
@@ -80,13 +74,7 @@ object Driver {
new Impl(Seq.empty)
private final class Impl(val passes: Seq[AnyPassCompanion]) extends Driver {
- def takeUpTo(pass: AnyPassCompanion): Driver =
- takeBefore(pass).append(pass)
-
- def takeBefore(pass: AnyPassCompanion): Driver =
- new Impl(passes takeWhile (_ != pass))
-
- def append(pass: AnyPassCompanion): Driver =
- new Impl(passes :+ pass)
+ def withPasses(passes: Seq[AnyPassCompanion]): Driver =
+ new Impl(passes)
}
}
diff --git a/tools/src/main/scala/scala/scalanative/optimizer/inject/Main.scala b/tools/src/main/scala/scala/scalanative/optimizer/inject/Main.scala
index 5baf05d174..89c823452e 100644
--- a/tools/src/main/scala/scala/scalanative/optimizer/inject/Main.scala
+++ b/tools/src/main/scala/scala/scalanative/optimizer/inject/Main.scala
@@ -93,6 +93,5 @@ object Main extends InjectCompanion {
Seq(InitDecl)
override def apply(config: tools.Config, top: Top) =
- if (config.injectMain) new Main(config.entry)(top.fresh)
- else NoPass
+ new Main(config.entry)(top.fresh)
}
diff --git a/tools/src/main/scala/scala/scalanative/tools/Config.scala b/tools/src/main/scala/scala/scalanative/tools/Config.scala
index d42712906d..7996dee2d0 100644
--- a/tools/src/main/scala/scala/scalanative/tools/Config.scala
+++ b/tools/src/main/scala/scala/scalanative/tools/Config.scala
@@ -1,6 +1,7 @@
package scala.scalanative
package tools
+import java.io.File
import scalanative.io.VirtualDirectory
import nir.Global
@@ -10,13 +11,10 @@ sealed trait Config {
def entry: Global
/** Sequence of all NIR locations. */
- def paths: Seq[LinkerPath]
+ def paths: Seq[File]
/** Directory to emit intermediate compilation results. */
- def targetDirectory: VirtualDirectory
-
- /** Should a main method be injected? */
- def injectMain: Boolean
+ def workdir: File
/** Target triple. */
def target: String
@@ -28,13 +26,10 @@ sealed trait Config {
def withEntry(value: Global): Config
/** Create a new config with given nir paths. */
- def withPaths(value: Seq[LinkerPath]): Config
+ def withPaths(value: Seq[File]): Config
/** Create a new config with given directory. */
- def withTargetDirectory(value: VirtualDirectory): Config
-
- /** Create a new config with given inject main flag. */
- def withInjectMain(value: Boolean): Config
+ def withWorkdir(value: File): Config
/** Create a new config with given target triple. */
def withTarget(value: String): Config
@@ -49,29 +44,24 @@ object Config {
val empty: Config =
Impl(entry = Global.None,
paths = Seq.empty,
- targetDirectory = VirtualDirectory.empty,
- injectMain = true,
+ workdir = new File(""),
target = "",
mode = Mode.Debug)
private final case class Impl(entry: Global,
- paths: Seq[LinkerPath],
- targetDirectory: VirtualDirectory,
- injectMain: Boolean,
+ paths: Seq[File],
+ workdir: File,
target: String,
mode: Mode)
extends Config {
def withEntry(value: Global): Config =
copy(entry = value)
- def withPaths(value: Seq[LinkerPath]): Config =
+ def withPaths(value: Seq[File]): Config =
copy(paths = value)
- def withTargetDirectory(value: VirtualDirectory): Config =
- copy(targetDirectory = value)
-
- def withInjectMain(value: Boolean): Config =
- copy(injectMain = value)
+ def withWorkdir(value: File): Config =
+ copy(workdir = value)
def withTarget(value: String): Config =
copy(target = value)
diff --git a/tools/src/main/scala/scala/scalanative/tools/package.scala b/tools/src/main/scala/scala/scalanative/tools/package.scala
index 8ac539926c..0bdb6a28c1 100644
--- a/tools/src/main/scala/scala/scalanative/tools/package.scala
+++ b/tools/src/main/scala/scala/scalanative/tools/package.scala
@@ -27,6 +27,9 @@ package object tools {
type LinkerReporter = linker.Reporter
val LinkerReporter = linker.Reporter
+ type LinkerResult = linker.Result
+ val LinkerResult = linker.Result
+
type OptimizerDriver = optimizer.Driver
val OptimizerDriver = optimizer.Driver
@@ -36,16 +39,15 @@ package object tools {
/** Given the classpath and entry point, link under closed-world assumption. */
def link(config: Config,
driver: OptimizerDriver,
- reporter: LinkerReporter = LinkerReporter.empty)
- : (Seq[nir.Global], Seq[nir.Attr.Link], Seq[nir.Defn], Seq[String]) = {
+ reporter: LinkerReporter = LinkerReporter.empty): LinkerResult = {
val deps = driver.passes.flatMap(_.depends).distinct
val injects = driver.passes.flatMap(_.injects).distinct
val entry =
nir.Global.Member(config.entry, "main_class.ssnr.ObjectArray_unit")
- val (unresolved, links, defns, dyns) =
+ val result =
(linker.Linker(config, reporter)).link(entry +: deps)
- (unresolved, links, defns ++ injects, dyns)
+ result.withDefns(result.defns ++ injects)
}
/** Transform high-level closed world to its lower-level counterpart. */
diff --git a/tools/src/test/scala/scala/scalanative/CodeGenSpec.scala b/tools/src/test/scala/scala/scalanative/CodeGenSpec.scala
index 62a7d297ca..fc479bdbaf 100644
--- a/tools/src/test/scala/scala/scalanative/CodeGenSpec.scala
+++ b/tools/src/test/scala/scala/scalanative/CodeGenSpec.scala
@@ -1,9 +1,10 @@
package scala.scalanative
-import io.VirtualFile
-import optimizer.Driver
import java.nio.file.Paths
-import tools.Config
+import scalanative.io.{VirtualDirectory, VirtualFile}
+import scalanative.optimizer.Driver
+import scalanative.tools.Config
+import scalanative.util.Scope
/** Base class to test code generation */
abstract class CodeGenSpec extends OptimizerSpec {
@@ -21,15 +22,18 @@ abstract class CodeGenSpec extends OptimizerSpec {
def codegen[T](entry: String,
sources: Map[String, String],
driver: Option[Driver] = None)(
- fn: (Config, Seq[nir.Attr.Link], VirtualFile) => T): T =
+ f: (Config, Seq[nir.Attr.Link], VirtualFile) => T): T =
optimize(entry, sources, driver) {
case (config, links, assembly) =>
- tools.codegen(config, assembly)
- val llFile =
- config.targetDirectory.get(Paths.get("out.ll")) getOrElse fail(
- "out.ll not found.")
+ Scope { implicit in =>
+ tools.codegen(config, assembly)
+ val workdir = VirtualDirectory.real(config.workdir)
+ val outfile =
+ workdir.get(Paths.get("out.ll")) getOrElse fail(
+ "out.ll not found.")
- fn(config, links, llFile)
+ f(config, links, outfile)
+ }
}
}
diff --git a/tools/src/test/scala/scala/scalanative/FrameworkTest.scala b/tools/src/test/scala/scala/scalanative/FrameworkTest.scala
index fd9748b712..0bf2a6ca54 100644
--- a/tools/src/test/scala/scala/scalanative/FrameworkTest.scala
+++ b/tools/src/test/scala/scala/scalanative/FrameworkTest.scala
@@ -12,8 +12,8 @@ class FrameworkTest extends CodeGenSpec with Matchers {
| def main(args: Array[String]): Unit =
| println("Hello, world!")
|}""".stripMargin) {
- case (_, _, defns, _) =>
- val defNames = defns map (_.name)
+ case (_, res) =>
+ val defNames = res.defns map (_.name)
defNames should contain(Global.Top("A$"))
}
}
@@ -27,8 +27,8 @@ class FrameworkTest extends CodeGenSpec with Matchers {
)
link("B$", sources) {
- case (_, _, defns, _) =>
- val defNames = defns map (_.name)
+ case (_, res) =>
+ val defNames = res.defns map (_.name)
defNames should contain(Global.Top("A"))
defNames should contain(Global.Top("B$"))
}
diff --git a/tools/src/test/scala/scala/scalanative/LinkerSpec.scala b/tools/src/test/scala/scala/scalanative/LinkerSpec.scala
index 84ddb63417..99d2f1a246 100644
--- a/tools/src/test/scala/scala/scalanative/LinkerSpec.scala
+++ b/tools/src/test/scala/scala/scalanative/LinkerSpec.scala
@@ -28,10 +28,10 @@ abstract class LinkerSpec extends FlatSpec {
* @param fn A function to apply to the products of the compilation.
* @return The result of applying `fn` to the resulting definitions.
*/
- def link[T](entry: String,
- sources: Map[String, String],
- driver: Option[Driver] = None)(
- fn: (Config, Seq[nir.Attr.Link], Seq[nir.Defn], Seq[String]) => T): T =
+ def link[T](
+ entry: String,
+ sources: Map[String, String],
+ driver: Option[Driver] = None)(f: (Config, linker.Result) => T): T =
Scope { implicit in =>
val outDir = Files.createTempDirectory("native-test-out").toFile()
val compiler = NIRCompiler.getCompiler(outDir)
@@ -39,10 +39,9 @@ abstract class LinkerSpec extends FlatSpec {
val files = compiler.compile(sourcesDir)
val config = makeConfig(outDir, entry)
val driver_ = driver.fold(Driver(config))(identity)
+ val result = tools.link(config, driver_)
- val (_, links, defns, dyns) = tools.link(config, driver_)
-
- fn(config, links, defns, dyns)
+ f(config, result)
}
private def makePaths(outDir: File)(implicit in: Scope) = {
@@ -52,7 +51,7 @@ abstract class LinkerSpec extends FlatSpec {
.split(File.pathSeparator)
.map(new File(_))
- (parts :+ outDir).map(p => Path(VirtualDirectory.real(p)))
+ parts :+ outDir
}
private def makeConfig(outDir: File, entryName: String)(
@@ -60,7 +59,7 @@ abstract class LinkerSpec extends FlatSpec {
val entry = Global.Top(entryName)
val paths = makePaths(outDir)
Config.empty
- .withTargetDirectory(VirtualDirectory.real(outDir))
+ .withWorkdir(outDir)
.withPaths(paths)
.withEntry(entry)
}
diff --git a/tools/src/test/scala/scala/scalanative/OptimizerSpec.scala b/tools/src/test/scala/scala/scalanative/OptimizerSpec.scala
index 246c516975..6edaab0f79 100644
--- a/tools/src/test/scala/scala/scalanative/OptimizerSpec.scala
+++ b/tools/src/test/scala/scala/scalanative/OptimizerSpec.scala
@@ -22,9 +22,11 @@ abstract class OptimizerSpec extends LinkerSpec {
driver: Option[Driver] = None)(
fn: (Config, Seq[nir.Attr.Link], Seq[nir.Defn]) => T): T =
link(entry, sources, driver) {
- case (config, links, assembly, dyns) =>
+ case (config, res) =>
val driver_ = driver.fold(Driver(config))(identity)
- fn(config, links, tools.optimize(config, driver_, assembly, dyns))
+ fn(config,
+ res.links,
+ tools.optimize(config, driver_, res.defns, res.dyns))
}
}
diff --git a/tools/src/test/scala/scala/scalanative/optimizer/DriverSpec.scala b/tools/src/test/scala/scala/scalanative/optimizer/DriverSpec.scala
index 7b9b85bb4c..f4740421d3 100644
--- a/tools/src/test/scala/scala/scalanative/optimizer/DriverSpec.scala
+++ b/tools/src/test/scala/scala/scalanative/optimizer/DriverSpec.scala
@@ -18,30 +18,11 @@ class DriverSpec extends FlatSpec with Matchers {
private val P1 = makeCompanion
private val P2 = makeCompanion
- "The driver" should "support `append`" in {
- val driver = Driver.empty.append(P0)
+ "The driver" should "support `passes` and `withPasses`" in {
+ val empty = Driver.empty
+ empty.passes should have length (0)
+ val driver = empty.withPasses(Seq(P0))
driver.passes should have length (1)
driver.passes(0) should be(P0)
}
-
- it should "support `takeUpTo`" in {
- val driver = Driver.empty.append(P0).append(P1).append(P2)
- driver.passes should have length (3)
- driver.passes should contain theSameElementsInOrderAs Seq(P0, P1, P2)
-
- val newDriver = driver.takeUpTo(P1)
- newDriver.passes should have length (2)
- newDriver.passes should contain theSameElementsInOrderAs Seq(P0, P1)
- }
-
- it should "support `takeBefore`" in {
- val driver = Driver.empty.append(P0).append(P1).append(P2)
- driver.passes should have length (3)
- driver.passes should contain theSameElementsInOrderAs Seq(P0, P1, P2)
-
- val newDriver = driver.takeBefore(P2)
- newDriver.passes should have length (2)
- newDriver.passes should contain theSameElementsInOrderAs Seq(P0, P1)
- }
-
}
diff --git a/tools/src/test/scala/scala/scalanative/optimizer/pass/AsLoweringTest.scala b/tools/src/test/scala/scala/scalanative/optimizer/pass/AsLoweringTest.scala
index 0415ad371d..12ecd98ba5 100644
--- a/tools/src/test/scala/scala/scalanative/optimizer/pass/AsLoweringTest.scala
+++ b/tools/src/test/scala/scala/scalanative/optimizer/pass/AsLoweringTest.scala
@@ -9,14 +9,15 @@ import tools._
class AsLoweringTest extends OptimizerSpec {
"The `AsLoweringPhase`" should "have an effect (this is a self-test)" in {
- val driver = Some(Driver.empty.append(AsLoweringCheck))
+ val driver = Some(Driver.empty.withPasses(Seq(AsLoweringCheck)))
assertThrows[Exception] {
optimize("A$", code, driver) { case (_, _, _) => () }
}
}
it should "remove all occurrences of `Op.As`" in {
- val driver = Some(Driver.empty.append(AsLowering).append(AsLoweringCheck))
+ val driver =
+ Some(Driver.empty.withPasses(Seq(AsLowering, AsLoweringCheck)))
optimize("A$", code, driver) { case (_, _, _) => () }
}
diff --git a/unit-tests/src/main/scala/java/lang/ExceptionSuite.scala b/unit-tests/src/main/scala/java/lang/ExceptionSuite.scala
index 2a66a3abc4..a8d6be010c 100644
--- a/unit-tests/src/main/scala/java/lang/ExceptionSuite.scala
+++ b/unit-tests/src/main/scala/java/lang/ExceptionSuite.scala
@@ -7,42 +7,22 @@ object ExceptionSuite extends tests.Suite {
val sw = new java.io.StringWriter
val pw = new java.io.PrintWriter(sw)
(new Exception).printStackTrace(pw)
-
- // Stacktrace when C/C++ files are compiled with -O2
- val expected = Seq(
- "java.lang.Exception",
- "\tat java.lang.Throwable::init_class.java.lang.String_class.java.lang.Throwable",
- "\tat java.lang.Exception::init_class.java.lang.String_class.java.lang.Throwable",
- "\tat java.lang.Exception::init",
- "\tat java.lang.ExceptionSuite$$anonfun$1::apply$mcV$sp_unit",
- "\tat tests.Suite$$anonfun$test$1::apply$mcZ$sp_bool",
- "\tat tests.Suite$$anonfun$run$1::apply_class.tests.Test_unit",
- "\tat tests.Suite$$anonfun$run$1::apply_class.java.lang.Object_class.java.lang.Object",
- "\tat scala.collection.mutable.UnrolledBuffer$Unrolled::foreach_trait.scala.Function1_unit",
- "\tat scala.collection.mutable.UnrolledBuffer::foreach_trait.scala.Function1_unit",
- "\tat tests.Suite::run_bool",
- "\tat tests.Main$$anonfun$main$1::apply_class.tests.Suite_unit",
- "\tat tests.Main$$anonfun$main$1::apply_class.java.lang.Object_class.java.lang.Object",
- "\tat scala.collection.immutable.List::foreach_trait.scala.Function1_unit",
- "\tat tests.Main$::main_class.ssnr.ObjectArray_unit",
- "\tat main"
- ).mkString("\n")
- // It's startsWith and not == as there could be additional stack
- // activations before main in the stack trace. For example on linux
- // there would be additional __libc_start_main and _start frames.
-
- // fails on nix https://github.com/scala-native/scala-native/issues/516
- assert(sw.toString.startsWith(expected))
+ val trace = sw.toString
+ assert(trace.startsWith("java.lang.Exception"))
+ assert(
+ trace.contains("\tat tests.Main$::main_class.ssnr.ObjectArray_unit"))
+ assert(trace.contains("\tat main"))
}
test("printStackTrace ") {
val sw = new java.io.StringWriter
val pw = new java.io.PrintWriter(sw)
(new DummyNoStackTraceException).printStackTrace(pw)
+ val trace = sw.toString
val expected = Seq(
"java.lang.DummyNoStackTraceException",
"\t"
).mkString("\n")
- assert(sw.toString.startsWith(expected))
+ assert(trace.startsWith(expected))
}
}
diff --git a/unit-tests/src/main/scala/java/util/regex/PatternSuite.scala b/unit-tests/src/main/scala/java/util/regex/PatternSuite.scala
index 820b34ae75..5379a3361a 100644
--- a/unit-tests/src/main/scala/java/util/regex/PatternSuite.scala
+++ b/unit-tests/src/main/scala/java/util/regex/PatternSuite.scala
@@ -373,10 +373,6 @@ object PatternSuite extends tests.Suite {
if (pass) ret
else !ret
- if (!ret0) {
- println(s" [fail] $pattern $mid $input")
- }
-
assert(ret0)
}
diff --git a/util/src/main/scala/scala/scalanative/util/Scope.scala b/util/src/main/scala/scala/scalanative/util/Scope.scala
index b9e0a94a51..7962740522 100644
--- a/util/src/main/scala/scala/scalanative/util/Scope.scala
+++ b/util/src/main/scala/scala/scalanative/util/Scope.scala
@@ -26,7 +26,7 @@ object Scope {
* resources as soon as execution leaves the demercated block.
*/
def apply[T](f: Scope => T): T = {
- val scope = new Impl()
+ val scope = new Impl
try f(scope)
finally scope.close()
}