diff --git a/README.md b/README.md index 07fd12a..d8e7c4a 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,9 @@ This needs to be done in all subprojects. You use the `versionsProvidingConfigur ```kotlin extraJavaModuleInfo { - versionsProvidingConfiguration = "mainRuntimeClasspath" + versionsProvidingConfiguration = project.provider { project.configurations.named("mainRuntimeClasspath").get() } + // or for older Gradle/Kotlin versions + // versionsProvidingConfiguration.set(project.provider { project.configurations.named("mainRuntimeClasspath").get() }) } ``` diff --git a/src/main/java/org/gradlex/javamodule/moduleinfo/ExtraJavaModuleInfoPluginExtension.java b/src/main/java/org/gradlex/javamodule/moduleinfo/ExtraJavaModuleInfoPluginExtension.java index 19f2e1f..df3d6b0 100644 --- a/src/main/java/org/gradlex/javamodule/moduleinfo/ExtraJavaModuleInfoPluginExtension.java +++ b/src/main/java/org/gradlex/javamodule/moduleinfo/ExtraJavaModuleInfoPluginExtension.java @@ -42,7 +42,7 @@ public abstract class ExtraJavaModuleInfoPluginExtension { 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 254914f..ecdb316 100644 --- a/src/main/java/org/gradlex/javamodule/moduleinfo/ExtraJavaModuleInfoTransform.java +++ b/src/main/java/org/gradlex/javamodule/moduleinfo/ExtraJavaModuleInfoTransform.java @@ -333,6 +333,10 @@ private void addModuleDescriptor(File originalJar, File moduleJar, ModuleInfo mo 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, @@ -390,7 +394,7 @@ private byte[] copyAndExtractProviders( String packagePath = i > 0 ? mrJarMatcher.matches() ? mrJarMatcher.group(1) : entryName.substring(0, i) : ""; - if (!removedPackages.contains(packagePath.replace('/', '.'))) { + if (!removedPackages.contains(pathToPackage(packagePath))) { if (entryName.endsWith(".class") && !packagePath.isEmpty()) { packages.add(packagePath); } @@ -462,6 +466,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, @@ -473,13 +495,13 @@ private void addModuleInfoEntires( for (Map.Entry> 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) { @@ -524,7 +546,7 @@ private void addModuleInfoEntires( moduleVisitor.visitRequire(requireName, Opcodes.ACC_STATIC_PHASE | Opcodes.ACC_TRANSITIVE, null); } for (String usesName : moduleInfo.uses) { - moduleVisitor.visitUse(usesName.replace('.', '/')); + moduleVisitor.visitUse(packageToPath(usesName)); } for (Map.Entry> entry : providers.entrySet()) { String name = entry.getKey(); @@ -539,9 +561,9 @@ private void addModuleInfoEntires( } if (!implementations.isEmpty()) { moduleVisitor.visitProvide( - name.replace('.', '/'), + packageToPath(name), implementations.stream() - .map(impl -> impl.replace('.', '/')) + .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 cad50b0..73ca225 100644 --- a/src/main/java/org/gradlex/javamodule/moduleinfo/ModuleInfo.java +++ b/src/main/java/org/gradlex/javamodule/moduleinfo/ModuleInfo.java @@ -2,8 +2,10 @@ package org.gradlex.javamodule.moduleinfo; import java.util.Arrays; +import java.util.Collections; import java.util.LinkedHashMap; import java.util.LinkedHashSet; +import java.util.List; import java.util.Map; import java.util.Set; import org.gradle.api.model.ObjectFactory; @@ -27,6 +29,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; @@ -119,7 +122,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 135dd6a..93d1ecc 100644 --- a/src/main/java/org/gradlex/javamodule/moduleinfo/PublishedMetadata.java +++ b/src/main/java/org/gradlex/javamodule/moduleinfo/PublishedMetadata.java @@ -67,12 +67,14 @@ public class PublishedMetadata implements Serializable { @SuppressWarnings({"UnstableApiUsage", "unchecked"}) private List componentVariant( - Provider versionsProvidingConfiguration, Project project, String usage) { + 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( 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..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.set("mainRuntimeClasspath") + 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")