diff options
author | Li Haoyi <haoyi.sg@gmail.com> | 2018-12-12 16:56:02 -0800 |
---|---|---|
committer | GitHub <noreply@github.com> | 2018-12-12 16:56:02 -0800 |
commit | 9ba4cb69331386dfde9bac69dc2d5b22401face3 (patch) | |
tree | 120349e8015ae5717d36bd44209cde6ff9543518 /scalalib/worker/src/ZincWorkerImpl.scala | |
parent | ea7fceb6e56f53bde3517586dfc57e10a605a524 (diff) | |
download | mill-9ba4cb69331386dfde9bac69dc2d5b22401face3.tar.gz mill-9ba4cb69331386dfde9bac69dc2d5b22401face3.tar.bz2 mill-9ba4cb69331386dfde9bac69dc2d5b22401face3.zip |
collapse boilerplate folder structure within src/ folders (#505)
* collapse boilerplate folder structure within src/ folders
* .
Diffstat (limited to 'scalalib/worker/src/ZincWorkerImpl.scala')
-rw-r--r-- | scalalib/worker/src/ZincWorkerImpl.scala | 284 |
1 files changed, 284 insertions, 0 deletions
diff --git a/scalalib/worker/src/ZincWorkerImpl.scala b/scalalib/worker/src/ZincWorkerImpl.scala new file mode 100644 index 00000000..705d4682 --- /dev/null +++ b/scalalib/worker/src/ZincWorkerImpl.scala @@ -0,0 +1,284 @@ +package mill.scalalib.worker + +import java.io.File +import java.util.Optional + +import mill.api.Loose.Agg +import mill.api.PathRef +import xsbti.compile.{CompilerCache => _, FileAnalysisStore => _, ScalaInstance => _, _} +import mill.scalalib.api.Util.{isDotty, grepJar, scalaBinaryVersion} +import sbt.internal.inc._ +import sbt.internal.util.{ConsoleOut, MainAppender} +import sbt.util.LogExchange +import mill.scalalib.api.CompilationResult +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(ctx0: mill.api.Ctx, + compilerBridgeClasspath: Array[String]) extends mill.scalalib.api.ZincWorkerApi{ + private val ic = new sbt.internal.inc.IncrementalCompilerImpl() + 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 + ) + } + + @volatile var mixedCompilersCache = Option.empty[(Long, Compilers)] + + def docJar(scalaVersion: String, + compilerBridgeSources: os.Path, + compilerClasspath: Agg[os.Path], + scalacPluginClasspath: Agg[os.Path], + args: Seq[String]) + (implicit ctx: mill.api.Ctx): Boolean = { + val compilers: Compilers = prepareCompilers( + scalaVersion, + compilerBridgeSources, + compilerClasspath, + scalacPluginClasspath + ) + 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, + sourcesJar: os.Path, + compilerJars: Array[File]): os.Path = { + 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(sourcesJar)(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: mill.api.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 compileJava(upstreamCompileOutput: Seq[CompilationResult], + sources: Agg[os.Path], + compileClasspath: Agg[os.Path], + javacOptions: Seq[String]) + (implicit ctx: mill.api.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, + scalacOptions: Seq[String], + compilerBridgeSources: os.Path, + compilerClasspath: Agg[os.Path], + scalacPluginClasspath: Agg[os.Path]) + (implicit ctx: mill.api.Ctx): mill.api.Result[CompilationResult] = { + val compilers: Compilers = prepareCompilers( + scalaVersion, + compilerBridgeSources, + compilerClasspath, + scalacPluginClasspath + ) + + compileInternal( + upstreamCompileOutput, + sources, + compileClasspath, + javacOptions, + scalacOptions = scalacPluginClasspath.map(jar => s"-Xplugin:${jar}").toSeq ++ scalacOptions, + compilers + ) + } + + private def prepareCompilers(scalaVersion: String, + compilerBridgeSources: os.Path, + compilerClasspath: Agg[os.Path], + scalacPluginClasspath: Agg[os.Path]) + (implicit ctx: mill.api.Ctx)= { + val combinedCompilerClasspath = compilerClasspath ++ scalacPluginClasspath + val combinedCompilerJars = combinedCompilerClasspath.toArray.map(_.toIO) + + val compilerBridge = compileZincBridgeIfNeeded( + scalaVersion, + compilerBridgeSources, + compilerClasspath.toArray.map(_.toIO) + ) + val compilerBridgeSig = os.mtime(compilerBridge) + + val compilersSig = + compilerBridgeSig + + combinedCompilerClasspath.map(p => p.toString().hashCode + os.mtime(p)).sum + + val compilers = mixedCompilersCache match { + case Some((k, v)) if k == compilersSig => v + case _ => + val compilerName = + if (isDotty(scalaVersion)) + s"dotty-compiler_${scalaBinaryVersion(scalaVersion)}" + else + "scala-compiler" + val scalaInstance = new ScalaInstance( + version = scalaVersion, + loader = mill.api.ClassLoader.create(combinedCompilerJars.map(_.toURI.toURL), null), + libraryJar = grepJar(compilerClasspath, "scala-library", scalaVersion).toIO, + compilerJar = grepJar(compilerClasspath, compilerName, scalaVersion).toIO, + allJars = combinedCompilerJars, + explicitActual = None + ) + val compilers = ic.compilers( + scalaInstance, + ClasspathOptionsUtil.boot, + None, + ZincUtil.scalaCompiler(scalaInstance, compilerBridge.toIO) + ) + mixedCompilersCache = Some((compilersSig, compilers)) + compilers + } + compilers + } + + private def compileInternal(upstreamCompileOutput: Seq[CompilationResult], + sources: Agg[os.Path], + compileClasspath: Agg[os.Path], + javacOptions: Seq[String], + scalacOptions: Seq[String], + compilers: Compilers) + (implicit ctx: mill.api.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)} + } +} |