diff options
-rwxr-xr-x | build.sc | 22 | ||||
-rw-r--r-- | core/src/mill/define/Task.scala | 2 | ||||
-rw-r--r-- | main/src/mill/modules/Jvm.scala | 32 | ||||
-rw-r--r-- | main/src/mill/modules/Util.scala | 5 | ||||
-rw-r--r-- | scalalib/backgroundwrapper/src/mill/scalalib/backgroundwrapper/BackgroundWrapper.java | 40 | ||||
-rw-r--r-- | scalalib/src/mill/scalalib/JavaModule.scala | 61 | ||||
-rw-r--r-- | scalalib/src/mill/scalalib/ScalaWorkerApi.scala | 7 |
7 files changed, 150 insertions, 19 deletions
@@ -167,7 +167,16 @@ object scalalib extends MillModule { "-DMILL_SCALA_LIB=" + runClasspath().map(_.path).mkString(",") ) } - + object backgroundwrapper extends MillPublishModule{ + def ivyDeps = Agg( + ivy"org.scala-sbt:test-interface:1.0" + ) + def testArgs = T{ + Seq( + "-DMILL_BACKGROUNDWRAPPER=" + runClasspath().map(_.path).mkString(",") + ) + } + } object worker extends MillModule{ def moduleDeps = Seq(main, scalalib) @@ -190,7 +199,10 @@ object scalajslib extends MillModule { "MILL_SCALAJS_BRIDGE_0_6" -> jsbridges("0.6").compile().classes.path, "MILL_SCALAJS_BRIDGE_1_0" -> jsbridges("1.0").compile().classes.path ) - Seq("-Djna.nosys=true") ++ scalalib.worker.testArgs() ++ (for((k, v) <- mapping.toSeq) yield s"-D$k=$v") + Seq("-Djna.nosys=true") ++ + scalalib.worker.testArgs() ++ + scalalib.backgroundwrapper.testArgs() ++ + (for((k, v) <- mapping.toSeq) yield s"-D$k=$v") } object jsbridges extends Cross[JsBridgeModule]("0.6", "1.0") @@ -232,7 +244,9 @@ object scalanativelib extends MillModule { .filter(_.toIO.exists) .mkString(",") ) - scalalib.worker.testArgs() ++ (for((k, v) <- mapping.toSeq) yield s"-D$k=$v") + scalalib.worker.testArgs() ++ + scalalib.backgroundwrapper.testArgs() ++ + (for((k, v) <- mapping.toSeq) yield s"-D$k=$v") } object scalanativebridges extends Cross[ScalaNativeBridgeModule]("0.3") @@ -276,6 +290,7 @@ object integration extends MillModule{ def testArgs = T{ scalajslib.testArgs() ++ scalalib.worker.testArgs() ++ + scalalib.backgroundwrapper.testArgs() ++ scalanativelib.testArgs() ++ Seq( "-DMILL_TESTNG=" + testng.runClasspath().map(_.path).mkString(","), @@ -332,6 +347,7 @@ object dev extends MillModule{ scalajslib.testArgs() ++ scalalib.worker.testArgs() ++ scalanativelib.testArgs() ++ + scalalib.backgroundwrapper.testArgs() ++ // Workaround for Zinc/JNA bug // https://github.com/sbt/sbt/blame/6718803ee6023ab041b045a6988fafcfae9d15b5/main/src/main/scala/sbt/Main.scala#L130 Seq( diff --git a/core/src/mill/define/Task.scala b/core/src/mill/define/Task.scala index 63de11f6..d5f8680e 100644 --- a/core/src/mill/define/Task.scala +++ b/core/src/mill/define/Task.scala @@ -39,7 +39,6 @@ abstract class Task[+T] extends Task.Ops[T] with Applyable[Task, T]{ def asTarget: Option[Target[T]] = None def asCommand: Option[Command[T]] = None - def asPersistent: Option[Persistent[T]] = None def asWorker: Option[Worker[T]] = None def self = this } @@ -293,7 +292,6 @@ class Persistent[+T](t: Task[T], extends TargetImpl[T](t, ctx0, readWrite) { override def flushDest = false - override def asPersistent = Some(this) } class Input[T](t: Task[T], ctx0: mill.define.Ctx, diff --git a/main/src/mill/modules/Jvm.scala b/main/src/mill/modules/Jvm.scala index be683e4a..3a6dcb9f 100644 --- a/main/src/mill/modules/Jvm.scala +++ b/main/src/mill/modules/Jvm.scala @@ -26,20 +26,30 @@ object Jvm { jvmArgs: Seq[String] = Seq.empty, envArgs: Map[String, String] = Map.empty, mainArgs: Seq[String] = Seq.empty, - workingDir: Path = null): Unit = { - baseInteractiveSubprocess( + workingDir: Path = null, + background: Boolean = false): Unit = { + val args = Vector("java") ++ - jvmArgs ++ - Vector("-cp", classPath.mkString(File.pathSeparator), mainClass) ++ - mainArgs, - envArgs, - workingDir - ) + jvmArgs ++ + Vector("-cp", classPath.mkString(File.pathSeparator), mainClass) ++ + mainArgs + + if (background) baseInteractiveSubprocess0(args, envArgs, workingDir) + else baseInteractiveSubprocess(args, envArgs, workingDir) } def baseInteractiveSubprocess(commandArgs: Seq[String], envArgs: Map[String, String], workingDir: Path) = { + val process = baseInteractiveSubprocess0(commandArgs, envArgs, workingDir) + + val exitCode = process.waitFor() + if (exitCode == 0) () + else throw InteractiveShelloutException() + } + def baseInteractiveSubprocess0(commandArgs: Seq[String], + envArgs: Map[String, String], + workingDir: Path) = { val builder = new java.lang.ProcessBuilder() for ((k, v) <- envArgs){ @@ -48,7 +58,7 @@ object Jvm { } builder.directory(workingDir.toIO) - val process = if (System.in.isInstanceOf[ByteArrayInputStream]){ + if (System.in.isInstanceOf[ByteArrayInputStream]){ val process = builder .command(commandArgs:_*) @@ -71,11 +81,9 @@ object Jvm { .start() } - val exitCode = process.waitFor() - if (exitCode == 0) () - else throw InteractiveShelloutException() } + def runLocal(mainClass: String, classPath: Agg[Path], mainArgs: Seq[String] = Seq.empty) diff --git a/main/src/mill/modules/Util.scala b/main/src/mill/modules/Util.scala index da7407d0..2d71c13e 100644 --- a/main/src/mill/modules/Util.scala +++ b/main/src/mill/modules/Util.scala @@ -59,7 +59,8 @@ object Util { def millProjectModule(key: String, artifact: String, repositories: Seq[Repository], - resolveFilter: Path => Boolean = _ => true) = { + resolveFilter: Path => Boolean = _ => true, + artifactSuffix: String = "_2.12") = { val localPath = sys.props(key) if (localPath != null) { mill.eval.Result.Success( @@ -70,7 +71,7 @@ object Util { repositories, Seq( coursier.Dependency( - coursier.Module("com.lihaoyi", artifact + "_2.12"), + coursier.Module("com.lihaoyi", artifact + artifactSuffix), sys.props("MILL_VERSION") ) ), diff --git a/scalalib/backgroundwrapper/src/mill/scalalib/backgroundwrapper/BackgroundWrapper.java b/scalalib/backgroundwrapper/src/mill/scalalib/backgroundwrapper/BackgroundWrapper.java new file mode 100644 index 00000000..64719b53 --- /dev/null +++ b/scalalib/backgroundwrapper/src/mill/scalalib/backgroundwrapper/BackgroundWrapper.java @@ -0,0 +1,40 @@ +package mill.scalalib.backgroundwrapper; + +public class BackgroundWrapper { + public static void main(String[] args) throws Exception{ + String watched = args[0]; + String tombstone = args[1]; + String expected = args[2]; + Thread watcher = new Thread(new Runnable() { + @Override + public void run() { + while (true) { + try{ + Thread.sleep(10); + String token = new String( + java.nio.file.Files.readAllBytes(java.nio.file.Paths.get(watched)) + ); + if (!token.equals(expected)) { + new java.io.File(tombstone).createNewFile(); + System.exit(0); + } + }catch(Exception e){ + try { + new java.io.File(tombstone).createNewFile(); + }catch(Exception e2){} + System.exit(0); + } + + } + } + }); + watcher.setDaemon(true); + watcher.start(); + String realMain = args[3]; + String[] realArgs = new String[args.length - 4]; + for(int i = 0; i < args.length-4; i++){ + realArgs[i] = args[i+4]; + } + Class.forName(realMain).getMethod("main", String[].class).invoke(null, (Object)realArgs); + } +} diff --git a/scalalib/src/mill/scalalib/JavaModule.scala b/scalalib/src/mill/scalalib/JavaModule.scala index 3907b6a6..87dfb1b9 100644 --- a/scalalib/src/mill/scalalib/JavaModule.scala +++ b/scalalib/src/mill/scalalib/JavaModule.scala @@ -274,6 +274,67 @@ trait JavaModule extends mill.Module with TaskModule { outer => } } + private[this] def backgroundSetup(dest: Path) = { + val token = java.util.UUID.randomUUID().toString + val procId = dest / ".mill-background-process-id" + val procTombstone = dest / ".mill-background-process-tombstone" + // The backgrounded subprocesses poll the procId file, and kill themselves + // when the procId file is deleted. This deletion happens immediately before + // the body of these commands run, but we cannot be sure the subprocess has + // had time to notice. + // + // To make sure we wait for the previous subprocess to + // die, we make the subprocess write a tombstone file out when it kills + // itself due to procId being deleted, and we wait a short time on task-start + // to see if such a tombstone appears. If a tombstone appears, we can be sure + // the subprocess has killed itself, and can continue. If a tombstone doesn't + // appear in a short amount of time, we assume the subprocess exited or was + // killed via some other means, and continue anyway. + val start = System.currentTimeMillis() + while({ + if (exists(procTombstone)) { + Thread.sleep(10) + rm(procTombstone) + true + } else { + Thread.sleep(10) + System.currentTimeMillis() - start < 100 + } + })() + + write(procId, token) + write(procTombstone, token) + (procId, procTombstone, token) + } + def runBackground(args: String*) = T.command{ + val (procId, procTombstone, token) = backgroundSetup(T.ctx().dest) + try Result.Success(Jvm.interactiveSubprocess( + "mill.scalalib.backgroundwrapper.BackgroundWrapper", + (runClasspath() ++ scalaWorker.backgroundWrapperClasspath()).map(_.path), + forkArgs(), + forkEnv(), + Seq(procId.toString, procTombstone.toString, token, finalMainClass()) ++ args, + workingDir = ammonite.ops.pwd, + background = true + )) catch { case e: InteractiveShelloutException => + Result.Failure("subprocess failed") + } + } + + def runMainBackground(mainClass: String, args: String*) = T.command{ + val (procId, procTombstone, token) = backgroundSetup(T.ctx().dest) + try Result.Success(Jvm.interactiveSubprocess( + "mill.scalalib.backgroundwrapper.BackgroundWrapper", + (runClasspath() ++ scalaWorker.backgroundWrapperClasspath()).map(_.path), + forkArgs(), + forkEnv(), + Seq(procId.toString, procTombstone.toString, token, mainClass) ++ args, + workingDir = ammonite.ops.pwd, + background = true + )) catch { case e: InteractiveShelloutException => + Result.Failure("subprocess failed") + } + } def runMainLocal(mainClass: String, args: String*) = T.command { Jvm.runLocal( diff --git a/scalalib/src/mill/scalalib/ScalaWorkerApi.scala b/scalalib/src/mill/scalalib/ScalaWorkerApi.scala index 6865c541..3d1a195a 100644 --- a/scalalib/src/mill/scalalib/ScalaWorkerApi.scala +++ b/scalalib/src/mill/scalalib/ScalaWorkerApi.scala @@ -29,6 +29,13 @@ trait ScalaWorkerModule extends mill.Module{ mill.modules.Util.millProjectModule("MILL_SCALA_LIB", "mill-scalalib", repositories) } + def backgroundWrapperClasspath = T{ + mill.modules.Util.millProjectModule( + "MILL_BACKGROUNDWRAPPER", "mill-scalalib-backgroundwrapper", + repositories, artifactSuffix = "" + ) + } + def worker: Worker[ScalaWorkerApi] = T.worker{ val cl = mill.util.ClassLoader.create( classpath().map(_.path.toNIO.toUri.toURL).toVector, |