2024-05-11 10:51:11 +03:00
|
|
|
import sbt._
|
|
|
|
import sbt.Keys._
|
2024-09-25 22:33:13 +03:00
|
|
|
import sbt.internal.util.ManagedLogger
|
2023-12-18 20:22:16 +03:00
|
|
|
|
|
|
|
import java.io.File
|
2024-09-25 22:33:13 +03:00
|
|
|
import java.util.jar.JarFile
|
|
|
|
import scala.collection.mutable
|
2023-12-18 20:22:16 +03:00
|
|
|
|
|
|
|
/** 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.
|
|
|
|
*
|
2024-09-25 22:33:13 +03:00
|
|
|
* Note that the settings of this plugin are *scoped* to `Compile` and `Test` configurations, so
|
|
|
|
* you need to always specify the configuration, for example, `Compile / moduleDependencies` instead
|
|
|
|
* of just `moduleDependencies`.
|
|
|
|
*
|
2023-12-18 20:22:16 +03:00
|
|
|
* 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`.
|
2024-08-19 20:39:03 +03:00
|
|
|
*
|
|
|
|
* == How to work with this plugin ==
|
2024-09-25 22:33:13 +03:00
|
|
|
*
|
|
|
|
* - Specify external dependencies in `Compile / moduleDependencies` with something like:
|
2024-08-19 20:39:03 +03:00
|
|
|
* {{{
|
2024-09-25 22:33:13 +03:00
|
|
|
* Compile / moduleDependencies := Seq(
|
2024-08-19 20:39:03 +03:00
|
|
|
* "org.apache.commons" % "commons-lang3" % "3.11",
|
|
|
|
* )
|
|
|
|
* }}}
|
2024-09-25 22:33:13 +03:00
|
|
|
* - Specify internal dependencies in `Compile / internalModuleDependencies` with something like:
|
|
|
|
* {{{
|
|
|
|
* Compile / internalModuleDependencies := Seq(
|
|
|
|
* (myProject / Compile / exportedModule).value
|
|
|
|
* )
|
|
|
|
* }}}
|
|
|
|
* - This ensures that `myProject` will be compiled, along with its `module-info.java`, and
|
|
|
|
* the resulting directory with all the classes, along with `module-info.class` will be put on
|
|
|
|
* the module path.
|
2024-08-19 20:39:03 +03:00
|
|
|
* - Ensure that all the module dependencies were gathered by the plugin correctly by
|
|
|
|
* `print modulePath`.
|
|
|
|
* - If not, make sure that these dependencies are in `libraryDependencies`.
|
|
|
|
* Debug this with `print dependencyClasspath`.
|
|
|
|
*
|
2024-09-25 22:33:13 +03:00
|
|
|
* == Mixed projects ==
|
|
|
|
*
|
|
|
|
* A project with both Java and Scala sources that call into each other is called *mixed*.
|
|
|
|
* sbt sets `compileOrder := CompileOrder.Mixed` for these projects.
|
|
|
|
* Having `module-info.java` and trying to compile such project fails, because sbt first
|
|
|
|
* tries to parse all the Java sources with its own custom parser, that does not recognize
|
|
|
|
* `module-info.java`.
|
|
|
|
* One has to **exclude** the `module-info.java` from the compilation with setting
|
|
|
|
* {{{
|
|
|
|
* excludeFilter := excludeFilter.value || "module-info.java"
|
|
|
|
* }}}
|
|
|
|
* This plugin tries to determine this case and will force the compilation of `module-info.java`.
|
|
|
|
* To see if this will be the case, check the value of `shouldCompileModuleInfoManually` task by
|
|
|
|
* `print shouldCompileModuleInfoManually`.
|
|
|
|
* In rare case, you have to override either `shouldCompileInfoManually` or `forceModuleInfoCompilation`.
|
|
|
|
*
|
2024-08-19 20:39:03 +03:00
|
|
|
* == Caveats ==
|
2024-09-25 22:33:13 +03:00
|
|
|
*
|
2024-08-19 20:39:03 +03:00
|
|
|
* - This plugin cannot determine transitive dependencies of modules in `moduleDependencies`.
|
2024-09-25 22:33:13 +03:00
|
|
|
* As opposed to `libraryDependencies` which automatically gathers all the transitive dependencies.
|
2023-12-18 20:22:16 +03:00
|
|
|
*/
|
|
|
|
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"
|
|
|
|
)
|
2024-08-19 20:39:03 +03:00
|
|
|
val moduleDependencies = taskKey[Seq[ModuleID]](
|
|
|
|
"Modules dependencies that will be added to --module-path option. List all the sbt modules " +
|
2024-09-25 22:33:13 +03:00
|
|
|
"that should be added on module-path. Use it only for external dependencies."
|
|
|
|
)
|
|
|
|
|
|
|
|
val internalModuleDependencies = taskKey[Seq[File]](
|
|
|
|
"""
|
|
|
|
|Inter-project JPMS module dependencies. This task has a different return type than
|
|
|
|
|`moduleDependencies` task. It returns a sequence of files on purpose - that way,
|
|
|
|
|projects are able to override their `exportedModule` task to somehow prepare for
|
|
|
|
|modularization.
|
|
|
|
|""".stripMargin
|
2024-08-19 20:39:03 +03:00
|
|
|
)
|
2024-09-25 22:33:13 +03:00
|
|
|
|
2023-12-18 20:22:16 +03:00
|
|
|
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
|
|
|
|
)
|
2024-09-25 22:33:13 +03:00
|
|
|
|
|
|
|
val addOpens = taskKey[Map[String, Seq[String]]](
|
|
|
|
"""
|
|
|
|
|A map of module names with packages to modules that will be put into --add-opens option to java.
|
|
|
|
|Note that this option is not added to `javac`, only to `java`.
|
|
|
|
|For example `org.enso.runtime/org.enso.my.package=ALL-UNNAMED` will open the package
|
|
|
|
|`org.enso.my.package` in the module `org.enso.runtime` to all unnamed modules.
|
|
|
|
|Specify it as `addOpens := Map("org.enso.runtime/org.enso.my.package" -> List("ALL-UNNAMED"))`.
|
|
|
|
|""".stripMargin
|
|
|
|
)
|
|
|
|
|
|
|
|
val exportedModule = taskKey[File](
|
|
|
|
"""
|
|
|
|
|Similarly to `exportedProducts` task, this task returns a file that can be
|
|
|
|
|directly put on module-path. For majority of projects, this task will have
|
|
|
|
|the same result as `exportedProducts`. The purpose of this task is to be able
|
|
|
|
|for the projects to *prepare* for modularization. For example, mixed Scala/Java
|
|
|
|
|projects are known to be problematic for modularization, and one needs to manually
|
|
|
|
|compile `module-info.java` file. For this mixed project, this task can be declared
|
|
|
|
|to depend on `compileModuleInfo`.
|
|
|
|
|""".stripMargin
|
|
|
|
)
|
|
|
|
|
|
|
|
val exportedModuleBin = taskKey[File](
|
|
|
|
"Similar to `packageBin` task. This task returns a modular JAR archive that can be " +
|
|
|
|
"directly put on module-path"
|
|
|
|
)
|
|
|
|
|
|
|
|
val shouldCompileModuleInfoManually = taskKey[Boolean](
|
|
|
|
"If module-info.java should be compiled by us and not by sbt. " +
|
|
|
|
"DO NOT USE DIRECTLY."
|
|
|
|
)
|
|
|
|
|
|
|
|
val forceModuleInfoCompilation = taskKey[Boolean](
|
|
|
|
"Force module-info.java compilation. " +
|
|
|
|
"DO NOT USE DIRECTLY."
|
|
|
|
)
|
|
|
|
|
|
|
|
val compileModuleInfo = taskKey[Unit](
|
|
|
|
"Compiles only module-info.java in some special cases. " +
|
|
|
|
"DO NOT USE DIRECTLY."
|
|
|
|
)
|
|
|
|
|
2023-12-18 20:22:16 +03:00
|
|
|
}
|
|
|
|
|
2024-05-11 10:51:11 +03:00
|
|
|
import autoImport._
|
2023-12-18 20:22:16 +03:00
|
|
|
|
2024-09-25 22:33:13 +03:00
|
|
|
override lazy val projectSettings: Seq[Setting[_]] = {
|
|
|
|
// All the settings are scoped for Compile and Test
|
|
|
|
Seq(Compile, Test).flatMap { config: Configuration =>
|
|
|
|
Seq(
|
|
|
|
config / addModules := Seq.empty,
|
|
|
|
config / moduleDependencies := Seq.empty,
|
|
|
|
config / internalModuleDependencies := Seq.empty,
|
|
|
|
config / shouldCompileModuleInfoManually := {
|
|
|
|
val javaSrcDir = (config / javaSource).value
|
|
|
|
val modInfo =
|
|
|
|
javaSrcDir.toPath.resolve("module-info.java").toFile
|
|
|
|
val hasModInfo = modInfo.exists
|
|
|
|
val projName = moduleName.value
|
|
|
|
val logger = streams.value.log
|
|
|
|
val hasScalaSources = (config / scalaSource).value.exists()
|
|
|
|
val _compileOrder = (config / compileOrder).value
|
|
|
|
val res =
|
|
|
|
_compileOrder == CompileOrder.Mixed &&
|
|
|
|
hasModInfo &&
|
|
|
|
hasScalaSources
|
|
|
|
if (res) {
|
|
|
|
logger.debug(
|
|
|
|
s"[JPMSPlugin] Project '$projName' will have `module-info.java` compiled " +
|
|
|
|
"manually. If this is not the intended behavior, consult the documentation " +
|
|
|
|
"of JPMSPlugin."
|
|
|
|
)
|
|
|
|
}
|
|
|
|
// Check excludeFilter - there should be module-info.java specified
|
|
|
|
if (res && !excludeFilter.value.accept(modInfo)) {
|
|
|
|
logger.error(
|
|
|
|
s"[JPMSPlugin/$projName] `module-info.java` is not in `excludeFilter`. " +
|
|
|
|
"You should add module-info.java to " +
|
|
|
|
"`excludedFilter` so that sbt does not handle the compilation. Check docs of JPMSPlugin."
|
|
|
|
)
|
|
|
|
}
|
|
|
|
res
|
|
|
|
},
|
|
|
|
// module-info.java compilation will be forced iff there are no other Java sources except
|
|
|
|
// for module-info.java.
|
|
|
|
config / forceModuleInfoCompilation := Def.taskIf {
|
|
|
|
if ((config / shouldCompileModuleInfoManually).value) {
|
|
|
|
val javaSources = (config / unmanagedSources).value
|
|
|
|
.filter(_.getName.endsWith(".java"))
|
|
|
|
// If there are no Java source in `unmanagedSources`, it means that sbt will
|
|
|
|
// not call Java compiler. So we force it to compile `module-info.java`.
|
|
|
|
javaSources.isEmpty
|
|
|
|
} else {
|
|
|
|
false
|
|
|
|
}
|
|
|
|
}.value,
|
|
|
|
config / compileModuleInfo := Def.taskIf {
|
|
|
|
if ((config / forceModuleInfoCompilation).value) {
|
|
|
|
JPMSUtils.compileModuleInfo().value
|
|
|
|
} else {
|
|
|
|
// nop
|
|
|
|
()
|
|
|
|
}
|
|
|
|
}.value,
|
|
|
|
// modulePath is set based on `moduleDependencies` and `internalModuleDependencies`
|
|
|
|
config / modulePath := {
|
|
|
|
// Do not use fullClasspath here - it will result in an infinite recursion
|
|
|
|
// and sbt will not be able to detect the cycle.
|
|
|
|
transformModuleDependenciesToModulePath(
|
|
|
|
(config / moduleDependencies).value,
|
|
|
|
(config / internalModuleDependencies).value,
|
|
|
|
(config / dependencyClasspath).value,
|
|
|
|
streams.value.log,
|
|
|
|
moduleName.value,
|
|
|
|
scalaBinaryVersion.value
|
|
|
|
)
|
|
|
|
},
|
|
|
|
// Returns the reference to target/classes directory and ensures that module-info
|
|
|
|
// is compiled and present in the target directory.
|
|
|
|
config / exportedModule := Def
|
|
|
|
.task {
|
|
|
|
val targetClassDir = (config / exportedProducts).value
|
|
|
|
.map(_.data)
|
|
|
|
.head
|
|
|
|
val logger = streams.value.log
|
|
|
|
val projName = moduleName.value
|
|
|
|
if (!isModule(targetClassDir)) {
|
|
|
|
logger.warn(
|
|
|
|
s"[JPMSPlugin/$projName] The target classes directory ${targetClassDir.getAbsolutePath} is not " +
|
|
|
|
"a module - it does not contain module-info.class. Make sure the `compileModuleInfo` task " +
|
|
|
|
"is set correctly."
|
|
|
|
)
|
|
|
|
}
|
|
|
|
targetClassDir
|
|
|
|
}
|
|
|
|
.dependsOn(config / compileModuleInfo)
|
|
|
|
.dependsOn(config / compile)
|
|
|
|
.value,
|
|
|
|
config / exportedModuleBin := {
|
|
|
|
(config / packageBin)
|
|
|
|
.dependsOn(config / exportedModule)
|
|
|
|
.value
|
|
|
|
},
|
|
|
|
// All the exported artifact names will be stripped.
|
|
|
|
// Do not use the default sbt artifact name which inserts scala version and module
|
|
|
|
// revision.
|
|
|
|
config / artifactName := stripArtifactName,
|
|
|
|
config / patchModules := Map.empty,
|
|
|
|
config / addExports := Map.empty,
|
|
|
|
config / addReads := Map.empty,
|
|
|
|
config / addOpens := Map.empty,
|
|
|
|
// No --add-opens option to javac
|
|
|
|
config / javacOptions ++= {
|
|
|
|
constructOptions(
|
|
|
|
streams.value.log,
|
|
|
|
moduleName.value,
|
|
|
|
(config / modulePath).value,
|
|
|
|
(config / addModules).value,
|
|
|
|
(config / patchModules).value,
|
|
|
|
(config / addExports).value,
|
|
|
|
(config / addReads).value
|
|
|
|
)
|
|
|
|
},
|
|
|
|
config / javaOptions ++= {
|
|
|
|
constructOptions(
|
|
|
|
streams.value.log,
|
|
|
|
moduleName.value,
|
|
|
|
(config / modulePath).value,
|
|
|
|
(config / addModules).value,
|
|
|
|
(config / patchModules).value,
|
|
|
|
(config / addExports).value,
|
|
|
|
(config / addReads).value,
|
|
|
|
(config / addOpens).value
|
|
|
|
)
|
|
|
|
},
|
|
|
|
// Sanitize cmd line arguments
|
|
|
|
config / javacOptions := joinModulePathOption(
|
|
|
|
(config / javacOptions).value
|
|
|
|
),
|
|
|
|
config / javaOptions := joinModulePathOption(
|
|
|
|
(config / javaOptions).value
|
|
|
|
)
|
2024-08-19 20:39:03 +03:00
|
|
|
)
|
2023-12-18 20:22:16 +03:00
|
|
|
}
|
2024-09-25 22:33:13 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
/** @param moduleDeps External module dependencies, fetched from `moduleDependencies` task.
|
|
|
|
* @param classPath Dependency class path of the project. From this class path, external dependencies
|
|
|
|
* will be searched for.
|
|
|
|
* @param internalModuleDeps Internal module dependencies, fetched from `internalModuleDependencies` task.
|
|
|
|
* It is assumed that there is `module-info.class` in the root of the internal
|
|
|
|
* module dependency.
|
|
|
|
* @param logger
|
|
|
|
* @param currProjName Current name of the local project, for debugging purposes.
|
|
|
|
* @return
|
|
|
|
*/
|
|
|
|
private def transformModuleDependenciesToModulePath(
|
|
|
|
moduleDeps: Seq[ModuleID],
|
|
|
|
internalModuleDeps: Seq[File],
|
|
|
|
classPath: Def.Classpath,
|
|
|
|
logger: ManagedLogger,
|
|
|
|
scalaBinaryVersion: String,
|
|
|
|
currProjName: String
|
|
|
|
): Seq[File] = {
|
|
|
|
moduleDeps.foreach { moduleDep =>
|
|
|
|
if (moduleDep.organization == "org.enso") {
|
|
|
|
logger.warn(
|
|
|
|
s"[JPMSPlugin/$currProjName] ModuleID $moduleDep specified inside " +
|
|
|
|
"`moduleDependencies` task. This is and internal dependency " +
|
|
|
|
"and should be specified in `internalModuleDependencies`. "
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
internalModuleDeps.foreach { internalModuleDep =>
|
|
|
|
if (internalModuleDep.isDirectory) {
|
|
|
|
val modInfo =
|
|
|
|
internalModuleDep.toPath.resolve("module-info.class").toFile
|
|
|
|
if (!modInfo.exists()) {
|
|
|
|
logger.warn(
|
|
|
|
s"[JPMSPlugin/$currProjName] Internal module dependency $internalModuleDep does not contain " +
|
|
|
|
"module-info.class file. Ensure it is an automatic module."
|
|
|
|
)
|
|
|
|
}
|
|
|
|
} else if (internalModuleDep.getName.endsWith(".jar")) {
|
|
|
|
val jarFile = new JarFile(internalModuleDep)
|
|
|
|
val modInfoEntry = jarFile.getJarEntry("module-info.class")
|
|
|
|
if (modInfoEntry == null) {
|
|
|
|
logger.warn(
|
|
|
|
s"[JPMSPlugin/$currProjName] Internal module dependency (JAR) $internalModuleDep does not contain " +
|
|
|
|
"module-info.class file. Ensure it is an automatic module."
|
|
|
|
)
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
logger.error(
|
|
|
|
s"[JPMSPlugin/$currProjName] Internal module dependency $internalModuleDep is not a directory " +
|
|
|
|
"nor a jar file. This is not supported. "
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
val cp = JPMSUtils.filterModulesFromClasspath(
|
|
|
|
classPath,
|
|
|
|
moduleDeps,
|
|
|
|
logger,
|
|
|
|
currProjName,
|
|
|
|
scalaBinaryVersion,
|
|
|
|
shouldContainAll = true
|
|
|
|
)
|
|
|
|
val externalFiles = cp.map(_.data)
|
|
|
|
externalFiles ++ internalModuleDeps
|
|
|
|
}
|
|
|
|
|
|
|
|
private def isModule(file: File): Boolean = {
|
|
|
|
if (file.isDirectory) {
|
|
|
|
val modInfo = file.toPath.resolve("module-info.class").toFile
|
|
|
|
modInfo.exists()
|
|
|
|
} else if (file.getName.endsWith(".jar")) {
|
|
|
|
val jarFile = new JarFile(file)
|
|
|
|
val modInfoEntry = jarFile.getJarEntry("module-info")
|
|
|
|
modInfoEntry == null
|
|
|
|
} else {
|
|
|
|
false
|
|
|
|
}
|
|
|
|
}
|
2023-12-18 20:22:16 +03:00
|
|
|
|
2024-09-25 22:33:13 +03:00
|
|
|
private def constructOptions(
|
2023-12-18 20:22:16 +03:00
|
|
|
log: Logger,
|
2024-09-25 22:33:13 +03:00
|
|
|
curProjName: String,
|
2023-12-18 20:22:16 +03:00
|
|
|
modulePath: Seq[File],
|
|
|
|
addModules: Seq[String] = Seq.empty,
|
|
|
|
patchModules: Map[String, Seq[File]] = Map.empty,
|
|
|
|
addExports: Map[String, Seq[String]] = Map.empty,
|
2024-09-25 22:33:13 +03:00
|
|
|
addReads: Map[String, Seq[String]] = Map.empty,
|
|
|
|
addOpens: Map[String, Seq[String]] = Map.empty
|
2023-12-18 20:22:16 +03:00
|
|
|
): Seq[String] = {
|
|
|
|
val patchOpts: Seq[String] = patchModules.flatMap {
|
|
|
|
case (moduleName, dirsToPatch) =>
|
|
|
|
val patchStr = dirsToPatch
|
|
|
|
.map(_.getAbsolutePath)
|
|
|
|
.mkString(File.pathSeparator)
|
|
|
|
Seq(
|
|
|
|
"--patch-module",
|
|
|
|
s"$moduleName=$patchStr"
|
|
|
|
)
|
|
|
|
}.toSeq
|
|
|
|
|
|
|
|
val addExportsOpts: Seq[String] = addExports.flatMap {
|
|
|
|
case (modPkgName, targetModules) =>
|
|
|
|
if (!modPkgName.contains("/")) {
|
2024-09-25 22:33:13 +03:00
|
|
|
log.error(
|
|
|
|
s"[JPMSPlugin/$curProjName] Invalid module/package name: $modPkgName " +
|
|
|
|
"in `addExports` task."
|
|
|
|
)
|
2023-12-18 20:22:16 +03:00
|
|
|
}
|
|
|
|
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
|
|
|
|
|
2024-09-25 22:33:13 +03:00
|
|
|
val addOpensOpts = addOpens.flatMap { case (modPkgName, targetModules) =>
|
|
|
|
if (!modPkgName.contains("/")) {
|
|
|
|
log.error(
|
|
|
|
s"[JPMSPlugin/$curProjName] Invalid module/package name: $modPkgName " +
|
|
|
|
"in `addOpens` task."
|
|
|
|
)
|
|
|
|
}
|
|
|
|
Seq(
|
|
|
|
"--add-opens",
|
|
|
|
modPkgName + "=" + targetModules.mkString(",")
|
|
|
|
)
|
|
|
|
}.toSeq
|
|
|
|
|
|
|
|
modulePathOpts ++ addModsOpts ++ patchOpts ++ addExportsOpts ++ addReadsOpts ++ addOpensOpts
|
|
|
|
}
|
|
|
|
|
|
|
|
/** Searches for multiple `--module-path` cmd line options and joins them into a single
|
|
|
|
* option.
|
|
|
|
* If there are multiple `--module-path` options passed to `java` or `javac`, only the
|
|
|
|
* last one specified is considered.
|
|
|
|
* Note that this is not an issue for other JPMS-related cmd line options, like
|
|
|
|
* `--add-modules`
|
|
|
|
* @param opts Current value of cmd line options
|
|
|
|
* @return
|
|
|
|
*/
|
|
|
|
private def joinModulePathOption(
|
|
|
|
opts: Seq[String]
|
|
|
|
): Seq[String] = {
|
|
|
|
val modulePathOpt = new StringBuilder()
|
|
|
|
val optIdxToRemove = mutable.HashSet[Int]()
|
|
|
|
// Find all `--module-path` options and join them into a single option
|
|
|
|
for ((opt, idx) <- opts.zipWithIndex) {
|
|
|
|
if (opt == "--module-path" || opt == "-p") {
|
|
|
|
optIdxToRemove += idx
|
|
|
|
optIdxToRemove += idx + 1
|
|
|
|
modulePathOpt.append(opts(idx + 1))
|
|
|
|
modulePathOpt.append(File.pathSeparator)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (modulePathOpt.nonEmpty) {
|
|
|
|
// Remove the last colon
|
|
|
|
modulePathOpt.deleteCharAt(modulePathOpt.length - 1)
|
|
|
|
val newOpts = mutable.ArrayBuffer[String]()
|
|
|
|
for ((opt, idx) <- opts.zipWithIndex) {
|
|
|
|
if (!optIdxToRemove.contains(idx)) {
|
|
|
|
newOpts += opt
|
|
|
|
}
|
|
|
|
}
|
|
|
|
Seq(
|
|
|
|
"--module-path",
|
|
|
|
modulePathOpt.toString
|
|
|
|
) ++ newOpts
|
|
|
|
} else {
|
|
|
|
opts
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/** Does not use the default artifact name which inserts scala version and module version.
|
|
|
|
*/
|
|
|
|
private def stripArtifactName(
|
|
|
|
scalaVersion: ScalaVersion,
|
|
|
|
modId: ModuleID,
|
|
|
|
artifact: Artifact
|
|
|
|
): String = {
|
|
|
|
// Classifier optionally adds e.g. `-test` or `-sources` to the artifact name
|
|
|
|
// This needs to be retained for the tests to work.
|
|
|
|
val classifierStr = artifact.classifier match {
|
|
|
|
case None => ""; case Some(c) => "-" + c
|
|
|
|
}
|
|
|
|
artifact.name + classifierStr + "." + artifact.extension
|
2023-12-18 20:22:16 +03:00
|
|
|
}
|
|
|
|
}
|