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/09/02 10:29:25 UTC
(maven) branch master updated: Fix #13004: enable deterministic profile activation for BUILD_CONSUMER
This is an automated email from the ASF dual-hosted git repository.
gnodet pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/maven.git
The following commit(s) were added to refs/heads/master by this push:
new d96bf89eec Fix #13004: enable deterministic profile activation for BUILD_CONSUMER
d96bf89eec is described below
commit d96bf89eecb962e5a1a94f7e253079b43154bd31
Author: Guillaume Nodet <gn...@gmail.com>
AuthorDate: Wed Sep 2 12:29:19 2026 +0200
Fix #13004: enable deterministic profile activation for BUILD_CONSUMER
Enable only deterministic profile activations (OS, JDK, activeByDefault)
for BUILD_CONSUMER, so 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.
Packaging-activated profiles are excluded — they are handled separately
by inlinePackagingActivatedProfiles() in the consumer POM builder.
Repositories from activated profiles are stripped to prevent leaking
into the published consumer POM.
Also fixes pre-existing Spotless violations and a compilation error on
master (DefaultVersionResolver, DefaultVersionRangeResolver,
DistributionManagementArtifactRelocationSourceTest).
---
.../maven/impl/model/DefaultModelBuilder.java | 29 +++-
.../maven/impl/model/DefaultModelBuilderTest.java | 180 ++++++++++++++++++++-
.../poms/factory/consumer-os-profile-child.xml | 35 ++++
.../poms/factory/consumer-os-profile-parent.xml | 61 +++++++
.../factory/consumer-profile-artifactid-child.xml | 35 ++++
.../factory/consumer-profile-artifactid-parent.xml | 37 +++++
...nITgh13004ConsumerPomProfileArtifactIdTest.java | 106 ++++++++++++
.../child/pom.xml | 41 +++++
.../java/org/apache/maven/its/gh13004/Child.java | 3 +
.../lib/pom.xml | 31 ++++
.../java/org/apache/maven/its/gh13004/Lib.java | 3 +
.../pom.xml | 79 +++++++++
12 files changed, 633 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 ab28f9b462..91aabe162e 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
@@ -1734,7 +1734,23 @@ private List<Profile> 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.
+ // Packaging-activated profiles are also excluded: the consumer POM builder
+ // handles them separately via inlinePackagingActivatedProfiles().
+ // See GH-13004.
+ Collection<Profile> deterministicProfiles = interpolatedProfiles.stream()
+ .filter(profile ->
+ !hasFileOrPropertyOrConditionActivation(profile) && !hasPackagingActivation(profile))
+ .map(profile -> profile.withRepositories(List.of()).withPluginRepositories(List.of()))
+ .toList();
+ return profileSelector.getActiveProfiles(deterministicProfiles, profileActivationContext, this);
}
}
@@ -1752,6 +1768,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<>());
}
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 cf52c6b1e5..3386dcd726 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())));
}
@@ -1106,6 +1110,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 <dependencyManagement>}
* 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 0000000000..c377507386
--- /dev/null
+++ b/impl/maven-impl/src/test/resources/poms/factory/consumer-os-profile-child.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ 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.
+-->
+<project xmlns="http://maven.apache.org/POM/4.1.0">
+ <parent>
+ <groupId>org.apache.maven.tests</groupId>
+ <artifactId>consumer-os-profile-parent</artifactId>
+ <relativePath>consumer-os-profile-parent.xml</relativePath>
+ </parent>
+ <artifactId>consumer-os-profile-child</artifactId>
+ <version>1.0-SNAPSHOT</version>
+ <packaging>jar</packaging>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.example</groupId>
+ <artifactId>${platform.artifactId}</artifactId>
+ <version>1.0</version>
+ </dependency>
+ </dependencies>
+</project>
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 0000000000..7a70c95a3c
--- /dev/null
+++ b/impl/maven-impl/src/test/resources/poms/factory/consumer-os-profile-parent.xml
@@ -0,0 +1,61 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ 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.
+-->
+<project xmlns="http://maven.apache.org/POM/4.1.0">
+ <groupId>org.apache.maven.tests</groupId>
+ <artifactId>consumer-os-profile-parent</artifactId>
+ <version>1.0-SNAPSHOT</version>
+ <packaging>pom</packaging>
+
+ <!-- OS-activated profile defines a property used in child dependency artifactId.
+ This simulates the Apache Hop scenario (GH-13004). -->
+ <profiles>
+ <profile>
+ <id>platform-linux</id>
+ <activation>
+ <os>
+ <family>unix</family>
+ </os>
+ </activation>
+ <properties>
+ <platform.artifactId>platform-lib-linux</platform.artifactId>
+ </properties>
+ </profile>
+ <profile>
+ <id>platform-windows</id>
+ <activation>
+ <os>
+ <family>windows</family>
+ </os>
+ </activation>
+ <properties>
+ <platform.artifactId>platform-lib-windows</platform.artifactId>
+ </properties>
+ </profile>
+ <profile>
+ <id>platform-mac</id>
+ <activation>
+ <os>
+ <family>mac</family>
+ </os>
+ </activation>
+ <properties>
+ <platform.artifactId>platform-lib-mac</platform.artifactId>
+ </properties>
+ </profile>
+ </profiles>
+</project>
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 0000000000..89659a8deb
--- /dev/null
+++ b/impl/maven-impl/src/test/resources/poms/factory/consumer-profile-artifactid-child.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ 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.
+-->
+<project xmlns="http://maven.apache.org/POM/4.1.0">
+ <parent>
+ <groupId>org.apache.maven.tests</groupId>
+ <artifactId>consumer-profile-artifactid-parent</artifactId>
+ <relativePath>consumer-profile-artifactid-parent.xml</relativePath>
+ </parent>
+ <artifactId>consumer-profile-artifactid-child</artifactId>
+ <version>1.0-SNAPSHOT</version>
+ <packaging>jar</packaging>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.eclipse.platform</groupId>
+ <artifactId>${swt.artifactId}</artifactId>
+ <version>1.0</version>
+ </dependency>
+ </dependencies>
+</project>
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 0000000000..c0d9c99237
--- /dev/null
+++ b/impl/maven-impl/src/test/resources/poms/factory/consumer-profile-artifactid-parent.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ 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.
+-->
+<project xmlns="http://maven.apache.org/POM/4.1.0">
+ <groupId>org.apache.maven.tests</groupId>
+ <artifactId>consumer-profile-artifactid-parent</artifactId>
+ <version>1.0-SNAPSHOT</version>
+ <packaging>pom</packaging>
+
+ <profiles>
+ <profile>
+ <id>default-platform</id>
+ <activation>
+ <property>
+ <name>!skipDefaultPlatform</name>
+ </property>
+ </activation>
+ <properties>
+ <swt.artifactId>org.eclipse.swt.gtk.linux.x86-64</swt.artifactId>
+ </properties>
+ </profile>
+ </profiles>
+</project>
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 0000000000..16150e1a7c
--- /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.
+ * <p>
+ * 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.
+ * <p>
+ * The consumer POM builder must resolve these properties before coordinate
+ * validation, otherwise it fails with:
+ * <pre>
+ * 'dependencies.dependency.artifactId' ... with value '${swt.artifactId}'
+ * does not match a valid coordinate id pattern.
+ * </pre>
+ *
+ * @see <a href="https://github.com/apache/maven/issues/13004">GH-13004</a>
+ */
+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("<family>unix</family>") || parentContent.contains("<family>windows</family>"),
+ "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("<artifactId>lib</artifactId>"),
+ "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 0000000000..ff93b09a93
--- /dev/null
+++ b/its/core-it-suite/src/test/resources/gh-13004-consumer-pom-profile-artifactid/child/pom.xml
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+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.
+-->
+<project xmlns="http://maven.apache.org/POM/4.1.0">
+ <modelVersion>4.1.0</modelVersion>
+
+ <parent>
+ <groupId>org.apache.maven.its.gh13004</groupId>
+ <artifactId>parent</artifactId>
+ <version>1.0-SNAPSHOT</version>
+ </parent>
+
+ <artifactId>child</artifactId>
+ <packaging>jar</packaging>
+
+ <!-- Uses property from OS-activated parent profile in dependency artifactId.
+ The consumer POM builder must resolve this before coordinate validation. -->
+ <dependencies>
+ <dependency>
+ <groupId>org.apache.maven.its.gh13004</groupId>
+ <artifactId>${platform.artifactId}</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ </dependencies>
+</project>
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 0000000000..6fcc99a322
--- /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 0000000000..47065c00bc
--- /dev/null
+++ b/its/core-it-suite/src/test/resources/gh-13004-consumer-pom-profile-artifactid/lib/pom.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+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.
+-->
+<project xmlns="http://maven.apache.org/POM/4.1.0">
+ <modelVersion>4.1.0</modelVersion>
+
+ <parent>
+ <groupId>org.apache.maven.its.gh13004</groupId>
+ <artifactId>parent</artifactId>
+ <version>1.0-SNAPSHOT</version>
+ </parent>
+
+ <artifactId>lib</artifactId>
+ <packaging>jar</packaging>
+</project>
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 0000000000..21d12cbf7b
--- /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 0000000000..e6b2239e7b
--- /dev/null
+++ b/its/core-it-suite/src/test/resources/gh-13004-consumer-pom-profile-artifactid/pom.xml
@@ -0,0 +1,79 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+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.
+-->
+<project xmlns="http://maven.apache.org/POM/4.1.0" root="true">
+ <modelVersion>4.1.0</modelVersion>
+
+ <groupId>org.apache.maven.its.gh13004</groupId>
+ <artifactId>parent</artifactId>
+ <version>1.0-SNAPSHOT</version>
+ <packaging>pom</packaging>
+
+ <modules>
+ <module>lib</module>
+ <module>child</module>
+ </modules>
+
+ <!-- OS-activated profiles defining a property used in child dependency artifactId.
+ This simulates the Apache Hop scenario (GH-13004) where the root POM uses
+ OS-activated profiles to select platform-specific dependencies. -->
+ <profiles>
+ <profile>
+ <id>platform-unix</id>
+ <activation>
+ <os>
+ <family>unix</family>
+ </os>
+ </activation>
+ <properties>
+ <platform.artifactId>lib</platform.artifactId>
+ </properties>
+ <!-- Dependency management inside the profile, like Apache Hop's SWT setup -->
+ <dependencyManagement>
+ <dependencies>
+ <dependency>
+ <groupId>org.apache.maven.its.gh13004</groupId>
+ <artifactId>${platform.artifactId}</artifactId>
+ <version>1.0-SNAPSHOT</version>
+ </dependency>
+ </dependencies>
+ </dependencyManagement>
+ </profile>
+ <profile>
+ <id>platform-windows</id>
+ <activation>
+ <os>
+ <family>windows</family>
+ </os>
+ </activation>
+ <properties>
+ <platform.artifactId>lib</platform.artifactId>
+ </properties>
+ <dependencyManagement>
+ <dependencies>
+ <dependency>
+ <groupId>org.apache.maven.its.gh13004</groupId>
+ <artifactId>${platform.artifactId}</artifactId>
+ <version>1.0-SNAPSHOT</version>
+ </dependency>
+ </dependencies>
+ </dependencyManagement>
+ </profile>
+ </profiles>
+</project>