From 7239d6bd64ec0d025686f5f97aaaf94b7ab43951 Mon Sep 17 00:00:00 2001 From: Li Haoyi Date: Wed, 6 Dec 2017 20:53:29 -0800 Subject: Explicitly pass built compiler-bridge jar locations from the build system into Mill as JVM properties. This makes the dependency between the compiler-bridge jar and the Mill executable explicit, allowing us to swap out the locations compiler-bridge jars (which end up in different places, depending on whether you're building with SBT or Mill) or eventually making them load from Maven Central in a "release" Mill executable Since Mill (and uTest) both do not support SBT-style test arguments, we need to use `forkTest` instead of `test` to run the Mill tests passing the compiler-jar locations as JVM props. This necessitated some fixes to make `forkTest` behave properly --- build.sbt | 54 +++++++++++----------- build.sc | 21 ++++++--- core/src/main/scala/mill/modules/Jvm.scala | 24 +++++++++- .../main/scala/mill/scalaplugin/ScalaModule.scala | 21 +++++---- .../main/scala/mill/scalaplugin/TestRunner.scala | 5 ++ 5 files changed, 80 insertions(+), 45 deletions(-) diff --git a/build.sbt b/build.sbt index 1177cf26..1dc993bf 100644 --- a/build.sbt +++ b/build.sbt @@ -8,19 +8,6 @@ val sharedSettings = Seq( parallelExecution in Test := false, test in assembly := {}, - assemblyOption in assembly := (assemblyOption in assembly).value.copy( - prependShellScript = Some( - // G1 Garbage Collector is awesome https://github.com/lihaoyi/Ammonite/issues/216 - Seq("#!/usr/bin/env sh", """exec java -cp "$0" mill.Main "$@" """) - ) - ), - assembly in Test := { - val dest = target.value/"mill" - IO.copyFile(assembly.value, dest) - import sys.process._ - Seq("chmod", "+x", dest.getAbsolutePath).! - dest - }, libraryDependencies += "com.lihaoyi" %% "acyclic" % "0.1.7" % "provided", scalacOptions += "-P:acyclic:force", autoCompilerPlugins := true, @@ -97,26 +84,39 @@ lazy val core = project ) ) +val bridgeProps = Def.task{ + val mapping = Map( + "MILL_COMPILER_BRIDGE_2_10_6" -> (packageBin in (bridge2_10_6, Compile)).value.absolutePath, + "MILL_COMPILER_BRIDGE_2_11_8" -> (packageBin in (bridge2_11_8, Compile)).value.absolutePath, + "MILL_COMPILER_BRIDGE_2_11_11" -> (packageBin in (bridge2_11_11, Compile)).value.absolutePath, + "MILL_COMPILER_BRIDGE_2_12_3" -> (packageBin in (bridge2_12_3, Compile)).value.absolutePath, + "MILL_COMPILER_BRIDGE_2_12_4" -> (packageBin in (bridge2_12_4, Compile)).value.absolutePath + ) + for((k, v) <- mapping) yield s"-D$k=$v" +} lazy val scalaplugin = project .dependsOn(core % "compile->compile;test->test") .settings( sharedSettings, name := "mill-scalaplugin", - (compile in Test) := { - val a = (packageBin in (bridge2_10_6, Compile)).value - val b = (packageBin in (bridge2_11_8, Compile)).value -// val c = (packageBin in (bridge2_11_9, Compile)).value -// val d = (packageBin in (bridge2_11_10, Compile)).value - val e = (packageBin in (bridge2_11_11, Compile)).value -// val f = (packageBin in (bridge2_12_0, Compile)).value -// val g = (packageBin in (bridge2_12_1, Compile)).value -// val h = (packageBin in (bridge2_12_2, Compile)).value - val i = (packageBin in (bridge2_12_3, Compile)).value - val j = (packageBin in (bridge2_12_4, Compile)).value - (compile in Test).value + fork in Test := true, + baseDirectory in (Test, test) := (baseDirectory in (Test, test)).value / "..", + javaOptions in (Test, test) := bridgeProps.value.toSeq, + assemblyOption in assembly := { + (assemblyOption in assembly).value.copy( + prependShellScript = Some( + Seq( + "#!/usr/bin/env sh", + s"""exec java ${bridgeProps.value.mkString(" ")} -cp "$$0" mill.Main "$$@" """ + ) + ) + ) }, assembly in Test := { - (compile in Test).value - (assembly in Test).value + val dest = target.value/"mill" + IO.copyFile(assembly.value, dest) + import sys.process._ + Seq("chmod", "+x", dest.getAbsolutePath).! + dest } ) diff --git a/build.sc b/build.sc index 7b261892..5d581364 100755 --- a/build.sc +++ b/build.sc @@ -5,7 +5,10 @@ import mill.scalaplugin._ trait MillModule extends ScalaModule{ outer => def scalaVersion = "2.12.4" override def sources = basePath/'src/'main/'scala + def testArgs = T{ Seq.empty[String] } object test extends this.Tests{ + override def defaultCommandName() = "forkTest" + override def forkArgs = T{ testArgs() } override def projectDeps = if (this == Core.test) Seq(Core) else Seq(outer, Core.test) @@ -80,14 +83,20 @@ object ScalaPlugin extends MillModule { override def projectDeps = Seq(Core) def basePath = pwd / 'scalaplugin override def compile = T.persistent[mill.eval.PathRef]{ - bridges("2.10.6").compile() - bridges("2.11.8").compile() - bridges("2.11.11").compile() - bridges("2.12.3").compile() - bridges("2.12.4").compile() super.compile() } + override def testArgs = T{ + val mapping = Map( + "MILL_COMPILER_BRIDGE_2_10_6" -> bridges("2.10.6").compile().path, + "MILL_COMPILER_BRIDGE_2_11_8" -> bridges("2.11.8").compile().path, + "MILL_COMPILER_BRIDGE_2_11_11" -> bridges("2.11.11").compile().path, + "MILL_COMPILER_BRIDGE_2_12_3" -> bridges("2.12.3").compile().path, + "MILL_COMPILER_BRIDGE_2_12_4" -> bridges("2.12.4").compile().path, + ) + for((k, v) <- mapping.toSeq) yield s"-D$k=$v" + } + override def prependShellScript = "#!/usr/bin/env sh\n" + - """exec java $JAVA_OPTS -cp "$0" mill.Main "$@" """ + s"""exec java ${testArgs().mkString(" ")} $$JAVA_OPTS -cp "$$0" mill.Main "$$@" """ } diff --git a/core/src/main/scala/mill/modules/Jvm.scala b/core/src/main/scala/mill/modules/Jvm.scala index 43382b8d..ddc8b427 100644 --- a/core/src/main/scala/mill/modules/Jvm.scala +++ b/core/src/main/scala/mill/modules/Jvm.scala @@ -1,6 +1,7 @@ package mill.modules import java.io.FileOutputStream +import java.net.URLClassLoader import java.nio.file.attribute.PosixFilePermission import java.util.jar.{JarEntry, JarFile, JarOutputStream} @@ -14,16 +15,35 @@ import scala.collection.mutable object Jvm { - + def gatherClassloaderJars(): Seq[Path] = { + val allJars = collection.mutable.Buffer.empty[Path] + var currentClassloader = Thread.currentThread().getContextClassLoader + while(currentClassloader != null){ + currentClassloader match{ + case u: URLClassLoader => allJars.appendAll(u.getURLs.map(x => Path(x.getFile))) + case _ => + } + currentClassloader = currentClassloader.getParent + } + allJars + } def subprocess(mainClass: String, classPath: Seq[Path], + jvmOptions: Seq[String] = Seq.empty, options: Seq[String] = Seq.empty, workingDir: Path = ammonite.ops.pwd) (implicit ctx: Ctx) = { + + val commandArgs = + Vector("java") ++ + jvmOptions ++ + Vector("-cp", classPath.mkString(":"), mainClass) ++ + options + val proc = new java.lang.ProcessBuilder() .directory(workingDir.toIO) - .command(Vector("java", "-cp", classPath.mkString(":"), mainClass) ++ options:_*) + .command(commandArgs:_*) .redirectOutput(ProcessBuilder.Redirect.PIPE) .redirectError(ProcessBuilder.Redirect.PIPE) .start() diff --git a/scalaplugin/src/main/scala/mill/scalaplugin/ScalaModule.scala b/scalaplugin/src/main/scala/mill/scalaplugin/ScalaModule.scala index 49d9660f..0686f131 100644 --- a/scalaplugin/src/main/scala/mill/scalaplugin/ScalaModule.scala +++ b/scalaplugin/src/main/scala/mill/scalaplugin/ScalaModule.scala @@ -34,6 +34,7 @@ object ScalaModule{ } var scalaInstanceCache = Option.empty[(Long, ScalaInstance)] + def compileScala(scalaVersion: String, sources: Seq[Path], compileClasspath: Seq[Path], @@ -52,11 +53,10 @@ object ScalaModule{ } val compilerJars = compilerClasspath.toArray.map(_.toIO) - def binaryScalaVersion = scalaVersion.split('.').dropRight(1).mkString(".") - val compilerBridgeJar = new java.io.File( - s"bridge/${scalaVersion.replace('.', '_')}/target/scala-$binaryScalaVersion/mill-bridge_$scalaVersion-0.1-SNAPSHOT.jar" -// s"out/bridges/$scalaVersion/compile/classes" - ) + val compilerBridgeKey = "MILL_COMPILER_BRIDGE_"+scalaVersion.replace('.', '_') + val compilerBridgePath = sys.props(compilerBridgeKey) + assert(compilerBridgePath != null, "Cannot find compiler bridge " + compilerBridgeKey) + val compilerBridgeJar = new java.io.File(compilerBridgePath) val classloaderSig = compilerClasspath.map(p => p.toString().hashCode + p.mtime.toMillis).sum @@ -184,14 +184,15 @@ trait TestScalaModule extends ScalaModule with TaskModule { def testFramework: T[String] def forkWorkingDir = ammonite.ops.pwd + def forkArgs = T{ Seq.empty[String] } def forkTest(args: String*) = T.command{ val outputPath = tmp.dir()/"out.json" + Jvm.subprocess( - "mill.scalaplugin.TestRunner", - getClass.getClassLoader.asInstanceOf[URLClassLoader].getURLs.toList.map( - u => Path(new java.io.File(u.toURI)) - ), - Seq( + mainClass = "mill.scalaplugin.TestRunner", + classPath = Jvm.gatherClassloaderJars(), + jvmOptions = forkArgs(), + options = Seq( testFramework(), (runDepClasspath().map(_.path) :+ compile().path).mkString(" "), Seq(compile().path).mkString(" "), diff --git a/scalaplugin/src/main/scala/mill/scalaplugin/TestRunner.scala b/scalaplugin/src/main/scala/mill/scalaplugin/TestRunner.scala index 43e15974..bc36d9c7 100644 --- a/scalaplugin/src/main/scala/mill/scalaplugin/TestRunner.scala +++ b/scalaplugin/src/main/scala/mill/scalaplugin/TestRunner.scala @@ -50,6 +50,11 @@ object TestRunner { }) val outputPath = args(4) ammonite.ops.write(Path(outputPath), upickle.default.write(result)) + + // Tests are over, kill the JVM whether or not anyone's threads are still running + // Always return 0, even if tests fail. The caller can pick up the detailed test + // results from the outputPath + System.exit(0) } def apply(frameworkName: String, entireClasspath: Seq[Path], -- cgit v1.2.3