summaryrefslogtreecommitdiff
path: root/main/src
diff options
context:
space:
mode:
Diffstat (limited to 'main/src')
-rw-r--r--main/src/mill/Main.scala20
-rw-r--r--main/src/mill/main/MainModule.scala6
-rw-r--r--main/src/mill/main/Server.scala210
-rw-r--r--main/src/mill/modules/Jvm.scala62
4 files changed, 258 insertions, 40 deletions
diff --git a/main/src/mill/Main.scala b/main/src/mill/Main.scala
index c9ec00ca..a349321e 100644
--- a/main/src/mill/Main.scala
+++ b/main/src/mill/Main.scala
@@ -8,27 +8,11 @@ import ammonite.main.Cli._
import ammonite.ops._
import ammonite.util.Util
import io.github.retronym.java9rtexport.Export
+import mill.client.ClientServer
import mill.eval.Evaluator
import mill.util.DummyInputStream
-object ServerMain extends mill.clientserver.ServerMain[Evaluator.State]{
- def main0(args: Array[String],
- stateCache: Option[Evaluator.State],
- mainInteractive: Boolean,
- stdin: InputStream,
- stdout: PrintStream,
- stderr: PrintStream,
- env : Map[String, String]) = Main.main0(
- args,
- stateCache,
- mainInteractive,
- DummyInputStream,
- stdout,
- stderr,
- env
- )
-}
object Main {
def main(args: Array[String]): Unit = {
@@ -126,7 +110,7 @@ object Main {
env
)
- if (mill.clientserver.ClientServer.isJava9OrAbove) {
+ if (ClientServer.isJava9OrAbove) {
val rt = cliConfig.home / Export.rtJarName
if (!exists(rt)) {
runner.printInfo(s"Preparing Java ${System.getProperty("java.version")} runtime; this may take a minute or two ...")
diff --git a/main/src/mill/main/MainModule.scala b/main/src/mill/main/MainModule.scala
index 7c84f74a..32281407 100644
--- a/main/src/mill/main/MainModule.scala
+++ b/main/src/mill/main/MainModule.scala
@@ -30,7 +30,11 @@ trait MainModule extends mill.Module{
implicit def millDiscover: mill.define.Discover[_]
implicit def millScoptTasksReads[T] = new mill.main.Tasks.Scopt[T]()
implicit def millScoptEvaluatorReads[T] = new mill.main.EvaluatorScopt[T]()
-
+ def version() = mill.T.command {
+ val res = System.getProperty("MILL_VERSION")
+ println(res)
+ res
+ }
/**
* Resolves a mill query string and prints out the tasks it resolves to.
*/
diff --git a/main/src/mill/main/Server.scala b/main/src/mill/main/Server.scala
new file mode 100644
index 00000000..14aade4c
--- /dev/null
+++ b/main/src/mill/main/Server.scala
@@ -0,0 +1,210 @@
+package mill.main
+
+import java.io._
+import java.net.Socket
+
+import mill.Main
+import scala.collection.JavaConverters._
+import org.scalasbt.ipcsocket._
+import mill.client._
+import mill.eval.Evaluator
+import mill.util.DummyInputStream
+
+trait ServerMain[T]{
+ var stateCache = Option.empty[T]
+ def main0(args: Array[String],
+ stateCache: Option[T],
+ mainInteractive: Boolean,
+ stdin: InputStream,
+ stdout: PrintStream,
+ stderr: PrintStream,
+ env : Map[String, String]): (Boolean, Option[T])
+}
+
+object ServerMain extends mill.main.ServerMain[Evaluator.State]{
+ def main(args0: Array[String]): Unit = {
+ new Server(
+ args0(0),
+ this,
+ () => System.exit(0),
+ 300000,
+ mill.client.Locks.files(args0(0))
+ ).run()
+ }
+ def main0(args: Array[String],
+ stateCache: Option[Evaluator.State],
+ mainInteractive: Boolean,
+ stdin: InputStream,
+ stdout: PrintStream,
+ stderr: PrintStream,
+ env : Map[String, String]) = Main.main0(
+ args,
+ stateCache,
+ mainInteractive,
+ DummyInputStream,
+ stdout,
+ stderr,
+ env
+ )
+}
+
+
+class Server[T](lockBase: String,
+ sm: ServerMain[T],
+ interruptServer: () => Unit,
+ acceptTimeout: Int,
+ locks: Locks) {
+
+ val originalStdout = System.out
+ def run() = {
+ Server.tryLockBlock(locks.processLock){
+ var running = true
+ while (running) {
+ Server.lockBlock(locks.serverLock){
+ val (serverSocket, socketClose) = if (ClientServer.isWindows) {
+ val socketName = ClientServer.WIN32_PIPE_PREFIX + new File(lockBase).getName
+ (new Win32NamedPipeServerSocket(socketName), () => new Win32NamedPipeSocket(socketName).close())
+ } else {
+ val socketName = lockBase + "/io"
+ new File(socketName).delete()
+ (new UnixDomainServerSocket(socketName), () => new UnixDomainSocket(socketName).close())
+ }
+
+ val sockOpt = Server.interruptWith(
+ acceptTimeout,
+ socketClose(),
+ serverSocket.accept()
+ )
+
+ sockOpt match{
+ case None => running = false
+ case Some(sock) =>
+ try {
+ handleRun(sock)
+ serverSocket.close()
+ }
+ catch{case e: Throwable => e.printStackTrace(originalStdout) }
+ }
+ }
+ // Make sure you give an opportunity for the client to probe the lock
+ // and realize the server has released it to signal completion
+ Thread.sleep(10)
+ }
+ }.getOrElse(throw new Exception("PID already present"))
+ }
+
+ def handleRun(clientSocket: Socket) = {
+
+ val currentOutErr = clientSocket.getOutputStream
+ val socketIn = clientSocket.getInputStream
+ val argStream = new FileInputStream(lockBase + "/run")
+ val interactive = argStream.read() != 0;
+ val args = ClientServer.parseArgs(argStream)
+ val env = ClientServer.parseMap(argStream)
+ argStream.close()
+
+ var done = false
+ val t = new Thread(() =>
+
+ try {
+ val stdout = new PrintStream(new ProxyOutputStream(currentOutErr, 0), true)
+ val stderr = new PrintStream(new ProxyOutputStream(currentOutErr, 1), true)
+ val (result, newStateCache) = sm.main0(
+ args,
+ sm.stateCache,
+ interactive,
+ socketIn,
+ stdout,
+ stderr,
+ env.asScala.toMap
+ )
+
+ sm.stateCache = newStateCache
+ java.nio.file.Files.write(
+ java.nio.file.Paths.get(lockBase + "/exitCode"),
+ (if (result) 0 else 1).toString.getBytes
+ )
+ } catch{case WatchInterrupted(sc: Option[T]) =>
+ sm.stateCache = sc
+ } finally{
+ done = true
+ }
+ )
+
+ t.start()
+ // We cannot simply use Lock#await here, because the filesystem doesn't
+ // realize the clientLock/serverLock are held by different threads in the
+ // two processes and gives a spurious deadlock error
+ while(!done && !locks.clientLock.probe()) {
+ Thread.sleep(3)
+ }
+
+ if (!done) interruptServer()
+
+ t.interrupt()
+ t.stop()
+
+ if (ClientServer.isWindows) {
+ // Closing Win32NamedPipeSocket can often take ~5s
+ // It seems OK to exit the client early and subsequently
+ // start up mill client again (perhaps closing the server
+ // socket helps speed up the process).
+ val t = new Thread(() => clientSocket.close())
+ t.setDaemon(true)
+ t.start()
+ } else clientSocket.close()
+ }
+}
+object Server{
+ def lockBlock[T](lock: Lock)(t: => T): T = {
+ val l = lock.lock()
+ try t
+ finally l.release()
+ }
+ def tryLockBlock[T](lock: Lock)(t: => T): Option[T] = {
+ lock.tryLock() match{
+ case null => None
+ case l =>
+ try Some(t)
+ finally l.release()
+ }
+
+ }
+ def interruptWith[T](millis: Int, close: => Unit, t: => T): Option[T] = {
+ @volatile var interrupt = true
+ @volatile var interrupted = false
+ new Thread(() => {
+ Thread.sleep(millis)
+ if (interrupt) {
+ interrupted = true
+ close
+ }
+ }).start()
+
+ try {
+ val res =
+ try Some(t)
+ catch {case e: Throwable => None}
+
+ if (interrupted) None
+ else res
+
+ } finally {
+ interrupt = false
+ }
+ }
+}
+
+class ProxyOutputStream(x: => java.io.OutputStream,
+ key: Int) extends java.io.OutputStream {
+ override def write(b: Int) = x.synchronized{
+ x.write(key)
+ x.write(b)
+ }
+}
+class ProxyInputStream(x: => java.io.InputStream) extends java.io.InputStream{
+ def read() = x.read()
+ override def read(b: Array[Byte], off: Int, len: Int) = x.read(b, off, len)
+ override def read(b: Array[Byte]) = x.read(b)
+}
+case class WatchInterrupted[T](stateCache: Option[T]) extends Exception
diff --git a/main/src/mill/modules/Jvm.scala b/main/src/mill/modules/Jvm.scala
index 92469988..e7fd6a79 100644
--- a/main/src/mill/modules/Jvm.scala
+++ b/main/src/mill/modules/Jvm.scala
@@ -1,6 +1,6 @@
package mill.modules
-import java.io.{ByteArrayInputStream, FileOutputStream, File}
+import java.io.{ByteArrayInputStream, File, FileOutputStream}
import java.lang.reflect.Modifier
import java.net.{URI, URLClassLoader}
import java.nio.file.{FileSystems, Files, OpenOption, StandardOpenOption}
@@ -9,7 +9,7 @@ import java.util.jar.{JarEntry, JarFile, JarOutputStream}
import ammonite.ops._
import geny.Generator
-import mill.clientserver.InputPumper
+import mill.client.InputPumper
import mill.eval.PathRef
import mill.util.{Ctx, IO}
import mill.util.Loose.Agg
@@ -277,7 +277,7 @@ object Jvm {
// Prepend shell script and make it executable
if (prependShellScript.isEmpty) mv(tmp, output)
else{
- val lineSep = if (isWin) "\r\n" else "\n"
+ val lineSep = if (!prependShellScript.endsWith("\n")) "\n\r\n" else ""
val outputStream = newOutputStream(output.toNIO)
IO.stream(new ByteArrayInputStream((prependShellScript + lineSep).getBytes()), outputStream)
IO.stream(read.getInputStream(tmp), outputStream)
@@ -319,30 +319,50 @@ object Jvm {
}
- def launcherShellScript(isWin: Boolean,
- mainClass: String,
- classPath: Agg[String],
- jvmArgs: Seq[String]) = {
- val cp = classPath.mkString(File.pathSeparator)
- if (isWin)
- s"""@echo off
- |
- |java ${jvmArgs.mkString(" ")} %JAVA_OPTS% -cp "$cp" $mainClass %*
- """.stripMargin.split('\n').mkString("\r\n")
- else
- s"""#!/usr/bin/env sh
- |
- |exec java ${jvmArgs.mkString(" ")} $$JAVA_OPTS -cp "$cp" $mainClass "$$@"
- """.stripMargin
+ def universalScript(shellCommands: String,
+ cmdCommands: String,
+ shebang: Boolean = false): String = {
+ Seq(
+ if (shebang) "#!/usr/bin/env sh" else "",
+ "@ 2>/dev/null # 2>nul & echo off & goto BOF\r",
+ ":",
+ shellCommands.replaceAll("\r\n|\n", "\n"),
+ "exit",
+ Seq(
+ "",
+ ":BOF",
+ "@echo off",
+ cmdCommands.replaceAll("\r\n|\n", "\r\n"),
+ "exit /B %errorlevel%",
+ ""
+ ).mkString("\r\n")
+ ).filterNot(_.isEmpty).mkString("\n")
+ }
+
+ def launcherUniversalScript(mainClass: String,
+ shellClassPath: Agg[String],
+ cmdClassPath: Agg[String],
+ jvmArgs: Seq[String]) = {
+ universalScript(
+ shellCommands =
+ s"""exec java ${jvmArgs.mkString(" ")} $$JAVA_OPTS -cp "${shellClassPath.mkString(":")}" $mainClass "$$@"""",
+ cmdCommands =
+ s"""java ${jvmArgs.mkString(" ")} %JAVA_OPTS% -cp "${cmdClassPath.mkString(";")}" $mainClass %*""",
+ )
}
def createLauncher(mainClass: String,
classPath: Agg[Path],
jvmArgs: Seq[String])
(implicit ctx: Ctx.Dest)= {
val isWin = scala.util.Properties.isWin
- val outputPath = ctx.dest / (if (isWin) "run.bat" else "run")
-
- write(outputPath, launcherShellScript(isWin, mainClass, classPath.map(_.toString), jvmArgs))
+ val isBatch = isWin &&
+ !(org.jline.utils.OSUtils.IS_CYGWIN
+ || org.jline.utils.OSUtils.IS_MINGW
+ || "MSYS" == System.getProperty("MSYSTEM"))
+ val outputPath = ctx.dest / (if (isBatch) "run.bat" else "run")
+ val classPathStrs = classPath.map(_.toString)
+
+ write(outputPath, launcherUniversalScript(mainClass, classPathStrs, classPathStrs, jvmArgs))
if (!isWin) {
val perms = Files.getPosixFilePermissions(outputPath.toNIO)