diff --git a/api/maven-api-core/src/main/java/org/apache/maven/api/Project.java b/api/maven-api-core/src/main/java/org/apache/maven/api/Project.java index 27cf17908cae..8e989ad4ae98 100644 --- a/api/maven-api-core/src/main/java/org/apache/maven/api/Project.java +++ b/api/maven-api-core/src/main/java/org/apache/maven/api/Project.java @@ -26,6 +26,7 @@ import org.apache.maven.api.annotations.Nonnull; import org.apache.maven.api.model.Build; import org.apache.maven.api.model.Model; +import org.apache.maven.api.model.Profile; /** * Interface representing a Maven project which can be created using the @@ -237,4 +238,69 @@ default String getId() { */ @Nonnull Optional getParent(); + + /** + * Returns all profiles defined in this project. + *

+ * This method returns only the profiles defined directly in the current project's POM + * and does not include profiles from parent projects. + * + * @return a non-null, possibly empty list of profiles defined in this project + * @see Profile + * @see #getEffectiveProfiles() + */ + @Nonnull + List getDeclaredProfiles(); + + /** + * Returns all profiles defined in this project and all of its parent projects. + *

+ * This method traverses the parent hierarchy and includes profiles defined in parent POMs. + * The returned list contains profiles from the current project and all of its ancestors in + * the project inheritance chain. + * + * @return a non-null, possibly empty list of all profiles from this project and its parents + * @see Profile + * @see #getDeclaredProfiles() + */ + @Nonnull + List getEffectiveProfiles(); + + /** + * Returns all active profiles for the current project build. + *

+ * Active profiles are those that have been explicitly activated through one of the following means: + *

+ *

+ * The active profiles control various aspects of the build configuration including but not + * limited to dependencies, plugins, properties, and build resources. + * + * @return a non-null, possibly empty list of active profiles for this project + * @see Profile + * @see #getEffectiveActiveProfiles() + */ + @Nonnull + List getDeclaredActiveProfiles(); + + /** + * Returns all active profiles for this project and all of its parent projects. + *

+ * This method traverses the parent hierarchy and collects all active profiles from + * the current project and its ancestors. Active profiles are those that meet the + * activation criteria through explicit activation or automatic conditions. + *

+ * The combined set of active profiles from the entire project hierarchy affects + * the effective build configuration. + * + * @return a non-null, possibly empty list of all active profiles from this project and its parents + * @see Profile + * @see #getDeclaredActiveProfiles() + */ + @Nonnull + List getEffectiveActiveProfiles(); } diff --git a/impl/maven-core/src/main/java/org/apache/maven/internal/impl/DefaultProject.java b/impl/maven-core/src/main/java/org/apache/maven/internal/impl/DefaultProject.java index 6b7903b6d6ca..8cbfeaddc15e 100644 --- a/impl/maven-core/src/main/java/org/apache/maven/internal/impl/DefaultProject.java +++ b/impl/maven-core/src/main/java/org/apache/maven/internal/impl/DefaultProject.java @@ -23,7 +23,9 @@ import java.util.Collection; import java.util.Collections; import java.util.List; +import java.util.Objects; import java.util.Optional; +import java.util.stream.Stream; import org.apache.maven.RepositoryUtils; import org.apache.maven.api.DependencyCoordinates; @@ -38,6 +40,7 @@ import org.apache.maven.api.annotations.Nullable; 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.impl.MappedCollection; import org.apache.maven.impl.MappedList; import org.apache.maven.project.MavenProject; @@ -165,6 +168,37 @@ public Optional getParent() { return Optional.ofNullable(session.getProject(parent)); } + @Override + @Nonnull + public List getDeclaredProfiles() { + return getModel().getProfiles(); + } + + @Override + @Nonnull + public List getEffectiveProfiles() { + return Stream.iterate(this.project, Objects::nonNull, MavenProject::getParent) + .flatMap(project -> project.getModel().getDelegate().getProfiles().stream()) + .toList(); + } + + @Override + @Nonnull + public List getDeclaredActiveProfiles() { + return project.getActiveProfiles().stream() + .map(org.apache.maven.model.Profile::getDelegate) + .toList(); + } + + @Override + @Nonnull + public List getEffectiveActiveProfiles() { + return Stream.iterate(this.project, Objects::nonNull, MavenProject::getParent) + .flatMap(project -> project.getActiveProfiles().stream()) + .map(org.apache.maven.model.Profile::getDelegate) + .toList(); + } + @Nonnull private DependencyCoordinates toDependency(org.apache.maven.api.model.Dependency dependency) { return new DependencyCoordinates() { diff --git a/impl/maven-core/src/test/java/org/apache/maven/project/DefaultMavenProjectBuilderTest.java b/impl/maven-core/src/test/java/org/apache/maven/project/DefaultMavenProjectBuilderTest.java index 60994b8213f4..d41697d6a548 100644 --- a/impl/maven-core/src/test/java/org/apache/maven/project/DefaultMavenProjectBuilderTest.java +++ b/impl/maven-core/src/test/java/org/apache/maven/project/DefaultMavenProjectBuilderTest.java @@ -25,20 +25,26 @@ import java.nio.file.StandardCopyOption; import java.util.List; +import org.apache.maven.api.model.InputLocation; +import org.apache.maven.api.model.InputSource; import org.apache.maven.artifact.Artifact; import org.apache.maven.artifact.repository.ArtifactRepository; import org.apache.maven.impl.InternalSession; +import org.apache.maven.internal.impl.DefaultProject; import org.apache.maven.internal.impl.InternalMavenSession; +import org.apache.maven.model.Profile; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; +import org.mockito.Mockito; import static org.apache.maven.project.ProjectBuildingResultWithProblemMessageMatcher.projectBuildingResultWithProblemMessage; import static org.codehaus.plexus.testing.PlexusExtension.getTestFile; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.contains; import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.greaterThan; import static org.hamcrest.Matchers.is; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; @@ -345,6 +351,130 @@ void rereadPom_mng7063() throws Exception { assertThat(project.getName(), is("PROJECT NAME")); } + @Test + void testActivatedProfileBySource() throws Exception { + File testPom = getTestFile("src/test/resources/projects/pom-with-profiles/pom.xml"); + + ProjectBuildingRequest request = newBuildingRequest(); + request.setLocalRepository(getLocalRepository()); + request.setActiveProfileIds(List.of("profile1")); + + MavenProject project = projectBuilder.build(testPom, request).getProject(); + + assertTrue(project.getInjectedProfileIds().keySet().containsAll(List.of("external", project.getId()))); + assertTrue(project.getInjectedProfileIds().get("external").isEmpty()); + assertTrue(project.getInjectedProfileIds().get(project.getId()).stream().anyMatch("profile1"::equals)); + assertTrue(project.getInjectedProfileIds().get(project.getId()).stream().noneMatch("profile2"::equals)); + assertTrue( + project.getInjectedProfileIds().get(project.getId()).stream().noneMatch("active-by-default"::equals)); + } + + @Test + void testActivatedDefaultProfileBySource() throws Exception { + File testPom = getTestFile("src/test/resources/projects/pom-with-profiles/pom.xml"); + + ProjectBuildingRequest request = newBuildingRequest(); + request.setLocalRepository(getLocalRepository()); + + MavenProject project = projectBuilder.build(testPom, request).getProject(); + + assertTrue(project.getInjectedProfileIds().keySet().containsAll(List.of("external", project.getId()))); + assertTrue(project.getInjectedProfileIds().get("external").isEmpty()); + assertTrue(project.getInjectedProfileIds().get(project.getId()).stream().noneMatch("profile1"::equals)); + assertTrue(project.getInjectedProfileIds().get(project.getId()).stream().noneMatch("profile2"::equals)); + assertTrue(project.getInjectedProfileIds().get(project.getId()).stream().anyMatch("active-by-default"::equals)); + + InternalMavenSession session = Mockito.mock(InternalMavenSession.class); + List activeProfiles = + new DefaultProject(session, project).getDeclaredActiveProfiles(); + assertEquals(1, activeProfiles.size()); + org.apache.maven.api.model.Profile profile = activeProfiles.get(0); + assertEquals("active-by-default", profile.getId()); + InputLocation location = profile.getLocation(""); + assertNotNull(location); + assertThat(location.getLineNumber(), greaterThan(0)); + assertThat(location.getColumnNumber(), greaterThan(0)); + assertNotNull(location.getSource()); + assertThat(location.getSource().getLocation(), containsString("pom-with-profiles/pom.xml")); + } + + @Test + void testActivatedExternalProfileBySource() throws Exception { + File testPom = getTestFile("src/test/resources/projects/pom-with-profiles/pom.xml"); + + ProjectBuildingRequest request = newBuildingRequest(); + request.setLocalRepository(getLocalRepository()); + + final Profile externalProfile = new Profile(); + externalProfile.setLocation( + "", + new org.apache.maven.model.InputLocation( + 1, 1, new org.apache.maven.model.InputSource(new InputSource(null, "settings.xml", null)))); + externalProfile.setId("external-profile"); + request.addProfile(externalProfile); + request.setActiveProfileIds(List.of(externalProfile.getId())); + + MavenProject project = projectBuilder.build(testPom, request).getProject(); + + assertTrue(project.getInjectedProfileIds().keySet().containsAll(List.of("external", project.getId()))); + assertTrue(project.getInjectedProfileIds().get("external").stream().anyMatch("external-profile"::equals)); + assertTrue(project.getInjectedProfileIds().get(project.getId()).stream().noneMatch("profile1"::equals)); + assertTrue(project.getInjectedProfileIds().get(project.getId()).stream().noneMatch("profile2"::equals)); + assertTrue(project.getInjectedProfileIds().get(project.getId()).stream().anyMatch("active-by-default"::equals)); + + InternalMavenSession session = Mockito.mock(InternalMavenSession.class); + List activeProfiles = + new DefaultProject(session, project).getDeclaredActiveProfiles(); + assertEquals(2, activeProfiles.size()); + org.apache.maven.api.model.Profile profile = activeProfiles.get(0); + assertEquals("active-by-default", profile.getId()); + InputLocation location = profile.getLocation(""); + assertNotNull(location); + assertThat(location.getLineNumber(), greaterThan(0)); + assertThat(location.getColumnNumber(), greaterThan(0)); + assertNotNull(location.getSource()); + assertThat(location.getSource().getLocation(), containsString("pom-with-profiles/pom.xml")); + profile = activeProfiles.get(1); + assertEquals("external-profile", profile.getId()); + location = profile.getLocation(""); + assertNotNull(location); + assertThat(location.getLineNumber(), greaterThan(0)); + assertThat(location.getColumnNumber(), greaterThan(0)); + assertNotNull(location.getSource()); + assertThat(location.getSource().getLocation(), containsString("settings.xml")); + } + + @Test + void testActivatedProfileIsResolved() throws Exception { + File testPom = getTestFile("src/test/resources/projects/pom-with-profiles/pom.xml"); + + ProjectBuildingRequest request = newBuildingRequest(); + request.setLocalRepository(getLocalRepository()); + request.setActiveProfileIds(List.of("profile1")); + + MavenProject project = projectBuilder.build(testPom, request).getProject(); + + assertEquals(1, project.getActiveProfiles().size()); + assertTrue(project.getActiveProfiles().stream().anyMatch(p -> "profile1".equals(p.getId()))); + assertTrue(project.getActiveProfiles().stream().noneMatch(p -> "profile2".equals(p.getId()))); + assertTrue(project.getActiveProfiles().stream().noneMatch(p -> "active-by-default".equals(p.getId()))); + } + + @Test + void testActivatedProfileByDefaultIsResolved() throws Exception { + File testPom = getTestFile("src/test/resources/projects/pom-with-profiles/pom.xml"); + + ProjectBuildingRequest request = newBuildingRequest(); + request.setLocalRepository(getLocalRepository()); + + MavenProject project = projectBuilder.build(testPom, request).getProject(); + + assertEquals(1, project.getActiveProfiles().size()); + assertTrue(project.getActiveProfiles().stream().noneMatch(p -> "profile1".equals(p.getId()))); + assertTrue(project.getActiveProfiles().stream().noneMatch(p -> "profile2".equals(p.getId()))); + assertTrue(project.getActiveProfiles().stream().anyMatch(p -> "active-by-default".equals(p.getId()))); + } + /** * Tests whether external version range parent references are build correctly. * diff --git a/impl/maven-core/src/test/resources/projects/pom-with-profiles/pom.xml b/impl/maven-core/src/test/resources/projects/pom-with-profiles/pom.xml new file mode 100644 index 000000000000..fdf4e9b47d4c --- /dev/null +++ b/impl/maven-core/src/test/resources/projects/pom-with-profiles/pom.xml @@ -0,0 +1,46 @@ + + + + + + 4.0.0 + + maven + maven-core + 2.0-SNAPSHOT + + Maven + + + + active-by-default + + true + + + + profile1 + + + profile2 + + + diff --git a/impl/maven-impl/src/main/java/org/apache/maven/impl/SettingsUtilsV4.java b/impl/maven-impl/src/main/java/org/apache/maven/impl/SettingsUtilsV4.java index a4f2ba618f11..8a5d1e81a2bb 100644 --- a/impl/maven-impl/src/main/java/org/apache/maven/impl/SettingsUtilsV4.java +++ b/impl/maven-impl/src/main/java/org/apache/maven/impl/SettingsUtilsV4.java @@ -79,16 +79,23 @@ public static Settings merge(Settings dominant, Settings recessive) { public static Profile convertToSettingsProfile(org.apache.maven.api.model.Profile modelProfile) { Profile.Builder profile = Profile.newBuilder(); + profile.location("", toLocation(modelProfile.getLocation(""))); + profile.id(modelProfile.getId()); + profile.location("id", toLocation(modelProfile.getLocation("id"))); org.apache.maven.api.model.Activation modelActivation = modelProfile.getActivation(); if (modelActivation != null) { Activation.Builder activation = Activation.newBuilder(); + activation.location("", toLocation(modelActivation.getLocation(""))); + activation.activeByDefault(modelActivation.isActiveByDefault()); + activation.location("activeByDefault", toLocation(modelActivation.getLocation("activeByDefault"))); activation.jdk(modelActivation.getJdk()); + activation.location("jdk", toLocation(modelActivation.getLocation("jdk"))); org.apache.maven.api.model.ActivationProperty modelProp = modelActivation.getProperty(); @@ -96,6 +103,9 @@ public static Profile convertToSettingsProfile(org.apache.maven.api.model.Profil ActivationProperty prop = ActivationProperty.newBuilder() .name(modelProp.getName()) .value(modelProp.getValue()) + .location("", toLocation(modelProp.getLocation(""))) + .location("name", toLocation(modelProp.getLocation("name"))) + .location("value", toLocation(modelProp.getLocation("value"))) .build(); activation.property(prop); } @@ -108,6 +118,11 @@ public static Profile convertToSettingsProfile(org.apache.maven.api.model.Profil .family(modelOs.getFamily()) .name(modelOs.getName()) .version(modelOs.getVersion()) + .location("", toLocation(modelOs.getLocation(""))) + .location("arch", toLocation(modelOs.getLocation("arch"))) + .location("family", toLocation(modelOs.getLocation("family"))) + .location("name", toLocation(modelOs.getLocation("name"))) + .location("version", toLocation(modelOs.getLocation("version"))) .build(); activation.os(os); @@ -120,14 +135,19 @@ public static Profile convertToSettingsProfile(org.apache.maven.api.model.Profil org.apache.maven.api.settings.ActivationFile.newBuilder() .exists(modelFile.getExists()) .missing(modelFile.getMissing()) + .location("", toLocation(modelFile.getLocation(""))) + .location("exists", toLocation(modelFile.getLocation("exists"))) + .location("missing", toLocation(modelFile.getLocation("missing"))) .build(); activation.file(file); } activation.packaging(modelActivation.getPackaging()); + activation.location("packaging", toLocation(modelActivation.getLocation("packaging"))); activation.condition(modelActivation.getCondition()); + activation.location("condition", toLocation(modelActivation.getLocation("condition"))); profile.activation(activation.build()); } @@ -135,6 +155,7 @@ public static Profile convertToSettingsProfile(org.apache.maven.api.model.Profil profile.properties(modelProfile.getProperties().entrySet().stream() .collect(Collectors.toMap( e -> e.getKey().toString(), e -> e.getValue().toString()))); + profile.location("properties", toLocation(modelProfile.getLocation("properties"))); List repos = modelProfile.getRepositories(); if (repos != null) { @@ -143,6 +164,7 @@ public static Profile convertToSettingsProfile(org.apache.maven.api.model.Profil repositories.add(convertToSettingsRepository(repo)); } profile.repositories(repositories); + profile.location("repositories", toLocation(modelProfile.getLocation("repositories"))); } List pluginRepos = modelProfile.getPluginRepositories(); @@ -152,6 +174,7 @@ public static Profile convertToSettingsProfile(org.apache.maven.api.model.Profil repositories.add(convertToSettingsRepository(pluginRepo)); } profile.pluginRepositories(repositories); + profile.location("pluginRepositories", toLocation(modelProfile.getLocation("pluginRepositories"))); } return profile.build(); @@ -164,7 +187,10 @@ public static Profile convertToSettingsProfile(org.apache.maven.api.model.Profil public static org.apache.maven.api.model.Profile convertFromSettingsProfile(Profile settingsProfile) { org.apache.maven.api.model.Profile.Builder profile = org.apache.maven.api.model.Profile.newBuilder(); + profile.location("", toLocation(settingsProfile.getLocation(""))); + profile.id(settingsProfile.getId()); + profile.location("id", toLocation(settingsProfile.getLocation("id"))); Activation settingsActivation = settingsProfile.getActivation(); @@ -183,6 +209,7 @@ public static org.apache.maven.api.model.Profile convertFromSettingsProfile(Prof activation.property(org.apache.maven.api.model.ActivationProperty.newBuilder() .name(settingsProp.getName()) .value(settingsProp.getValue()) + .location("", toLocation(settingsProp.getLocation(""))) .location("name", toLocation(settingsProp.getLocation("name"))) .location("value", toLocation(settingsProp.getLocation("value"))) .build()); @@ -195,6 +222,7 @@ public static org.apache.maven.api.model.Profile convertFromSettingsProfile(Prof .family(settingsOs.getFamily()) .name(settingsOs.getName()) .version(settingsOs.getVersion()) + .location("", toLocation(settingsOs.getLocation(""))) .location("arch", toLocation(settingsOs.getLocation("arch"))) .location("family", toLocation(settingsOs.getLocation("family"))) .location("name", toLocation(settingsOs.getLocation("name"))) @@ -207,14 +235,17 @@ public static org.apache.maven.api.model.Profile convertFromSettingsProfile(Prof activation.file(ActivationFile.newBuilder() .exists(settingsFile.getExists()) .missing(settingsFile.getMissing()) + .location("", toLocation(settingsFile.getLocation(""))) .location("exists", toLocation(settingsFile.getLocation("exists"))) .location("missing", toLocation(settingsFile.getLocation("missing"))) .build()); } activation.packaging(settingsActivation.getPackaging()); + activation.location("packaging", toLocation(settingsActivation.getLocation("packaging"))); activation.condition(settingsActivation.getCondition()); + activation.location("condition", toLocation(settingsActivation.getLocation("condition"))); profile.activation(activation.build()); } @@ -297,6 +328,11 @@ private static Repository convertToSettingsRepository(org.apache.maven.api.model .url(modelRepo.getUrl()) .snapshots(modelRepo.getSnapshots() != null ? convertRepositoryPolicy(modelRepo.getSnapshots()) : null) .releases(modelRepo.getReleases() != null ? convertRepositoryPolicy(modelRepo.getReleases()) : null) + .location("", toLocation(modelRepo.getLocation(""))) + .location("id", toLocation(modelRepo.getLocation("id"))) + .location("layout", toLocation(modelRepo.getLocation("layout"))) + .location("name", toLocation(modelRepo.getLocation("name"))) + .location("url", toLocation(modelRepo.getLocation("url"))) .build(); return repo; @@ -311,10 +347,30 @@ private static RepositoryPolicy convertRepositoryPolicy(org.apache.maven.api.mod .enabled(modelPolicy.isEnabled()) .updatePolicy(modelPolicy.getUpdatePolicy()) .checksumPolicy(modelPolicy.getChecksumPolicy()) + .location("", toLocation(modelPolicy.getLocation(""))) + .location("enabled", toLocation(modelPolicy.getLocation("enabled"))) + .location("updatePolicy", toLocation(modelPolicy.getLocation("updatePolicy"))) + .location("checksumPolicy", toLocation(modelPolicy.getLocation("checksumPolicy"))) .build(); return policy; } + private static org.apache.maven.api.settings.InputLocation toLocation( + org.apache.maven.api.model.InputLocation location) { + if (location != null) { + org.apache.maven.api.model.InputSource source = location.getSource(); + Map locs = location.getLocations().entrySet().stream() + .collect(Collectors.toMap(Map.Entry::getKey, e -> toLocation(e.getValue()))); + return new org.apache.maven.api.settings.InputLocation( + location.getLineNumber(), + location.getColumnNumber(), + source != null ? new org.apache.maven.api.settings.InputSource(source.getLocation()) : null, + locs); + } else { + return null; + } + } + private static org.apache.maven.api.model.InputLocation toLocation( org.apache.maven.api.settings.InputLocation location) { if (location != null) { diff --git a/src/mdo/model.vm b/src/mdo/model.vm index 8e4c46243c9d..1a6e923a870f 100644 --- a/src/mdo/model.vm +++ b/src/mdo/model.vm @@ -173,11 +173,7 @@ public class ${class.name} #end #end #if ( $locationTracking && ! $class.superClass ) - #if ( ! $class.getFields($version).isEmpty() ) this.locations = builder.computeLocations(); - #else - this.locations = Map.of(); - #end this.importedFrom = builder.importedFrom; #end } @@ -243,55 +239,24 @@ public class ${class.name} * Gets the location of the specified field in the input source. */ public InputLocation getLocation(Object key) { - #if ( $class.getFields($version).isEmpty() ) - #if ( $class.superClass ) - return super.getLocation(key); - #else - return null; - #end - #elseif ( $class.superClass ) - return locations.containsKey(key) ? locations.get(key) : super.getLocation(key); - #else return locations.get(key); - #end } /** * Gets the keys of the locations of the input source. */ public Set getLocationKeys() { - #if ( $class.getFields($version).isEmpty() ) - #if ( $class.superClass ) - return super.getLocationKeys(); - #else - return Set.of(); - #end - #elseif ( $class.superClass ) - return getLocationKeyStream().collect(Collectors.toUnmodifiableSet()); - #else return locations.keySet(); - #end } protected Stream getLocationKeyStream() { - #if ( $class.getFields($version).isEmpty() ) - #if ( $class.superClass ) - return super.getLocationKeyStream(); - #else - return Stream.empty(); - #end - #elseif ( $class.superClass ) - return Stream.concat(locations.keySet().stream(), super.getLocationKeyStream()); - #else return locations.keySet().stream(); - #end } /** * Gets the input location that caused this model to be read. */ - public InputLocation getImportedFrom() - { + public InputLocation getImportedFrom() { return importedFrom; } @@ -534,7 +499,6 @@ public class ${class.name} #if ( $locationTracking && ! $class.superClass ) Map computeLocations() { - #if ( ! $class.getFields($version).isEmpty() ) Map newlocs = locations != null ? locations : Map.of(); Map oldlocs = base != null ? base.locations : Map.of(); if (newlocs.isEmpty()) { @@ -546,9 +510,6 @@ public class ${class.name} return Stream.concat(newlocs.entrySet().stream(), oldlocs.entrySet().stream()) // Keep value from newlocs in case of duplicates .collect(Collectors.toUnmodifiableMap(Map.Entry::getKey, Map.Entry::getValue, (v1, v2) -> v1)); - #else - return Map.of(); - #end } #end }