enso/project/FrgaalJavaCompiler.scala
Pavel Marek 9182f91e35
engine-runner and language-server are separate JPMS modules (#10823)
* Use moduleDependencies instead of modulePath

* Fix compilation of editions

* Fix compilation of distribution-manager

* polyglot-api needs to explicitly compile module-info

* Fix compilationOrder in library-manager and edition-updater

* engine-runner-common is module

* JPMSPlugin provides default implementation of compileModuleInfo

* Remove unused setting key from JPMSUtils.compileModuleInfo

* JPMSPlugin has internalModuleDependencies and exportedModule tasks.

* Use BuildVersion instead of buildInfo

* Manual compilation of module-info.java is reported as warning

* Define org.enso.scalalibs.wrapper meta project.

* Fix module check in JPMSPlugin.

This is a fix for projects that declare `Compile /exportJars := true`

* version-output is a module

* ydoc-server uses internalModuleDependencies

* persistance is module

* engine-common uses internalModuleDependencies

* polyglot-api does not override compileModuleInfo task

* runtime-parser uses internalModuleDependencies

* edition-updater is module

* Update moduleDependencies for distribution-manager

* editions is module

* Fix some dependencies of modules

* scala-yaml is a module

* Add scala-compiler to scala-libs-wrapper

* cli depends on scala-library module

* Add dependencies for distribution-manager module

* Add some scala-library dependencies in some modules

* engine-runner uses internalModuleDependencies

* Fix module dependencies of library-manager

* Rename org.enso.scalalibs.wrapper to org.enso.scala.wrapper

* Add jsoniter-scala-macros to org.enso.scala.wrapper fat module

* Fix dependencies of some projects

* polyglot-api does not depend on truffle-api

* Fix dependencies of some projects

* runtime does not use com.google.common

* runtime is a module

* text-buffer is a module

* refactoring-utils is a module

* runtime-compiler is a module

* runtime-instrument-common is a module

* connected-lock-manager is a module

* JPMSUtils reports project name in some error messages

* Modularize some instruments

* module-info compilation is cached

* runtime-instrument-runtime-server is module

* runtime-language-epb is module

* Remove runtime-fat-jar

* engine-runner is not a fat jar

* JPMSPlugin defines exportedModuleBin task

* Redefine componentModulesPaths task

* interpreter-dsl is module

* Redefine componentModulesPaths task

* fmt sbt

* scala-libs-wrapper is a modular fat jar

* Add some module deps to org.enso.runtime

* engine-runner is not a fat jar

* Rename package in logging-config

* Rename package in logging-service

* Rename package in logging-service-logback

* Fix dependencies of exportedModuleBin task

* Mixed projects have own compileJava task

this task does not compile only module-info.java but all the java sources. So that we can see errors more easily.

When only module-info.java is compiled, the only errors that we can see are that we did not include some modules on module-path.

* Fix definition of exportedModule task.

* Remove usages of non-existing buildInfo and replace it with BuildVersion

* Fix some dependencies of org.enso.runtime module

* module-info compilation is handled directly by FrgaalCompiler

* module-info compilation is forced for projects that has only Scala sources with single module-info.java

* Fix compilation of org.enso.runtime

* manual module-info compilation is not a warning

* Rename packages in logging-utils-akka

* Create org.enso.language.server.deps.wrapper module

* language-server is module

* Creat akka-wrapper modular fat jar

* fmt

* Define common settings for modularFatJarWrapper

* Fix compilation of json-rpc-server

* Use akka and zio wrappers

* language-server depends on org.eclipse.jgit

* Fix some dependencies - update library manifests works now!

* update library manifests invokes runner directly

* buildEngineDistribution does not copy runner.jar

* Remove EngineRunnerBootLoader

* Fix compilation of std libs

* --patch-module and --add-exports are also passed to javac

* Rename package in runtime-integration-tests.

The package name org.enso.compiler clashes with the package from the module

* Remove usage of buildInfo

* FrgaalJavaCompiler can deal with non-existing module-info.java when shouldCompileModuleInfo is true.

It just generates a warning in such case as it suggests that there is something wrong with the project configuration.

* Revert AliasAnalysisTest.scala

* Fix dependencies and java cmdline options for runtime-integration-tests

* Rename test package

* runtime-integration-test depends on logging-service-logback/Test/compile

* Rename package in logging-service-logback/Test

* Fix FrgaalJavaCompiler creation for projects

* Sanitize Test/javaOptions arguments

* Sanitize Test/javaOptions arguments

* All the JPMSPlugin settings are scoped

* Remove unused sbt tasks

* modularFatJarWrapperSettings do not override javacOptions

* Resolve issue "Cannot find TestLoggerProvider" in runtime-integration-tests

* org.enso.runtime module is open

* Test that test classes are unconditionally opened and exported

* polyglot-api-macros is a module

* JPMSPlugin handles --add-opens cmdline option

* RuntimeServerTest ensures instruments are initialized

* Add some exports to org.enso.runtime.compiler

* Add instruments on module-path to runtime-integration-tests

* Replace TestLogProviderOnClassPath with TestLogProviderOnModulePath

* Replace buildInfo with BuildVersion

* Add jpms-wrapper-scalatest

* ReportLogsOnFailure is in non-modular testkit project

* Add necessary dependencies to testkit project

* Revert "Add jpms-wrapper-scalatest"

This reverts commit 732b3427a2.

* modularize filewatcher and wrap its dependencies

* Initial fix for language-server/test

* frgaal compiler setting are scoped for Compile and Test

* Rename package in language-server/test

* Exclude com.sun.jna from wrapper jars

* Rename package in library-manager-test

* testkit is an automatic module

* process-utils is module

* akka-wrapper contains akka-http

* Some fixes for library-manager-test

* Fix dependencies for akka-wrapper

* scala-libs-wrapper exports shapeless

* lang server deps wrapper exports pureconfig

* json-rpc-server requires org.slf4j

* Add some dependencies

* lang server deps wrapper exports pureconfig.generic

* language server test requires bouncycastle provider

* language server depends on cli

* directory-watcher wrapper requires org.slf4j

* WatcherAdapter logs unsuccessful initialization errors

* Fix error reporting in WatcherAdapter

* Fix rest of the language-server tests

* language-server-deps-wrapper depends on scala-libs-wrapper

* Fix rest of the language-server tests

* Missing module-info.class in an internal project is a warning, not an error

* Rename jpms-methvin-directory-watcher-wrapper to a simpler name

* compileOrder has to be specified before libraryDependencies

* exclude module-info.java from polyglot-api-macros

* Remove temporary logging in customFrgaalCompilerSettings

* Fix compilation of logging-service-logback

* Fix compilation of runtime-benchmarks

* Fix runtime-benchmarks/run

* HostClassLoader delegates to org.graalvm.polyglot class loader if org.enso.runtime is not on boot layer

* org.enso.runtime.lnaguage.epb module must be opened to allow it to be used by annnotation processor

* fmt

* Fix afetr merge

* Add module deps after merge

* Print stack trace of the uncaught exception from the annotation processor

* Remove akka-actor-typed from akka-wrapper

* runtime-instrument-common depends on slf4j

* Fix module-path for runtime-instrument-repl-debugger

* runtime-benchmarks depends on runtime-language-arrow

* --module-path is passed directly to frgaal

* Fix some module-related cmd line options for std-benchmarks

* Revert "--module-path is passed directly to frgaal"

This reverts commit da63f66a0e.

* Avoid closing of System.err when closing Context

* Avoid processing altogether when requested annotations are empty

* Pass shouldNotLimitModules opt to frgaal

* Pass module-path and add-modules options with -J prefix to frgaal

* BenchProcessor annotation processor creates its own truffle module layer

* bench-processor and benchmarks-common are modules

* fmt

* Fix after mege

* Enable JMH annotation processor

* Fix compileOrder in some projects

* Insert TruffleBoundary to QualifiedName.

This is a revert

* Fix building of engine-runner native image

* Add more deps to the native image

* Force module-info compilation in instruments.

This fixes some weird sbt bug

* Don't run engine-runner/assembly from Rust build script

* Update docs of JPMSPlugin

* fmt

* runtime-benchmarks depends on benchmarks-common module

* Fix benchmark report writing

* std-benchmarks annot processing does not take settings from runtime-benchmarks

* Suppress interpreter only warning in annotation processor

* Runtime version manager does not expect runtime.jar fat jar

* fmt

* Fix module entry point

* Move some polyglot tests to runtime-integration-tests.

Also make their output silent

* pkg has no dependency on org.graalvm.truffle

* Fix compiler dependencies test

* Rename all runtime.jar in fake releases

* Add language-server with dependencies to component dir

* No module-info.class in target dir is warning not error

* language-server does not depend on netbeans lookup uitl

* Declare LanguageServerApi service provider in module-info

* connected-lock-manager-server is JPMS module

* task-progress-notifications is module

* Add fansi-wrapper module

* Fix compilation of connected-lock-manager-server

* Define correct Test/internalModuleDependencies for project-manager

* fmt

* Fix LauncherRunnerSpec - no runtime.jar

* Add fansi-wrapper to runtime-integration-tests and runtime-benchmarks

* Fix engine-runner native image build

* Use newer JNA version - fixes running of hyperd

* DRY

* scala-compiler DRY

* fmt

* More build.sbt refactoring

* Include runtime-instrument-id-execution in engine-runner native image

* TruffleBoundary for QualifiedName.toString

* Finding a needle in a haystack

🤦

* More scala-library DRY

* more mixed-java/scala goodies

* Fix compilation of syntax-rust-definition

* Test that engine-runner does not depend on language-server

* Append rather than assign `moduleDependencies`

`++=` is less error prone than `:=`. Also discovered some unnecessary
dependencies.

* Replace : with File.pathSeparator

* [WIP] Make logging in ProjectService more verbose

* language-server/test didn't start because of missing lookup and fansi modules

* Formatting

* org.enso.cli.task.notifications needs Akka and Circe to link

* project-manager/test depends on buildEngineDistribution

* [WIP] Even more verbose logging for creating projects

* [WIP] Even more verbose logging for creating projects

* Revert "[WIP] Even more verbose logging for creating projects"

This reverts commit a7067c8472.

* Revert "[WIP] Even more verbose logging for creating projects"

This reverts commit fc6f53d4f1.

* Revert "[WIP] Make logging in ProjectService more verbose"

This reverts commit 427428e142.

* All the project with JPMSPlugin has stripped artifact names

* Revert all placeholder fake release components to runtime.jar without version

* Eliminate a cross version hack

We shouldn't be specifying Scala dependencies with a Scala cross version
in the suffix.

* Address SBT lint warnings

* Revert "Eliminate a cross version hack"

This reverts commit 8861dab288.

* logging-service-logback  is mixedJavaScalaProject

* fmt

* Stripped artifact name contains classifier.

This fixes tests as those were named like `artifact-tests.jar`.

* Don't use LocalProject unless really needed

* Add more logging when BenchProcessor fails

* logging-service-logback is not mixed project

* Work with java.io.File.getPath to avoid mixing slash and backslash on Windows

* Reapply "Eliminate a cross version hack"

This reverts commit edaa436ee8.

* Pass scalaBinaryVersion correctly

* Remove scala-compiler from the distribution

* Fix IllegalAccessErrors from serde

* typos

* License review

* fmt

* Move testLogProviderOnModulePath to TestJPMSConfiguration

* logging-service-logback is not a mixed project

---------

Co-authored-by: Jaroslav Tulach <jaroslav.tulach@enso.org>
Co-authored-by: Hubert Plociniczak <hubert.plociniczak@gmail.com>
2024-09-25 21:33:13 +02:00

400 lines
13 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
import scala.util.Using
import java.io.FileWriter
import xsbti.Severity
import xsbti.Position
object FrgaalJavaCompiler {
private val ENSO_SOURCES = ".enso-sources"
val frgaal = "org.frgaal" % "compiler" % "21.0.0" % "provided"
val sourceLevel = "21"
val debugArg =
"-J-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=localhost:8000"
/** Returns custom setting for compiler that delegates to `Frgaal`.
* @param classpath The dependencyClasspath of the current project
* @param sbtCompilers Configuration for compilers for current sbt project
* @param javaVersion Target Java version
* @param shouldCompileModuleInfo If true, module-info.java will be compiled
* in addition to other Java sources. See the docs
* of `shouldCompileModuleInfo` task in `build.sbt` and
* the docs of [[JPMSPlugin]].
* @param javaSourceDir The directory where Java sources are located
* @param shouldNotLimitModules Should `--limit-modules` cmdline option be passed to java process
* to limit set of modules frgaal is able to see?
* @return
*/
def compilers(
classpath: sbt.Keys.Classpath,
sbtCompilers: xsbti.compile.Compilers,
javaVersion: String,
shouldCompileModuleInfo: Boolean,
javaSourceDir: File,
shouldNotLimitModules: Boolean
) = {
// 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,
javaSourceDir = javaSourceDir,
target = javaVersion,
shouldCompileModuleInfo = shouldCompileModuleInfo,
shouldNotLimitModules = shouldNotLimitModules
)
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,
shouldCompileModuleInfo: Boolean,
javaSourceDir: File,
shouldNotLimitModules: Boolean
): Boolean = {
val (jArgs, nonJArgs) = options.partition(_.startsWith("-J"))
val debugAnotProcessorOpt = jArgs.contains(debugArg)
val strippedJArgs = jArgs
.map(_.stripPrefix("-J"))
val outputOption = CompilerArguments.outputOption(output)
val sources = sources0 map { case x: PathBasedFile =>
x.toPath.toAbsolutePath.toString
}
def asPath(a: Any): Path = a match {
case p: PathBasedFile => p.toPath
case p: Path => p
}
def asCommon(a: Any, b: Any): Path = {
var ap = asPath(a)
val bp = asPath(b)
var i = 0
while (
i < Math.min(ap.getNameCount(), bp.getNameCount()) && ap.getName(
i
) == bp.getName(i)
) {
i += 1;
}
while (ap.getNameCount() > i) {
ap = ap.getParent()
}
ap
}
val out = output.getSingleOutputAsPath().get()
val shared = sources0.fold(out)(asCommon).asInstanceOf[Path]
val allSources = if (shouldCompileModuleInfo) {
val moduleInfo = javaSourceDir.toPath.resolve("module-info.java").toFile
if (!moduleInfo.exists()) {
log.warn(
s"[FrgaalJavaCompiler] module-info.java not found in $javaSourceDir, but " +
"settings of the project require to compile it. Ensure that the setting of " +
"`shouldCompileModuleInfoManually` is correctly set."
)
log.info(
s"[FrgaalJavaCompiler] compiling ${sources.size} Java sources to $out ..."
)
sources
} else {
log.info(
s"[FrgaalJavaCompiler] compiling ${sources.size + 1} Java sources with module-info.java to $out ..."
)
Seq(moduleInfo.getAbsolutePath) ++ sources
}
} else {
log.info(
s"[FrgaalJavaCompiler] compiling ${sources.size} Java sources to $out ..."
)
sources
}
// searching for $shared/src/main/java or
// $shared/src/test/java or
// $shared/src/bench/java or etc.
def findUnder(depth: Int, dir: Path): Path = {
var d = dir
while (d.getNameCount() > depth) {
val threeUp = d.subpath(0, d.getNameCount() - depth)
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
)
}
def checkTarget(x: Any) = {
val p = asPath(x)
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")
val inATargetDir = namesCheck.exists(x => x)
inATargetDir
}
val (withTarget, noTarget) = sources0.partition(checkTarget)
val in = if (noTarget.isEmpty) {
None
} else {
Some(
findUnder(
1,
noTarget.tail.fold(asPath(noTarget.head))(asCommon).asInstanceOf[Path]
)
)
}
val generated = if (withTarget.isEmpty) {
None
} else {
Some(
findUnder(
4,
withTarget.tail
.fold(asPath(withTarget.head))(asCommon)
.asInstanceOf[Path]
)
)
}
if (shared.toFile().exists()) {
val ensoMarker = new File(shared.toFile(), ENSO_SOURCES)
val ensoConfig = new File(
shared.toFile(),
ENSO_SOURCES + "-" + out.getFileName().toString()
)
val ensoProperties = new java.util.Properties()
def storeArray(name: String, values: Seq[String]) = {
values.zipWithIndex.foreach { case (value, idx) =>
ensoProperties.setProperty(s"$name.$idx", value)
}
}
if (in.isDefined) {
ensoProperties.setProperty("input", in.get.toString())
}
if (generated.isDefined) {
ensoProperties.setProperty("generated", generated.get.toString())
}
ensoProperties.setProperty("output", out.toString())
storeArray("options", options)
source.foreach(v => ensoProperties.setProperty("source", v))
ensoProperties.setProperty("target", target)
javaHome.foreach(v =>
ensoProperties.setProperty("java.home", v.toString())
)
Using(new FileWriter(ensoConfig)) { w =>
ensoProperties.store(w, "# Enso compiler configuration")
}
Using(new FileWriter(ensoMarker)) { _ => }
} else {
throw new IllegalStateException(
"Cannot write Enso source options to " + shared + " values:\n" +
"options: " + options + " sources0: " + sources + " output: " + output
)
}
val frgaalOptions: Seq[String] =
source.map(v => Seq("-source", v)).getOrElse(Seq()) ++ Seq(
"-target",
target
)
val allArguments = outputOption ++ frgaalOptions ++ nonJArgs ++ allSources
withArgumentFile(allArguments) { argsFile =>
// List of modules that Frgaal can use for compilation
val limitModules = Seq(
"java.base",
"jdk.zipfs",
"jdk.internal.vm.compiler.management",
"java.desktop",
"java.net.http",
"java.sql",
"jdk.jfr"
)
val limitModulesArgs =
if (shouldNotLimitModules) Seq()
else
Seq(
"--limit-modules",
limitModules.mkString(",")
)
// strippedJArgs needs to be passed via cmd line, and not via the argument file
val forkArgs = (strippedJArgs ++ limitModulesArgs ++ Seq(
"-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
if (debugAnotProcessorOpt) {
log.info(
s"Frgaal compiler is about to be launched with $debugArg, which means that" +
" it will wait for a debugger to attach. The output from the compiler is by default" +
" redirected, therefore \"Listening to the debugger\" message will not be displayed." +
" You should attach the debugger now."
)
}
log.debug("[frgaal] Running " + (exe +: forkArgs).mkString(" "))
try {
exitCode = Process(exe +: forkArgs, cwd) ! javacLogger
} finally {
javacLogger.flush("frgaal", exitCode)
}
if (exitCode != 0) {
class FrgaalPosition extends xsbti.Position {
def line(): java.util.Optional[Integer] = java.util.Optional.empty()
def lineContent() = "Frgaal errors"
def offset(): java.util.Optional[Integer] = java.util.Optional.empty()
def pointer(): java.util.Optional[Integer] =
java.util.Optional.empty()
def pointerSpace(): java.util.Optional[String] =
java.util.Optional.empty()
def sourceFile(): java.util.Optional[java.io.File] =
java.util.Optional.empty()
def sourcePath(): java.util.Optional[String] =
java.util.Optional.empty()
}
class FrgaalProblem extends xsbti.Problem {
def category() = "Compiler error"
def severity(): Severity = Severity.Error
def message() = "Error in frgaal compilation"
def position(): Position = new FrgaalPosition()
}
reporter.log(new FrgaalProblem())
}
// 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.{withTemporaryDirectory, write, Newline}
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,
javaSourceDir: File,
source: Option[String] = None,
shouldCompileModuleInfo: Boolean = false,
shouldNotLimitModules: Boolean = false
) 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,
shouldCompileModuleInfo,
javaSourceDir,
shouldNotLimitModules
)
}