2022-05-04 22:18:40 +03:00
|
|
|
/*
|
|
|
|
* 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
|
2022-11-15 10:05:53 +03:00
|
|
|
import scala.util.Using
|
|
|
|
import java.io.FileWriter
|
2022-05-04 22:18:40 +03:00
|
|
|
|
|
|
|
object FrgaalJavaCompiler {
|
2022-11-15 10:05:53 +03:00
|
|
|
private val ENSO_SOURCES = ".enso-sources"
|
2022-05-04 22:18:40 +03:00
|
|
|
|
2022-10-05 07:45:31 +03:00
|
|
|
val frgaal = "org.frgaal" % "compiler" % "19.0.0" % "provided"
|
2022-05-04 22:18:40 +03:00
|
|
|
|
2023-03-11 03:15:58 +03:00
|
|
|
def compilers(
|
|
|
|
classpath: sbt.Keys.Classpath,
|
|
|
|
sbtCompilers: xsbti.compile.Compilers,
|
|
|
|
javaVersion: String
|
|
|
|
) = {
|
2022-05-04 22:18:40 +03:00
|
|
|
// 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 &&
|
2023-03-11 03:15:58 +03:00
|
|
|
module.name == frgaalModule.name &&
|
|
|
|
module.revision == frgaalModule.revision
|
2022-05-04 22:18:40 +03:00
|
|
|
val frgaalOnClasspath =
|
2023-03-11 03:15:58 +03:00
|
|
|
classpath
|
|
|
|
.find(f =>
|
|
|
|
f.metadata
|
|
|
|
.get(AttributeKey[ModuleID]("moduleID"))
|
|
|
|
.map(frgaalCheck)
|
|
|
|
.getOrElse(false)
|
|
|
|
)
|
2022-05-04 22:18:40 +03:00
|
|
|
.map(_.data.toPath)
|
|
|
|
if (frgaalOnClasspath.isEmpty) {
|
|
|
|
throw new RuntimeException("Failed to resolve Frgaal compiler. Aborting!")
|
|
|
|
}
|
2023-03-11 03:15:58 +03:00
|
|
|
val frgaalJavac = new FrgaalJavaCompiler(
|
|
|
|
javaHome,
|
|
|
|
frgaalOnClasspath.get,
|
|
|
|
target = javaVersion
|
|
|
|
)
|
|
|
|
val javaTools = sbt.internal.inc.javac
|
|
|
|
.JavaTools(frgaalJavac, sbtCompilers.javaTools.javadoc())
|
2022-05-04 22:18:40 +03:00
|
|
|
xsbti.compile.Compilers.of(sbtCompilers.scalac, javaTools)
|
|
|
|
}
|
|
|
|
|
|
|
|
/** Helper method to launch programs. */
|
|
|
|
def launch(
|
2023-03-11 03:15:58 +03:00
|
|
|
javaHome: Option[Path],
|
|
|
|
compilerJar: Path,
|
|
|
|
sources0: Seq[VirtualFile],
|
|
|
|
options: Seq[String],
|
|
|
|
output: Output,
|
|
|
|
log: Logger,
|
|
|
|
reporter: Reporter,
|
|
|
|
source: Option[String],
|
|
|
|
target: String
|
|
|
|
): Boolean = {
|
2022-05-04 22:18:40 +03:00
|
|
|
val (jArgs, nonJArgs) = options.partition(_.startsWith("-J"))
|
2023-03-11 03:15:58 +03:00
|
|
|
val outputOption = CompilerArguments.outputOption(output)
|
|
|
|
val sources = sources0 map { case x: PathBasedFile =>
|
|
|
|
x.toPath.toAbsolutePath.toString
|
2022-05-04 22:18:40 +03:00
|
|
|
}
|
2022-12-30 08:30:32 +03:00
|
|
|
|
2023-03-11 03:15:58 +03:00
|
|
|
def asPath(a: Any): Path = a match {
|
|
|
|
case p: PathBasedFile => p.toPath
|
|
|
|
case p: Path => p
|
2022-12-30 08:30:32 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
def asCommon(a: Any, b: Any): Path = {
|
|
|
|
var ap = asPath(a)
|
|
|
|
val bp = asPath(b)
|
2022-11-15 10:05:53 +03:00
|
|
|
|
|
|
|
var i = 0
|
2023-03-11 03:15:58 +03:00
|
|
|
while (
|
|
|
|
i < Math.min(ap.getNameCount(), bp.getNameCount()) && ap.getName(
|
|
|
|
i
|
|
|
|
) == bp.getName(i)
|
|
|
|
) {
|
2022-11-15 10:05:53 +03:00
|
|
|
i += 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
while (ap.getNameCount() > i) {
|
|
|
|
ap = ap.getParent()
|
|
|
|
}
|
|
|
|
ap
|
2022-12-30 08:30:32 +03:00
|
|
|
}
|
|
|
|
|
2023-03-11 03:15:58 +03:00
|
|
|
val out = output.getSingleOutputAsPath().get()
|
2022-12-30 08:30:32 +03:00
|
|
|
val shared = sources0.fold(out)(asCommon).asInstanceOf[Path]
|
|
|
|
|
|
|
|
// searching for $shared/src/main/java or
|
|
|
|
// $shared/src/test/java or
|
|
|
|
// $shared/src/bench/java or etc.
|
2023-03-11 03:15:58 +03:00
|
|
|
def findUnder(depth: Int, dir: Path): Path = {
|
2022-12-30 08:30:32 +03:00
|
|
|
var d = dir
|
|
|
|
while (d.getNameCount() > depth) {
|
2023-03-11 03:15:58 +03:00
|
|
|
val threeUp = d.subpath(0, d.getNameCount() - depth)
|
2022-12-30 08:30:32 +03:00
|
|
|
val relShare = shared.subpath(0, shared.getNameCount())
|
|
|
|
if (relShare.equals(threeUp)) {
|
|
|
|
return d
|
|
|
|
} else {
|
|
|
|
d = d.getParent()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
throw new IllegalArgumentException(
|
|
|
|
"Cannot findUnder for " + dir + " and " + shared +
|
|
|
|
"\nout: " + out + "\nsources: " + sources
|
|
|
|
)
|
|
|
|
}
|
2023-03-11 03:15:58 +03:00
|
|
|
def checkTarget(x: Any) = {
|
2022-12-30 08:30:32 +03:00
|
|
|
val p = asPath(x)
|
2023-03-11 03:15:58 +03:00
|
|
|
val namesCheck =
|
|
|
|
for (i <- 0 until p.getNameCount)
|
|
|
|
yield "target".equals(p.getName(i).toString()) || p
|
|
|
|
.getName(i)
|
|
|
|
.toString()
|
|
|
|
.endsWith("-windows") || p.getName(i).toString().endsWith("-unix")
|
2022-12-30 08:30:32 +03:00
|
|
|
val inATargetDir = namesCheck.exists(x => x)
|
|
|
|
inATargetDir
|
|
|
|
}
|
|
|
|
|
|
|
|
val (withTarget, noTarget) = sources0.partition(checkTarget)
|
2023-01-09 16:39:14 +03:00
|
|
|
val in = if (noTarget.isEmpty) {
|
|
|
|
None
|
|
|
|
} else {
|
2023-03-11 03:15:58 +03:00
|
|
|
Some(
|
|
|
|
findUnder(
|
|
|
|
3,
|
|
|
|
noTarget.tail.fold(asPath(noTarget.head))(asCommon).asInstanceOf[Path]
|
|
|
|
)
|
|
|
|
)
|
2023-01-09 16:39:14 +03:00
|
|
|
}
|
2022-12-30 08:30:32 +03:00
|
|
|
val generated = if (withTarget.isEmpty) {
|
|
|
|
None
|
|
|
|
} else {
|
2023-03-11 03:15:58 +03:00
|
|
|
Some(
|
|
|
|
findUnder(
|
|
|
|
4,
|
|
|
|
withTarget.tail
|
|
|
|
.fold(asPath(withTarget.head))(asCommon)
|
|
|
|
.asInstanceOf[Path]
|
|
|
|
)
|
|
|
|
)
|
2022-12-30 08:30:32 +03:00
|
|
|
}
|
2022-11-15 10:05:53 +03:00
|
|
|
|
|
|
|
if (shared.toFile().exists()) {
|
|
|
|
val ensoMarker = new File(shared.toFile(), ENSO_SOURCES)
|
2023-03-11 03:15:58 +03:00
|
|
|
val ensoConfig = new File(
|
|
|
|
shared.toFile(),
|
|
|
|
ENSO_SOURCES + "-" + out.getFileName().toString()
|
|
|
|
)
|
2022-11-15 10:05:53 +03:00
|
|
|
val ensoProperties = new java.util.Properties()
|
|
|
|
|
2023-03-11 03:15:58 +03:00
|
|
|
def storeArray(name: String, values: Seq[String]) = {
|
|
|
|
values.zipWithIndex.foreach { case (value, idx) =>
|
|
|
|
ensoProperties.setProperty(s"$name.$idx", value)
|
|
|
|
}
|
2022-11-15 10:05:53 +03:00
|
|
|
}
|
2023-01-09 16:39:14 +03:00
|
|
|
if (in.isDefined) {
|
|
|
|
ensoProperties.setProperty("input", in.get.toString())
|
|
|
|
}
|
2022-12-30 08:30:32 +03:00
|
|
|
if (generated.isDefined) {
|
|
|
|
ensoProperties.setProperty("generated", generated.get.toString())
|
|
|
|
}
|
2022-11-15 10:05:53 +03:00
|
|
|
ensoProperties.setProperty("output", out.toString())
|
|
|
|
storeArray("options", options)
|
|
|
|
source.foreach(v => ensoProperties.setProperty("source", v))
|
|
|
|
ensoProperties.setProperty("target", target)
|
2023-03-11 03:15:58 +03:00
|
|
|
javaHome.foreach(v =>
|
|
|
|
ensoProperties.setProperty("java.home", v.toString())
|
|
|
|
)
|
2022-11-15 10:05:53 +03:00
|
|
|
|
|
|
|
Using(new FileWriter(ensoConfig)) { w =>
|
|
|
|
ensoProperties.store(w, "# Enso compiler configuration")
|
|
|
|
}
|
2023-03-11 03:15:58 +03:00
|
|
|
Using(new FileWriter(ensoMarker)) { _ => }
|
2022-11-15 10:05:53 +03:00
|
|
|
} else {
|
2023-03-11 03:15:58 +03:00
|
|
|
throw new IllegalStateException(
|
|
|
|
"Cannot write Enso source options to " + shared + " values:\n" +
|
|
|
|
"options: " + options + " sources0: " + sources + " output: " + output
|
2022-11-15 10:05:53 +03:00
|
|
|
)
|
|
|
|
}
|
|
|
|
|
2023-03-11 03:15:58 +03:00
|
|
|
val frgaalOptions: Seq[String] =
|
|
|
|
source.map(v => Seq("-source", v)).getOrElse(Seq()) ++ Seq(
|
|
|
|
"-target",
|
|
|
|
target
|
|
|
|
)
|
2022-05-04 22:18:40 +03:00
|
|
|
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.
|
2023-03-11 03:15:58 +03:00
|
|
|
val forkArgs = (jArgs ++ Seq(
|
|
|
|
"--limit-modules",
|
|
|
|
"java.base,jdk.zipfs,jdk.internal.vm.compiler.management",
|
|
|
|
"-jar",
|
|
|
|
compilerJar.toString
|
|
|
|
)) :+
|
|
|
|
s"@${normalizeSlash(argsFile.getAbsolutePath)}"
|
|
|
|
val exe = getJavaExecutable(javaHome, "java")
|
|
|
|
val cwd = new File(new File(".").getAbsolutePath).getCanonicalFile
|
2022-05-04 22:18:40 +03:00
|
|
|
val javacLogger = new JavacLogger(log, reporter, cwd)
|
2023-03-11 03:15:58 +03:00
|
|
|
var exitCode = -1
|
2022-05-04 22:18:40 +03:00
|
|
|
try {
|
|
|
|
exitCode = Process(exe +: forkArgs, cwd) ! javacLogger
|
|
|
|
} finally {
|
|
|
|
javacLogger.flush("frgaal", exitCode)
|
|
|
|
}
|
|
|
|
// We return true or false, depending on success.
|
|
|
|
exitCode == 0
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-03-11 03:15:58 +03:00
|
|
|
/** Helper method to create an argument file that we pass to Javac. Gets over the windows
|
2022-05-04 22:18:40 +03:00
|
|
|
* 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 = {
|
2023-03-11 03:15:58 +03:00
|
|
|
import IO.{withTemporaryDirectory, write, Newline}
|
2022-05-04 22:18:40 +03:00
|
|
|
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) + '\"'
|
2023-03-11 03:15:58 +03:00
|
|
|
private def normalizeSlash(s: String) = s.replace(File.separatorChar, '/')
|
2022-05-04 22:18:40 +03:00
|
|
|
|
|
|
|
/** 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. */
|
2023-03-11 03:15:58 +03:00
|
|
|
final class FrgaalJavaCompiler(
|
|
|
|
javaHome: Option[Path],
|
|
|
|
compilerPath: Path,
|
|
|
|
target: String,
|
|
|
|
source: Option[String] = None
|
|
|
|
) extends XJavaCompiler {
|
2022-05-04 22:18:40 +03:00
|
|
|
def run(
|
2023-03-11 03:15:58 +03:00
|
|
|
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
|
|
|
|
)
|
2022-05-11 14:21:01 +03:00
|
|
|
}
|