Skip to content

Add removePackage option #163

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Feb 3, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down
13 changes: 13 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -276,7 +276,7 @@ private void addAutomaticModuleName(File originalJar, File moduleJar, AutomaticM
try (JarOutputStream outputStream = newJarOutputStream(Files.newOutputStream(moduleJar.toPath()), manifest)) {
Map<String, List<String>> providers = new LinkedHashMap<>();
Set<String> 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) {
Expand All @@ -289,7 +289,7 @@ private void addModuleDescriptor(File originalJar, File moduleJar, ModuleInfo mo
try (JarOutputStream outputStream = newJarOutputStream(Files.newOutputStream(moduleJar.toPath()), inputStream.getManifest())) {
Map<String, List<String>> providers = new LinkedHashMap<>();
Set<String> 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()),
Expand All @@ -314,7 +314,7 @@ private JarOutputStream newJarOutputStream(OutputStream out, @Nullable Manifest
}

@Nullable
private byte[] copyAndExtractProviders(JarInputStream inputStream, JarOutputStream outputStream, boolean willMergeJars, Map<String, List<String>> providers, Set<String> packages) throws IOException {
private byte[] copyAndExtractProviders(JarInputStream inputStream, JarOutputStream outputStream, List<String> removedPackages, boolean willMergeJars, Map<String, List<String>> providers, Set<String> packages) throws IOException {
JarEntry jarEntry = inputStream.getNextJarEntry();
byte[] existingModuleInfo = null;
while (jarEntry != null) {
Expand All @@ -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);
}
}
}
Expand Down Expand Up @@ -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);
Expand Down
15 changes: 15 additions & 0 deletions src/main/java/org/gradlex/javamodule/moduleinfo/ModuleSpec.java
Original file line number Diff line number Diff line change
Expand Up @@ -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<String> removedPackages = new ArrayList<>();
private final List<String> mergedJars = new ArrayList<>();

boolean overrideModuleName;
Expand Down Expand Up @@ -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<String> getRemovedPackages() {
return removedPackages;
}

/**
* @param identifier group:name coordinates _or_ Jar file name (of the Jar file to merge)
*/
Expand Down
Original file line number Diff line number Diff line change
@@ -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()
}

}