summaryrefslogtreecommitdiff
path: root/scalalib/worker/src/ZincWorkerImpl.scala
diff options
context:
space:
mode:
Diffstat (limited to 'scalalib/worker/src/ZincWorkerImpl.scala')
-rw-r--r--scalalib/worker/src/ZincWorkerImpl.scala287
1 files changed, 287 insertions, 0 deletions
diff --git a/scalalib/worker/src/ZincWorkerImpl.scala b/scalalib/worker/src/ZincWorkerImpl.scala
new file mode 100644
index 00000000..c37ef162
--- /dev/null
+++ b/scalalib/worker/src/ZincWorkerImpl.scala
@@ -0,0 +1,287 @@
+package mill.scalalib.worker
+
+import java.io.File
+import java.util.Optional
+
+import mill.api.Loose.Agg
+import mill.api.{KeyedLockedCache, PathRef}
+import xsbti.compile.{CompilerCache => _, FileAnalysisStore => _, ScalaInstance => _, _}
+import mill.scalalib.api.Util.{grepJar, isDotty, scalaBinaryVersion}
+import sbt.internal.inc._
+import sbt.internal.util.{ConsoleOut, MainAppender}
+import sbt.util.LogExchange
+import mill.scalalib.api.{CompilationResult, ZincWorkerApi}
+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)
+}
+
+class ZincWorkerImpl(compilerBridge: Either[
+ (ZincWorkerApi.Ctx, Array[os.Path], (String, String) => os.Path),
+ String => os.Path
+ ],
+ libraryJarNameGrep: (Agg[os.Path], String) => os.Path,
+ compilerJarNameGrep: (Agg[os.Path], String) => os.Path,
+ compilerCache: KeyedLockedCache[Compilers])
+ extends ZincWorkerApi{
+ private val ic = new sbt.internal.inc.IncrementalCompilerImpl()
+ lazy val javaOnlyCompilers = {
+ // Keep the classpath as written by the user
+ val classpathOptions = ClasspathOptions.of(false, false, false, false, false)
+
+ val dummyFile = new java.io.File("")
+ // Zinc does not have an entry point for Java-only compilation, so we need
+ // to make up a dummy ScalaCompiler instance.
+ val scalac = ZincUtil.scalaCompiler(
+ new ScalaInstance("", null, null, dummyFile, dummyFile, new Array(0), Some("")), null,
+ classpathOptions // this is used for javac too
+ )
+
+ ic.compilers(
+ instance = null,
+ classpathOptions,
+ None,
+ scalac
+ )
+ }
+
+ def docJar(scalaVersion: String,
+ scalaOrganization: String,
+ compilerClasspath: Agg[os.Path],
+ scalacPluginClasspath: Agg[os.Path],
+ args: Seq[String])
+ (implicit ctx: ZincWorkerApi.Ctx): Boolean = {
+ withCompilers(
+ scalaVersion,
+ scalaOrganization,
+ compilerClasspath,
+ scalacPluginClasspath,
+ ) { compilers: Compilers =>
+ val scaladocClass = compilers.scalac().scalaInstance().loader().loadClass("scala.tools.nsc.ScalaDoc")
+ val scaladocMethod = scaladocClass.getMethod("process", classOf[Array[String]])
+ scaladocMethod.invoke(scaladocClass.newInstance(), args.toArray).asInstanceOf[Boolean]
+ }
+ }
+ /** Compile the bridge if it doesn't exist yet and return the output directory.
+ * TODO: Proper invalidation, see #389
+ */
+ def compileZincBridgeIfNeeded(scalaVersion: String, scalaOrganization: String, compilerJars: Array[File]): os.Path = {
+ compilerBridge match{
+ case Right(compiled) => compiled(scalaVersion)
+ case Left((ctx0, compilerBridgeClasspath, srcJars)) =>
+ val workingDir = ctx0.dest / scalaVersion
+ val compiledDest = workingDir / 'compiled
+ if (!os.exists(workingDir)) {
+ ctx0.log.info("Compiling compiler interface...")
+
+ os.makeDir.all(workingDir)
+ os.makeDir.all(compiledDest)
+
+ val sourceFolder = mill.api.IO.unpackZip(srcJars(scalaVersion, scalaOrganization))(workingDir)
+ val classloader = mill.api.ClassLoader.create(compilerJars.map(_.toURI.toURL), null)(ctx0)
+ val compilerMain = classloader.loadClass(
+ if (isDotty(scalaVersion)) "dotty.tools.dotc.Main"
+ else "scala.tools.nsc.Main"
+ )
+ val argsArray = Array[String](
+ "-d", compiledDest.toString,
+ "-classpath", (compilerJars ++ compilerBridgeClasspath).mkString(File.pathSeparator)
+ ) ++ os.walk(sourceFolder.path).filter(_.ext == "scala").map(_.toString)
+
+ compilerMain.getMethod("process", classOf[Array[String]])
+ .invoke(null, argsArray)
+ }
+ compiledDest
+ }
+
+ }
+
+
+
+ def discoverMainClasses(compilationResult: CompilationResult)
+ (implicit ctx: ZincWorkerApi.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.flatMap(_.getMainClasses).toSeq.sorted)
+ case _ =>
+ None
+ }
+ .getOrElse(Seq.empty[String])
+ }
+
+ def compileJava(upstreamCompileOutput: Seq[CompilationResult],
+ sources: Agg[os.Path],
+ compileClasspath: Agg[os.Path],
+ javacOptions: Seq[String])
+ (implicit ctx: ZincWorkerApi.Ctx): mill.api.Result[CompilationResult] = {
+ compileInternal(
+ upstreamCompileOutput,
+ sources,
+ compileClasspath,
+ javacOptions,
+ scalacOptions = Nil,
+ javaOnlyCompilers
+ )
+ }
+
+ def compileMixed(upstreamCompileOutput: Seq[CompilationResult],
+ sources: Agg[os.Path],
+ compileClasspath: Agg[os.Path],
+ javacOptions: Seq[String],
+ scalaVersion: String,
+ scalaOrganization: String,
+ scalacOptions: Seq[String],
+ compilerClasspath: Agg[os.Path],
+ scalacPluginClasspath: Agg[os.Path])
+ (implicit ctx: ZincWorkerApi.Ctx): mill.api.Result[CompilationResult] = {
+ withCompilers(
+ scalaVersion,
+ scalaOrganization,
+ compilerClasspath,
+ scalacPluginClasspath,
+ ) {compilers: Compilers =>
+ compileInternal(
+ upstreamCompileOutput,
+ sources,
+ compileClasspath,
+ javacOptions,
+ scalacOptions = scalacPluginClasspath.map(jar => s"-Xplugin:$jar").toSeq ++ scalacOptions,
+ compilers
+ )
+ }
+ }
+
+ private def withCompilers[T](scalaVersion: String,
+ scalaOrganization: String,
+ compilerClasspath: Agg[os.Path],
+ scalacPluginClasspath: Agg[os.Path])
+ (f: Compilers => T)
+ (implicit ctx: ZincWorkerApi.Ctx)= {
+ val combinedCompilerClasspath = compilerClasspath ++ scalacPluginClasspath
+ val combinedCompilerJars = combinedCompilerClasspath.toArray.map(_.toIO)
+
+ val compiledCompilerBridge = compileZincBridgeIfNeeded(
+ scalaVersion,
+ scalaOrganization,
+ compilerClasspath.toArray.map(_.toIO)
+ )
+
+ val compilerBridgeSig = os.mtime(compiledCompilerBridge)
+
+ val compilersSig =
+ compilerBridgeSig +
+ combinedCompilerClasspath.map(p => p.toString().hashCode + os.mtime(p)).sum
+
+ compilerCache.withCachedValue(compilersSig){
+ val compilerJar =
+ if (isDotty(scalaVersion))
+ grepJar(compilerClasspath, s"dotty-compiler_${scalaBinaryVersion(scalaVersion)}", scalaVersion)
+ else
+ compilerJarNameGrep(compilerClasspath, scalaVersion)
+ val scalaInstance = new ScalaInstance(
+ version = scalaVersion,
+ loader = mill.api.ClassLoader.create(combinedCompilerJars.map(_.toURI.toURL), null),
+ libraryJar = libraryJarNameGrep(compilerClasspath, scalaVersion).toIO,
+ compilerJar = compilerJar.toIO,
+ allJars = combinedCompilerJars,
+ explicitActual = None
+ )
+ ic.compilers(
+ scalaInstance,
+ ClasspathOptionsUtil.boot,
+ None,
+ ZincUtil.scalaCompiler(scalaInstance, compiledCompilerBridge.toIO)
+ )
+ }(f)
+ }
+
+ private def compileInternal(upstreamCompileOutput: Seq[CompilationResult],
+ sources: Agg[os.Path],
+ compileClasspath: Agg[os.Path],
+ javacOptions: Seq[String],
+ scalacOptions: Seq[String],
+ compilers: Compilers)
+ (implicit ctx: ZincWorkerApi.Ctx): mill.api.Result[CompilationResult] = {
+ os.makeDir.all(ctx.dest)
+
+ 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)
+
+ val inputs = ic.inputs(
+ classpath = classesIODir +: compileClasspath.map(_.toIO).toArray,
+ sources = sources.toArray.map(_.toIO),
+ classesDirectory = classesIODir,
+ scalacOptions = scalacOptions.toArray,
+ javacOptions = javacOptions.toArray,
+ maxErrors = 10,
+ sourcePositionMappers = Array(),
+ order = CompileOrder.Mixed,
+ compilers = compilers,
+ 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))
+ }
+ )
+
+ try {
+ val newResult = ic.compile(
+ in = inputs,
+ logger = logger
+ )
+
+ store.set(
+ AnalysisContents.create(
+ newResult.analysis(),
+ newResult.setup()
+ )
+ )
+
+ mill.api.Result.Success(CompilationResult(zincFile, PathRef(classesDir)))
+ }catch{case e: CompileFailed => mill.api.Result.Failure(e.toString)}
+ }
+}