Added automatic JPMS support if module file is present in src dir.

This commit is contained in:
Robinson 2023-09-18 02:06:21 +02:00
parent 8d94f7536b
commit 9b5fef1987
No known key found for this signature in database
GPG Key ID: 8E7DB78588BD6F5C
4 changed files with 200 additions and 45 deletions

View File

@ -16,15 +16,11 @@
package dorkbox.gradle
import dorkbox.gradle.deps.DependencyScanner
import dorkbox.gradle.jpms.JavaXConfiguration
import dorkbox.gradle.jpms.SourceSetContainer2
import dorkbox.gradle.jpms.JpmsMultiRelease
import dorkbox.gradle.jpms.JpmsOnly
import dorkbox.gradle.jpms.JpmsSourceSetContainer
import dorkbox.os.OS
import org.gradle.api.Action
import org.gradle.api.GradleException
import org.gradle.api.JavaVersion
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.api.Task
import org.gradle.api.*
import org.gradle.api.file.DuplicatesStrategy
import org.gradle.api.specs.Specs
import org.gradle.api.tasks.SourceSetContainer
@ -115,7 +111,6 @@ open class StaticMethodsAndTools(private val project: Project) {
* Shows info if kotlin is enabled, shows exact information as to what the source-set directories are for java and kotlin
*/
fun debug() {
println("\tEnabling debug")
debug = true
project.afterEvaluate {
@ -687,6 +682,9 @@ open class StaticMethodsAndTools(private val project: Project) {
kotlinActions(task.kotlinOptions)
}
})
// now we auto-check if it's necessary to enable JPMS support for PRIMARY locations (ie, not the src9 location)
JpmsOnly.runIfNecessary(javaVersion, project, this)
} catch (ignored: Exception) {
}
}
@ -762,16 +760,16 @@ open class StaticMethodsAndTools(private val project: Project) {
/**
* Load JPMS for a specific java version using the default configuration
*/
fun jpms(javaVersion: JavaVersion): JavaXConfiguration {
return JavaXConfiguration(javaVersion, project, this)
fun jpms(javaVersion: JavaVersion): JpmsMultiRelease {
return JpmsMultiRelease(javaVersion, project, this)
}
/**
* Load and configure JPMS for a specific java version
*/
fun jpms(javaVersion: JavaVersion, block: SourceSetContainer2.() -> Unit): JavaXConfiguration {
val javaX = JavaXConfiguration(javaVersion, project, this)
block(SourceSetContainer2(javaX))
fun jpms(javaVersion: JavaVersion, block: JpmsSourceSetContainer.() -> Unit): JpmsMultiRelease {
val javaX = JpmsMultiRelease(javaVersion, project, this)
block(JpmsSourceSetContainer(javaX))
return javaX
}

View File

@ -17,11 +17,7 @@
package dorkbox.gradle.jpms
import dorkbox.gradle.StaticMethodsAndTools
import org.gradle.api.Action
import org.gradle.api.GradleException
import org.gradle.api.JavaVersion
import org.gradle.api.Project
import org.gradle.api.Task
import org.gradle.api.*
import org.gradle.api.file.SourceDirectorySet
import org.gradle.api.tasks.SourceSet
import org.gradle.api.tasks.SourceSetContainer
@ -42,13 +38,10 @@ import java.io.File
//target/classes-java9 .
@Suppress("MemberVisibilityCanBePrivate", "ObjectLiteralToLambda")
class JavaXConfiguration(javaVersion: JavaVersion, private val project: Project, gradleUtils: StaticMethodsAndTools) {
class JpmsMultiRelease(javaVersion: JavaVersion, private val project: Project, gradleUtils: StaticMethodsAndTools) {
val ver: String = javaVersion.majorVersion
// this cannot be ONLY a number, there must be something else -- intellij will *not* pickup the name if it's only a number
val nameX = "_$ver"
// If the kotlin plugin is applied, and there is a compileKotlin task.. Then kotlin is enabled
// If the kotlin plugin is applied, and there is a compileKotlin task. Then kotlin is enabled
val hasKotlin = gradleUtils.hasKotlin
val moduleFile = project.projectDir.walkTopDown().find { it.name == "module-info.java" }
@ -65,17 +58,19 @@ class JavaXConfiguration(javaVersion: JavaVersion, private val project: Project,
lateinit var compileMainKotlin: KotlinCompile
lateinit var compileTestKotlin: KotlinCompile
val lower = "jpms"
val upper = "Jpms"
// plugin provided
val mainX: SourceSet = sourceSets.maybeCreate("main$nameX")
val testX: SourceSet = sourceSets.maybeCreate("test$nameX")
// These generate warnings in Gradle 8.x -- Fixed in 8.4
val mainX: SourceSet = sourceSets.maybeCreate("${lower}Main")
val testX: SourceSet = sourceSets.maybeCreate("${lower}Test")
// the compile task NAME must match the source-set name
val compileMainXJava: JavaCompile = project.tasks.named("compileMain${nameX}Java", JavaCompile::class.java).get()
val compileTestXJava: JavaCompile = project.tasks.named("compileTest${nameX}Java", JavaCompile::class.java).get()
val compileMainXJava: JavaCompile = project.tasks.named("compile${upper}MainJava", JavaCompile::class.java).get()
val compileTestXJava: JavaCompile = project.tasks.named("compile${upper}TestJava", JavaCompile::class.java).get()
val compileModuleInfoX: JavaCompile = project.tasks.create("compileModuleInfo$nameX", JavaCompile::class.java)
val compileModuleInfoX: JavaCompile = project.tasks.create("compileJpmsModuleInfo", JavaCompile::class.java)
lateinit var compileMainXKotlin: TaskProvider<KotlinCompile>
lateinit var compileTestXKotlin: TaskProvider<KotlinCompile>
@ -83,7 +78,7 @@ class JavaXConfiguration(javaVersion: JavaVersion, private val project: Project,
// have to create a task in order to the files to get "picked up" by intellij/gradle. No *test* task? Then gradle/intellij won't be able run
// the tests, even if you MANUALLY tell intellij to run a test from the sources dir
// https://docs.gradle.org/current/dsl/org.gradle.api.tasks.testing.Test.html
val runTestX: Test = project.tasks.create("test${nameX}Test", Test::class.java)
val runTestX: Test = project.tasks.create("${lower}Test", Test::class.java)
init {
if (moduleFile == null) {
@ -135,7 +130,7 @@ class JavaXConfiguration(javaVersion: JavaVersion, private val project: Project,
})
if (hasKotlin) {
val kotlin = project.extensions.getByType(org.jetbrains.kotlin.gradle.dsl.KotlinJvmProjectExtension::class.java).sourceSets.getByName("main$nameX")
val kotlin = project.extensions.getByType(org.jetbrains.kotlin.gradle.dsl.KotlinJvmProjectExtension::class.java).sourceSets.getByName("${lower}Main")
kotlin.kotlin.apply {
// I don't like the opinionated sonatype directory structure.
@ -174,7 +169,7 @@ class JavaXConfiguration(javaVersion: JavaVersion, private val project: Project,
})
if (hasKotlin) {
val kotlin = project.extensions.getByType(org.jetbrains.kotlin.gradle.dsl.KotlinJvmProjectExtension::class.java).sourceSets.getByName("test$nameX")
val kotlin = project.extensions.getByType(org.jetbrains.kotlin.gradle.dsl.KotlinJvmProjectExtension::class.java).sourceSets.getByName("${lower}Test")
kotlin.kotlin.apply {
// I don't like the opinionated sonatype directory structure.
@ -219,20 +214,20 @@ class JavaXConfiguration(javaVersion: JavaVersion, private val project: Project,
//////////////
if (hasKotlin) {
compileMainXKotlin = project.tasks.named("compileMain${nameX}Kotlin", KotlinCompile::class.java)
compileTestXKotlin = project.tasks.named("compileTest${nameX}Kotlin", KotlinCompile::class.java)
compileMainXKotlin = project.tasks.named("compile${upper}MainKotlin", KotlinCompile::class.java)
compileTestXKotlin = project.tasks.named("compile${upper}TestKotlin", KotlinCompile::class.java)
}
// have to setup the configurations, so dependencies work correctly
val configs = project.configurations
configs.maybeCreate("main${nameX}Implementation").extendsFrom(configs.getByName("implementation")).extendsFrom(configs.getByName("compileOnly"))
configs.maybeCreate("main${nameX}Runtime").extendsFrom(configs.getByName("implementation")).extendsFrom(configs.getByName("runtimeOnly"))
configs.maybeCreate("main${nameX}CompileOnly").extendsFrom(configs.getByName("compileOnly"))
configs.maybeCreate("${lower}MainImplementation").extendsFrom(configs.getByName("implementation")).extendsFrom(configs.getByName("compileOnly"))
configs.maybeCreate("${lower}MainRuntime").extendsFrom(configs.getByName("implementation")).extendsFrom(configs.getByName("runtimeOnly"))
configs.maybeCreate("${lower}MainCompileOnly").extendsFrom(configs.getByName("compileOnly"))
configs.maybeCreate("test${nameX}Implementation").extendsFrom(configs.getByName("testImplementation")).extendsFrom(configs.getByName("testCompileOnly"))
configs.maybeCreate("test${nameX}Runtime").extendsFrom(configs.getByName("testImplementation")).extendsFrom(configs.getByName("testRuntimeOnly"))
configs.maybeCreate("test${nameX}CompileOnly").extendsFrom(configs.getByName("testCompileOnly"))
configs.maybeCreate("${upper}TestImplementation").extendsFrom(configs.getByName("testImplementation")).extendsFrom(configs.getByName("testCompileOnly"))
configs.maybeCreate("${upper}TestRuntime").extendsFrom(configs.getByName("testImplementation")).extendsFrom(configs.getByName("testRuntimeOnly"))
configs.maybeCreate("${upper}TestCompileOnly").extendsFrom(configs.getByName("testCompileOnly"))
// setup task graph and compile version
@ -278,7 +273,7 @@ class JavaXConfiguration(javaVersion: JavaVersion, private val project: Project,
dependsOn(compileMainKotlin)
}
val proj = this@JavaXConfiguration.project
val proj = this@JpmsMultiRelease.project
val allSource = proj.files(
main.allSource.srcDirs,
@ -294,7 +289,7 @@ class JavaXConfiguration(javaVersion: JavaVersion, private val project: Project,
inputs.property("moduleName", moduleName)
destinationDirectory.set(compileMainXJava.destinationDirectory.asFile.orNull)
classpath = this@JavaXConfiguration.project.files() // this resets the classpath. we use the module-path instead!
classpath = this@JpmsMultiRelease.project.files() // this resets the classpath. we use the module-path instead!
// modules require this!
@ -323,7 +318,7 @@ class JavaXConfiguration(javaVersion: JavaVersion, private val project: Project,
override fun execute(task: Task) {
task as JavaCompile
val intellijClasses = File("${this@JavaXConfiguration.project.buildDir}/classes-intellij")
val intellijClasses = File("${this@JpmsMultiRelease.project.buildDir}/classes-intellij")
if (intellijClasses.exists()) {
// copy everything to intellij also. FORTUNATELY, we know it's only going to be the `module-info` and `package-info` classes!
val directory = task.destinationDirectory.asFile.get()

View File

@ -0,0 +1,162 @@
/*
* Copyright 2021 dorkbox, llc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dorkbox.gradle.jpms
import dorkbox.gradle.StaticMethodsAndTools
import org.gradle.api.*
import org.gradle.api.tasks.SourceSet
import org.gradle.api.tasks.SourceSetContainer
import org.gradle.api.tasks.compile.JavaCompile
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
import java.io.File
// from: http://mail.openjdk.java.net/pipermail/jigsaw-dev/2017-February/011306.html
/// If you move the module-info.java to the top-level directory directory then I would expect this should work:
//
//javac --release 8 -d target/classes src/main/java/com/example/A.java src/main/java/com/example/Version.java
//javac -d target/classes src/main/java/module-info.java
//javac -d target/classes-java9 -cp target/classes src/main/java9/com/example/A.java
//jar --create --file mr.jar -C target/classes . --release 9 -C
//target/classes-java9 .
object JpmsOnly {
// check to see if we have a module-info.java file.
// if we do, then we are "JPMS" enabled, and require some fiddling.
// it is INCREDIBLY stupid this is as difficult as this is.
// https://youtrack.jetbrains.com/issue/KT-55389/Gradle-plugin-should-expose-an-extension-method-to-automatically-enable-JPMS-for-Kotlin-compiled-output
fun runIfNecessary(javaVersion: JavaVersion, project: Project, gradleUtils: StaticMethodsAndTools) {
val ver = javaVersion.majorVersion.toIntOrNull()
if (ver == null || ver < 9) {
// obviously not going to happen!
return
}
// If the kotlin plugin is applied, and there is a compileKotlin task.. Then kotlin is enabled
val hasKotlin = gradleUtils.hasKotlin
val moduleFile = project.projectDir.walkTopDown().find { it.name == "module-info.java" }
var moduleName: String
if (moduleFile == null) {
// Cannot manage JPMS build without a `module-info.java` file....
return
}
val srcName = moduleFile.parentFile.name
if (srcName.startsWith("src") && srcName.last().isDigit()) {
// if the module-file is in our srcX directory, we ignore these steps, since we would be building a multi-release jar
return
}
// also the source dirs have been configured/setup.
moduleName = moduleFile.readLines()[0].split(" ")[1].trimEnd('{')
if (moduleName.isEmpty()) {
throw GradleException("The module name must be specified in the module-info file! Verify file: $moduleFile")
}
val info = when {
hasKotlin -> "Initializing JPMS $ver, Java/Kotlin [$moduleName]"
else -> "Initializing JPMS $ver, Java [$moduleName]"
}
println("\t$info")
val sourceSets = project.extensions.getByName("sourceSets") as SourceSetContainer
val main: SourceSet = sourceSets.named("main", org.gradle.api.tasks.SourceSet::class.java).get()
val compileMainJava: JavaCompile = project.tasks.named("compileJava", JavaCompile::class.java).get()
lateinit var compileMainKotlin: KotlinCompile
if (hasKotlin) {
// can only setup the NORMAL tasks first
compileMainKotlin = project.tasks.named("compileKotlin", KotlinCompile::class.java).get()
}
// make sure defaults are loaded
StaticMethodsAndTools.idea(project) {
if (module.sourceDirs == null) {
module.sourceDirs = setOf<File>()
}
// make sure there is something there!
module.testSources.from(setOf<File>())
module.testResources.from(setOf<File>())
}
project.tasks.named("compileJava", JavaCompile::class.java) {
// modules require this!
it.doFirst(object: Action<Task> {
override fun execute(task: Task) {
task as JavaCompile
val allCompiled = if (hasKotlin) {
project.files(compileMainJava.destinationDirectory.asFile.orNull, compileMainKotlin.destinationDirectory.asFile.orNull)
} else {
project.files(compileMainJava.destinationDirectory.asFile.orNull)
}
// the SOURCE of the module-info.java file. It uses **EVERYTHING**
task.options.compilerArgs.addAll(listOf(
"-implicit:none",
"-Xpkginfo:always", // compile the package-info.java files as well (normally it does not)
"--module-path", main.compileClasspath.asPath,
"--patch-module", "$moduleName=" + allCompiled.asPath // add our existing, compiled classes so module-info can find them
))
}
})
it.doLast(object: Action<Task> {
override fun execute(task: Task) {
task as JavaCompile
val intellijClasses = File("${project.layout.buildDirectory.locationOnly.get()}/classes-intellij")
if (intellijClasses.exists()) {
// copy everything to intellij also. FORTUNATELY, we know it's only going to be the `module-info` and `package-info` classes!
val directory = task.destinationDirectory.asFile.get()
val moduleInfo = directory.walkTopDown().filter { it.name == "module-info.class" }.toList()
val packageInfo = directory.walkTopDown().filter { it.name == "package-info.class" }.toList()
val name = when {
moduleInfo.isNotEmpty() && packageInfo.isNotEmpty() -> "module-info and package-info"
moduleInfo.isNotEmpty() && packageInfo.isEmpty() -> "module-info"
else -> "package-info"
}
println("\tCopying $name files into the intellij classes location...")
moduleInfo.forEach {
val newLocation = File(intellijClasses, it.relativeTo(directory).path)
it.copyTo(newLocation, overwrite = true)
}
packageInfo.forEach {
val newLocation = File(intellijClasses, it.relativeTo(directory).path)
it.copyTo(newLocation, overwrite = true)
}
}
}
})
}
}
}

View File

@ -18,7 +18,7 @@ package dorkbox.gradle.jpms
import org.gradle.api.tasks.SourceSet
class SourceSetContainer2(private val javaX: JavaXConfiguration) {
class JpmsSourceSetContainer(private val javaX: JpmsMultiRelease) {
fun main(block: SourceSet.() -> Unit) {
block(javaX.mainX)
}