diff --git a/CHANGELOG.md b/CHANGELOG.md index 741bfd2..9de913e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ## Version 1.10 * [New] [#160](https://github.com/gradlex-org/extra-java-module-info/pull/160) - Add 'preserveExisting' option to patch real modules +* [New] [#140](https://github.com/gradlex-org/extra-java-module-info/pull/140) - Add 'removePackage' option to deal with duplicated packages ## Version 1.9 * [New] [#137](https://github.com/gradlex-org/extra-java-module-info/pull/137) - Configuration option for 'versionsProvidingConfiguration' diff --git a/README.md b/README.md index e243917..98df01f 100644 --- a/README.md +++ b/README.md @@ -311,6 +311,19 @@ This plugin offers the option to merge multiple Jars into one in such situations Note: The merged Jar will include the *first* appearance of duplicated files (like the `MANIFEST.MF`). +In some cases, it may also be sufficient to remove appearances of the problematic package completely from some of the Jars. +This can be the case if classes are in fact duplicated, or if classes are not used. +For this, you can utilise the `removePackage` functionality: + +``` +extraJavaModuleInfo { + module("xerces:xercesImpl", "xerces") { + removePackage("org.w3c.dom.html") + // ... + } +} +``` + ## How can I fix a library with a broken `module-info.class`? To fix a library with a broken `module-info.class`, you can override the modular descriptor in the same way it is done with non-modular JARs. diff --git a/src/main/java/org/gradlex/javamodule/moduleinfo/ExtraJavaModuleInfoTransform.java b/src/main/java/org/gradlex/javamodule/moduleinfo/ExtraJavaModuleInfoTransform.java index f3afb0a..b1437ec 100644 --- a/src/main/java/org/gradlex/javamodule/moduleinfo/ExtraJavaModuleInfoTransform.java +++ b/src/main/java/org/gradlex/javamodule/moduleinfo/ExtraJavaModuleInfoTransform.java @@ -276,7 +276,7 @@ private void addAutomaticModuleName(File originalJar, File moduleJar, AutomaticM try (JarOutputStream outputStream = newJarOutputStream(Files.newOutputStream(moduleJar.toPath()), manifest)) { Map> providers = new LinkedHashMap<>(); Set packages = new TreeSet<>(); - copyAndExtractProviders(inputStream, outputStream, !automaticModule.getMergedJars().isEmpty(), providers, packages); + copyAndExtractProviders(inputStream, outputStream, automaticModule.getRemovedPackages(), !automaticModule.getMergedJars().isEmpty(), providers, packages); mergeJars(automaticModule, outputStream, providers, packages); } } catch (IOException e) { @@ -289,7 +289,7 @@ private void addModuleDescriptor(File originalJar, File moduleJar, ModuleInfo mo try (JarOutputStream outputStream = newJarOutputStream(Files.newOutputStream(moduleJar.toPath()), inputStream.getManifest())) { Map> providers = new LinkedHashMap<>(); Set packages = new TreeSet<>(); - byte[] existingModuleInfo = copyAndExtractProviders(inputStream, outputStream, !moduleInfo.getMergedJars().isEmpty(), providers, packages); + byte[] existingModuleInfo = copyAndExtractProviders(inputStream, outputStream, moduleInfo.getRemovedPackages(), !moduleInfo.getMergedJars().isEmpty(), providers, packages); mergeJars(moduleInfo, outputStream, providers, packages); outputStream.putNextEntry(newReproducibleEntry("module-info.class")); outputStream.write(addModuleInfo(moduleInfo, providers, versionFromFilePath(originalJar.toPath()), @@ -314,7 +314,7 @@ private JarOutputStream newJarOutputStream(OutputStream out, @Nullable Manifest } @Nullable - private byte[] copyAndExtractProviders(JarInputStream inputStream, JarOutputStream outputStream, boolean willMergeJars, Map> providers, Set packages) throws IOException { + private byte[] copyAndExtractProviders(JarInputStream inputStream, JarOutputStream outputStream, List removedPackages, boolean willMergeJars, Map> providers, Set packages) throws IOException { JarEntry jarEntry = inputStream.getNextJarEntry(); byte[] existingModuleInfo = null; while (jarEntry != null) { @@ -334,25 +334,26 @@ private byte[] copyAndExtractProviders(JarInputStream inputStream, JarOutputStre existingModuleInfo = content; } else if (!JAR_SIGNATURE_PATH.matcher(entryName).matches() && !"META-INF/MANIFEST.MF".equals(entryName)) { if (!willMergeJars || !isFileInServicesFolder) { // service provider files will be merged later - jarEntry.setCompressedSize(-1); - try { - outputStream.putNextEntry(jarEntry); - outputStream.write(content); - outputStream.closeEntry(); - } catch (ZipException e) { - if (!e.getMessage().startsWith("duplicate entry:")) { - throw new RuntimeException(e); + Matcher mrJarMatcher = MRJAR_VERSIONS_PATH.matcher(entryName); + int i = entryName.lastIndexOf("/"); + String packagePath = i > 0 ? mrJarMatcher.matches() + ? mrJarMatcher.group(1) + : entryName.substring(0, i) + : ""; + + if (!removedPackages.contains(packagePath.replace('/', '.'))) { + if (entryName.endsWith(".class") && !packagePath.isEmpty()) { + packages.add(packagePath); } - } - if (entryName.endsWith(".class")) { - int i = entryName.lastIndexOf("/"); - if (i > 0) { - Matcher mrJarMatcher = MRJAR_VERSIONS_PATH.matcher(entryName); - if (mrJarMatcher.matches()) { - // Strip the 'META-INF/versions/11' part - packages.add(mrJarMatcher.group(1)); - } else { - packages.add(entryName.substring(0, i)); + + try { + jarEntry.setCompressedSize(-1); + outputStream.putNextEntry(jarEntry); + outputStream.write(content); + outputStream.closeEntry(); + } catch (ZipException e) { + if (!e.getMessage().startsWith("duplicate entry:")) { + throw new RuntimeException(e); } } } @@ -497,7 +498,7 @@ private void mergeJars(ModuleSpec moduleSpec, JarOutputStream outputStream, Map< if (mergeJarFile != null) { try (JarInputStream toMergeInputStream = new JarInputStream(Files.newInputStream(mergeJarFile.getAsFile().toPath()))) { - copyAndExtractProviders(toMergeInputStream, outputStream, true, providers, packages); + copyAndExtractProviders(toMergeInputStream, outputStream, moduleSpec.getRemovedPackages(), true, providers, packages); } } else { throw new RuntimeException("Jar not found: " + identifier); diff --git a/src/main/java/org/gradlex/javamodule/moduleinfo/ModuleSpec.java b/src/main/java/org/gradlex/javamodule/moduleinfo/ModuleSpec.java index 45de690..c56b54f 100644 --- a/src/main/java/org/gradlex/javamodule/moduleinfo/ModuleSpec.java +++ b/src/main/java/org/gradlex/javamodule/moduleinfo/ModuleSpec.java @@ -35,6 +35,7 @@ public abstract class ModuleSpec implements Serializable { private final String identifier; private final String classifier; // optional private final String moduleName; + private final List removedPackages = new ArrayList<>(); private final List mergedJars = new ArrayList<>(); boolean overrideModuleName; @@ -73,6 +74,20 @@ public String getModuleName() { return moduleName; } + /** + * @param packageName a package to remove from the Jar because it is a duplicate + */ + public void removePackage(String packageName) { + removedPackages.add(packageName); + } + + /** + * @return packages that are removed by the transform + */ + public List getRemovedPackages() { + return removedPackages; + } + /** * @param identifier group:name coordinates _or_ Jar file name (of the Jar file to merge) */ diff --git a/src/test/groovy/org/gradlex/javamodule/moduleinfo/test/RemovePackageFunctionalTest.groovy b/src/test/groovy/org/gradlex/javamodule/moduleinfo/test/RemovePackageFunctionalTest.groovy new file mode 100644 index 0000000..fab446d --- /dev/null +++ b/src/test/groovy/org/gradlex/javamodule/moduleinfo/test/RemovePackageFunctionalTest.groovy @@ -0,0 +1,61 @@ +package org.gradlex.javamodule.moduleinfo.test + +import org.gradlex.javamodule.moduleinfo.test.fixture.GradleBuild +import org.gradlex.javamodule.moduleinfo.test.fixture.LegacyLibraries +import spock.lang.Specification + +class RemovePackageFunctionalTest extends Specification { + + @Delegate + GradleBuild build = new GradleBuild() + + LegacyLibraries libs = new LegacyLibraries(false) + + def setup() { + settingsFile << 'rootProject.name = "test-project"' + buildFile << ''' + plugins { + id("application") + id("org.gradlex.extra-java-module-info") + } + application { + mainModule.set("org.example.app") + mainClass.set("org.example.app.Main") + } + ''' + file("src/main/java/module-info.java") << """ + module org.example.app { + requires jdk.xml.dom; + requires xerces; + } + """ + file("src/main/java/org/gradle/sample/app/Main.java") << """ + package org.example.app; + public class Main { + public static void main(String[] args) { + org.apache.xerces.util.DOMUtil util; + } + } + """ + } + + def "can remove duplicated packages"() { + given: + buildFile << """ + dependencies { + implementation("xerces:xercesImpl:2.12.2") { isTransitive = false } + } + extraJavaModuleInfo { + module("xerces:xercesImpl", "xerces") { + removePackage("org.w3c.dom.html") + exportAllPackages() + requires("java.xml") + } + } + """ + + expect: + run() + } + +}