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/08/31 19:26:58 UTC
(maven) branch maven-4.0.x updated: [MNG-8287] Backport consumer POM packaging profile resolution to 4.0.x (#12967)
This is an automated email from the ASF dual-hosted git repository.
gnodet pushed a commit to branch maven-4.0.x
in repository https://gitbox.apache.org/repos/asf/maven.git
The following commit(s) were added to refs/heads/maven-4.0.x by this push:
new 49bbf988c9 [MNG-8287] Backport consumer POM packaging profile resolution to 4.0.x (#12967)
49bbf988c9 is described below
commit 49bbf988c94a833aac342c44f7da27b5465b0999
Author: Guillaume Nodet <gn...@gmail.com>
AuthorDate: Mon Aug 31 21:26:52 2026 +0200
[MNG-8287] Backport consumer POM packaging profile resolution to 4.0.x (#12967)
* [MNG-8287] Dependencies should always use consumer pom
When generating a consumer POM, profiles activated by packaging that match the current project's packaging should have their content inlined into the consumer POM and the activation should be removed. This ensures consistent dependency resolution across different tools.
* Fix consumer POM dependency resolution for packaging-activated profiles
- Inline packaging-activated profiles during transformPom for non-flattened builds
- Drop profiles activated by non-matching packaging entirely
- Update ConsumerPomBuilderTest to enable flattening and assert on dropped profiles
Signed-off-by: Hitesh <hi...@saividya.ac.in>
* Fix consumer POM packaging profile handling
* Address review feedback: add dependency deduplication and test coverage
- Add key-based deduplication when merging inlined dependencies,
managed dependencies, and repositories from packaging-activated
profiles to prevent duplicates in the consumer POM
- Add unit tests for dependency management and repository inlining
paths in inlinePackagingActivatedProfiles
- Add unit test for duplicate dependency guard (model deps take
precedence over profile duplicates)
- Widen transformBom visibility to package-private to match
transformNonPom and transformPom
Signed-off-by: Hitesh <hi...@saividya.ac.in>
* Adapt BOM test for 4.0.x: construct model directly
The 'bom' packaging type is not supported by the model builder on
the 4.0.x branch, so the testBomPackagingActivatedProfilesArePreserved
test is rewritten as a pure unit test that constructs the Model
directly instead of loading it from a file.
Co-Authored-By: Claude Opus 4.6 <no...@anthropic.com>
* Filter import-scoped managed deps from inlined packaging profiles
When inlining packaging-activated profiles, import-scoped entries in
dependencyManagement (BOM imports) were not filtered out. These entries
are flattened during resolution and must not reappear in the consumer
POM.
Co-Authored-By: Claude Opus 4.6 <no...@anthropic.com>
* Remove .oss-ai-helper-rules from PR branch
These files were inadvertently included during the merge and cause the
RAT license check to fail.
Co-Authored-By: Claude Opus 4.6 <no...@anthropic.com>
---------
Signed-off-by: Hitesh <hi...@saividya.ac.in>
Co-authored-by: Hitesh <hi...@saividya.ac.in>
Co-authored-by: Claude Opus 4.6 <no...@anthropic.com>
---
.../impl/DefaultConsumerPomBuilder.java | 126 ++++++++-
.../impl/ConsumerPomBuilderTest.java | 295 ++++++++++++++++++++-
.../consumer/packaging-parent-profiles/pom.xml | 23 ++
.../resources/consumer/packaging-profiles/pom.xml | 62 +++++
4 files changed, 502 insertions(+), 4 deletions(-)
diff --git a/impl/maven-core/src/main/java/org/apache/maven/internal/transformation/impl/DefaultConsumerPomBuilder.java b/impl/maven-core/src/main/java/org/apache/maven/internal/transformation/impl/DefaultConsumerPomBuilder.java
index 4a9bf7a173..d83e3b8ea8 100644
--- a/impl/maven-core/src/main/java/org/apache/maven/internal/transformation/impl/DefaultConsumerPomBuilder.java
+++ b/impl/maven-core/src/main/java/org/apache/maven/internal/transformation/impl/DefaultConsumerPomBuilder.java
@@ -25,6 +25,7 @@
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
+import java.util.Objects;
import java.util.Properties;
import java.util.function.Function;
import java.util.stream.Collectors;
@@ -37,6 +38,7 @@
import org.apache.maven.api.feature.Features;
import org.apache.maven.api.model.Activation;
import org.apache.maven.api.model.Dependency;
+import org.apache.maven.api.model.DependencyManagement;
import org.apache.maven.api.model.DistributionManagement;
import org.apache.maven.api.model.Model;
import org.apache.maven.api.model.ModelBase;
@@ -406,6 +408,10 @@ private ModelBuilderResult buildModel(RepositorySystemSession session, MavenProj
static Model transformNonPom(Model model, MavenProject project) {
boolean preserveModelVersion = model.isPreserveModelVersion();
+ String packaging = model.getPackaging();
+
+ // Inline packaging-activated profiles into the model
+ model = inlinePackagingActivatedProfiles(model, packaging);
Model.Builder builder = prune(
Model.newBuilder(model, true)
@@ -436,7 +442,7 @@ static Model transformNonPom(Model model, MavenProject project) {
return model;
}
- private static Model transformBom(Model model, MavenProject project) {
+ static Model transformBom(Model model, MavenProject project) {
boolean preserveModelVersion = model.isPreserveModelVersion();
Model.Builder builder = prune(
@@ -500,6 +506,124 @@ private static void warnNotDowngraded(MavenProject project) {
+ "attribute on the <project> element of your POM.");
}
+ /**
+ * Inlines packaging-activated profiles into the model.
+ * <p>
+ * When a profile is activated by packaging and the packaging matches the project's packaging,
+ * the profile's content (dependencies, dependency management, repositories) is merged into
+ * the main model and the profile is removed. This ensures consistent behavior across all
+ * tools consuming the POM, since packaging activation is a 4.1.0+ feature not available
+ * in Maven 3 or other tools like Gradle.
+ * <p>
+ * If the profile has other activation conditions besides packaging, only the packaging
+ * part is stripped from the activation; the profile's content is <b>not</b> inlined to
+ * preserve AND semantics (the content remains gated by the remaining conditions).
+ * <p>
+ * Profiles with a non-matching packaging activation are dropped entirely, since they
+ * can never activate for this artifact's fixed packaging and their presence would block
+ * model version downgrade to 4.0.0.
+ * <p>
+ * Non-transitive scope dependencies (test, provided, system) from inlined profiles are
+ * filtered out to prevent leakage into the consumer POM.
+ *
+ * @param model the model to process
+ * @param packaging the project's packaging type
+ * @return the model with packaging-activated profiles inlined
+ */
+ static Model inlinePackagingActivatedProfiles(Model model, String packaging) {
+ List<Profile> remainingProfiles = new ArrayList<>();
+ List<Dependency> additionalDeps = new ArrayList<>();
+ List<Dependency> additionalManagedDeps = new ArrayList<>();
+ List<Repository> additionalRepos = new ArrayList<>();
+
+ for (Profile profile : model.getProfiles()) {
+ Activation activation = profile.getActivation();
+ if (activation != null && activation.getPackaging() != null) {
+ if (Objects.equals(activation.getPackaging(), packaging)) {
+ Activation strippedActivation = stripPackagingActivation(activation);
+ if (strippedActivation != null) {
+ // Keep the profile but remove the packaging activation part
+ // Do not inline its contents since it has other activation conditions
+ remainingProfiles.add(profile.withActivation(strippedActivation));
+ } else {
+ // Packaging is the ONLY condition.
+ // Inline profile content into the model
+ additionalDeps.addAll(profile.getDependencies());
+ if (profile.getDependencyManagement() != null) {
+ additionalManagedDeps.addAll(
+ profile.getDependencyManagement().getDependencies());
+ }
+ additionalRepos.addAll(profile.getRepositories());
+ }
+ } else {
+ // Packaging does not match — drop the profile entirely
+ }
+ } else {
+ // No packaging activation — keep the profile as-is
+ remainingProfiles.add(profile);
+ }
+ }
+
+ // Merge additional dependencies into the model, deduplicating by key
+ if (!additionalDeps.isEmpty()) {
+ additionalDeps.removeIf(DefaultConsumerPomBuilder::hasDependencyScope);
+ Map<String, Dependency> mergedDeps = new LinkedHashMap<>();
+ for (Dependency dep : model.getDependencies()) {
+ mergedDeps.put(getDependencyKey(dep), dep);
+ }
+ for (Dependency dep : additionalDeps) {
+ mergedDeps.putIfAbsent(getDependencyKey(dep), dep);
+ }
+ model = model.withDependencies(mergedDeps.values());
+ }
+
+ // Merge additional managed dependencies into the model, deduplicating by key
+ if (!additionalManagedDeps.isEmpty()) {
+ // Filter out import-scoped entries — they are BOM references that get
+ // flattened during resolution and must not reappear in the consumer POM
+ additionalManagedDeps.removeIf(dep -> "import".equals(dep.getScope()));
+ DependencyManagement dm = model.getDependencyManagement();
+ Map<String, Dependency> mergedManagedDeps = new LinkedHashMap<>();
+ if (dm != null) {
+ for (Dependency dep : dm.getDependencies()) {
+ mergedManagedDeps.put(getDependencyKey(dep), dep);
+ }
+ }
+ for (Dependency dep : additionalManagedDeps) {
+ mergedManagedDeps.putIfAbsent(getDependencyKey(dep), dep);
+ }
+ model = model.withDependencyManagement((dm != null ? dm : DependencyManagement.newInstance())
+ .withDependencies(mergedManagedDeps.values()));
+ }
+
+ // Merge additional repositories into the model, deduplicating by id
+ if (!additionalRepos.isEmpty()) {
+ Map<String, Repository> mergedRepos = new LinkedHashMap<>();
+ for (Repository repo : model.getRepositories()) {
+ mergedRepos.put(repo.getId(), repo);
+ }
+ for (Repository repo : additionalRepos) {
+ mergedRepos.putIfAbsent(repo.getId(), repo);
+ }
+ model = model.withRepositories(mergedRepos.values());
+ }
+
+ return model.withProfiles(remainingProfiles);
+ }
+
+ /**
+ * Strips the packaging activation from an activation, returning the remaining activation
+ * or {@code null} if packaging was the only activation condition.
+ */
+ private static Activation stripPackagingActivation(Activation activation) {
+ Activation stripped =
+ Activation.newBuilder(activation, true).packaging(null).build();
+ if (isActivationEmpty(stripped)) {
+ return null;
+ }
+ return stripped;
+ }
+
/**
* Strips {@code executable()} conditions from profile activations.
* <p>
diff --git a/impl/maven-core/src/test/java/org/apache/maven/internal/transformation/impl/ConsumerPomBuilderTest.java b/impl/maven-core/src/test/java/org/apache/maven/internal/transformation/impl/ConsumerPomBuilderTest.java
index 7d7da413bc..6ba8579420 100644
--- a/impl/maven-core/src/test/java/org/apache/maven/internal/transformation/impl/ConsumerPomBuilderTest.java
+++ b/impl/maven-core/src/test/java/org/apache/maven/internal/transformation/impl/ConsumerPomBuilderTest.java
@@ -23,6 +23,7 @@
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.List;
import java.util.Map;
@@ -35,8 +36,10 @@
import org.apache.maven.api.model.ActivationOS;
import org.apache.maven.api.model.ActivationProperty;
import org.apache.maven.api.model.Dependency;
+import org.apache.maven.api.model.DependencyManagement;
import org.apache.maven.api.model.Model;
import org.apache.maven.api.model.Profile;
+import org.apache.maven.api.model.Repository;
import org.apache.maven.api.model.Scm;
import org.apache.maven.api.services.DependencyResolver;
import org.apache.maven.api.services.DependencyResolverResult;
@@ -130,6 +133,17 @@ private MavenProject getEffectiveModel(Path file) {
return project;
}
+ private Model getConsumerModel(Path file, boolean raw) {
+ ModelBuilder.ModelBuilderSession mbs = modelBuilder.newSession();
+ InternalSession.from(session).getData().set(SessionData.key(ModelBuilder.ModelBuilderSession.class), mbs);
+ var result = mbs.build(ModelBuilderRequest.builder()
+ .session(InternalSession.from(session))
+ .source(Sources.buildSource(file))
+ .requestType(ModelBuilderRequest.RequestType.BUILD_CONSUMER)
+ .build());
+ return raw ? result.getRawModel() : result.getEffectiveModel();
+ }
+
@Test
void testTrivialConsumer() throws Exception {
setRootDirectory("trivial");
@@ -142,6 +156,131 @@ void testTrivialConsumer() throws Exception {
assertNotNull(model.getDependencies());
}
+ @Test
+ void testPackagingActivatedProfiles() throws Exception {
+ setRootDirectory("packaging-profiles");
+ Path file = Paths.get("src/test/resources/consumer/packaging-profiles/pom.xml");
+
+ MavenProject project = getEffectiveModel(file);
+
+ Model model = DefaultConsumerPomBuilder.transformNonPom(getConsumerModel(file, false), project);
+
+ assertNotNull(model);
+
+ assertEquals(1, model.getProfiles().size());
+ org.apache.maven.api.model.Profile mixedProfile = model.getProfiles().get(0);
+ assertEquals("mixed-profile", mixedProfile.getId());
+ assertNotNull(mixedProfile.getActivation());
+ assertNull(mixedProfile.getActivation().getPackaging());
+ assertNotNull(mixedProfile.getActivation().getProperty());
+ assertEquals("foo", mixedProfile.getActivation().getProperty().getName());
+
+ assertNotNull(model.getDependencies());
+ assertEquals(1, model.getDependencies().size());
+ assertEquals("slf4j-api", model.getDependencies().get(0).getArtifactId());
+ }
+
+ @Test
+ void testParentPomPackagingActivatedProfilesArePreserved() throws Exception {
+ setRootDirectory("packaging-parent-profiles");
+ Path file = Paths.get("src/test/resources/consumer/packaging-parent-profiles/pom.xml");
+
+ MavenProject project = getEffectiveModel(file);
+ Model model = DefaultConsumerPomBuilder.transformPom(getConsumerModel(file, true), project);
+
+ assertEquals(1, model.getProfiles().size());
+ Profile profile = model.getProfiles().get(0);
+ assertEquals("jar-profile", profile.getId());
+ assertEquals("jar", profile.getActivation().getPackaging());
+ assertEquals("slf4j-api", profile.getDependencies().get(0).getArtifactId());
+ }
+
+ @Test
+ void testBomPackagingActivatedProfilesArePreserved() {
+ // Build the model directly since 'bom' packaging is not supported
+ // by the model builder on the 4.0.x branch
+ Dependency managedDep = Dependency.newBuilder()
+ .groupId("org.slf4j")
+ .artifactId("slf4j-api")
+ .version("2.0.0")
+ .build();
+
+ Profile jarProfile = Profile.newBuilder()
+ .id("jar-profile")
+ .activation(Activation.newBuilder().packaging("jar").build())
+ .dependencyManagement(DependencyManagement.newBuilder()
+ .dependencies(List.of(managedDep))
+ .build())
+ .build();
+
+ Model model = Model.newBuilder()
+ .groupId("org.my.group")
+ .artifactId("packaging-bom-profiles-test")
+ .version("1.0.0-SNAPSHOT")
+ .packaging("pom")
+ .profiles(List.of(jarProfile))
+ .build();
+
+ MavenProject project = new MavenProject(model);
+ Model transformed = DefaultConsumerPomBuilder.transformBom(model, project);
+
+ assertEquals(1, transformed.getProfiles().size());
+ Profile profile = transformed.getProfiles().get(0);
+ assertEquals("jar-profile", profile.getId());
+ assertEquals("jar", profile.getActivation().getPackaging());
+ assertEquals(
+ "slf4j-api",
+ profile.getDependencyManagement().getDependencies().get(0).getArtifactId());
+ }
+
+ @Test
+ void testImportScopedManagedDepsAreFilteredFromInlinedProfiles() {
+ // Verifies that import-scoped managed dependencies (BOM imports) inside
+ // a packaging-activated profile are NOT re-added to the consumer POM.
+ // Import-scoped entries are flattened during resolution and must not leak.
+ Dependency bomImport = Dependency.newBuilder()
+ .groupId("org.example")
+ .artifactId("some-bom")
+ .version("1.0.0")
+ .type("pom")
+ .scope("import")
+ .build();
+
+ Dependency regularManagedDep = Dependency.newBuilder()
+ .groupId("org.slf4j")
+ .artifactId("slf4j-api")
+ .version("2.0.0")
+ .build();
+
+ Profile profile = Profile.newBuilder()
+ .id("jar-profile")
+ .activation(Activation.newBuilder().packaging("jar").build())
+ .dependencyManagement(DependencyManagement.newBuilder()
+ .dependencies(List.of(bomImport, regularManagedDep))
+ .build())
+ .build();
+
+ Model model = Model.newBuilder()
+ .groupId("org.test")
+ .artifactId("import-filter-test")
+ .version("1.0.0-SNAPSHOT")
+ .packaging("jar")
+ .profiles(List.of(profile))
+ .build();
+
+ Model result = DefaultConsumerPomBuilder.inlinePackagingActivatedProfiles(model, "jar");
+
+ // Profile should be inlined (removed)
+ assertTrue(result.getProfiles().isEmpty());
+
+ // Managed deps should contain only the regular dep, NOT the import-scoped BOM
+ assertNotNull(result.getDependencyManagement());
+ List<Dependency> managedDeps = result.getDependencyManagement().getDependencies();
+ assertEquals(1, managedDeps.size());
+ assertEquals("slf4j-api", managedDeps.get(0).getArtifactId());
+ assertNull(managedDeps.get(0).getScope());
+ }
+
@Test
void testSimpleConsumer() throws Exception {
MavenExecutionRequest request = setRootDirectory("simple");
@@ -340,7 +479,7 @@ void testConsumerPomIncludesActiveProfileProperties() throws Exception {
userProps.containsKey("dep.version"), "User properties should contain properties from active profiles");
assertTrue(
"1.0.0".equals(userProps.get("dep.version")),
- "Profile property 'dep.version' should have value '1.0.0'");
+ "Profile property \'dep.version\' should have value \'1.0.0\'");
}
/**
@@ -388,8 +527,158 @@ void testConsumerPomSessionPropertiesOverrideProfileProperties() throws Exceptio
Map<String, String> userProps = consumerRequest.getUserProperties();
assertTrue(
"2.0.0".equals(userProps.get("dep.version")),
- "Session user property should override profile property; expected '2.0.0' but got '"
- + userProps.get("dep.version") + "'");
+ "Session user property should override profile property; expected \'2.0.0\' but got \'"
+ + userProps.get("dep.version") + "\'");
+ }
+
+ @Test
+ void testInlinePackagingActivatedProfiles() {
+ Dependency dep1 = Dependency.newBuilder()
+ .groupId("g")
+ .artifactId("a1")
+ .version("1")
+ .build();
+ Dependency dep2 = Dependency.newBuilder()
+ .groupId("g")
+ .artifactId("a2")
+ .version("2")
+ .build();
+
+ Profile profileMatching = Profile.newBuilder()
+ .id("matching")
+ .activation(Activation.newBuilder().packaging("jar").build())
+ .dependencies(List.of(dep1))
+ .build();
+
+ Profile profileNotMatching = Profile.newBuilder()
+ .id("not-matching")
+ .activation(Activation.newBuilder().packaging("war").build())
+ .dependencies(List.of(dep2))
+ .build();
+
+ Profile profileMixed = Profile.newBuilder()
+ .id("mixed")
+ .activation(Activation.newBuilder().packaging("jar").jdk("11").build())
+ .dependencies(List.of(dep2))
+ .build();
+
+ Model model = Model.newBuilder()
+ .packaging("jar")
+ .profiles(List.of(profileMatching, profileNotMatching, profileMixed))
+ .build();
+
+ Model transformed = DefaultConsumerPomBuilder.inlinePackagingActivatedProfiles(model, "jar");
+
+ // matching profile should be removed and its deps added to model
+ // not-matching profile should be dropped entirely since its packaging condition can never be met
+ // mixed profile should be kept but packaging activation stripped
+ assertEquals(1, transformed.getProfiles().size());
+
+ assertEquals("mixed", transformed.getProfiles().get(0).getId());
+ assertNull(transformed.getProfiles().get(0).getActivation().getPackaging());
+ assertEquals("11", transformed.getProfiles().get(0).getActivation().getJdk());
+
+ assertEquals(1, transformed.getDependencies().size());
+ assertEquals("a1", transformed.getDependencies().get(0).getArtifactId());
+ }
+
+ @Test
+ void testInlinePackagingActivatedProfilesDependencyManagement() {
+ Dependency managedDep = Dependency.newBuilder()
+ .groupId("g")
+ .artifactId("managed1")
+ .version("1.0")
+ .build();
+
+ Profile profileWithDM = Profile.newBuilder()
+ .id("dm-profile")
+ .activation(Activation.newBuilder().packaging("jar").build())
+ .dependencyManagement(DependencyManagement.newBuilder()
+ .dependencies(Collections.singletonList(managedDep))
+ .build())
+ .build();
+
+ Model model = Model.newBuilder()
+ .packaging("jar")
+ .profiles(List.of(profileWithDM))
+ .build();
+
+ Model transformed = DefaultConsumerPomBuilder.inlinePackagingActivatedProfiles(model, "jar");
+
+ // Profile should be removed
+ assertTrue(transformed.getProfiles().isEmpty());
+
+ // Managed dependency should be inlined
+ assertNotNull(transformed.getDependencyManagement());
+ assertEquals(1, transformed.getDependencyManagement().getDependencies().size());
+ assertEquals(
+ "managed1",
+ transformed.getDependencyManagement().getDependencies().get(0).getArtifactId());
+ }
+
+ @Test
+ void testInlinePackagingActivatedProfilesRepositories() {
+ Repository repo = Repository.newBuilder()
+ .id("custom-repo")
+ .url("https://repo.example.com/maven2")
+ .build();
+
+ Profile profileWithRepo = Profile.newBuilder()
+ .id("repo-profile")
+ .activation(Activation.newBuilder().packaging("jar").build())
+ .repositories(Collections.singletonList(repo))
+ .build();
+
+ Model model = Model.newBuilder()
+ .packaging("jar")
+ .profiles(List.of(profileWithRepo))
+ .build();
+
+ Model transformed = DefaultConsumerPomBuilder.inlinePackagingActivatedProfiles(model, "jar");
+
+ // Profile should be removed
+ assertTrue(transformed.getProfiles().isEmpty());
+
+ // Repository should be inlined
+ assertEquals(1, transformed.getRepositories().size());
+ assertEquals("custom-repo", transformed.getRepositories().get(0).getId());
+ }
+
+ @Test
+ void testInlinePackagingActivatedProfilesDeduplication() {
+ Dependency existingDep = Dependency.newBuilder()
+ .groupId("g")
+ .artifactId("a1")
+ .version("1.0")
+ .build();
+
+ // Profile re-declares the same dependency with a different version
+ Dependency duplicateDep = Dependency.newBuilder()
+ .groupId("g")
+ .artifactId("a1")
+ .version("2.0")
+ .build();
+
+ Profile profileWithDuplicate = Profile.newBuilder()
+ .id("dup-profile")
+ .activation(Activation.newBuilder().packaging("jar").build())
+ .dependencies(List.of(duplicateDep))
+ .build();
+
+ Model model = Model.newBuilder()
+ .packaging("jar")
+ .dependencies(List.of(existingDep))
+ .profiles(List.of(profileWithDuplicate))
+ .build();
+
+ Model transformed = DefaultConsumerPomBuilder.inlinePackagingActivatedProfiles(model, "jar");
+
+ // Should NOT have duplicates — existing model dependency takes precedence
+ assertEquals(1, transformed.getDependencies().size());
+ assertEquals(
+ "1.0",
+ transformed.getDependencies().get(0).getVersion(),
+ "Existing model dependency should take precedence over profile duplicate");
}
// ── executable() condition stripping (GH-12570) ──────────────────────────
diff --git a/impl/maven-core/src/test/resources/consumer/packaging-parent-profiles/pom.xml b/impl/maven-core/src/test/resources/consumer/packaging-parent-profiles/pom.xml
new file mode 100644
index 0000000000..91adafe027
--- /dev/null
+++ b/impl/maven-core/src/test/resources/consumer/packaging-parent-profiles/pom.xml
@@ -0,0 +1,23 @@
+<project xmlns="http://maven.apache.org/POM/4.1.0">
+ <modelVersion>4.1.0</modelVersion>
+ <groupId>org.my.group</groupId>
+ <artifactId>packaging-parent-profiles-test</artifactId>
+ <version>1.0.0-SNAPSHOT</version>
+ <packaging>pom</packaging>
+
+ <profiles>
+ <profile>
+ <id>jar-profile</id>
+ <activation>
+ <packaging>jar</packaging>
+ </activation>
+ <dependencies>
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-api</artifactId>
+ <version>2.0.0</version>
+ </dependency>
+ </dependencies>
+ </profile>
+ </profiles>
+</project>
diff --git a/impl/maven-core/src/test/resources/consumer/packaging-profiles/pom.xml b/impl/maven-core/src/test/resources/consumer/packaging-profiles/pom.xml
new file mode 100644
index 0000000000..44932fb1b4
--- /dev/null
+++ b/impl/maven-core/src/test/resources/consumer/packaging-profiles/pom.xml
@@ -0,0 +1,62 @@
+<project xmlns="http://maven.apache.org/POM/4.1.0">
+ <modelVersion>4.1.0</modelVersion>
+ <groupId>org.my.group</groupId>
+ <artifactId>packaging-profiles-test</artifactId>
+ <version>1.0.0-SNAPSHOT</version>
+ <packaging>jar</packaging>
+
+ <profiles>
+ <profile>
+ <id>matching-profile</id>
+ <activation>
+ <packaging>jar</packaging>
+ </activation>
+ <dependencies>
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-api</artifactId>
+ <version>2.0.0</version>
+ <!-- Using compile scope since api scope does not exist in this branch -->
+ <scope>compile</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-simple</artifactId>
+ <version>2.0.0</version>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+ </profile>
+ <profile>
+ <id>non-matching-profile</id>
+ <activation>
+ <packaging>war</packaging>
+ </activation>
+ <dependencies>
+ <dependency>
+ <groupId>org.junit.jupiter</groupId>
+ <artifactId>junit-jupiter-api</artifactId>
+ <version>5.9.0</version>
+ <scope>compile</scope>
+ </dependency>
+ </dependencies>
+ </profile>
+ <profile>
+ <id>mixed-profile</id>
+ <activation>
+ <packaging>jar</packaging>
+ <property>
+ <name>foo</name>
+ </property>
+ </activation>
+ <dependencies>
+ <dependency>
+ <groupId>org.apache.commons</groupId>
+ <artifactId>commons-lang3</artifactId>
+ <version>3.12.0</version>
+ <scope>compile</scope>
+ </dependency>
+ </dependencies>
+ </profile>
+ </profiles>
+</project>