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>