[MWAR-220] filter overlay jars conflicting with managed dependencies - #639
[MWAR-220] filter overlay jars conflicting with managed dependencies#639elharo wants to merge 10 commits into
Conversation
| return false; | ||
| } | ||
|
|
||
| System.out.println( "Overlay WAR correctly contains only plexus-utils-3.0.24.jar." ); |
There was a problem hiding this comment.
remove this; passing tests generate no output
| if ( mainHasOld ) | ||
| { | ||
| // Both versions found -- issue STILL EXISTS | ||
| System.out.println( "ISSUE #389 (MWAR-220) STILL EXISTS:" ); |
There was a problem hiding this comment.
remove this; passing tests generate no output
| // Both versions found -- issue STILL EXISTS | ||
| System.out.println( "ISSUE #389 (MWAR-220) STILL EXISTS:" ); | ||
| System.out.println( " Both plexus-utils versions found in main WAR WEB-INF/lib:" ); | ||
| System.out.println( " - plexus-utils-3.0.24.jar (from overlay unpacking)" ); |
There was a problem hiding this comment.
remove this; passing tests generate no output
| { | ||
| // Both versions found -- issue STILL EXISTS | ||
| System.out.println( "ISSUE #389 (MWAR-220) STILL EXISTS:" ); | ||
| System.out.println( " Both plexus-utils versions found in main WAR WEB-INF/lib:" ); |
There was a problem hiding this comment.
remove this; passing tests generate no output
| System.out.println( " Both plexus-utils versions found in main WAR WEB-INF/lib:" ); | ||
| System.out.println( " - plexus-utils-3.0.24.jar (from overlay unpacking)" ); | ||
| System.out.println( " - plexus-utils-" + managedVersion + ".jar (from dependencyManagement)" ); | ||
| System.out.println( " dependencyManagement does not filter overlay-provided jars." ); |
There was a problem hiding this comment.
remove this; passing tests generate no output
| System.out.println( "ISSUE #389 (MWAR-220) STILL EXISTS:" ); | ||
| System.out.println( " Both plexus-utils versions found in main WAR WEB-INF/lib:" ); | ||
| System.out.println( " - plexus-utils-3.0.24.jar (from overlay unpacking)" ); | ||
| System.out.println( " - plexus-utils-" + managedVersion + ".jar (from dependencyManagement)" ); |
There was a problem hiding this comment.
remove this; passing tests generate no output
| else | ||
| { | ||
| // Only managed version -- issue has been fixed | ||
| System.out.println( "ISSUE #389 (MWAR-220) APPEARS FIXED:" ); |
There was a problem hiding this comment.
remove this; passing tests generate no output
| { | ||
| // Only managed version -- issue has been fixed | ||
| System.out.println( "ISSUE #389 (MWAR-220) APPEARS FIXED:" ); | ||
| System.out.println( " Only plexus-utils-" + managedVersion + ".jar found in WEB-INF/lib." ); |
There was a problem hiding this comment.
remove this; passing tests generate no output
There was a problem hiding this comment.
Pull request overview
This PR addresses MWAR-220 / issue #389 by preventing WAR overlays from contributing conflicting library JARs into the final WAR when the consuming project already resolves that dependency (typically via dependencyManagement), so only the project-resolved version remains in WEB-INF/lib.
Changes:
- Add overlay-side filtering to remove conflicting
WEB-INF/lib/*.jarentries during overlay processing. - Add a new integration test scenario (
src/it/MWAR-389) reproducing the dependencyManagement + overlay version-conflict case. - Update the IT verification script to validate the overlay contains the “old” version while the final WAR contains only the managed version.
Reviewed changes
Copilot reviewed 8 out of 8 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| src/main/java/org/apache/maven/plugins/war/packaging/OverlayPackagingTask.java | Adds overlay WEB-INF/lib filtering logic to remove JARs that conflict with project-resolved dependencies. |
| src/it/MWAR-389/verify.bsh | Verifies the overlay WAR contains the old dependency version and the main WAR contains only the managed version. |
| src/it/MWAR-389/pom.xml | Adds a multi-module IT root POM for the MWAR-389 scenario. |
| src/it/MWAR-389/overlay-war/src/main/webapp/WEB-INF/web.xml | Minimal overlay WAR web.xml for the IT module. |
| src/it/MWAR-389/overlay-war/pom.xml | Defines overlay WAR with a fixed dependency version (plexus-utils 3.0.24). |
| src/it/MWAR-389/main-war/src/main/webapp/WEB-INF/web.xml | Minimal main WAR web.xml for the IT module. |
| src/it/MWAR-389/main-war/pom.xml | Defines main WAR with dependencyManagement-pinned plexus-utils and overlay dependency. |
| src/it/MWAR-389/invoker.properties | Invoker configuration to run the IT build. |
Comments suppressed due to low confidence (1)
src/it/MWAR-389/verify.bsh:84
- mainLib.list() can return null, which will cause a NullPointerException when iterating mainJars. Add the same directory/list guards used elsewhere in the IT suite.
String[] mainJars = mainLib.list();
boolean mainHasOld = false;
boolean mainHasManaged = false;
for ( int i = 0; i < mainJars.length; i++ )
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Only consider project artifacts that will actually be packaged into WEB-INF/lib (runtime scope). Otherwise provided-scope artifacts cause overlay jars to be removed even though no project jar replaces them, resulting in a missing dependency at runtime.
- Check overlayJar.delete() return value, log warning on failure - Guard mainLib.list() against null return in verify.bsh - Add missing directory existence check for main WAR WEB-INF/lib
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 8 out of 8 changed files in this pull request and generated 1 comment.
Comments suppressed due to low confidence (3)
src/main/java/org/apache/maven/plugins/war/packaging/OverlayPackagingTask.java:70
- filterConflictingDependencyJars() deletes JARs directly out of the overlay temp directory (tmpDir). Because unpackOverlay() intentionally caches the unpacked overlay when the overlay artifact timestamp hasn’t changed, those deletions will persist across subsequent builds without a clean — even if the project dependency set changes and the overlay JAR should be included again.
// Step1b: Remove jars from overlay that conflict with managed dependencies
filterConflictingDependencyJars(context, tmpDir);
src/main/java/org/apache/maven/plugins/war/packaging/OverlayPackagingTask.java:201
- extractArtifactId() assumes the first digit in the filename marks the start of the version. That fails for artifactIds that themselves contain digits (e.g. commons-lang3 → commons-lang3-3.20.0.jar), causing artifactId extraction to return null and the overlay JAR never to be filtered.
int versionStart = -1;
for (int i = 0; i < baseName.length(); i++) {
if (Character.isDigit(baseName.charAt(i))) {
versionStart = i;
break;
src/main/java/org/apache/maven/plugins/war/packaging/OverlayPackagingTask.java:176
- The debug message says the removed dependency is the “managed version”, but the current logic is based on the resolved runtime dependency set and may include non-managed dependencies. Tweaking the message would avoid misleading diagnostics.
context.getLog()
.debug("Removing dependency [" + jarName + "] from overlay [" + overlay.getId()
+ "]; managed version in project already provides it");
…aining IDs, debug msg, JavaDoc
| Set<String> conflicting = new HashSet<>(); | ||
| for (File overlayJar : overlayJars) { | ||
| String jarName = overlayJar.getName(); | ||
| String artifactId = extractArtifactId(jarName); | ||
| if (artifactId != null && projectArtifactIds.contains(artifactId)) { | ||
| context.getLog() | ||
| .debug("Excluding dependency [" + jarName + "] from overlay [" + overlay.getId() | ||
| + "]; project runtime dependencies already include a version"); | ||
| conflicting.add("WEB-INF/lib/" + jarName); | ||
| } | ||
| } |
There was a problem hiding this comment.
overlay is a field of OverlayPackagingTask (line 46: private final Overlay overlay;). Since computeConflictingJars is an instance method, this.overlay is accessible in scope. The code compiles and runs correctly.
| ScopeArtifactFilter filter = new ScopeArtifactFilter(Artifact.SCOPE_RUNTIME); | ||
| Set<String> projectArtifactIds = new HashSet<>(); | ||
| for (Artifact artifact : context.getProject().getArtifacts()) { | ||
| if (!artifact.isOptional() && filter.include(artifact) && "jar".equals(artifact.getType())) { | ||
| projectArtifactIds.add(artifact.getArtifactId()); | ||
| } | ||
| } |
There was a problem hiding this comment.
Addressed in commit fc54f70. Added getJarGroupId() which reads META-INF/maven/**/pom.properties from each overlay jar to obtain the groupId for precise groupId:artifactId matching. Falls back to artifactId-only matching when the metadata is absent.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 8 out of 8 changed files in this pull request and generated 2 comments.
Comments suppressed due to low confidence (3)
src/main/java/org/apache/maven/plugins/war/packaging/OverlayPackagingTask.java:193
getJarGroupId(overlayJar)opens and scans each jar, even when the extractedartifactIdis not present inprojectFallbackIdsand therefore can never match. A straightforward optimization is to first checkprojectFallbackIds.contains(artifactId)and only then open the jar to resolvegroupIdfor precise matching.
for (File overlayJar : overlayJars) {
String jarName = overlayJar.getName();
String artifactId = extractArtifactId(jarName);
if (artifactId == null) {
continue;
}
String groupId = getJarGroupId(overlayJar);
String key = (groupId != null) ? groupId + ":" + artifactId : artifactId;
src/main/java/org/apache/maven/plugins/war/packaging/OverlayPackagingTask.java:230
- The current approach mixes metadata-based matching (groupId from
pom.properties) with filename parsing forartifactId. SinceMETA-INF/maven/<groupId>/<artifactId>/pom.propertiesalready includes both coordinates (and the path itself encodes them), consider extracting bothgroupIdandartifactIdfrom theMETA-INF/maven/.../pom.propertiesentry when present, and only falling back to filename parsing when the metadata is absent. This improves correctness for jars whose filenames don’t matchartifactId-version...and reduces reliance on heuristics.
/**
* Reads the Maven groupId from a jar's {@code META-INF/maven/**\/pom.properties} metadata, if
* present.
*
* @param jarFile the jar file
* @return the groupId, or null if it could not be determined
*/
private static String getJarGroupId(File jarFile) {
try (ZipFile zip = new ZipFile(jarFile)) {
Enumeration<? extends ZipEntry> entries = zip.entries();
while (entries.hasMoreElements()) {
ZipEntry entry = entries.nextElement();
String name = entry.getName();
if (name.startsWith("META-INF/maven/") && name.endsWith("/pom.properties")) {
Properties props = new Properties();
try (InputStream is = zip.getInputStream(entry)) {
props.load(is);
}
return props.getProperty("groupId");
}
}
} catch (IOException e) {
// groupId not available; fall back to artifactId-only matching
}
return null;
}
src/main/java/org/apache/maven/plugins/war/packaging/OverlayPackagingTask.java:255
- The current approach mixes metadata-based matching (groupId from
pom.properties) with filename parsing forartifactId. SinceMETA-INF/maven/<groupId>/<artifactId>/pom.propertiesalready includes both coordinates (and the path itself encodes them), consider extracting bothgroupIdandartifactIdfrom theMETA-INF/maven/.../pom.propertiesentry when present, and only falling back to filename parsing when the metadata is absent. This improves correctness for jars whose filenames don’t matchartifactId-version...and reduces reliance on heuristics.
/**
* Extracts the Maven artifactId from a jar filename following the
* {@code artifactId-version(-classifier)?.jar} convention.
*
| private Set<String> computeConflictingJars(WarPackagingContext context, File overlayDir) { | ||
| File libDir = new File(overlayDir, "WEB-INF/lib"); | ||
| if (!libDir.isDirectory()) { | ||
| return Collections.emptySet(); | ||
| } |
| if ((groupId != null && projectArtifactKeys.contains(key)) | ||
| || (groupId == null && projectFallbackIds.contains(artifactId))) { | ||
| context.getLog() | ||
| .debug("Excluding dependency [" + jarName + "] from overlay [" + overlay.getId() | ||
| + "]; project runtime dependencies already include a version"); | ||
| conflicting.add("WEB-INF/lib/" + jarName); | ||
| } |
Fixes #389
When a project uses dependencyManagement to pin a specific version of a library, any jar in a WAR overlay's
WEB-INF/libwith the same artifactId but a different version will now be removed during overlay processing.How it works
In
OverlayPackagingTask.performPackaging(), after unpacking the overlay and before copying files, the newfilterConflictingDependencyJars()method scans the overlay'sWEB-INF/libdirectory. For each jar, it extracts the artifactId from the filename (following theartifactId-version(-classifier)?.jarconvention). If the artifactId matches a non-optional jar artifact from the project's dependency tree, the overlay jar is removed — the managed version will be provided by the project's own dependency resolution.Additional changes
verify.bsh) to remove print statements for the passing case per review feedback.The IT test in PR #638 documents this issue and now passes with this fix applied.