diff --git a/impl/maven-core/src/main/java/org/apache/maven/internal/transformation/impl/DefaultConsumerPomBuilder.java b/impl/maven-core/src/main/java/org/apache/maven/internal/transformation/impl/DefaultConsumerPomBuilder.java index d631e8fd7e2c..6a22927970a7 100644 --- a/impl/maven-core/src/main/java/org/apache/maven/internal/transformation/impl/DefaultConsumerPomBuilder.java +++ b/impl/maven-core/src/main/java/org/apache/maven/internal/transformation/impl/DefaultConsumerPomBuilder.java @@ -48,7 +48,9 @@ import org.apache.maven.api.services.ModelBuilderResult; import org.apache.maven.api.services.ModelSource; import org.apache.maven.api.services.model.LifecycleBindingsInjector; +import org.apache.maven.execution.MavenExecutionRequest; import org.apache.maven.impl.InternalSession; +import org.apache.maven.internal.impl.InternalMavenSession; import org.apache.maven.model.v4.MavenModelVersion; import org.apache.maven.project.MavenProject; import org.apache.maven.project.SourceQueries; @@ -161,14 +163,14 @@ public Model build(RepositorySystemSession session, MavenProject project, ModelS protected Model buildPom(RepositorySystemSession session, MavenProject project, ModelSource src) throws ModelBuilderException { - ModelBuilderResult result = buildModel(session, src); + ModelBuilderResult result = buildModel(session, project, src); Model model = result.getRawModel(); return transformPom(model, project); } protected Model buildBomWithoutFlatten(RepositorySystemSession session, MavenProject project, ModelSource src) throws ModelBuilderException { - ModelBuilderResult result = buildModel(session, src); + ModelBuilderResult result = buildModel(session, project, src); Model model = result.getRawModel(); // For BOMs without flattening, we just need to transform the packaging from "bom" to "pom" // but keep everything else from the raw model (including unresolved versions) @@ -177,20 +179,21 @@ protected Model buildBomWithoutFlatten(RepositorySystemSession session, MavenPro protected Model buildBom(RepositorySystemSession session, MavenProject project, ModelSource src) throws ModelBuilderException { - ModelBuilderResult result = buildModel(session, src); + ModelBuilderResult result = buildModel(session, project, src); Model model = result.getEffectiveModel(); return transformBom(model, project); } protected Model buildNonPom(RepositorySystemSession session, MavenProject project, ModelSource src) throws ModelBuilderException { - Model model = buildEffectiveModel(session, src); + Model model = buildEffectiveModel(session, project, src); return transformNonPom(model, project); } - private Model buildEffectiveModel(RepositorySystemSession session, ModelSource src) throws ModelBuilderException { + private Model buildEffectiveModel(RepositorySystemSession session, MavenProject project, ModelSource src) + throws ModelBuilderException { InternalSession iSession = InternalSession.from(session); - ModelBuilderResult result = buildModel(session, src); + ModelBuilderResult result = buildModel(session, project, src); Model model = result.getEffectiveModel(); if (model.getDependencyManagement() != null @@ -295,7 +298,7 @@ private static String getDependencyKey(Dependency dependency) { + (dependency.getClassifier() != null ? dependency.getClassifier() : ""); } - private ModelBuilderResult buildModel(RepositorySystemSession session, ModelSource src) + private ModelBuilderResult buildModel(RepositorySystemSession session, MavenProject project, ModelSource src) throws ModelBuilderException { InternalSession iSession = InternalSession.from(session); ModelBuilderRequest.ModelBuilderRequestBuilder request = ModelBuilderRequest.builder(); @@ -306,6 +309,38 @@ private ModelBuilderResult buildModel(RepositorySystemSession session, ModelSour request.systemProperties(session.getSystemProperties()); request.userProperties(session.getUserProperties()); request.lifecycleBindingsInjector(lifecycleBindingsInjector::injectLifecycleBindings); + // Pass remote repositories so that the model builder can resolve BOM imports + // from non-central repositories (e.g., repositories defined in settings.xml profiles). + // Prefer project repositories, but fall back to session repositories if the project's + // remote repository list is not populated (e.g., during install/deploy phases). + if (project != null + && project.getRemoteProjectRepositories() != null + && !project.getRemoteProjectRepositories().isEmpty()) { + request.repositories(project.getRemoteProjectRepositories().stream() + .map(iSession::getRemoteRepository) + .toList()); + } else { + request.repositories(iSession.getRemoteRepositories()); + } + // Pass profiles and active/inactive profile IDs from the execution request + // so that settings.xml profiles are applied during consumer POM model building. + if (iSession instanceof InternalMavenSession mavenSession) { + MavenExecutionRequest executionRequest = + mavenSession.getMavenSession().getRequest(); + if (executionRequest.getProfiles() != null) { + request.profiles(executionRequest.getProfiles().stream() + .map(org.apache.maven.model.Profile::getDelegate) + .toList()); + } + request.activeProfileIds(executionRequest.getActiveProfiles()); + request.inactiveProfileIds(executionRequest.getInactiveProfiles()); + } else { + LOGGER.debug( + "Session is not an InternalMavenSession ({}); settings.xml profiles will not be " + + "passed to the consumer POM model builder. BOM imports from repositories " + + "defined only in settings.xml profiles may fail to resolve.", + iSession.getClass().getName()); + } ModelBuilder.ModelBuilderSession mbSession = iSession.getData().get(SessionData.key(ModelBuilder.ModelBuilderSession.class)); return mbSession.build(request.build()); diff --git a/impl/maven-core/src/test/java/org/apache/maven/internal/transformation/impl/ConsumerPomBuilderTest.java b/impl/maven-core/src/test/java/org/apache/maven/internal/transformation/impl/ConsumerPomBuilderTest.java index d9744cad5fcb..495600db4acf 100644 --- a/impl/maven-core/src/test/java/org/apache/maven/internal/transformation/impl/ConsumerPomBuilderTest.java +++ b/impl/maven-core/src/test/java/org/apache/maven/internal/transformation/impl/ConsumerPomBuilderTest.java @@ -48,7 +48,9 @@ import org.apache.maven.internal.impl.InternalMavenSession; import org.apache.maven.internal.transformation.AbstractRepositoryTestCase; import org.apache.maven.project.MavenProject; +import org.eclipse.aether.repository.RemoteRepository; import org.junit.jupiter.api.Test; +import org.mockito.ArgumentCaptor; import org.mockito.Mockito; import static org.junit.jupiter.api.Assertions.assertFalse; @@ -198,4 +200,65 @@ void testScmInheritance() throws Exception { assertNull(transformed.getScm().getChildScmUrlInheritAppendPath()); assertNull(transformed.getScm().getChildScmDeveloperConnectionInheritAppendPath()); } + + /** + * Verifies that the consumer POM builder passes the project's remote repositories + * to the model builder request, so that BOM imports from non-central repositories + * (e.g. repositories defined in settings.xml profiles) can be resolved. + *
+ * Without the fix in {@code DefaultConsumerPomBuilder.buildModel()}, the
+ * {@code ModelBuilderRequest} is constructed without repositories, profiles, or
+ * active profile IDs. This causes the model builder to only see Maven Central
+ * when resolving BOM imports, leading to "Non-resolvable import POM" failures
+ * for artifacts hosted in private/corporate repositories.
+ */
+ @Test
+ void testConsumerPomPassesProjectRepositoriesToModelBuilder() throws Exception {
+ setRootDirectory("trivial");
+ Path file = Paths.get("src/test/resources/consumer/trivial/child/pom.xml");
+
+ MavenProject project = getEffectiveModel(file);
+
+ // Add a custom remote repository to the project, simulating a repository
+ // injected from settings.xml profile (e.g. a corporate/private repository)
+ RemoteRepository customRepo =
+ new RemoteRepository.Builder("custom-repo", "default", "https://repo.example.com/maven2").build();
+ project.getRemoteProjectRepositories().add(customRepo);
+
+ // Spy on the ModelBuilderSession to capture the ModelBuilderRequest
+ ModelBuilder.ModelBuilderSession originalMbs = modelBuilder.newSession();
+ ModelBuilder.ModelBuilderSession spyMbs = Mockito.spy(originalMbs);
+ InternalSession.from(session).getData().set(SessionData.key(ModelBuilder.ModelBuilderSession.class), spyMbs);
+
+ // Build the consumer POM
+ builder.build(session, project, Sources.buildSource(file));
+
+ // Capture the ModelBuilderRequest passed to the ModelBuilderSession
+ ArgumentCaptor
+ * This is critical for consumer POM building: the consumer POM builder reuses the
+ * existing {@code ModelBuilderSession} and calls {@code build()} with a request
+ * containing the project's repositories (which may include non-central repos from
+ * settings.xml profiles). Without this, BOM imports from non-central repositories fail.
+ */
+ @Test
+ public void testBuildConsumerWithExplicitRepositories() {
+ // First build to create the mainSession (simulates project build phase)
+ ModelBuilderRequest firstRequest = ModelBuilderRequest.builder()
+ .session(session)
+ .requestType(ModelBuilderRequest.RequestType.BUILD_PROJECT)
+ .source(Sources.buildSource(getPom("simple-standalone")))
+ .build();
+ ModelBuilder.ModelBuilderSession mbs = builder.newSession();
+ mbs.build(firstRequest);
+
+ // Access the mainSession (package-private) to call derive() and verify state
+ DefaultModelBuilder.ModelBuilderSessionState mainState =
+ ((DefaultModelBuilder.ModelBuilderSessionImpl) mbs).mainSession;
+
+ // Verify the main session only has central
+ assertEquals(1, mainState.getRepositories().size());
+ assertEquals("central", mainState.getRepositories().get(0).getId());
+
+ // Derive a BUILD_CONSUMER session with explicit repositories
+ RemoteRepository customRepo = session.createRemoteRepository("custom-repo", "https://repo.example.com/maven2");
+ ModelBuilderRequest consumerRequest = ModelBuilderRequest.builder()
+ .session(session)
+ .requestType(ModelBuilderRequest.RequestType.BUILD_CONSUMER)
+ .source(Sources.buildSource(getPom("simple-standalone")))
+ .repositories(List.of(
+ customRepo, session.createRemoteRepository("central", "https://repo.maven.apache.org/maven2")))
+ .build();
+
+ DefaultModelBuilder.ModelBuilderSessionState derived = mainState.derive(consumerRequest);
+
+ // Verify the derived session includes the custom repository
+ assertTrue(
+ derived.getRepositories().stream().anyMatch(r -> "custom-repo".equals(r.getId())),
+ "Derived session repositories should include the custom repo from the request");
+ assertTrue(
+ derived.getExternalRepositories().stream().anyMatch(r -> "custom-repo".equals(r.getId())),
+ "Derived session externalRepositories should include the custom repo from the request");
+ }
+
private Path getPom(String name) {
return Paths.get("src/test/resources/poms/factory/" + name + ".xml").toAbsolutePath();
}
diff --git a/impl/maven-impl/src/test/resources/poms/factory/simple-standalone.xml b/impl/maven-impl/src/test/resources/poms/factory/simple-standalone.xml
new file mode 100644
index 000000000000..1ccfe631c16a
--- /dev/null
+++ b/impl/maven-impl/src/test/resources/poms/factory/simple-standalone.xml
@@ -0,0 +1,22 @@
+
+
+
+ * This is a regression test for a bug where {@code DefaultConsumerPomBuilder.buildModel()}
+ * did not pass repositories, profiles, or active profile IDs to the {@code ModelBuilderRequest},
+ * and {@code DefaultModelBuilder.derive()} ignored the request's repositories when creating
+ * derived sessions. This caused "Non-resolvable import POM" failures during the install phase
+ * for artifacts hosted in private/corporate repositories configured via settings.xml.
+ *
+ * @since 4.0.0
+ */
+class MavenITConsumerPomBomFromSettingsRepoTest extends AbstractMavenIntegrationTestCase {
+
+ /**
+ * Verifies that consumer POM flattening works when the BOM is only available
+ * from a repository defined in a settings.xml profile.
+ *
+ * Without the fix, this test fails with:
+ *
+ * Non-resolvable import POM: Could not find artifact
+ * org.apache.maven.its.cpbom:the-bom:pom:1.0 in central
+ *
+ */
+ @Test
+ void testConsumerPomWithBomFromSettingsProfileRepo() throws Exception {
+ Path basedir = extractResources("/gh-11767-consumer-pom-bom-from-settings-repo")
+ .toPath()
+ .toAbsolutePath();
+
+ Verifier verifier = newVerifier(basedir.toString());
+ verifier.deleteArtifacts("org.apache.maven.its.cpbom");
+
+ // Apply settings template with the custom repository URL
+ verifier.filterFile("settings-template.xml", "settings.xml");
+ verifier.addCliArgument("--settings");
+ verifier.addCliArgument("settings.xml");
+
+ // Enable consumer POM flattening to trigger full BOM resolution
+ // during the install phase consumer POM transformation
+ verifier.addCliArgument("-Dmaven.consumer.pom.flatten=true");
+ verifier.addCliArgument("install");
+ verifier.execute();
+ verifier.verifyErrorFreeLog();
+
+ // Verify the consumer POM was generated
+ Path consumerPom = basedir.resolve(Path.of(
+ "target",
+ "project-local-repo",
+ "org.apache.maven.its.cpbom",
+ "consumer-pom-bom-settings-repo",
+ "1.0",
+ "consumer-pom-bom-settings-repo-1.0-consumer.pom"));
+ assertTrue(Files.exists(consumerPom), "consumer POM not found at " + consumerPom);
+
+ // Read and validate the consumer POM content
+ Model consumerPomModel;
+ try (Reader r = Files.newBufferedReader(consumerPom)) {
+ consumerPomModel = new MavenStaxReader().read(r);
+ }
+
+ // The consumer POM should have the dependency with the version resolved from the BOM
+ assertNotNull(consumerPomModel.getDependencies(), "Consumer POM should have dependencies");
+ assertFalse(consumerPomModel.getDependencies().isEmpty(), "Consumer POM should have at least one dependency");
+
+ boolean hasLibA = consumerPomModel.getDependencies().stream()
+ .anyMatch(d -> "lib-a".equals(d.getArtifactId())
+ && "org.apache.maven.its.cpbom".equals(d.getGroupId())
+ && "2.0".equals(d.getVersion()));
+ assertTrue(
+ hasLibA,
+ "Consumer POM should contain lib-a with version 2.0 resolved from the BOM. "
+ + "Actual dependencies: " + consumerPomModel.getDependencies());
+
+ // The BOM import should NOT appear in the consumer POM (it's been flattened)
+ if (consumerPomModel.getDependencyManagement() != null) {
+ boolean hasBomImport = consumerPomModel.getDependencyManagement().getDependencies().stream()
+ .anyMatch(d -> "the-bom".equals(d.getArtifactId()) && "import".equals(d.getScope()));
+ assertFalse(hasBomImport, "Consumer POM should not contain the BOM import after flattening");
+ }
+ }
+}
diff --git a/its/core-it-suite/src/test/resources/gh-11767-consumer-pom-bom-from-settings-repo/pom.xml b/its/core-it-suite/src/test/resources/gh-11767-consumer-pom-bom-from-settings-repo/pom.xml
new file mode 100644
index 000000000000..4f92898f82c3
--- /dev/null
+++ b/its/core-it-suite/src/test/resources/gh-11767-consumer-pom-bom-from-settings-repo/pom.xml
@@ -0,0 +1,62 @@
+
+
+