summaryrefslogtreecommitdiff
path: root/scalalib/worker/src
diff options
context:
space:
mode:
authorLi Haoyi <haoyi.sg@gmail.com>2018-04-12 21:24:18 -0700
committerLi Haoyi <haoyi.sg@gmail.com>2018-04-12 22:08:03 -0700
commit2bc041237984bf674ea144ad1a14710c3ed2e47c (patch)
tree1ac405a266c03fdf4c406fd6cf8d23ea7ea09a05 /scalalib/worker/src
parent948b697aa85ccd6062ce1d001703e1c428cfa397 (diff)
downloadmill-2bc041237984bf674ea144ad1a14710c3ed2e47c.tar.gz
mill-2bc041237984bf674ea144ad1a14710c3ed2e47c.tar.bz2
mill-2bc041237984bf674ea144ad1a14710c3ed2e47c.zip
rename modules scalaworker -> scalalib.worker, client -> main.client
Diffstat (limited to 'scalalib/worker/src')
-rw-r--r--scalalib/worker/src/mill/scalalib/worker/ScalaWorker.scala254
1 files changed, 254 insertions, 0 deletions
diff --git a/scalalib/worker/src/mill/scalalib/worker/ScalaWorker.scala b/scalalib/worker/src/mill/scalalib/worker/ScalaWorker.scala
new file mode 100644
index 00000000..cebef9b7
--- /dev/null
+++ b/scalalib/worker/src/mill/scalalib/worker/ScalaWorker.scala
@@ -0,0 +1,254 @@
+package mill.scalalib.worker
+
+import java.io.File
+import java.util.Optional
+
+import ammonite.ops.{Path, exists, ls, mkdir}
+import ammonite.util.Colors
+import mill.Agg
+import mill.eval.PathRef
+import mill.scalalib.{CompilationResult, Lib, TestRunner}
+import xsbti.compile.{CompilerCache => _, FileAnalysisStore => _, ScalaInstance => _, _}
+import mill.scalalib.Lib.grepJar
+import mill.util.{Ctx, PrintLogger}
+import sbt.internal.inc._
+import sbt.internal.util.{ConsoleOut, MainAppender}
+import sbt.util.LogExchange
+
+case class MockedLookup(am: File => Optional[CompileAnalysis]) extends PerClasspathEntryLookup {
+ override def analysis(classpathEntry: File): Optional[CompileAnalysis] =
+ am(classpathEntry)
+
+ override def definesClass(classpathEntry: File): DefinesClass =
+ Locate.definesClass(classpathEntry)
+}
+
+object ScalaWorker{
+
+ def main(args: Array[String]): Unit = {
+ try{
+ var i = 0
+ def readArray() = {
+ val count = args(i).toInt
+ val slice = args.slice(i + 1, i + count + 1)
+ i = i + count + 1
+ slice
+ }
+ val frameworks = readArray()
+ val classpath = readArray()
+ val arguments = readArray()
+ val outputPath = args(i + 0)
+ val colored = args(i + 1)
+ val testCp = args(i + 2)
+ val homeStr = args(i + 3)
+ val ctx = new Ctx.Log with Ctx.Home {
+ val log = PrintLogger(
+ colored == "true",
+ if(colored == "true") Colors.Default
+ else Colors.BlackWhite,
+ System.out,
+ System.err,
+ System.err,
+ System.in
+ )
+ val home = Path(homeStr)
+ }
+ val result = Lib.runTests(
+ frameworkInstances = TestRunner.frameworks(frameworks),
+ entireClasspath = Agg.from(classpath.map(Path(_))),
+ testClassfilePath = Agg(Path(testCp)),
+ args = arguments
+ )(ctx)
+
+ // Clear interrupted state in case some badly-behaved test suite
+ // dirtied the thread-interrupted flag and forgot to clean up. Otherwise
+ // that flag causes writing the results to disk to fail
+ Thread.interrupted()
+ ammonite.ops.write(Path(outputPath), upickle.default.write(result))
+ }catch{case e: Throwable =>
+ println(e)
+ e.printStackTrace()
+ }
+ // 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)
+ }
+}
+
+class ScalaWorker(ctx0: mill.util.Ctx,
+ compilerBridgeClasspath: Array[String]) extends mill.scalalib.ScalaWorkerApi{
+ @volatile var scalaClassloaderCache = Option.empty[(Long, ClassLoader)]
+ @volatile var scalaInstanceCache = Option.empty[(Long, ScalaInstance)]
+
+ def compileZincBridge(scalaVersion: String,
+ sourcesJar: Path,
+ compilerJars: Array[File]) = {
+ val workingDir = ctx0.dest / scalaVersion
+ val compiledDest = workingDir / 'compiled
+ if (!exists(workingDir)) {
+
+ println("Compiling compiler interface...")
+
+ mkdir(workingDir)
+ mkdir(compiledDest)
+
+ val sourceFolder = mill.modules.Util.unpackZip(sourcesJar)(workingDir)
+ val classloader = mill.util.ClassLoader.create(compilerJars.map(_.toURI.toURL), null)(ctx0)
+ val scalacMain = classloader.loadClass("scala.tools.nsc.Main")
+ val argsArray = Array[String](
+ "-d", compiledDest.toString,
+ "-classpath", (compilerJars ++ compilerBridgeClasspath).mkString(File.pathSeparator)
+ ) ++ ls.rec(sourceFolder.path).filter(_.ext == "scala").map(_.toString)
+
+ scalacMain.getMethods
+ .find(_.getName == "process")
+ .get
+ .invoke(null, argsArray)
+ }
+
+ compiledDest
+ }
+
+
+
+ def discoverMainClasses(compilationResult: CompilationResult)(implicit ctx: mill.util.Ctx): Seq[String] = {
+ def toScala[A](o: Optional[A]): Option[A] = if (o.isPresent) Some(o.get) else None
+
+ toScala(FileAnalysisStore.binary(compilationResult.analysisFile.toIO).get())
+ .map(_.getAnalysis)
+ .flatMap{
+ case analysis: Analysis =>
+ Some(analysis.infos.allInfos.values.map(_.getMainClasses).flatten.toSeq.sorted)
+ case _ =>
+ None
+ }
+ .getOrElse(Seq.empty[String])
+ }
+
+
+ def compileScala(scalaVersion: String,
+ sources: Agg[Path],
+ compilerBridgeSources: Path,
+ compileClasspath: Agg[Path],
+ compilerClasspath: Agg[Path],
+ scalacOptions: Seq[String],
+ scalacPluginClasspath: Agg[Path],
+ javacOptions: Seq[String],
+ upstreamCompileOutput: Seq[CompilationResult])
+ (implicit ctx: mill.util.Ctx): mill.eval.Result[CompilationResult] = {
+ val compileClasspathFiles = compileClasspath.map(_.toIO).toArray
+ val compilerJars = compilerClasspath.toArray.map(_.toIO)
+
+ val compilerBridge = compileZincBridge(scalaVersion, compilerBridgeSources, compilerJars)
+
+ val pluginJars = scalacPluginClasspath.toArray.map(_.toIO)
+
+ val compilerClassloaderSig = compilerClasspath.map(p => p.toString().hashCode + p.mtime.toMillis).sum
+ val scalaInstanceSig =
+ compilerClassloaderSig + scalacPluginClasspath.map(p => p.toString().hashCode + p.mtime.toMillis).sum
+
+ val compilerClassLoader = scalaClassloaderCache match{
+ case Some((k, v)) if k == compilerClassloaderSig => v
+ case _ =>
+ val classloader = mill.util.ClassLoader.create(compilerJars.map(_.toURI.toURL), null)
+ scalaClassloaderCache = Some((compilerClassloaderSig, classloader))
+ classloader
+ }
+
+ val scalaInstance = scalaInstanceCache match{
+ case Some((k, v)) if k == scalaInstanceSig => v
+ case _ =>
+ val scalaInstance = new ScalaInstance(
+ version = scalaVersion,
+ loader = mill.util.ClassLoader.create(pluginJars.map(_.toURI.toURL), compilerClassLoader),
+ libraryJar = grepJar(compilerClasspath, s"scala-library-$scalaVersion.jar"),
+ compilerJar = grepJar(compilerClasspath, s"scala-compiler-$scalaVersion.jar"),
+ allJars = compilerJars ++ pluginJars,
+ explicitActual = None
+ )
+ scalaInstanceCache = Some((scalaInstanceSig, scalaInstance))
+ scalaInstance
+ }
+
+ mkdir(ctx.dest)
+
+ val ic = new sbt.internal.inc.IncrementalCompilerImpl()
+
+ val logger = {
+ val consoleAppender = MainAppender.defaultScreen(ConsoleOut.printStreamOut(
+ ctx.log.outputStream
+ ))
+ val l = LogExchange.logger("Hello")
+ LogExchange.unbindLoggerAppenders("Hello")
+ LogExchange.bindLoggerAppenders("Hello", (consoleAppender -> sbt.util.Level.Info) :: Nil)
+ l
+ }
+
+ def analysisMap(f: File): Optional[CompileAnalysis] = {
+ if (f.isFile) {
+ Optional.empty[CompileAnalysis]
+ } else {
+ upstreamCompileOutput.collectFirst {
+ case CompilationResult(zincPath, classFiles) if classFiles.path.toNIO == f.toPath =>
+ FileAnalysisStore.binary(zincPath.toIO).get().map[CompileAnalysis](_.getAnalysis)
+ }.getOrElse(Optional.empty[CompileAnalysis])
+ }
+ }
+
+ val lookup = MockedLookup(analysisMap)
+
+ val zincFile = ctx.dest / 'zinc
+ val classesDir = ctx.dest / 'classes
+
+ val zincIOFile = zincFile.toIO
+ val classesIODir = classesDir.toIO
+
+ val store = FileAnalysisStore.binary(zincIOFile)
+
+ try {
+ val newResult = ic.compile(
+ ic.inputs(
+ classpath = classesIODir +: compileClasspathFiles,
+ sources = sources.toArray.map(_.toIO),
+ classesDirectory = classesIODir,
+ scalacOptions = (scalacPluginClasspath.map(jar => s"-Xplugin:${jar}") ++ scalacOptions).toArray,
+ javacOptions = javacOptions.toArray,
+ maxErrors = 10,
+ sourcePositionMappers = Array(),
+ order = CompileOrder.Mixed,
+ compilers = ic.compilers(
+ scalaInstance,
+ ClasspathOptionsUtil.boot,
+ None,
+ ZincUtil.scalaCompiler(scalaInstance, compilerBridge.toIO)
+ ),
+ setup = ic.setup(
+ lookup,
+ skip = false,
+ zincIOFile,
+ new FreshCompilerCache,
+ IncOptions.of(),
+ new ManagedLoggedReporter(10, logger),
+ None,
+ Array()
+ ),
+ pr = {
+ val prev = store.get()
+ PreviousResult.of(prev.map(_.getAnalysis), prev.map(_.getMiniSetup))
+ }
+ ),
+ logger = logger
+ )
+
+ store.set(
+ AnalysisContents.create(
+ newResult.analysis(),
+ newResult.setup()
+ )
+ )
+
+ mill.eval.Result.Success(CompilationResult(zincFile, PathRef(classesDir)))
+ }catch{case e: CompileFailed => mill.eval.Result.Failure(e.toString)}
+ }
+}