From e8797269be662d8043ddcb44ef269ce553811952 Mon Sep 17 00:00:00 2001 From: Tim Hurman Date: Tue, 4 Nov 2025 11:26:40 +0900 Subject: [PATCH 1/2] Converted the versionsProvidingConfiguration to be a configuration rather than a string identifying the configuration. Added the ability to export all packages with the exception of named ones. --- README.md | 4 +- build.gradle.kts | 19 +++--- .../ExtraJavaModuleInfoPluginExtension.java | 2 +- .../ExtraJavaModuleInfoTransform.java | 34 ++++++++-- .../javamodule/moduleinfo/ModuleInfo.java | 24 +++++-- .../moduleinfo/PublishedMetadata.java | 7 ++- .../test/EdgeCasesFunctionalTest.groovy | 63 +++++++++++++++++++ ...llDefinedDependenciesFunctionalTest.groovy | 2 +- 8 files changed, 131 insertions(+), 24 deletions(-) diff --git a/README.md b/README.md index 07fd12a..f7bbf35 100644 --- a/README.md +++ b/README.md @@ -78,6 +78,8 @@ extraJavaModuleInfo { // "org.mycompany.server", "org.mycompany.client") // or simply export all packages // exportAllPackages() + // or export all packages except specific named ones + // exportAllPackagesExcept("org.mycompany.notgood1", "org.mycompany.notgood2") requiresTransitive("org.apache.commons.logging") requires("java.sql") @@ -216,7 +218,7 @@ This needs to be done in all subprojects. You use the `versionsProvidingConfigur ```kotlin extraJavaModuleInfo { - versionsProvidingConfiguration = "mainRuntimeClasspath" + versionsProvidingConfiguration = project.provider { project.configurations.named("mainRuntimeClasspath").get() } } ``` diff --git a/build.gradle.kts b/build.gradle.kts index 3984dda..1c051f5 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,5 +1,6 @@ plugins { id("groovy") + `java-gradle-plugin` id("org.gradlex.internal.plugin-publish-conventions") version "0.6" } @@ -15,7 +16,9 @@ java { dependencies { implementation("org.ow2.asm:asm:9.9") - testImplementation("org.spockframework:spock-core:2.3-groovy-4.0") + testImplementation("org.spockframework:spock-core:2.3-groovy-4.0") { + exclude(group = "org.codehaus.groovy") + } testRuntimeOnly("org.junit.platform:junit-platform-launcher") } @@ -33,21 +36,21 @@ pluginPublishConventions { } } -tasks.test { - description = "Runs tests against the Gradle version the plugin is built with" +tasks.withType().configureEach { + group = "verification" classpath = sourceSets.test.get().runtimeClasspath + testClassesDirs = sourceSets.test.get().output.classesDirs useJUnitPlatform() maxParallelForks = 4 } +tasks.named("test") { + description = "Runs tests against the Gradle version the plugin is built with" +} + listOf("6.8.3", "6.9.4", "7.6.5", "8.14.2").forEach { gradleVersionUnderTest -> val testGradle = tasks.register("testGradle$gradleVersionUnderTest") { - group = "verification" description = "Runs tests against Gradle $gradleVersionUnderTest" - testClassesDirs = sourceSets.test.get().output.classesDirs - classpath = sourceSets.test.get().runtimeClasspath - useJUnitPlatform() - maxParallelForks = 4 systemProperty("gradleVersionUnderTest", gradleVersionUnderTest) if (gradleVersionUnderTest.startsWith("6")) { javaLauncher = javaToolchains.launcherFor { languageVersion = JavaLanguageVersion.of(11) } diff --git a/src/main/java/org/gradlex/javamodule/moduleinfo/ExtraJavaModuleInfoPluginExtension.java b/src/main/java/org/gradlex/javamodule/moduleinfo/ExtraJavaModuleInfoPluginExtension.java index c850712..5d6eafd 100644 --- a/src/main/java/org/gradlex/javamodule/moduleinfo/ExtraJavaModuleInfoPluginExtension.java +++ b/src/main/java/org/gradlex/javamodule/moduleinfo/ExtraJavaModuleInfoPluginExtension.java @@ -52,7 +52,7 @@ public abstract class ExtraJavaModuleInfoPluginExtension { public abstract Property getFailOnModifiedDerivedModuleNames(); public abstract Property getSkipLocalJars(); public abstract Property getDeriveAutomaticModuleNamesFromFileNames(); - public abstract Property getVersionsProvidingConfiguration(); + public abstract Property getVersionsProvidingConfiguration(); /** * Add full module information for a given Jar file. diff --git a/src/main/java/org/gradlex/javamodule/moduleinfo/ExtraJavaModuleInfoTransform.java b/src/main/java/org/gradlex/javamodule/moduleinfo/ExtraJavaModuleInfoTransform.java index 8f385e1..b365c6a 100644 --- a/src/main/java/org/gradlex/javamodule/moduleinfo/ExtraJavaModuleInfoTransform.java +++ b/src/main/java/org/gradlex/javamodule/moduleinfo/ExtraJavaModuleInfoTransform.java @@ -309,6 +309,10 @@ private void addModuleDescriptor(File originalJar, File moduleJar, ModuleInfo mo byte[] existingModuleInfo = copyAndExtractProviders(inputStream, outputStream, moduleInfo.getRemovedPackages(), !moduleInfo.getMergedJars().isEmpty(), providers, packages); mergeJars(moduleInfo, outputStream, providers, packages); outputStream.putNextEntry(newReproducibleEntry("module-info.class")); + + if (moduleInfo.exportAllPackages) { + moduleInfo.exportAllPackagesExceptions.forEach(it -> packages.remove(packageToPath(it))); + } outputStream.write(addModuleInfo(moduleInfo, providers, versionFromFilePath(originalJar.toPath()), moduleInfo.exportAllPackages ? packages : Collections.emptySet(), existingModuleInfo)); @@ -358,7 +362,7 @@ private byte[] copyAndExtractProviders(JarInputStream inputStream, JarOutputStre : entryName.substring(0, i) : ""; - if (!removedPackages.contains(packagePath.replace('/', '.'))) { + if (!removedPackages.contains(pathToPackage(packagePath))) { if (entryName.endsWith(".class") && !packagePath.isEmpty()) { packages.add(packagePath); } @@ -424,6 +428,24 @@ public void visitEnd() { return classWriter.toByteArray(); } + /** + * Convert a Java package name to a path + * @param packageName The package name + * @return The package name converted to a path + */ + private static String packageToPath(String packageName) { + return packageName.replace('.', '/'); + } + + /** + * Convert a path to a Java package name + * @param path The path + * @return The path converted to a package name + */ + private static String pathToPackage(String path) { + return path.replace('/', '.'); + } + private void addModuleInfoEntires(ModuleInfo moduleInfo, Map> providers, Set autoExportedPackages, ModuleVisitor moduleVisitor) { for (String packageName : autoExportedPackages) { moduleVisitor.visitExport(packageName, 0); @@ -431,13 +453,13 @@ private void addModuleInfoEntires(ModuleInfo moduleInfo, Map> entry : moduleInfo.exports.entrySet()) { String packageName = entry.getKey(); Set modules = entry.getValue(); - moduleVisitor.visitExport(packageName.replace('.', '/'), 0, modules.toArray(new String[0])); + moduleVisitor.visitExport(packageToPath(packageName), 0, modules.toArray(new String[0])); } for (Map.Entry> entry : moduleInfo.opens.entrySet()) { String packageName = entry.getKey(); Set modules = entry.getValue(); - moduleVisitor.visitOpen(packageName.replace('.', '/'), 0, modules.toArray(new String[0])); + moduleVisitor.visitOpen(packageToPath(packageName), 0, modules.toArray(new String[0])); } if (moduleInfo.requireAllDefinedDependencies) { @@ -482,7 +504,7 @@ private void addModuleInfoEntires(ModuleInfo moduleInfo, Map> entry : providers.entrySet()) { String name = entry.getKey(); @@ -497,8 +519,8 @@ private void addModuleInfoEntires(ModuleInfo moduleInfo, Map impl.replace('.', '/')).toArray(String[]::new) + packageToPath(name), + implementations.stream().map(ExtraJavaModuleInfoTransform::packageToPath).toArray(String[]::new) ); } } diff --git a/src/main/java/org/gradlex/javamodule/moduleinfo/ModuleInfo.java b/src/main/java/org/gradlex/javamodule/moduleinfo/ModuleInfo.java index 83934ea..c3c6106 100644 --- a/src/main/java/org/gradlex/javamodule/moduleinfo/ModuleInfo.java +++ b/src/main/java/org/gradlex/javamodule/moduleinfo/ModuleInfo.java @@ -19,11 +19,7 @@ import org.gradle.api.model.ObjectFactory; import org.jspecify.annotations.Nullable; -import java.util.Arrays; -import java.util.LinkedHashMap; -import java.util.LinkedHashSet; -import java.util.Map; -import java.util.Set; +import java.util.*; /** * Data class to hold the information that should be added as module-info.class to an existing Jar file. @@ -43,6 +39,7 @@ public class ModuleInfo extends ModuleSpec { final Set requiresStaticTransitive = new LinkedHashSet<>(); final Map> ignoreServiceProviders = new LinkedHashMap<>(); final Set uses = new LinkedHashSet<>(); + final Set exportAllPackagesExceptions = new LinkedHashSet<>(); boolean exportAllPackages; boolean requireAllDefinedDependencies; @@ -135,7 +132,24 @@ public String getModuleVersion() { * Automatically export all packages of the Jar. Can be used instead of individual 'exports()' statements. */ public void exportAllPackages() { + exportAllPackagesExcept(Collections.emptyList()); + } + + /** + * Automatically export all packages of the Jar. Can be used instead of individual 'exports()' statements. + * @param exceptions A list of packages not to export + */ + public void exportAllPackagesExcept(String... exceptions) { + exportAllPackagesExcept(Arrays.asList(exceptions)); + } + + /** + * Automatically export all packages of the Jar. Can be used instead of individual 'exports()' statements. + * @param exceptions A list of packages not to export + */ + public void exportAllPackagesExcept(List exceptions) { this.exportAllPackages = true; + exportAllPackagesExceptions.addAll(exceptions); } /** diff --git a/src/main/java/org/gradlex/javamodule/moduleinfo/PublishedMetadata.java b/src/main/java/org/gradlex/javamodule/moduleinfo/PublishedMetadata.java index 2f34f97..6932d74 100644 --- a/src/main/java/org/gradlex/javamodule/moduleinfo/PublishedMetadata.java +++ b/src/main/java/org/gradlex/javamodule/moduleinfo/PublishedMetadata.java @@ -76,10 +76,13 @@ public class PublishedMetadata implements Serializable { } @SuppressWarnings({"UnstableApiUsage", "unchecked"}) - private List componentVariant(Provider versionsProvidingConfiguration, Project project, String usage) { + private List componentVariant(Provider versionsProvidingConfiguration, Project project, String usage) { Configuration versionsSource; if (versionsProvidingConfiguration.isPresent()) { - versionsSource = project.getConfigurations().named(versionsProvidingConfiguration.get()).get(); + versionsSource = versionsProvidingConfiguration.get(); + if (!versionsSource.isCanBeResolved()) { + throw new IllegalArgumentException("Configuration '" + versionsSource.getName() + "' must be resolvable"); + } } else { // version provider is not configured, create on adhoc based on ALL classpaths of the project versionsSource = maybeCreateDefaultVersionSourcConfiguration(project.getConfigurations(), project.getObjects(), diff --git a/src/test/groovy/org/gradlex/javamodule/moduleinfo/test/EdgeCasesFunctionalTest.groovy b/src/test/groovy/org/gradlex/javamodule/moduleinfo/test/EdgeCasesFunctionalTest.groovy index 6f6f644..1fea3a5 100644 --- a/src/test/groovy/org/gradlex/javamodule/moduleinfo/test/EdgeCasesFunctionalTest.groovy +++ b/src/test/groovy/org/gradlex/javamodule/moduleinfo/test/EdgeCasesFunctionalTest.groovy @@ -177,6 +177,69 @@ class EdgeCasesFunctionalTest extends Specification { run().task(':run').outcome == TaskOutcome.SUCCESS } + def "can automatically export all packages except specified of a legacy Jar"() { + given: + file("src/main/java/org/gradle/sample/app/Main.java") << """ + package org.gradle.sample.app; + + import javax.json.JsonString; + import javax.json.JsonValue; + + public class Main { + public static void main(String[] args) { + JsonString jsonString = new JsonString() { + @Override + public boolean equals(Object obj) { + return false; + } + @Override + public CharSequence getChars() { + return null; + } + @Override + public String getString() { + return null; + } + @Override + public int hashCode() { + return 0; + } + @Override + public JsonValue.ValueType getValueType() { + return null; + } + }; + } + } + """ + file("src/main/java/module-info.java") << """ + module org.gradle.sample.app { + exports org.gradle.sample.app; + + requires org.glassfish.java.json; + requires java.json; + } + """ + buildFile << """ + dependencies { + implementation("org.glassfish:jakarta.json:1.1.6") + implementation("jakarta.json:jakarta.json-api:1.1.6") + } + + extraJavaModuleInfo { + module("org.glassfish:jakarta.json", "org.glassfish.java.json") { + exportAllPackagesExcept("javax.json", "javax.json.spi", "javax.json.stream") + overrideModuleName() + } + knownModule("jakarta.json:jakarta.json-api", "java.json") + } + """ + + expect: + def result = failRun() + result.output.matches(/(?s).*Package javax\.json[.a-z]* in both.*/) + } + def "deriveAutomaticModuleNamesFromFileNames produces a build time error for invalid module names"() { given: buildFile << """ diff --git a/src/test/groovy/org/gradlex/javamodule/moduleinfo/test/RequireAllDefinedDependenciesFunctionalTest.groovy b/src/test/groovy/org/gradlex/javamodule/moduleinfo/test/RequireAllDefinedDependenciesFunctionalTest.groovy index 5b9c582..3438145 100644 --- a/src/test/groovy/org/gradlex/javamodule/moduleinfo/test/RequireAllDefinedDependenciesFunctionalTest.groovy +++ b/src/test/groovy/org/gradlex/javamodule/moduleinfo/test/RequireAllDefinedDependenciesFunctionalTest.groovy @@ -374,7 +374,7 @@ class RequireAllDefinedDependenciesFunctionalTest extends Specification { given: def sharedBuildScript = """ extraJavaModuleInfo { - versionsProvidingConfiguration.set("mainRuntimeClasspath") + versionsProvidingConfiguration = project.provider { project.configurations.named("mainRuntimeClasspath").get() } module(${libs.commonsHttpClient}, "org.apache.httpcomponents.httpclient") module(${libs.commonsLogging}, "org.apache.commons.logging") knownModule("commons-codec:commons-codec", "org.apache.commons.codec") From 2c4dcade47c1c3b59b4cd0d1cd82168641464b23 Mon Sep 17 00:00:00 2001 From: Tim Hurman Date: Tue, 4 Nov 2025 11:56:29 +0900 Subject: [PATCH 2/2] Updated test case for old Gradle/kotlin versions --- build.gradle.kts | 32 ++++++++++--------- ...llDefinedDependenciesFunctionalTest.groovy | 2 +- 2 files changed, 18 insertions(+), 16 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 1c051f5..e2cf28b 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -8,9 +8,7 @@ group = "org.gradlex" version = "1.13.1" java { - toolchain.languageVersion = JavaLanguageVersion.of(17) - sourceCompatibility = JavaVersion.VERSION_1_8 - targetCompatibility = JavaVersion.VERSION_1_8 + toolchain.languageVersion = JavaLanguageVersion.of(11) } dependencies { @@ -36,15 +34,7 @@ pluginPublishConventions { } } -tasks.withType().configureEach { - group = "verification" - classpath = sourceSets.test.get().runtimeClasspath - testClassesDirs = sourceSets.test.get().output.classesDirs - useJUnitPlatform() - maxParallelForks = 4 -} - -tasks.named("test") { +tasks.named("test") { description = "Runs tests against the Gradle version the plugin is built with" } @@ -52,11 +42,23 @@ listOf("6.8.3", "6.9.4", "7.6.5", "8.14.2").forEach { gradleVersionUnderTest -> val testGradle = tasks.register("testGradle$gradleVersionUnderTest") { description = "Runs tests against Gradle $gradleVersionUnderTest" systemProperty("gradleVersionUnderTest", gradleVersionUnderTest) - if (gradleVersionUnderTest.startsWith("6")) { - javaLauncher = javaToolchains.launcherFor { languageVersion = JavaLanguageVersion.of(11) } - } } tasks.check { dependsOn(testGradle) } } + +tasks.withType().configureEach { + group = "verification" + classpath = sourceSets.test.get().runtimeClasspath + testClassesDirs = sourceSets.test.get().output.classesDirs + useJUnitPlatform() + maxParallelForks = 4 + + val gradleMajorVersion = (systemProperties["gradleVersionUnderTest"] as String? ?: gradle.gradleVersion).split(".")[0].toInt() + if (gradleMajorVersion >= 7) { + javaLauncher = javaToolchains.launcherFor { languageVersion = JavaLanguageVersion.of(17) } + } else { + javaLauncher = javaToolchains.launcherFor { languageVersion = JavaLanguageVersion.of(11) } + } +} diff --git a/src/test/groovy/org/gradlex/javamodule/moduleinfo/test/RequireAllDefinedDependenciesFunctionalTest.groovy b/src/test/groovy/org/gradlex/javamodule/moduleinfo/test/RequireAllDefinedDependenciesFunctionalTest.groovy index 3438145..f06e509 100644 --- a/src/test/groovy/org/gradlex/javamodule/moduleinfo/test/RequireAllDefinedDependenciesFunctionalTest.groovy +++ b/src/test/groovy/org/gradlex/javamodule/moduleinfo/test/RequireAllDefinedDependenciesFunctionalTest.groovy @@ -374,7 +374,7 @@ class RequireAllDefinedDependenciesFunctionalTest extends Specification { given: def sharedBuildScript = """ extraJavaModuleInfo { - versionsProvidingConfiguration = project.provider { project.configurations.named("mainRuntimeClasspath").get() } + versionsProvidingConfiguration.set(project.provider { project.configurations.named("mainRuntimeClasspath").get() }) module(${libs.commonsHttpClient}, "org.apache.httpcomponents.httpclient") module(${libs.commonsLogging}, "org.apache.commons.logging") knownModule("commons-codec:commons-codec", "org.apache.commons.codec")