From 3529bdffadb19b58716c9be041060b6448565396 Mon Sep 17 00:00:00 2001 From: Guillaume Nodet Date: Tue, 1 Sep 2026 21:43:40 +0200 Subject: [PATCH 1/4] Add tests for GH-13004: consumer POM profile-activated property in artifactId Add unit tests and integration test verifying that the consumer POM builder correctly handles OS-activated profile properties used in dependency artifactId coordinates. This scenario (from Apache Hop) is already handled by the CONSUMER_PARENT request type (#11799) and the MNG-8709 user-property fix, but had no regression test. Unit tests cover: - BUILD_CONSUMER resolving parent profile properties in artifactId - BUILD_CONSUMER after BUILD_PROJECT (cache hit scenario) - OS-activated profile properties with full reactor simulation Integration test covers: - Multi-module project with OS-activated profiles in parent POM - Dependency management entries inside profiles using ${...} artifactId - Non-flattened consumer POM preserving parent and profiles - Flattened consumer POM fully resolving the property Closes #13004 Co-Authored-By: Claude Opus 4.6 --- .../impl/model/DefaultModelBuilderTest.java | 164 ++++++++++++++++++ .../factory/consumer-os-profile-child.xml | 35 ++++ .../factory/consumer-os-profile-parent.xml | 61 +++++++ .../consumer-profile-artifactid-child.xml | 35 ++++ .../consumer-profile-artifactid-parent.xml | 37 ++++ ...13004ConsumerPomProfileArtifactIdTest.java | 106 +++++++++++ .../child/pom.xml | 41 +++++ .../org/apache/maven/its/gh13004/Child.java | 3 + .../lib/pom.xml | 31 ++++ .../org/apache/maven/its/gh13004/Lib.java | 3 + .../pom.xml | 79 +++++++++ 11 files changed, 595 insertions(+) create mode 100644 impl/maven-impl/src/test/resources/poms/factory/consumer-os-profile-child.xml create mode 100644 impl/maven-impl/src/test/resources/poms/factory/consumer-os-profile-parent.xml create mode 100644 impl/maven-impl/src/test/resources/poms/factory/consumer-profile-artifactid-child.xml create mode 100644 impl/maven-impl/src/test/resources/poms/factory/consumer-profile-artifactid-parent.xml create mode 100644 its/core-it-suite/src/test/java/org/apache/maven/it/MavenITgh13004ConsumerPomProfileArtifactIdTest.java create mode 100644 its/core-it-suite/src/test/resources/gh-13004-consumer-pom-profile-artifactid/child/pom.xml create mode 100644 its/core-it-suite/src/test/resources/gh-13004-consumer-pom-profile-artifactid/child/src/main/java/org/apache/maven/its/gh13004/Child.java create mode 100644 its/core-it-suite/src/test/resources/gh-13004-consumer-pom-profile-artifactid/lib/pom.xml create mode 100644 its/core-it-suite/src/test/resources/gh-13004-consumer-pom-profile-artifactid/lib/src/main/java/org/apache/maven/its/gh13004/Lib.java create mode 100644 its/core-it-suite/src/test/resources/gh-13004-consumer-pom-profile-artifactid/pom.xml diff --git a/impl/maven-impl/src/test/java/org/apache/maven/impl/model/DefaultModelBuilderTest.java b/impl/maven-impl/src/test/java/org/apache/maven/impl/model/DefaultModelBuilderTest.java index cf52c6b1e547..3bb52989328b 100644 --- a/impl/maven-impl/src/test/java/org/apache/maven/impl/model/DefaultModelBuilderTest.java +++ b/impl/maven-impl/src/test/java/org/apache/maven/impl/model/DefaultModelBuilderTest.java @@ -1106,6 +1106,170 @@ public void testBuildConsumerResolvesParentProfileProperties() { "Managed dependency version should be interpolated, not ${managed.version}"); } + /** + * Verifies that BUILD_CONSUMER resolves properties defined in parent POM profiles + * when those properties are used in dependency artifactId fields. + * This reproduces GH-13004: the effective model coordinate validation rejects + * ${swt.artifactId} because profiles are not activated for BUILD_CONSUMER. + */ + @Test + public void testBuildConsumerResolvesParentProfilePropertyInArtifactId() { + Path parentPom = getPom("consumer-profile-artifactid-parent"); + Path childPom = getPom("consumer-profile-artifactid-child"); + + ModelBuilder.ModelBuilderSession mbs = builder.newSession(); + + mbs.build(ModelBuilderRequest.builder() + .session(session) + .requestType(ModelBuilderRequest.RequestType.BUILD_PROJECT) + .source(Sources.buildSource(parentPom)) + .build()); + + ModelBuilderResult consumerResult = assertDoesNotThrow( + () -> mbs.build(ModelBuilderRequest.builder() + .session(session) + .requestType(ModelBuilderRequest.RequestType.BUILD_CONSUMER) + .source(Sources.buildSource(childPom)) + .build()), + "BUILD_CONSUMER should not fail when parent profile property is used in dependency artifactId"); + + assertNotNull(consumerResult); + Model effectiveModel = consumerResult.getEffectiveModel(); + assertNotNull(effectiveModel); + + // The property from the parent's profile should be inherited and available + assertEquals( + "org.eclipse.swt.gtk.linux.x86-64", + effectiveModel.getProperties().get("swt.artifactId"), + "Property from parent's profile should be resolved in BUILD_CONSUMER effective model"); + + // The dependency artifactId should be interpolated (not ${swt.artifactId}) + Dependency dep = effectiveModel.getDependencies().stream() + .filter(d -> "org.eclipse.platform".equals(d.getGroupId())) + .findFirst() + .orElse(null); + assertNotNull(dep, "Dependency with org.eclipse.platform groupId should exist"); + assertEquals( + "org.eclipse.swt.gtk.linux.x86-64", + dep.getArtifactId(), + "Dependency artifactId should be interpolated from parent profile property"); + } + + /** + * Same as above but builds the child as BUILD_PROJECT first (simulating + * the full reactor build), then builds BUILD_CONSUMER for the child. + * This is closer to what happens in a real Maven build. + */ + @Test + public void testBuildConsumerAfterBuildProjectResolvesParentProfilePropertyInArtifactId() { + Path parentPom = getPom("consumer-profile-artifactid-parent"); + Path childPom = getPom("consumer-profile-artifactid-child"); + + ModelBuilder.ModelBuilderSession mbs = builder.newSession(); + + // Build parent as BUILD_PROJECT + mbs.build(ModelBuilderRequest.builder() + .session(session) + .requestType(ModelBuilderRequest.RequestType.BUILD_PROJECT) + .source(Sources.buildSource(parentPom)) + .build()); + + // Build child as BUILD_PROJECT (as in reactor build) + mbs.build(ModelBuilderRequest.builder() + .session(session) + .requestType(ModelBuilderRequest.RequestType.BUILD_PROJECT) + .source(Sources.buildSource(childPom)) + .build()); + + // Now build child as BUILD_CONSUMER (as in consumer POM generation) + ModelBuilderResult consumerResult = assertDoesNotThrow( + () -> mbs.build(ModelBuilderRequest.builder() + .session(session) + .requestType(ModelBuilderRequest.RequestType.BUILD_CONSUMER) + .source(Sources.buildSource(childPom)) + .build()), + "BUILD_CONSUMER should not fail after BUILD_PROJECT when parent profile property is used in artifactId"); + + assertNotNull(consumerResult); + Model effectiveModel = consumerResult.getEffectiveModel(); + assertNotNull(effectiveModel); + + assertEquals( + "org.eclipse.swt.gtk.linux.x86-64", + effectiveModel.getProperties().get("swt.artifactId"), + "Property from parent's profile should be resolved in BUILD_CONSUMER effective model"); + + Dependency dep = effectiveModel.getDependencies().stream() + .filter(d -> "org.eclipse.platform".equals(d.getGroupId())) + .findFirst() + .orElse(null); + assertNotNull(dep, "Dependency with org.eclipse.platform groupId should exist"); + assertEquals( + "org.eclipse.swt.gtk.linux.x86-64", + dep.getArtifactId(), + "Dependency artifactId should be interpolated from parent profile property"); + } + + /** + * Verifies that BUILD_CONSUMER resolves properties defined in parent POM + * OS-activated profiles when those properties are used in dependency artifactId fields. + * This is the exact scenario from GH-13004 (Apache Hop). + */ + @Test + public void testBuildConsumerResolvesOsActivatedProfilePropertyInArtifactId() { + Path parentPom = getPom("consumer-os-profile-parent"); + Path childPom = getPom("consumer-os-profile-child"); + + ModelBuilder.ModelBuilderSession mbs = builder.newSession(); + + // Build parent as BUILD_PROJECT first + mbs.build(ModelBuilderRequest.builder() + .session(session) + .requestType(ModelBuilderRequest.RequestType.BUILD_PROJECT) + .source(Sources.buildSource(parentPom)) + .build()); + + // Build child as BUILD_PROJECT (reactor build) + mbs.build(ModelBuilderRequest.builder() + .session(session) + .requestType(ModelBuilderRequest.RequestType.BUILD_PROJECT) + .source(Sources.buildSource(childPom)) + .build()); + + // Now build child as BUILD_CONSUMER + ModelBuilderResult consumerResult = assertDoesNotThrow( + () -> mbs.build(ModelBuilderRequest.builder() + .session(session) + .requestType(ModelBuilderRequest.RequestType.BUILD_CONSUMER) + .source(Sources.buildSource(childPom)) + .build()), + "BUILD_CONSUMER should not fail when parent defines OS-activated profile property used in artifactId"); + + assertNotNull(consumerResult); + Model effectiveModel = consumerResult.getEffectiveModel(); + assertNotNull(effectiveModel); + + // The platform.artifactId property should be resolved from one of the OS profiles + String platformArtifactId = effectiveModel.getProperties().get("platform.artifactId"); + assertNotNull( + platformArtifactId, + "Property from parent's OS profile should be resolved in BUILD_CONSUMER effective model"); + + // The dependency artifactId should be interpolated + Dependency dep = effectiveModel.getDependencies().stream() + .filter(d -> "org.example".equals(d.getGroupId())) + .findFirst() + .orElse(null); + assertNotNull(dep, "Dependency with org.example groupId should exist"); + assertFalse( + dep.getArtifactId().contains("${"), + "Dependency artifactId should be interpolated, got: " + dep.getArtifactId()); + assertEquals( + platformArtifactId, + dep.getArtifactId(), + "Dependency artifactId should match the platform property value"); + } + /** * Verifies that the versions of sibling reactor modules declared in {@code } * are inferred, just like they already are for regular dependencies (GH-11147). diff --git a/impl/maven-impl/src/test/resources/poms/factory/consumer-os-profile-child.xml b/impl/maven-impl/src/test/resources/poms/factory/consumer-os-profile-child.xml new file mode 100644 index 000000000000..c37750738695 --- /dev/null +++ b/impl/maven-impl/src/test/resources/poms/factory/consumer-os-profile-child.xml @@ -0,0 +1,35 @@ + + + + + org.apache.maven.tests + consumer-os-profile-parent + consumer-os-profile-parent.xml + + consumer-os-profile-child + 1.0-SNAPSHOT + jar + + + + org.example + ${platform.artifactId} + 1.0 + + + diff --git a/impl/maven-impl/src/test/resources/poms/factory/consumer-os-profile-parent.xml b/impl/maven-impl/src/test/resources/poms/factory/consumer-os-profile-parent.xml new file mode 100644 index 000000000000..7a70c95a3cc2 --- /dev/null +++ b/impl/maven-impl/src/test/resources/poms/factory/consumer-os-profile-parent.xml @@ -0,0 +1,61 @@ + + + + org.apache.maven.tests + consumer-os-profile-parent + 1.0-SNAPSHOT + pom + + + + + platform-linux + + + unix + + + + platform-lib-linux + + + + platform-windows + + + windows + + + + platform-lib-windows + + + + platform-mac + + + mac + + + + platform-lib-mac + + + + diff --git a/impl/maven-impl/src/test/resources/poms/factory/consumer-profile-artifactid-child.xml b/impl/maven-impl/src/test/resources/poms/factory/consumer-profile-artifactid-child.xml new file mode 100644 index 000000000000..89659a8deb11 --- /dev/null +++ b/impl/maven-impl/src/test/resources/poms/factory/consumer-profile-artifactid-child.xml @@ -0,0 +1,35 @@ + + + + + org.apache.maven.tests + consumer-profile-artifactid-parent + consumer-profile-artifactid-parent.xml + + consumer-profile-artifactid-child + 1.0-SNAPSHOT + jar + + + + org.eclipse.platform + ${swt.artifactId} + 1.0 + + + diff --git a/impl/maven-impl/src/test/resources/poms/factory/consumer-profile-artifactid-parent.xml b/impl/maven-impl/src/test/resources/poms/factory/consumer-profile-artifactid-parent.xml new file mode 100644 index 000000000000..c0d9c992370c --- /dev/null +++ b/impl/maven-impl/src/test/resources/poms/factory/consumer-profile-artifactid-parent.xml @@ -0,0 +1,37 @@ + + + + org.apache.maven.tests + consumer-profile-artifactid-parent + 1.0-SNAPSHOT + pom + + + + default-platform + + + !skipDefaultPlatform + + + + org.eclipse.swt.gtk.linux.x86-64 + + + + diff --git a/its/core-it-suite/src/test/java/org/apache/maven/it/MavenITgh13004ConsumerPomProfileArtifactIdTest.java b/its/core-it-suite/src/test/java/org/apache/maven/it/MavenITgh13004ConsumerPomProfileArtifactIdTest.java new file mode 100644 index 000000000000..16150e1a7c96 --- /dev/null +++ b/its/core-it-suite/src/test/java/org/apache/maven/it/MavenITgh13004ConsumerPomProfileArtifactIdTest.java @@ -0,0 +1,106 @@ +/* + * 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.it; + +import java.nio.file.Files; +import java.nio.file.Path; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * Verify that the consumer POM builder correctly resolves properties from + * OS-activated profiles in parent POMs when those properties are used in + * child dependency artifactId coordinates. + *

+ * This simulates the Apache Hop scenario where the root POM uses OS-activated + * profiles to set platform-specific dependency artifactIds (e.g., SWT) and + * dependency management entries that reference those properties. + *

+ * The consumer POM builder must resolve these properties before coordinate + * validation, otherwise it fails with: + *

+ *   'dependencies.dependency.artifactId' ... with value '${swt.artifactId}'
+ *   does not match a valid coordinate id pattern.
+ * 
+ * + * @see GH-13004 + */ +class MavenITgh13004ConsumerPomProfileArtifactIdTest extends AbstractMavenIntegrationTestCase { + + /** + * Test that the build succeeds when a parent POM defines OS-activated profiles + * with properties used in child dependency artifactIds. The default (non-flattened) + * consumer POM preserves the parent reference and profile activation, so consumers + * can resolve the property through the parent. + */ + @Test + void testConsumerPomResolvesOsProfilePropertyInArtifactId() throws Exception { + Path basedir = extractResources("/gh-13004-consumer-pom-profile-artifactid"); + + Verifier verifier = newVerifier(basedir); + verifier.addCliArgument("install"); + verifier.execute(); + verifier.verifyErrorFreeLog(); + + // Verify the parent consumer POM preserves the OS-activated profiles + Path parentConsumerPom = verifier.getArtifactPath( + "org.apache.maven.its.gh13004", "parent", "1.0-SNAPSHOT", "pom"); + assertTrue(Files.exists(parentConsumerPom), "Parent consumer POM should exist"); + String parentContent = Files.readString(parentConsumerPom); + assertTrue( + parentContent.contains("unix") || parentContent.contains("windows"), + "Parent consumer POM should preserve OS-activation profiles"); + assertTrue( + parentContent.contains("platform.artifactId"), + "Parent consumer POM should preserve the profile property definition"); + } + + /** + * Test that flattened consumer POM generation succeeds and fully resolves the + * profile property in the dependency artifactId. With flattening enabled, the + * effective (interpolated) model is used, so the consumer POM must contain the + * resolved artifactId, not the raw ${platform.artifactId} reference. + */ + @Test + void testFlattenedConsumerPomResolvesOsProfilePropertyInArtifactId() throws Exception { + Path basedir = extractResources("/gh-13004-consumer-pom-profile-artifactid"); + + Verifier verifier = newVerifier(basedir); + verifier.addCliArgument("-Dmaven.consumer.pom.flatten=true"); + verifier.addCliArgument("install"); + verifier.execute(); + verifier.verifyErrorFreeLog(); + + // With flattening, the consumer POM uses the effective model: + // ${platform.artifactId} must be resolved to "lib" + Path childConsumerPom = verifier.getArtifactPath( + "org.apache.maven.its.gh13004", "child", "1.0-SNAPSHOT", "pom"); + assertTrue(Files.exists(childConsumerPom), "Child consumer POM should exist"); + String childContent = Files.readString(childConsumerPom); + assertFalse( + childContent.contains("${platform.artifactId}"), + "Flattened consumer POM should not contain unresolved ${platform.artifactId}"); + assertTrue( + childContent.contains("lib"), + "Flattened consumer POM should contain the resolved artifactId 'lib'"); + } +} diff --git a/its/core-it-suite/src/test/resources/gh-13004-consumer-pom-profile-artifactid/child/pom.xml b/its/core-it-suite/src/test/resources/gh-13004-consumer-pom-profile-artifactid/child/pom.xml new file mode 100644 index 000000000000..ff93b09a9352 --- /dev/null +++ b/its/core-it-suite/src/test/resources/gh-13004-consumer-pom-profile-artifactid/child/pom.xml @@ -0,0 +1,41 @@ + + + + 4.1.0 + + + org.apache.maven.its.gh13004 + parent + 1.0-SNAPSHOT + + + child + jar + + + + + org.apache.maven.its.gh13004 + ${platform.artifactId} + ${project.version} + + + diff --git a/its/core-it-suite/src/test/resources/gh-13004-consumer-pom-profile-artifactid/child/src/main/java/org/apache/maven/its/gh13004/Child.java b/its/core-it-suite/src/test/resources/gh-13004-consumer-pom-profile-artifactid/child/src/main/java/org/apache/maven/its/gh13004/Child.java new file mode 100644 index 000000000000..6fcc99a32222 --- /dev/null +++ b/its/core-it-suite/src/test/resources/gh-13004-consumer-pom-profile-artifactid/child/src/main/java/org/apache/maven/its/gh13004/Child.java @@ -0,0 +1,3 @@ +package org.apache.maven.its.gh13004; + +public class Child {} diff --git a/its/core-it-suite/src/test/resources/gh-13004-consumer-pom-profile-artifactid/lib/pom.xml b/its/core-it-suite/src/test/resources/gh-13004-consumer-pom-profile-artifactid/lib/pom.xml new file mode 100644 index 000000000000..47065c00bc72 --- /dev/null +++ b/its/core-it-suite/src/test/resources/gh-13004-consumer-pom-profile-artifactid/lib/pom.xml @@ -0,0 +1,31 @@ + + + + 4.1.0 + + + org.apache.maven.its.gh13004 + parent + 1.0-SNAPSHOT + + + lib + jar + diff --git a/its/core-it-suite/src/test/resources/gh-13004-consumer-pom-profile-artifactid/lib/src/main/java/org/apache/maven/its/gh13004/Lib.java b/its/core-it-suite/src/test/resources/gh-13004-consumer-pom-profile-artifactid/lib/src/main/java/org/apache/maven/its/gh13004/Lib.java new file mode 100644 index 000000000000..21d12cbf7bdd --- /dev/null +++ b/its/core-it-suite/src/test/resources/gh-13004-consumer-pom-profile-artifactid/lib/src/main/java/org/apache/maven/its/gh13004/Lib.java @@ -0,0 +1,3 @@ +package org.apache.maven.its.gh13004; + +public class Lib {} diff --git a/its/core-it-suite/src/test/resources/gh-13004-consumer-pom-profile-artifactid/pom.xml b/its/core-it-suite/src/test/resources/gh-13004-consumer-pom-profile-artifactid/pom.xml new file mode 100644 index 000000000000..e6b2239e7b75 --- /dev/null +++ b/its/core-it-suite/src/test/resources/gh-13004-consumer-pom-profile-artifactid/pom.xml @@ -0,0 +1,79 @@ + + + + 4.1.0 + + org.apache.maven.its.gh13004 + parent + 1.0-SNAPSHOT + pom + + + lib + child + + + + + + platform-unix + + + unix + + + + lib + + + + + + org.apache.maven.its.gh13004 + ${platform.artifactId} + 1.0-SNAPSHOT + + + + + + platform-windows + + + windows + + + + lib + + + + + org.apache.maven.its.gh13004 + ${platform.artifactId} + 1.0-SNAPSHOT + + + + + + From 6dc97d4924855f1932369d5dde0bcf185eb8210c Mon Sep 17 00:00:00 2001 From: Guillaume Nodet Date: Wed, 2 Sep 2026 00:02:06 +0200 Subject: [PATCH 2/4] Fix #13004: enable deterministic profile activation for BUILD_CONSUMER MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The consumer POM builder previously skipped ALL profile activation for BUILD_CONSUMER requests, which left platform-dependent properties (e.g. ${swt.artifactId} from an OS-activated profile) unresolved. The coordinate validator then rejected these as invalid artifact IDs. Enable activation of deterministic profiles — those triggered by JDK version, operating system, or activeByDefault — for BUILD_CONSUMER, while continuing to skip file-, property-, and condition-activated profiles. This mirrors the filtering already applied to repository-resolved models in PR #12948 (externalOrigin). Repositories from activated profiles are stripped so they do not leak into the published consumer POM. Co-Authored-By: Claude Opus 4.6 --- .../maven/impl/model/DefaultModelBuilder.java | 14 +++++++++++++- .../impl/model/DefaultModelBuilderTest.java | 16 ++++++++++------ 2 files changed, 23 insertions(+), 7 deletions(-) diff --git a/impl/maven-impl/src/main/java/org/apache/maven/impl/model/DefaultModelBuilder.java b/impl/maven-impl/src/main/java/org/apache/maven/impl/model/DefaultModelBuilder.java index a2cee1684746..b7c14dee0a82 100644 --- a/impl/maven-impl/src/main/java/org/apache/maven/impl/model/DefaultModelBuilder.java +++ b/impl/maven-impl/src/main/java/org/apache/maven/impl/model/DefaultModelBuilder.java @@ -1698,7 +1698,19 @@ private List getActiveProfiles( } return profileSelector.getActiveProfiles(eligibleProfiles, profileActivationContext, this); } else { - return List.of(); + // BUILD_CONSUMER: activate only deterministic profiles whose activation is a + // function of the build platform (OS, JDK version, activeByDefault) rather than + // of environment-specific state (file existence, property values, condition + // expressions). This ensures that platform-dependent properties (e.g. + // ${swt.artifactId} from an OS-activated profile) are resolved before the + // coordinate validator runs, while keeping the consumer POM reproducible across + // environments. Repositories from these profiles are stripped — they must not + // leak into the published consumer POM. See GH-13004. + Collection deterministicProfiles = interpolatedProfiles.stream() + .filter(profile -> !hasFileOrPropertyOrConditionActivation(profile)) + .map(profile -> profile.withRepositories(List.of()).withPluginRepositories(List.of())) + .toList(); + return profileSelector.getActiveProfiles(deterministicProfiles, profileActivationContext, this); } } diff --git a/impl/maven-impl/src/test/java/org/apache/maven/impl/model/DefaultModelBuilderTest.java b/impl/maven-impl/src/test/java/org/apache/maven/impl/model/DefaultModelBuilderTest.java index 3bb52989328b..3386dcd726f3 100644 --- a/impl/maven-impl/src/test/java/org/apache/maven/impl/model/DefaultModelBuilderTest.java +++ b/impl/maven-impl/src/test/java/org/apache/maven/impl/model/DefaultModelBuilderTest.java @@ -375,13 +375,13 @@ public void testExternalOriginPropagatesThroughGrandparentHop() throws Exception } /** - * {@code BUILD_CONSUMER} requests already skip all profile activation - * ({@code isBuildRequestWithActivation()} returns false for that type) -- unrelated to, and - * unaffected by, the externalOrigin distinction. Locking that down explicitly since it is - * adjacent code this change reads but does not modify. + * {@code BUILD_CONSUMER} requests activate only deterministic profiles (JDK version, + * operating system, activeByDefault) and skip file-, property- and condition-activated + * profiles. Repositories contributed by deterministic profiles are stripped so they + * do not leak into the published consumer POM. See GH-13004. */ @Test - public void testBuildConsumerSkipsAllProfileActivation() throws Exception { + public void testBuildConsumerActivatesOnlyDeterministicProfiles() throws Exception { ModelBuilderRequest request = ModelBuilderRequest.builder() .session(session) .requestType(ModelBuilderRequest.RequestType.BUILD_PROJECT) @@ -402,10 +402,14 @@ public void testBuildConsumerSkipsAllProfileActivation() throws Exception { Model model = buildConsumerState.readAsParentModel(parentActivationContext(systemProperties), new HashSet<>()); + // File, property, and condition profiles must still be skipped assertNull(model.getProperties().get("profile.file")); assertNull(model.getProperties().get("profile.property")); assertNull(model.getProperties().get("profile.condition")); - assertNull(model.getProperties().get("profile.jdk")); + // JDK profile IS activated — deterministic, platform-derived activation (GH-13004) + assertEquals("activated", model.getProperties().get("profile.jdk")); + // Repositories from activated profiles must be stripped — they must not leak + // into the published consumer POM assertTrue(model.getRepositories().stream().noneMatch(r -> "profile-repo".equals(r.getId()))); } From 732899e13ef5e5ca76e902faa8bf34e1b3534e5c Mon Sep 17 00:00:00 2001 From: Guillaume Nodet Date: Wed, 2 Sep 2026 08:10:17 +0200 Subject: [PATCH 3/4] Fix pre-existing Spotless violations and compilation error on master - DefaultVersionResolver: remove extra blank lines (Spotless) - DefaultVersionRangeResolver: remove unused IOException import (Spotless) - DistributionManagementArtifactRelocationSourceTest: add throws clause for ArtifactDescriptorException after relocatedTarget signature change Co-Authored-By: Claude Opus 4.6 --- .../apache/maven/impl/resolver/DefaultVersionRangeResolver.java | 1 - .../org/apache/maven/impl/resolver/DefaultVersionResolver.java | 2 -- .../DistributionManagementArtifactRelocationSourceTest.java | 2 +- 3 files changed, 1 insertion(+), 4 deletions(-) diff --git a/impl/maven-impl/src/main/java/org/apache/maven/impl/resolver/DefaultVersionRangeResolver.java b/impl/maven-impl/src/main/java/org/apache/maven/impl/resolver/DefaultVersionRangeResolver.java index 5620c4c4268e..97a8853cd7b8 100644 --- a/impl/maven-impl/src/main/java/org/apache/maven/impl/resolver/DefaultVersionRangeResolver.java +++ b/impl/maven-impl/src/main/java/org/apache/maven/impl/resolver/DefaultVersionRangeResolver.java @@ -18,7 +18,6 @@ */ package org.apache.maven.impl.resolver; -import java.io.IOException; import java.io.InputStream; import java.nio.file.Files; import java.util.ArrayList; diff --git a/impl/maven-impl/src/main/java/org/apache/maven/impl/resolver/DefaultVersionResolver.java b/impl/maven-impl/src/main/java/org/apache/maven/impl/resolver/DefaultVersionResolver.java index 25740a0ff506..24f6b3488bc1 100644 --- a/impl/maven-impl/src/main/java/org/apache/maven/impl/resolver/DefaultVersionResolver.java +++ b/impl/maven-impl/src/main/java/org/apache/maven/impl/resolver/DefaultVersionResolver.java @@ -281,8 +281,6 @@ private Versioning readVersions( return (versioning != null) ? versioning : Versioning.newInstance(); } - - private void invalidMetadata( RepositorySystemSession session, RequestTrace trace, diff --git a/impl/maven-impl/src/test/java/org/apache/maven/impl/resolver/relocation/DistributionManagementArtifactRelocationSourceTest.java b/impl/maven-impl/src/test/java/org/apache/maven/impl/resolver/relocation/DistributionManagementArtifactRelocationSourceTest.java index 28aa6386ecf6..c4cc9282e45b 100644 --- a/impl/maven-impl/src/test/java/org/apache/maven/impl/resolver/relocation/DistributionManagementArtifactRelocationSourceTest.java +++ b/impl/maven-impl/src/test/java/org/apache/maven/impl/resolver/relocation/DistributionManagementArtifactRelocationSourceTest.java @@ -61,7 +61,7 @@ private static Model newModel(String groupId, String artifactId, String version) } @Test - void noRelocationReturnsNull() { + void noRelocationReturnsNull() throws Exception { Model model = Model.newBuilder().build(); Artifact result = source.relocatedTarget(null, newResult(), model); assertNull(result); From a9f668fb1600b51822865def72ca1bda0f4c5b78 Mon Sep 17 00:00:00 2001 From: Guillaume Nodet Date: Wed, 2 Sep 2026 08:20:52 +0200 Subject: [PATCH 4/4] Exclude packaging-activated profiles from BUILD_CONSUMER activation Packaging-activated profiles are handled separately by the consumer POM builder's inlinePackagingActivatedProfiles(), which properly filters non-transitive scoped dependencies. Activating them during model building would double-merge their contributions, leaking test-scoped dependencies into the consumer POM. Co-Authored-By: Claude Opus 4.6 --- .../maven/impl/model/DefaultModelBuilder.java | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/impl/maven-impl/src/main/java/org/apache/maven/impl/model/DefaultModelBuilder.java b/impl/maven-impl/src/main/java/org/apache/maven/impl/model/DefaultModelBuilder.java index b7c14dee0a82..d510f1656be6 100644 --- a/impl/maven-impl/src/main/java/org/apache/maven/impl/model/DefaultModelBuilder.java +++ b/impl/maven-impl/src/main/java/org/apache/maven/impl/model/DefaultModelBuilder.java @@ -1705,9 +1705,13 @@ private List getActiveProfiles( // ${swt.artifactId} from an OS-activated profile) are resolved before the // coordinate validator runs, while keeping the consumer POM reproducible across // environments. Repositories from these profiles are stripped — they must not - // leak into the published consumer POM. See GH-13004. + // leak into the published consumer POM. + // Packaging-activated profiles are also excluded: the consumer POM builder + // handles them separately via inlinePackagingActivatedProfiles(). + // See GH-13004. Collection deterministicProfiles = interpolatedProfiles.stream() - .filter(profile -> !hasFileOrPropertyOrConditionActivation(profile)) + .filter(profile -> + !hasFileOrPropertyOrConditionActivation(profile) && !hasPackagingActivation(profile)) .map(profile -> profile.withRepositories(List.of()).withPluginRepositories(List.of())) .toList(); return profileSelector.getActiveProfiles(deterministicProfiles, profileActivationContext, this); @@ -1728,6 +1732,17 @@ private static boolean hasFileOrPropertyOrConditionActivation(Profile profile) { && !activation.getCondition().isBlank())); } + /** + * Determines whether the given profile's activation includes a packaging condition. + * Packaging-activated profiles are handled separately by the consumer POM builder's + * {@code inlinePackagingActivatedProfiles()} and must not be activated during + * BUILD_CONSUMER model building to avoid double-merging their contributions. + */ + private static boolean hasPackagingActivation(Profile profile) { + Activation activation = profile.getActivation(); + return activation != null && activation.getPackaging() != null; + } + Model readFileModel() throws ModelBuilderException { return readFileModel(new HashSet<>()); }