You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@maven.apache.org by gn...@apache.org on 2026/07/24 21:08:02 UTC
(maven-war-plugin) 01/01: Upgrade to Maven 4.0.0-rc-5 and fix IT compatibility issues
This is an automated email from the ASF dual-hosted git repository.
gnodet pushed a commit to branch 4.0.x
in repository https://gitbox.apache.org/repos/asf/maven-war-plugin.git
commit a3ac494d4aefeacc45fe3aed607b5df5281bf4d8
Author: Guillaume Nodet <gn...@gmail.com>
AuthorDate: Fri Jul 24 23:07:49 2026 +0200
Upgrade to Maven 4.0.0-rc-5 and fix IT compatibility issues
- Upgrade mavenVersion from rc-4 to rc-5
- Add Providers.java with DI bindings for ProjectManager, BuildContext
(SimpleBuildContext impl), and ArchiverManager
- Fix ${basedir} → ${project.basedir} for warSourceDirectory default
- Fix Overlay excludes/includes parsing for Maven 4 config parser by
removing ambiguous String setter overloads
- Rewrite OverlayManager dependency resolution to walk the dependency
tree instead of using flattenDependencies (which filters non-classpath
types like war)
- Add null safety for collectDependencies return values
- Update test mocks to set up tree walking (getChildren) in addition
to flattenDependencies
- All 64 unit tests and 35 integration tests pass with Maven 4.0.0-rc-5
Co-Authored-By: Claude Opus 4.6 <no...@anthropic.com>
---
.gitignore | 1 +
pom.xml | 4 +-
.../apache/maven/plugins/war/AbstractWarMojo.java | 2 +-
.../java/org/apache/maven/plugins/war/Overlay.java | 27 ----
.../org/apache/maven/plugins/war/Providers.java | 171 +++++++++++++++++++++
.../maven/plugins/war/overlay/OverlayManager.java | 64 ++++----
.../apache/maven/plugins/war/WarOverlaysTest.java | 18 +--
.../plugins/war/overlay/OverlayManagerTest.java | 10 +-
.../maven/plugins/war/stub/MockSessionHelper.java | 5 +
9 files changed, 234 insertions(+), 68 deletions(-)
diff --git a/.gitignore b/.gitignore
index 45fe8c8..8f0ba66 100644
--- a/.gitignore
+++ b/.gitignore
@@ -16,3 +16,4 @@ out/
.checkstyle
*.versionsBackup
+.claude/
diff --git a/pom.xml b/pom.xml
index 672f9ee..9456c37 100644
--- a/pom.xml
+++ b/pom.xml
@@ -77,7 +77,7 @@
<properties>
<mavenArchiverVersion>4.0.0-beta-5</mavenArchiverVersion>
<mavenFilteringVersion>4.0.0-beta-1</mavenFilteringVersion>
- <mavenVersion>4.0.0-rc-4</mavenVersion>
+ <mavenVersion>4.0.0-rc-5</mavenVersion>
<javaVersion>17</javaVersion>
<guiceVersion>5.1.0</guiceVersion>
<plexusArchiverVersion>4.12.0</plexusArchiverVersion>
@@ -202,6 +202,8 @@
-->
<exclude>src/it/MWAR-167/src/main/resources/MANIFEST.MF</exclude>
<exclude>src/test/resources/**</exclude>
+ <!-- Created by unit tests (zip overlay) at project root -->
+ <exclude>zipGroupId/**</exclude>
</excludes>
</configuration>
</plugin>
diff --git a/src/main/java/org/apache/maven/plugins/war/AbstractWarMojo.java b/src/main/java/org/apache/maven/plugins/war/AbstractWarMojo.java
index f251842..c1046d9 100644
--- a/src/main/java/org/apache/maven/plugins/war/AbstractWarMojo.java
+++ b/src/main/java/org/apache/maven/plugins/war/AbstractWarMojo.java
@@ -135,7 +135,7 @@ public abstract class AbstractWarMojo implements Mojo {
/**
* Single directory for extra files to include in the WAR. This is where you place your JSP files.
*/
- @Parameter(defaultValue = "${basedir}/src/main/webapp", required = true)
+ @Parameter(defaultValue = "${project.basedir}/src/main/webapp", required = true)
private File warSourceDirectory;
/**
diff --git a/src/main/java/org/apache/maven/plugins/war/Overlay.java b/src/main/java/org/apache/maven/plugins/war/Overlay.java
index 68c3a42..bb2f8cb 100644
--- a/src/main/java/org/apache/maven/plugins/war/Overlay.java
+++ b/src/main/java/org/apache/maven/plugins/war/Overlay.java
@@ -18,9 +18,7 @@
*/
package org.apache.maven.plugins.war;
-import java.util.ArrayList;
import java.util.Arrays;
-import java.util.List;
import org.apache.maven.api.DownloadedArtifact;
@@ -180,13 +178,6 @@ public class Overlay {
return includes;
}
- /**
- * @param includes {@link #includes}
- */
- public void setIncludes(String includes) {
- this.includes = parse(includes);
- }
-
/**
* @param includes {@link #includes}
*/
@@ -201,13 +192,6 @@ public class Overlay {
return excludes;
}
- /**
- * @param excludes {@link #excludes}
- */
- public void setExcludes(String excludes) {
- this.excludes = parse(excludes);
- }
-
/**
* @param excludes {@link #excludes}
*/
@@ -322,15 +306,4 @@ public class Overlay {
result = 31 * result + (excludes != null ? Arrays.hashCode(excludes) : 0);
return result;
}
-
- private String[] parse(String s) {
- final List<String> result = new ArrayList<>();
- if (s != null) {
- String[] tokens = s.split(",");
- for (String token : tokens) {
- result.add(token.trim());
- }
- }
- return result.toArray(new String[0]);
- }
}
diff --git a/src/main/java/org/apache/maven/plugins/war/Providers.java b/src/main/java/org/apache/maven/plugins/war/Providers.java
new file mode 100644
index 0000000..58615c9
--- /dev/null
+++ b/src/main/java/org/apache/maven/plugins/war/Providers.java
@@ -0,0 +1,171 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.maven.plugins.war;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.nio.file.Files;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.maven.api.Session;
+import org.apache.maven.api.di.Named;
+import org.apache.maven.api.di.Provides;
+import org.apache.maven.api.services.ProjectManager;
+import org.codehaus.plexus.archiver.Archiver;
+import org.codehaus.plexus.archiver.UnArchiver;
+import org.codehaus.plexus.archiver.jar.JarArchiver;
+import org.codehaus.plexus.archiver.jar.JarUnArchiver;
+import org.codehaus.plexus.archiver.manager.ArchiverManager;
+import org.codehaus.plexus.archiver.manager.DefaultArchiverManager;
+import org.codehaus.plexus.archiver.war.WarArchiver;
+import org.codehaus.plexus.archiver.war.WarUnArchiver;
+import org.codehaus.plexus.archiver.zip.ZipArchiver;
+import org.codehaus.plexus.archiver.zip.ZipUnArchiver;
+import org.codehaus.plexus.util.DirectoryScanner;
+import org.codehaus.plexus.util.Scanner;
+import org.sonatype.plexus.build.incremental.BuildContext;
+
+/**
+ * DI provider bindings for services not directly available in the Maven 4 plugin DI container.
+ */
+@Named
+class Providers {
+
+ @Provides
+ static ProjectManager projectManager(Session session) {
+ return session.getService(ProjectManager.class);
+ }
+
+ @Provides
+ static BuildContext buildContext() {
+ return new SimpleBuildContext();
+ }
+
+ @Provides
+ static ArchiverManager archiverManager() {
+ Map<String, javax.inject.Provider<Archiver>> archivers = new HashMap<>();
+ archivers.put("jar", JarArchiver::new);
+ archivers.put("war", WarArchiver::new);
+ archivers.put("zip", ZipArchiver::new);
+
+ Map<String, javax.inject.Provider<UnArchiver>> unarchivers = new HashMap<>();
+ unarchivers.put("jar", JarUnArchiver::new);
+ unarchivers.put("war", WarUnArchiver::new);
+ unarchivers.put("zip", ZipUnArchiver::new);
+
+ return new DefaultArchiverManager(archivers, unarchivers, new HashMap<>());
+ }
+
+ /**
+ * A simple {@link BuildContext} implementation that does not depend on
+ * {@code plexus-container-default} (which is unavailable in Maven 4).
+ */
+ static class SimpleBuildContext implements BuildContext {
+
+ @Override
+ public boolean hasDelta(String relpath) {
+ return true;
+ }
+
+ @Override
+ public boolean hasDelta(File file) {
+ return true;
+ }
+
+ @Override
+ @SuppressWarnings("rawtypes")
+ public boolean hasDelta(List relpaths) {
+ return true;
+ }
+
+ @Override
+ public void refresh(File file) {
+ // no-op
+ }
+
+ @Override
+ public OutputStream newFileOutputStream(File file) throws IOException {
+ return Files.newOutputStream(file.toPath());
+ }
+
+ @Override
+ public Scanner newScanner(File basedir) {
+ DirectoryScanner scanner = new DirectoryScanner();
+ scanner.setBasedir(basedir);
+ return scanner;
+ }
+
+ @Override
+ public Scanner newDeleteScanner(File basedir) {
+ return newScanner(basedir);
+ }
+
+ @Override
+ public Scanner newScanner(File basedir, boolean ignoreDelta) {
+ return newScanner(basedir);
+ }
+
+ @Override
+ public boolean isIncremental() {
+ return false;
+ }
+
+ @Override
+ public void setValue(String key, Object value) {
+ // no-op
+ }
+
+ @Override
+ public Object getValue(String key) {
+ return null;
+ }
+
+ @Override
+ public void addWarning(File file, int line, int column, String message, Throwable cause) {
+ // no-op
+ }
+
+ @Override
+ public void addError(File file, int line, int column, String message, Throwable cause) {
+ // no-op
+ }
+
+ @Override
+ public void addMessage(File file, int line, int column, String message, int severity, Throwable cause) {
+ // no-op
+ }
+
+ @Override
+ public void removeMessages(File file) {
+ // no-op
+ }
+
+ @Override
+ public boolean isUptodate(File output, File input) {
+ return output != null
+ && output.exists()
+ && input != null
+ && input.exists()
+ && output.lastModified() > input.lastModified();
+ }
+ }
+}
diff --git a/src/main/java/org/apache/maven/plugins/war/overlay/OverlayManager.java b/src/main/java/org/apache/maven/plugins/war/overlay/OverlayManager.java
index 752645d..0ca7e34 100644
--- a/src/main/java/org/apache/maven/plugins/war/overlay/OverlayManager.java
+++ b/src/main/java/org/apache/maven/plugins/war/overlay/OverlayManager.java
@@ -204,12 +204,13 @@ public class OverlayManager {
* @return boolean true if equals
*/
private boolean compareOverlayWithArtifact(Overlay overlay, DownloadedArtifact artifact) {
+ String oc = overlay.getClassifier() == null ? "" : overlay.getClassifier();
+ String ac = artifact.getClassifier() == null ? "" : artifact.getClassifier();
return (Objects.equals(overlay.getGroupId(), artifact.getGroupId())
&& Objects.equals(overlay.getArtifactId(), artifact.getArtifactId())
&& Objects.equals(overlay.getType(), artifact.getExtension())
// MWAR-241 Make sure to treat null and "" as equal when comparing the classifier
- && Objects.equals(
- Objects.toString(overlay.getClassifier()), Objects.toString(artifact.getClassifier())));
+ && Objects.equals(oc, ac));
}
/**
@@ -219,21 +220,9 @@ public class OverlayManager {
*/
private List<DownloadedArtifact> getOverlaysAsArtifacts() {
final List<DownloadedArtifact> result = new ArrayList<>();
- try {
- Node root = session.collectDependencies(project, PathScope.MAIN_RUNTIME);
- List<Node> nodes = session.flattenDependencies(root, PathScope.MAIN_RUNTIME);
- for (Node node : nodes) {
- org.apache.maven.api.Dependency dep = node.getDependency();
- if (dep != null
- && !dep.isOptional()
- && isRuntimeScope(dep.getScope())
- && "war".equals(dep.getExtension())) {
- DownloadedArtifact resolved = session.resolveArtifact(dep);
- result.add(resolved);
- }
- }
- } catch (Exception e) {
- // If dependency resolution fails, return empty list
+ Node root = session.collectDependencies(project, PathScope.MAIN_RUNTIME);
+ if (root != null) {
+ collectArtifacts(root, result, true);
}
return result;
}
@@ -243,20 +232,43 @@ public class OverlayManager {
*/
private List<DownloadedArtifact> getAllResolvedArtifacts() {
final List<DownloadedArtifact> result = new ArrayList<>();
- try {
- Node root = session.collectDependencies(project, PathScope.MAIN_RUNTIME);
- List<Node> nodes = session.flattenDependencies(root, PathScope.MAIN_RUNTIME);
- for (Node node : nodes) {
- org.apache.maven.api.Dependency dep = node.getDependency();
- if (dep != null) {
+ Node root = session.collectDependencies(project, PathScope.MAIN_RUNTIME);
+ if (root != null) {
+ collectArtifacts(root, result, false);
+ }
+ return result;
+ }
+
+ /**
+ * Walks the dependency tree and collects resolved artifacts.
+ * Uses manual tree walking instead of {@code flattenDependencies} because the latter
+ * filters out non-classpath types (like war), which we need for overlay resolution.
+ *
+ * @param node the root node
+ * @param result the list to collect into
+ * @param warOnly if true, only collect WAR artifacts with runtime-compatible scope
+ */
+ private void collectArtifacts(Node node, List<DownloadedArtifact> result, boolean warOnly) {
+ org.apache.maven.api.Dependency dep = node.getDependency();
+ if (dep != null) {
+ boolean include = !warOnly
+ || (!dep.isOptional() && isRuntimeScope(dep.getScope()) && "war".equals(dep.getExtension()));
+ if (include) {
+ try {
DownloadedArtifact resolved = session.resolveArtifact(dep);
result.add(resolved);
+ } catch (Exception e) {
+ // Skip unresolvable artifacts
+ }
+ }
+ }
+ if (node.getChildren() != null) {
+ for (Node child : node.getChildren()) {
+ if (child != null) {
+ collectArtifacts(child, result, warOnly);
}
}
- } catch (Exception e) {
- // If dependency resolution fails, return empty list
}
- return result;
}
/**
diff --git a/src/test/java/org/apache/maven/plugins/war/WarOverlaysTest.java b/src/test/java/org/apache/maven/plugins/war/WarOverlaysTest.java
index abe69e6..4973912 100644
--- a/src/test/java/org/apache/maven/plugins/war/WarOverlaysTest.java
+++ b/src/test/java/org/apache/maven/plugins/war/WarOverlaysTest.java
@@ -405,14 +405,14 @@ public class WarOverlaysTest {
mojo.setProject(project);
Overlay over1 = new DefaultOverlay(overlay3);
- over1.setExcludes("**/a.*,**/c.*,**/*.xml");
+ over1.setExcludes(new String[] {"**/a.*", "**/c.*", "**/*.xml"});
Overlay over2 = new DefaultOverlay(overlay1);
- over2.setIncludes("jsp/d/*");
- over2.setExcludes("jsp/d/a.jsp");
+ over2.setIncludes(new String[] {"jsp/d/*"});
+ over2.setExcludes(new String[] {"jsp/d/a.jsp"});
Overlay over3 = new DefaultOverlay(overlay3);
- over3.setIncludes("**/*.jsp");
+ over3.setIncludes(new String[] {"**/*.jsp"});
Overlay over4 = new DefaultOverlay(overlay2);
@@ -493,15 +493,15 @@ public class WarOverlaysTest {
mojo.setProject(project);
Overlay over1 = new DefaultOverlay(overlay3);
- over1.setExcludes("**/a.*,**/c.*,**/*.xml,jsp/b.jsp");
+ over1.setExcludes(new String[] {"**/a.*", "**/c.*", "**/*.xml", "jsp/b.jsp"});
Overlay over2 = new DefaultOverlay(overlay1);
- over2.setIncludes("jsp/d/*");
- over2.setExcludes("jsp/d/a.jsp");
+ over2.setIncludes(new String[] {"jsp/d/*"});
+ over2.setExcludes(new String[] {"jsp/d/a.jsp"});
Overlay over3 = new DefaultOverlay(overlay3);
- over3.setIncludes("**/*.jsp");
- over3.setExcludes("jsp/b.jsp");
+ over3.setIncludes(new String[] {"**/*.jsp"});
+ over3.setExcludes(new String[] {"jsp/b.jsp"});
Overlay over4 = new DefaultOverlay(overlay2);
diff --git a/src/test/java/org/apache/maven/plugins/war/overlay/OverlayManagerTest.java b/src/test/java/org/apache/maven/plugins/war/overlay/OverlayManagerTest.java
index 4966f90..73a83da 100644
--- a/src/test/java/org/apache/maven/plugins/war/overlay/OverlayManagerTest.java
+++ b/src/test/java/org/apache/maven/plugins/war/overlay/OverlayManagerTest.java
@@ -155,6 +155,7 @@ class OverlayManagerTest {
/**
* Creates a mock Session that returns the given artifacts as resolved war dependencies.
+ * The root node's children are set up for tree walking (used by OverlayManager).
*/
@SuppressWarnings("unchecked")
private Session createMockSession(List<? extends DownloadedArtifact> artifacts) {
@@ -165,7 +166,7 @@ class OverlayManagerTest {
when(session.collectDependencies(any(Project.class), any(PathScope.class)))
.thenReturn(rootNode);
- List<Node> nodes = new ArrayList<>();
+ List<Node> childNodes = new ArrayList<>();
for (DownloadedArtifact artifact : artifacts) {
Node node = mock(Node.class);
org.apache.maven.api.Dependency dep = mock(org.apache.maven.api.Dependency.class);
@@ -176,12 +177,13 @@ class OverlayManagerTest {
when(dep.getScope()).thenReturn(DependencyScope.COMPILE);
when(dep.isOptional()).thenReturn(false);
when(node.getDependency()).thenReturn(dep);
+ when(node.getChildren()).thenReturn(Collections.emptyList());
when(session.resolveArtifact(eq(dep))).thenReturn(artifact);
- nodes.add(node);
+ childNodes.add(node);
}
- when(session.flattenDependencies(eq(rootNode), any(PathScope.class)))
- .thenReturn((List) nodes);
+ // Set up root node's children for tree walking
+ when(rootNode.getChildren()).thenReturn(childNodes);
} catch (Exception e) {
throw new RuntimeException(e);
}
diff --git a/src/test/java/org/apache/maven/plugins/war/stub/MockSessionHelper.java b/src/test/java/org/apache/maven/plugins/war/stub/MockSessionHelper.java
index fe048a6..f52c069 100644
--- a/src/test/java/org/apache/maven/plugins/war/stub/MockSessionHelper.java
+++ b/src/test/java/org/apache/maven/plugins/war/stub/MockSessionHelper.java
@@ -19,6 +19,7 @@
package org.apache.maven.plugins.war.stub;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -79,12 +80,16 @@ public final class MockSessionHelper {
lenient().when(dep.isOptional()).thenReturn(artifact.isOptional());
lenient().when(node.getDependency()).thenReturn(dep);
+ lenient().when(node.getChildren()).thenReturn(Collections.emptyList());
// Track this mock -> artifact mapping for resolveArtifact
resolveMap.put(dep, artifact);
nodes.add(node);
}
+ // Set up root node's children for tree walking (used by OverlayManager)
+ lenient().when(rootNode.getChildren()).thenReturn(nodes);
+
lenient()
.when(session.flattenDependencies(any(Node.class), any(PathScope.class)))
.thenReturn((List) nodes);