Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -1698,7 +1698,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);
}
}

Expand All @@ -1716,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<>());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -281,8 +281,6 @@ private Versioning readVersions(
return (versioning != null) ? versioning : Versioning.newInstance();
}



private void invalidMetadata(
RepositorySystemSession session,
RequestTrace trace,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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())));
}

Expand Down Expand Up @@ -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).
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
@@ -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>
Original file line number Diff line number Diff line change
@@ -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>
Loading
Loading