Skip to content

Commit

Permalink
Adds envVars propagation client -> server
Browse files Browse the repository at this point in the history
Since Mill now executes in a long-lived JVM, the builds do not have a
chance to use environment variables as inputs. This propagates the
environment variables from the client all the way down to the context
available to the tasks as a `Map[String, String]` so that they can be
used as inputs should the user choose to do so.

com-lihaoyi#257
  • Loading branch information
Baccata committed Mar 29, 2018
1 parent 680364f commit a1c9a22
Show file tree
Hide file tree
Showing 10 changed files with 146 additions and 56 deletions.
1 change: 1 addition & 0 deletions clientserver/src/mill/clientserver/Client.java
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ public static int run(String lockBase,

FileOutputStream f = new FileOutputStream(lockBase + "/run");
ClientServer.writeArgs(System.console() != null, args, f);
ClientServer.writeEnv(f);
f.close();

boolean serverInit = false;
Expand Down
84 changes: 70 additions & 14 deletions clientserver/src/mill/clientserver/ClientServer.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.HashMap;
import java.util.Map;

class ClientServer {
public static boolean isWindows = System.getProperty("os.name").toLowerCase().startsWith("windows");
Expand All @@ -19,23 +21,77 @@ public static String[] parseArgs(InputStream argStream) throws IOException {
int argsLength = argStream.read();
String[] args = new String[argsLength];
for (int i = 0; i < args.length; i++) {
int n = argStream.read();
byte[] arr = new byte[n];
argStream.read(arr);
args[i] = new String(arr);
args[i] = readString(argStream);
}
return args;
}
public static void writeArgs(Boolean interactive,
String[] args,
OutputStream argStream) throws IOException{
argStream.write(interactive ? 1 : 0);
argStream.write(args.length);
int i = 0;
while (i < args.length){
argStream.write(args[i].length());
argStream.write(args[i].getBytes());
i += 1;
OutputStream argStream) throws IOException {
argStream.write(interactive ? 1 : 0);
argStream.write(args.length);
int i = 0;
while (i < args.length) {
writeString(argStream, args[i]);
i += 1;
}
}

/**
* This allows the mill client to pass the environment as he sees it to the
* server (as the server remains alive over the course of several runs and
* does not see the environment changes the client would)
*/
public static void writeEnv(OutputStream argStream) throws IOException {
Map<String, String> env = System.getenv();
argStream.write(env.size());
for (Map.Entry<String, String> kv : env.entrySet()) {
writeString(argStream, kv.getKey());
writeString(argStream, kv.getValue());
}
}
}
}

public static Map<String, String> parseEnv(InputStream argStream) throws IOException {
Map<String, String> env = new HashMap<>();
int mapLength = argStream.read();
for (int i = 0; i < mapLength; i++) {
String key = readString(argStream);
String value = readString(argStream);
env.put(key, value);
}
return env;
}

private static String readString(InputStream inputStream) throws IOException {
// Result is between 0 and 255, hence the loop.
int read = inputStream.read();
int bytesToRead = read;
while(read == 255){
read = inputStream.read();
bytesToRead += read;
}
byte[] arr = new byte[bytesToRead];
int readTotal = 0;
while (readTotal < bytesToRead) {
read = inputStream.read(arr, readTotal, bytesToRead - readTotal);
readTotal += read;
}
return new String(arr);
}

private static void writeString(OutputStream outputStream, String string) throws IOException {
// When written, an int > 255 gets splitted. This logic performs the
// split beforehand so that the reading side knows that there is still
// more metadata to come before it's able to read the actual data.
// Could do with rewriting using logical masks / shifts.
byte[] bytes = string.getBytes();
int toWrite = bytes.length;
while(toWrite >= 255){
outputStream.write(255);
toWrite = toWrite - 255;
}
outputStream.write(toWrite);
outputStream.write(bytes);
}

}
9 changes: 7 additions & 2 deletions clientserver/src/mill/clientserver/Server.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package mill.clientserver
import java.io._
import java.net.Socket

import scala.collection.JavaConverters._
import org.scalasbt.ipcsocket._

trait ServerMain[T]{
Expand All @@ -21,7 +22,8 @@ trait ServerMain[T]{
mainInteractive: Boolean,
stdin: InputStream,
stdout: PrintStream,
stderr: PrintStream): (Boolean, Option[T])
stderr: PrintStream,
env : Map[String, String]): (Boolean, Option[T])
}


Expand Down Expand Up @@ -76,6 +78,7 @@ class Server[T](lockBase: String,
val argStream = new FileInputStream(lockBase + "/run")
val interactive = argStream.read() != 0;
val args = ClientServer.parseArgs(argStream)
val env = ClientServer.parseEnv(argStream)
argStream.close()

var done = false
Expand All @@ -89,7 +92,9 @@ class Server[T](lockBase: String,
sm.stateCache,
interactive,
socketIn,
stdout, stderr
stdout,
stderr,
env.asScala.toMap
)

sm.stateCache = newStateCache
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ class EchoServer extends ServerMain[Int]{
mainInteractive: Boolean,
stdin: InputStream,
stdout: PrintStream,
stderr: PrintStream) = {
stderr: PrintStream,
env: Map[String, String]) = {

val reader = new BufferedReader(new InputStreamReader(stdin))
val str = reader.readLine()
Expand Down
10 changes: 8 additions & 2 deletions core/src/mill/eval/Evaluator.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ package mill.eval

import java.net.URLClassLoader

import scala.collection.JavaConverters._

import mill.util.Router.EntryPoint
import ammonite.ops._
import ammonite.runtime.SpecialClassLoader
Expand Down Expand Up @@ -32,7 +34,8 @@ case class Evaluator[T](home: Path,
rootModule: mill.define.BaseModule,
log: Logger,
classLoaderSig: Seq[(Either[String, Path], Long)] = Evaluator.classLoaderSig,
workerCache: mutable.Map[Segments, (Int, Any)] = mutable.Map.empty){
workerCache: mutable.Map[Segments, (Int, Any)] = mutable.Map.empty,
env : Map[String, String] = Evaluator.defaultEnv){
val classLoaderSignHash = classLoaderSig.hashCode()
def evaluate(goals: Agg[Task[_]]): Evaluator.Results = {
mkdir(outPath)
Expand Down Expand Up @@ -271,7 +274,8 @@ case class Evaluator[T](home: Path,
}
},
multiLogger,
home
home,
env
)

val out = System.out
Expand Down Expand Up @@ -335,6 +339,8 @@ object Evaluator{
// in directly) we are forced to pass it in via a ThreadLocal
val currentEvaluator = new ThreadLocal[mill.eval.Evaluator[_]]

val defaultEnv: Map[String, String] = System.getenv().asScala.toMap

case class Paths(out: Path,
dest: Path,
meta: Path,
Expand Down
3 changes: 2 additions & 1 deletion core/src/mill/util/Ctx.scala
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,8 @@ object Ctx{
class Ctx(val args: IndexedSeq[_],
dest0: () => Path,
val log: Logger,
val home: Path)
val home: Path,
val env : Map[String, String])
extends Ctx.Dest
with Ctx.Log
with Ctx.Args
Expand Down
78 changes: 47 additions & 31 deletions main/src/mill/Main.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,60 +2,66 @@ package mill

import java.io.{InputStream, PrintStream}

import scala.collection.JavaConverters._

import ammonite.main.Cli._
import ammonite.ops._
import ammonite.util.Util
import mill.eval.Evaluator
import mill.util.DummyInputStream


object ServerMain extends mill.clientserver.ServerMain[Evaluator.State]{
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) = Main.main0(
stderr: PrintStream,
env: Map[String, String]) = Main.main0(
args,
stateCache,
mainInteractive,
DummyInputStream,
stdout,
stderr
stderr,
env
)
}
object Main {

def main(args: Array[String]): Unit = {
val as = args match {
case Array(s, _*) if s == "-i" || s == "--interactive" => args.tail
case _ => args
case _ => args
}
val (result, _) = main0(
as,
None,
ammonite.Main.isInteractive(),
System.in,
System.out,
System.err
System.err,
System.getenv().asScala.toMap
)
System.exit(if(result) 0 else 1)
System.exit(if (result) 0 else 1)
}

def main0(args: Array[String],
stateCache: Option[Evaluator.State],
mainInteractive: Boolean,
stdin: InputStream,
stdout: PrintStream,
stderr: PrintStream): (Boolean, Option[Evaluator.State]) = {
stderr: PrintStream,
env : Map[String, String]): (Boolean, Option[Evaluator.State]) = {
import ammonite.main.Cli

val removed = Set("predef-code", "no-home-predef")
var interactive = false
val interactiveSignature = Arg[Config, Unit](
"interactive", Some('i'),
"interactive",
Some('i'),
"Run Mill in interactive mode, suitable for opening REPLs and taking user input",
(c, v) =>{
(c, v) => {
interactive = true
c
}
Expand All @@ -69,37 +75,42 @@ object Main {
args.toList,
millArgSignature,
Cli.Config(home = millHome, remoteLogging = false)
) match{
) match {
case _ if interactive =>
stderr.println("-i/--interactive must be passed in as the first argument")
stderr.println(
"-i/--interactive must be passed in as the first argument")
(false, None)
case Left(msg) =>
stderr.println(msg)
(false, None)
case Right((cliConfig, _)) if cliConfig.help =>
val leftMargin = millArgSignature.map(ammonite.main.Cli.showArg(_).length).max + 2
val leftMargin = millArgSignature
.map(ammonite.main.Cli.showArg(_).length)
.max + 2
stdout.println(
s"""Mill Build Tool
s"""Mill Build Tool
|usage: mill [mill-options] [target [target-options]]
|
|${formatBlock(millArgSignature, leftMargin).mkString(Util.newLine)}""".stripMargin
|${formatBlock(millArgSignature, leftMargin)
.mkString(Util.newLine)}""".stripMargin
)
(true, None)
case Right((cliConfig, leftoverArgs)) =>

val repl = leftoverArgs.isEmpty
if (repl && stdin == DummyInputStream) {
stderr.println("Build repl needs to be run with the -i/--interactive flag")
stderr.println(
"Build repl needs to be run with the -i/--interactive flag")
(false, stateCache)
}else{
} else {
val tqs = "\"\"\""
val config =
if(!repl) cliConfig
else cliConfig.copy(
predefCode =
s"""import $$file.build, build._
if (!repl) cliConfig
else
cliConfig.copy(
predefCode = s"""import $$file.build, build._
|implicit val replApplyHandler = mill.main.ReplApplyHandler(
| ammonite.ops.Path($tqs${cliConfig.home.toIO.getCanonicalPath.replaceAllLiterally("$", "$$")}$tqs),
| ammonite.ops.Path($tqs${cliConfig.home.toIO.getCanonicalPath
.replaceAllLiterally("$", "$$")}$tqs),
| interp.colors(),
| repl.pprinter(),
| build.millSelf.get,
Expand All @@ -109,22 +120,27 @@ object Main {
|import replApplyHandler.generatedEval._
|
""".stripMargin,
welcomeBanner = None
)
welcomeBanner = None
)

val runner = new mill.main.MainRunner(
config.copy(colored = Some(mainInteractive)),
stdout, stderr, stdin,
stateCache
stdout,
stderr,
stdin,
stateCache,
env
)

if (repl){
if (repl) {
runner.printInfo("Loading...")
(runner.watchLoop(isRepl = true, printing = false, _.run()), runner.stateCache)
(runner.watchLoop(isRepl = true, printing = false, _.run()),
runner.stateCache)
} else {
(runner.runScript(pwd / "build.sc", leftoverArgs), runner.stateCache)
(runner.runScript(pwd / "build.sc", leftoverArgs),
runner.stateCache)
}
}
}

}
}
Expand Down
6 changes: 4 additions & 2 deletions main/src/mill/main/MainRunner.scala
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@ class MainRunner(val config: ammonite.main.Cli.Config,
outprintStream: PrintStream,
errPrintStream: PrintStream,
stdIn: InputStream,
stateCache0: Option[Evaluator.State] = None)
stateCache0: Option[Evaluator.State] = None,
env : Map[String, String])
extends ammonite.MainRunner(
config, outprintStream, errPrintStream,
stdIn, outprintStream, errPrintStream
Expand Down Expand Up @@ -75,7 +76,8 @@ class MainRunner(val config: ammonite.main.Cli.Config,
errPrintStream,
errPrintStream,
stdIn
)
),
env
)

result match{
Expand Down
Loading

0 comments on commit a1c9a22

Please sign in to comment.