Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[MNG-8588] Add activated profiles to Project interface #2132

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 4 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
65 changes: 65 additions & 0 deletions api/maven-api-core/src/main/java/org/apache/maven/api/Project.java
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -237,4 +238,68 @@ default String getId() {
*/
@Nonnull
Optional<Project> getParent();

/**
* Returns all profiles defined in this project.
* <p>
* 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 #getAllProfiles()
*/
@Nonnull
List<Profile> getProfiles();

/**
* Returns all profiles defined in this project and all of its parent projects.
* <p>
* 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 #getProfiles()
*/
@Nonnull
List<Profile> getAllProfiles();
Comment on lines +242 to +267
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm trying to think of a different name for the getAll(Active)Profiles() methods, as it might be interpreted the same as getProfiles(). I was thinking something alone the lines of getAll(Active)ProfilesIncludingInherited(), or rename get(Active)Profiles() to something like getCurrent(Active)Profiles()

Copy link
Contributor Author

@gnodet gnodet Feb 26, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about:

  • getDeclaredProfiles() / getEffectiveProfiles()
  • getDeclaredActiveProfiles() / getEffectiveActiveProfiles()

The declared would make reference to only profiles on the current project, while effective would make reference to profiles from the project and it's hierarchy, similar to the effective model which is computed with the parents merged into it.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thats a good one. As it also follows the terminology we use within other parts of the code base and documentation. It does require some knowledge of our domain as consumer of the API but the docs clearly describe the intent


/**
* Returns all active profiles for the current project build.
* <p>
* Active profiles are those that have been explicitly activated through one of the following means:
* <ul>
* <li>Command line activation using the -P flag</li>
* <li>Maven settings activation in settings.xml via &lt;activeProfiles&gt;</li>
* <li>Automatic activation via &lt;activation&gt; conditions</li>
* <li>The default active profile (marked with &lt;activeByDefault&gt;true&lt;/activeByDefault&gt;)</li>
* </ul>
* <p>
* 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
*/
@Nonnull
List<Profile> getActiveProfiles();

/**
* Returns all active profiles for this project and all of its parent projects.
* <p>
* 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.
* <p>
* 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 #getActiveProfiles()
*/
@Nonnull
List<Profile> getAllActiveProfiles();
}
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,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;
Expand Down Expand Up @@ -165,6 +166,46 @@ public Optional<Project> getParent() {
return Optional.ofNullable(session.getProject(parent));
}

@Override
@Nonnull
public List<Profile> getProfiles() {
return getModel().getProfiles();
}

@Override
@Nonnull
public List<Profile> getAllProfiles() {
List<org.apache.maven.model.Profile> activeProfiles = new ArrayList<>();
for (MavenProject project = this.project; project != null; project = project.getParent()) {
activeProfiles.addAll(project.getModel().getProfiles());
}
return activeProfiles.stream()
.map(org.apache.maven.model.Profile::getDelegate)
.toList();
}

@Override
@Nonnull
public List<Profile> getActiveProfiles() {
List<org.apache.maven.model.Profile> activeProfiles =
project.getActiveProfiles() != null ? project.getActiveProfiles() : List.of();
return activeProfiles.stream()
.map(org.apache.maven.model.Profile::getDelegate)
.toList();
}

@Override
@Nonnull
public List<Profile> getAllActiveProfiles() {
List<org.apache.maven.model.Profile> activeProfiles = new ArrayList<>();
for (MavenProject project = this.project; project != null; project = project.getParent()) {
activeProfiles.addAll(project.getActiveProfiles());
}
return activeProfiles.stream()
.map(org.apache.maven.model.Profile::getDelegate)
.toList();
}

Comment on lines +199 to +208
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You could make the methods a one liner, if you'd like with something like this I think:

    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() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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<org.apache.maven.api.model.Profile> activeProfiles =
new DefaultProject(session, project).getActiveProfiles();
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<org.apache.maven.api.model.Profile> activeProfiles =
new DefaultProject(session, project).getActiveProfiles();
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.
*
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
<?xml version="1.0" encoding="ISO-8859-1"?>

<!--
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.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>maven</groupId>
<artifactId>maven-core</artifactId>
<version>2.0-SNAPSHOT</version>

<name>Maven</name>

<profiles>
<profile>
<id>active-by-default</id>
<activation>
<activeByDefault>true</activeByDefault>
</activation>
</profile>
<profile>
<id>profile1</id>
</profile>
<profile>
<id>profile2</id>
</profile>
</profiles>
</project>
Loading
Loading