enso/project/JPMSPlugin.scala
2023-12-18 18:22:16 +01:00

183 lines
5.9 KiB
Scala

import sbt.*
import sbt.Keys.*
import sbt.internal.inc.{CompileOutput, PlainVirtualFile}
import sbt.util.CacheStore
import sbtassembly.Assembly.{Dependency, JarEntry, Project}
import sbtassembly.{CustomMergeStrategy, MergeStrategy}
import xsbti.compile.IncToolOptionsUtil
import java.io.File
/** An automatic plugin that handles everything related to JPMS modules. One needs to explicitly
* enable this plugin in a project with `.enablePlugins(JPMSPlugin)`. The keys and tasks provided by this plugin
* corresponds to the module-related options of `javac` and `java` commands.
*
* This plugin injects all the module-specific options to `javaOptions`, based on
* the settings of this plugin.
*
* If this plugin is enabled, and no settings/tasks from this plugin are used, then the plugin will
* not inject anything into `javaOptions` or `javacOptions`.
*/
object JPMSPlugin extends AutoPlugin {
object autoImport {
val javaModuleName =
settingKey[String]("The name of the Java (JPMS) module")
val addModules = settingKey[Seq[String]](
"Module names that will be added to --add-modules option"
)
val modulePath = taskKey[Seq[File]](
"Directories (Jar archives or expanded Jar archives) that will be put into " +
"--module-path option"
)
val patchModules = taskKey[Map[String, Seq[File]]](
"""
|A map of module names to directories (Jar archives or expanded Jar archives) that will be
|put into --patch-module option.
|""".stripMargin
)
val addExports = taskKey[Map[String, Seq[String]]](
"""
|A map of module names to packages that will be put into --add-exports option.
|The format of `--add-exports` option is `module/package=target-module(,target-module)*`
|The key in the map is `module/package` and the value is a sequence of target modules
|""".stripMargin
)
val addReads = taskKey[Map[String, Seq[String]]](
"""
|A map of module names to modules that will be put into --add-reads option.
|When a module A reads a module B, it means that it "depends" on it - it has the same
|effect as if module A would have `requires B` in its module-info.java file.
|""".stripMargin
)
val compileModuleInfo = taskKey[Unit]("Compile module-info.java")
val modulePathTestOptions_ = taskKey[Seq[String]](
"Assembles options for the JVM for running tests with all the required modules. " +
"Including truffle-compiler and org.enso.runtime modules and all their dependencies."
)
}
import autoImport.*
override lazy val projectSettings: Seq[Setting[_]] = Seq(
addModules := Seq.empty,
modulePath := Seq.empty,
patchModules := Map.empty,
addExports := Map.empty,
addReads := Map.empty,
compileModuleInfo := {},
// javacOptions only inject --module-path and --add-modules, not the rest of the
// options.
Compile / javacOptions ++= {
constructOptions(
streams.value.log,
modulePath = (Compile / modulePath).value,
addModules = (Compile / addModules).value
)
},
Compile / javaOptions ++= {
constructOptions(
streams.value.log,
(Compile / modulePath).value,
(Compile / addModules).value,
(Compile / patchModules).value,
(Compile / addExports).value,
(Compile / addReads).value
)
},
Test / javacOptions ++= {
constructOptions(
streams.value.log,
modulePath = (Test / modulePath).value,
addModules = (Test / addModules).value
)
},
Test / javaOptions ++= {
constructOptions(
streams.value.log,
(Test / modulePath).value,
(Test / addModules).value,
(Test / patchModules).value,
(Test / addExports).value,
(Test / addReads).value
)
}
)
def constructOptions(
log: Logger,
modulePath: Seq[File],
addModules: Seq[String] = Seq.empty,
patchModules: Map[String, Seq[File]] = Map.empty,
addExports: Map[String, Seq[String]] = Map.empty,
addReads: Map[String, Seq[String]] = Map.empty
): Seq[String] = {
val patchOpts: Seq[String] = patchModules.flatMap {
case (moduleName, dirsToPatch) =>
ensureDirectoriesExist(dirsToPatch, log)
val patchStr = dirsToPatch
.map(_.getAbsolutePath)
.mkString(File.pathSeparator)
Seq(
"--patch-module",
s"$moduleName=$patchStr"
)
}.toSeq
ensureDirectoriesExist(modulePath, log)
val addExportsOpts: Seq[String] = addExports.flatMap {
case (modPkgName, targetModules) =>
if (!modPkgName.contains("/")) {
log.error(s"JPMSPlugin: Invalid module/package name: $modPkgName")
}
Seq(
"--add-exports",
modPkgName + "=" + targetModules.mkString(",")
)
}.toSeq
val modulePathOpts = if (modulePath.isEmpty) {
Seq.empty
} else {
Seq(
"--module-path",
modulePath.map(_.getAbsolutePath).mkString(File.pathSeparator)
)
}
val addModsOpts = if (addModules.isEmpty) {
Seq.empty
} else {
Seq(
"--add-modules",
addModules.mkString(",")
)
}
val addReadsOpts = addReads.flatMap { case (modName, targetModules) =>
Seq(
"--add-reads",
modName + "=" + targetModules.mkString(",")
)
}.toSeq
modulePathOpts ++ addModsOpts ++ patchOpts ++ addExportsOpts ++ addReadsOpts
}
/** Java does not mandate that the directories specified in the module path or
* in --patch-module exist, but it is usefull to report at least warnings.
* @param dirs
* @param log
*/
private def ensureDirectoriesExist(
dirs: Seq[File],
log: Logger
): Unit = {
dirs.foreach { dir =>
if (!dir.exists()) {
log.warn(s"JPMSPlugin: Directory $dir does not exist.")
}
}
}
}