summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLi Haoyi <haoyi.sg@gmail.com>2018-07-21 21:05:37 +0800
committerLi Haoyi <haoyi.sg@gmail.com>2018-07-21 21:33:19 +0800
commit57f4968e8c37c857e4c8d6cac80dca739186754d (patch)
treeba15196f038511169bf0062405506e08219f368e
parent5c0016ebbda6ec74db2bdde92a6afca42fe6d076 (diff)
downloadmill-57f4968e8c37c857e4c8d6cac80dca739186754d.tar.gz
mill-57f4968e8c37c857e4c8d6cac80dca739186754d.tar.bz2
mill-57f4968e8c37c857e4c8d6cac80dca739186754d.zip
add JavaModule#runBackground, to allow a task to kick off processes to run in the background that only die when the task is re-run
-rwxr-xr-xbuild.sc22
-rw-r--r--core/src/mill/define/Task.scala2
-rw-r--r--main/src/mill/modules/Jvm.scala32
-rw-r--r--main/src/mill/modules/Util.scala5
-rw-r--r--scalalib/backgroundwrapper/src/mill/scalalib/backgroundwrapper/BackgroundWrapper.java40
-rw-r--r--scalalib/src/mill/scalalib/JavaModule.scala61
-rw-r--r--scalalib/src/mill/scalalib/ScalaWorkerApi.scala7
7 files changed, 150 insertions, 19 deletions
diff --git a/build.sc b/build.sc
index cc5115fa..f12c95ed 100755
--- a/build.sc
+++ b/build.sc
@@ -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,