enso/project/FrgaalJavaCompiler.scala
Hubert Plociniczak 79c82da21c
Frgaal integration in sbt (#3421)
* Initial integration with Frgaal in sbt

Half-working since it chokes on generated classes from annotation
processor.

* Replace AutoService with ServiceProvider

For reasons unknown AutoService would fail to initialize and fail to
generate required builtin method classes.
Hidden error message is not particularly revealing on the reason for
that:
```
[error] error: Bad service configuration file, or exception thrown while constructing Processor object: javax.annotation.processing.Processor: Provider com.google.auto.service.processor.AutoServiceProcessor could not be instantiated
```

The sample records is only to demonstrate that we can now use newer Java
features.

* Cleanup + fix benchmark compilation

Bench requires jmh classes which are not available because we obviously
had to limit `java.base` modules to get Frgaal to work nicely.
For now, we default to good ol' javac for Benchmarks.
Limiting Frgaal to runtime for now, if it plays nicely, we can expand it
to other projects.

* Update CHANGELOG

* Remove dummy record class

* Update licenses

* New line

* PR review

* Update legal review

Co-authored-by: Radosław Waśko <radoslaw.wasko@enso.org>
2022-05-04 21:18:40 +02:00

128 lines
5.1 KiB
Scala

/*
* The FrgaalJavaCompiler adapts ForkedJava from Zinc
* to invoke Frgaal compiler instead of javac.
*
* Zinc - The incremental compiler for Scala.
* Copyright Lightbend, Inc. and Mark Harrah
*
* Licensed under Apache License 2.0
* (http://www.apache.org/licenses/LICENSE-2.0).
*/
import sbt._
import sbt.internal.inc.CompilerArguments
import sbt.internal.inc.javac.JavacLogger
import sbt.io.IO
import sbt.util.Logger
import xsbti.{PathBasedFile, Reporter, VirtualFile, Logger => XLogger}
import xsbti.compile.{IncToolOptions, Output, JavaCompiler => XJavaCompiler}
import java.io.File
import java.nio.file.{Path, Paths}
import scala.sys.process.Process
object FrgaalJavaCompiler {
val frgaal = "org.frgaal" % "compiler" % "18.0.0" % "provided"
def compilers(classpath: sbt.Keys.Classpath, sbtCompilers: xsbti.compile.Compilers, javaVersion: String) = {
// Enable Java 11+ features by invoking Frgaal instead of regular javac
val javaHome = Option(System.getProperty("java.home")).map(Paths.get(_))
// Locate frgaal compiler jar from the list of dependencies
val frgaalModule = FrgaalJavaCompiler.frgaal
val frgaalCheck = (module: ModuleID) =>
module.organization == frgaalModule.organization &&
module.name == frgaalModule.name &&
module.revision == frgaalModule.revision
val frgaalOnClasspath =
classpath.find(f => f.metadata.get(AttributeKey[ModuleID]("moduleID")).map(frgaalCheck).getOrElse(false))
.map(_.data.toPath)
if (frgaalOnClasspath.isEmpty) {
throw new RuntimeException("Failed to resolve Frgaal compiler. Aborting!")
}
val frgaalJavac = new FrgaalJavaCompiler(javaHome, frgaalOnClasspath.get, target = javaVersion)
val javaTools = sbt.internal.inc.javac.JavaTools(frgaalJavac, sbtCompilers.javaTools.javadoc())
xsbti.compile.Compilers.of(sbtCompilers.scalac, javaTools)
}
/** Helper method to launch programs. */
def launch(
javaHome: Option[Path],
compilerJar: Path,
sources0: Seq[VirtualFile],
options: Seq[String],
output: Output,
log: Logger,
reporter: Reporter,
source: Option[String],
target: String
): Boolean = {
val (jArgs, nonJArgs) = options.partition(_.startsWith("-J"))
val outputOption = CompilerArguments.outputOption(output)
val sources = sources0 map {
case x: PathBasedFile => x.toPath.toAbsolutePath.toString
}
val frgaalOptions: Seq[String] = source.map(v => Seq("-source", v)).getOrElse(Seq()) ++ Seq("-target", target)
val allArguments = outputOption ++ frgaalOptions ++ nonJArgs ++ sources
withArgumentFile(allArguments) { argsFile =>
// Need to disable standard compiler tools that come with used jdk and replace them
// with the ones provided with Frgaal.
val forkArgs = (jArgs ++ Seq("--limit-modules", "java.base,jdk.zipfs", "-jar", compilerJar.toString)) :+
s"@${normalizeSlash(argsFile.getAbsolutePath)}"
val exe = getJavaExecutable(javaHome, "java")
val cwd = new File(new File(".").getAbsolutePath).getCanonicalFile
val javacLogger = new JavacLogger(log, reporter, cwd)
var exitCode = -1
try {
exitCode = Process(exe +: forkArgs, cwd) ! javacLogger
} finally {
javacLogger.flush("frgaal", exitCode)
}
// We return true or false, depending on success.
exitCode == 0
}
}
/**
* Helper method to create an argument file that we pass to Javac. Gets over the windows
* command line length limitation.
* @param args The string arguments to pass to Javac.
* @param f A function which is passed the arg file.
* @tparam T The return type.
* @return The result of using the argument file.
*/
def withArgumentFile[T](args: Seq[String])(f: File => T): T = {
import IO.{ Newline, withTemporaryDirectory, write }
withTemporaryDirectory { tmp =>
val argFile = new File(tmp, "argfile")
write(argFile, args.map(escapeSpaces).mkString(Newline))
f(argFile)
}
}
// javac's argument file seems to allow naive space escaping with quotes. escaping a quote with a backslash does not work
private def escapeSpaces(s: String): String = '\"' + normalizeSlash(s) + '\"'
private def normalizeSlash(s: String) = s.replace(File.separatorChar, '/')
/** create the executable name for java */
def getJavaExecutable(javaHome: Option[Path], name: String): String =
javaHome match {
case None => name
case Some(jh) =>
jh.resolve("bin").resolve(name).toAbsolutePath.toString
}
}
/** An implementation of compiling java which forks Frgaal instance. */
final class FrgaalJavaCompiler(javaHome: Option[Path], compilerPath: Path, target: String, source: Option[String] = None) extends XJavaCompiler {
def run(
sources: Array[VirtualFile],
options: Array[String],
output: Output,
incToolOptions: IncToolOptions,
reporter: Reporter,
log: XLogger
): Boolean =
FrgaalJavaCompiler.launch(javaHome, compilerPath, sources, options, output, log, reporter, source, target)
}