This is an automated email from the ASF dual-hosted git repository.
gnodet pushed a commit to branch maven-4.0.x
in repository https://gitbox.apache.org/repos/asf/maven.git
The following commit(s) were added to refs/heads/maven-4.0.x by this push:
new 4755cfcbd3 Fix race condition in concurrent builder: reactor artifact
file snapshotted too early (#13728) (#13734)
4755cfcbd3 is described below
commit 4755cfcbd33761441da9c6008eb4b6e1dc705396
Author: Guillaume Nodet <[email protected]>
AuthorDate: Wed Oct 28 18:39:05 2026 +0100
Fix race condition in concurrent builder: reactor artifact file snapshotted
too early (#13728) (#13734)
---
.../internal/LifecycleDependencyResolver.java | 20 ++++++---
.../internal/LifecycleDependencyResolverTest.java | 50 ++++++++++++++++++++++
2 files changed, 63 insertions(+), 7 deletions(-)
diff --git
a/impl/maven-core/src/main/java/org/apache/maven/lifecycle/internal/LifecycleDependencyResolver.java
b/impl/maven-core/src/main/java/org/apache/maven/lifecycle/internal/LifecycleDependencyResolver.java
index e25472ec92..cc4b2191bb 100644
---
a/impl/maven-core/src/main/java/org/apache/maven/lifecycle/internal/LifecycleDependencyResolver.java
+++
b/impl/maven-core/src/main/java/org/apache/maven/lifecycle/internal/LifecycleDependencyResolver.java
@@ -128,12 +128,15 @@ public void resolveProjectDependencies(
Set<Artifact> resolvedArtifacts = resolveProjectArtifacts(
project, scopesToCollect, scopesToResolve, session,
aggregating, projectArtifacts);
- Map<Artifact, File> reactorProjects =
+ // MNG-6300: build a live-lookup map so that reactor files are
always up-to-date.
+ // In the concurrent builder a sibling project may install its jar
*after* this
+ // resolution starts, so we must read getFile() at lookup time
rather than
+ // snapshotting it up-front (a null snapshot would prevent the
file from ever
+ // being set, causing NPEs in plugins such as
maven-dependency-plugin:properties).
+ Map<Artifact, MavenProject> reactorProjects =
new HashMap<>(session.getProjects().size());
for (MavenProject reactorProject : session.getProjects()) {
- reactorProjects.put(
- reactorProject.getArtifact(),
- reactorProject.getArtifact().getFile());
+ reactorProjects.put(reactorProject.getArtifact(),
reactorProject);
}
Map<String, Artifact> map = new HashMap<>();
@@ -143,9 +146,12 @@ public void resolveProjectDependencies(
* During lifecycle the Artifact.getFile() can change from
target/classes to the actual jar.
* This clearly shows that target/classes should not be abused
as artifactFile just for the classpath
*/
- File reactorProjectFile = reactorProjects.get(artifact);
- if (reactorProjectFile != null) {
- artifact.setFile(reactorProjectFile);
+ MavenProject reactorProject = reactorProjects.get(artifact);
+ if (reactorProject != null) {
+ File reactorProjectFile =
reactorProject.getArtifact().getFile();
+ if (reactorProjectFile != null) {
+ artifact.setFile(reactorProjectFile);
+ }
}
map.put(artifact.getDependencyConflictId(), artifact);
diff --git
a/impl/maven-core/src/test/java/org/apache/maven/lifecycle/internal/LifecycleDependencyResolverTest.java
b/impl/maven-core/src/test/java/org/apache/maven/lifecycle/internal/LifecycleDependencyResolverTest.java
index 3ff43bc2d2..f627a3b34c 100644
---
a/impl/maven-core/src/test/java/org/apache/maven/lifecycle/internal/LifecycleDependencyResolverTest.java
+++
b/impl/maven-core/src/test/java/org/apache/maven/lifecycle/internal/LifecycleDependencyResolverTest.java
@@ -79,4 +79,54 @@ void testCachedReactorProjectDependencies() throws Exception
{
.get("org.apache.maven.its.mng6300:mng6300-lib")
.getFile());
}
+
+ /**
+ * Verifies that resolving a downstream project (war) does not throw when
a reactor sibling
+ * (lib) has a {@code null} artifact file — i.e. the sibling has not yet
packaged its jar.
+ *
+ * <p>This guards the null-file code path introduced by the fix in
+ * <a href="https://github.com/apache/maven/pull/13728">#13728</a>. The
concurrent-builder
+ * race (sibling file is {@code null} at map-build time but non-null at
lookup time within the
+ * same call) cannot be reproduced in a sequential unit test; this test
covers the observable
+ * contract: when the sibling file is {@code null}, resolution completes
without error and the
+ * resolved artifact's file remains {@code null} rather than causing an
NPE downstream.
+ */
+ @Test
+ void testReactorDependencyWithNullArtifactFileDoesNotThrow() throws
Exception {
+ MavenSession session = createMavenSession(
+ new
File("src/test/projects/lifecycle-dependency-resolver/pom.xml"), new
Properties(), true);
+ Collection<String> scopesToCollect = null;
+ Collection<String> scopesToResolve =
Collections.singletonList("compile");
+ boolean aggregating = false;
+
+ MavenProject root = session.getProjects().get(0);
+ MavenProject lib = session.getProjects().get(1);
+ MavenProject war = session.getProjects().get(2);
+
+ // lib has not yet produced its jar (concurrent builder: packaging not
yet complete)
+ lib.getArtifact().setFile(null);
+
+ Set<Artifact> reactorArtifacts = new HashSet<>();
+ root.setArtifactFilter(artifact -> true);
+ lib.setArtifactFilter(artifact -> true);
+ war.setArtifactFilter(artifact -> true);
+
+ resolver.resolveProjectDependencies(
+ root, scopesToCollect, scopesToResolve, session, aggregating,
reactorArtifacts);
+ reactorArtifacts.add(root.getArtifact());
+
+ resolver.resolveProjectDependencies(
+ lib, scopesToCollect, scopesToResolve, session, aggregating,
reactorArtifacts);
+ reactorArtifacts.add(lib.getArtifact());
+
+ // Resolution of war must complete without NPE even though lib's
artifact file is null
+ resolver.resolveProjectDependencies(
+ war, scopesToCollect, scopesToResolve, session, aggregating,
reactorArtifacts);
+
+ assertNull(
+ war.getArtifactMap()
+ .get("org.apache.maven.its.mng6300:mng6300-lib")
+ .getFile(),
+ "resolved lib artifact file should remain null when lib has
not yet packaged its jar");
+ }
}