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-8286] Add a condition profile based on a simple expressions #1771

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
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
83 changes: 79 additions & 4 deletions api/maven-api-model/src/main/mdo/maven.mdo
Original file line number Diff line number Diff line change
Expand Up @@ -2746,10 +2746,79 @@
<class>
<name>Activation</name>
<version>4.0.0+</version>
<description>The conditions within the build runtime environment which will trigger the
automatic inclusion of the build profile. Multiple conditions can be defined, which must
be all satisfied to activate the profile.
</description>
<description><![CDATA[
The conditions within the build runtime environment which will trigger the
automatic inclusion of the build profile. Multiple conditions can be defined, which must
be all satisfied to activate the profile.

<p>In addition to the traditional activation mechanisms (JDK version, OS properties,
file existence, etc.), Maven now supports a powerful condition-based activation
through the {@code condition} field. This new mechanism allows for more flexible
and expressive profile activation rules.</p>

<h2>Condition Syntax</h2>

<p>The condition is specified as a string expression that can include various
functions, comparisons, and logical operators. Some key features include:</p>

<ul>
<li>Property access: {@code ${property.name}}</li>
<li>Comparison operators: {@code ==}, {@code !=}, {@code <}, {@code >}, {@code <=}, {@code >=}</li>
<li>Logical operators: {@code &&} (AND), {@code ||} (OR), {@code not(...)}</li>
<li>Functions: {@code exists(...)}, {@code missing(...)}, {@code matches(...)}, {@code inrange(...)}, and more</li>
</ul>

<h2>Supported Functions</h2>

<p>The following functions are supported in condition expressions:</p>

<ul>
<li>{@code length(string)}: Returns the length of the given string.</li>
<li>{@code upper(string)}: Converts the string to uppercase.</li>
<li>{@code lower(string)}: Converts the string to lowercase.</li>
<li>{@code substring(string, start, [end])}: Returns a substring of the given string.</li>
<li>{@code indexOf(string, substring)}: Returns the index of the first occurrence of substring in string, or -1 if not found.</li>
<li>{@code contains(string, substring)}: Checks if the string contains the substring.</li>
<li>{@code matches(string, regex)}: Checks if the string matches the given regular expression.</li>
<li>{@code not(condition)}: Negates the given condition.</li>
<li>{@code if(condition, trueValue, falseValue)}: Returns trueValue if the condition is true, falseValue otherwise.</li>
<li>{@code exists(path)}: Checks if a file matching the given glob pattern exists.</li>
<li>{@code missing(path)}: Checks if a file matching the given glob pattern does not exist.</li>
<li>{@code inrange(version, range)}: Checks if the given version is within the specified version range.</li>
</ul>

<h2>Supported properties</h2>

<p>The following properties are supported in expressions:</p>

<ul>
<li>`project.basedir`: The project directory</li>
<li>`project.rootDirectory`: The root directory of the project</li>
<li>`project.artifactId`: The artifactId of the project</li>
<li>`project.packaging`: The packaging of the project</li>
<li>user properties</li>
<li>system properties (including environment variables prefixed with `env.`)</li>
</ul>

<h2>Examples</h2>

<ul>
<li>JDK version range: {@code inrange(${java.version}, '[11,)')} (JDK 11 or higher)</li>
<li>OS check: {@code ${os.name} == 'windows'}</li>
<li>File existence: {@code exists('${project.basedir}/src/**}{@code /*.xsd')}</li>
<li>Property check: {@code ${my.property} != 'some-value'}</li>
<li>Regex matching: {@code matches(${os.version}, '.*aws')}</li>
<li>Complex condition: {@code ${os.name} == 'windows' && ${os.arch} != 'amd64' && inrange(${os.version}, '[10,)')}</li>
<li>String length check: {@code length(${user.name}) > 5}</li>
<li>Substring with version: {@code substring(${java.version}, 0, 3) == '1.8'}</li>
<li>Using indexOf: {@code indexOf(${java.version}, '-') > 0}</li>
<li>Conditional logic: {@code if(contains(${java.version}, '-'), substring(${java.version}, 0, indexOf(${java.version}, '-')), ${java.version})}</li>
</ul>

<p>This flexible condition mechanism allows for more precise control over profile
activation, enabling developers to create profiles that respond to a wide range of
environmental factors and project states.</p>
]]></description>
<fields>
<field>
<name>activeByDefault</name>
Expand Down Expand Up @@ -2802,6 +2871,12 @@
<type>String</type>
<description>Specifies that this profile will be activated based on the project's packaging.</description>
</field>
<field>
<name>condition</name>
<version>4.1.0+</version>
<type>String</type>
<description>The condition which must be satisfied to activate the profile.</description>
</field>
<!--
This could be included once we teach Maven to deal with multiple versions of the model
<field>
Expand Down
8 changes: 8 additions & 0 deletions api/maven-api-settings/src/main/mdo/settings.mdo
Original file line number Diff line number Diff line change
Expand Up @@ -744,6 +744,14 @@
Specifies that this profile will be activated based on the project's packaging.
</description>
</field>
<field>
<name>condition</name>
<version>2.0.0+</version>
<type>String</type>
<description>
The condition which must be satisfied to activate the profile.
</description>
</field>
</fields>
</class>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,34 +18,35 @@
*/
package org.apache.maven.api.services.model;

import java.nio.file.Path;
import java.util.List;
import java.util.Map;

import org.apache.maven.api.annotations.Nonnull;
import org.apache.maven.api.model.Model;

/**
* Describes the environmental context used to determine the activation status of profiles.
*
* The {@link Model} is available from the activation context, but only static parts of it
* are allowed to be used, i.e. those that do not change between file model and effective model.
*
*/
public interface ProfileActivationContext {
/**
* Key of the property containing the project's packaging.
* Available in {@link #getUserProperties()}.
* @since 4.0.0
*/
String PROPERTY_NAME_PACKAGING = "packaging";

/**
* Gets the identifiers of those profiles that should be activated by explicit demand.
*
* @return The identifiers of those profiles to activate, never {@code null}.
*/
@Nonnull
List<String> getActiveProfileIds();

/**
* Gets the identifiers of those profiles that should be deactivated by explicit demand.
*
* @return The identifiers of those profiles to deactivate, never {@code null}.
*/
@Nonnull
List<String> getInactiveProfileIds();

/**
Expand All @@ -54,6 +55,7 @@ public interface ProfileActivationContext {
*
* @return The execution properties, never {@code null}.
*/
@Nonnull
Map<String, String> getSystemProperties();

/**
Expand All @@ -63,19 +65,14 @@ public interface ProfileActivationContext {
*
* @return The user properties, never {@code null}.
*/
@Nonnull
Map<String, String> getUserProperties();

/**
* Gets the base directory of the current project (if any).
*
* @return The base directory of the current project or {@code null} if none.
*/
Path getProjectDirectory();

/**
* Gets current calculated project properties
* Gets the model which is being activated.
*
* @return The project properties, never {@code null}.
* @return The project model, never {@code null}.
*/
Map<String, String> getProjectProperties();
@Nonnull
Model getModel();
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.function.Supplier;

import org.apache.maven.api.di.Inject;
import org.apache.maven.api.di.Named;
Expand All @@ -43,6 +44,7 @@
import org.apache.maven.api.services.xml.SettingsXmlFactory;
import org.apache.maven.api.services.xml.XmlReaderException;
import org.apache.maven.api.services.xml.XmlReaderRequest;
import org.apache.maven.api.settings.Activation;
import org.apache.maven.api.settings.Profile;
import org.apache.maven.api.settings.Repository;
import org.apache.maven.api.settings.RepositoryPolicy;
Expand Down Expand Up @@ -228,10 +230,23 @@ private Settings interpolate(Settings settings, SettingsBuilderRequest request,
Map<String, String> userProperties = request.getSession().getUserProperties();
Map<String, String> systemProperties = request.getSession().getSystemProperties();
Function<String, String> src = Interpolator.chain(List.of(userProperties::get, systemProperties::get));
return new SettingsTransformer(value -> value != null ? interpolator.interpolate(value, src) : null)
return new DefSettingsTransformer(value -> value != null ? interpolator.interpolate(value, src) : null)
.visit(settings);
}

static class DefSettingsTransformer extends SettingsTransformer {
DefSettingsTransformer(Function<String, String> transformer) {
super(transformer);
}

@Override
protected Activation.Builder transformActivation_Condition(
Supplier<? extends Activation.Builder> creator, Activation.Builder builder, Activation target) {
// do not interpolate the condition activation
return builder;
}
}

@Override
public List<BuilderProblem> validate(Settings settings, boolean isProjectSettings) {
ArrayList<BuilderProblem> problems = new ArrayList<>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,8 @@ public static Profile convertToSettingsProfile(org.apache.maven.api.model.Profil

activation.packaging(modelActivation.getPackaging());

activation.condition(modelActivation.getCondition());

profile.activation(activation.build());
}

Expand Down Expand Up @@ -212,6 +214,8 @@ public static org.apache.maven.api.model.Profile convertFromSettingsProfile(Prof

activation.packaging(settingsActivation.getPackaging());

activation.condition(settingsActivation.getCondition());

profile.activation(activation.build());
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,6 @@
import org.apache.maven.api.services.model.ModelVersionParser;
import org.apache.maven.api.services.model.PluginConfigurationExpander;
import org.apache.maven.api.services.model.PluginManagementInjector;
import org.apache.maven.api.services.model.ProfileActivationContext;
import org.apache.maven.api.services.model.ProfileInjector;
import org.apache.maven.api.services.model.ProfileSelector;
import org.apache.maven.api.services.model.RootLocator;
Expand Down Expand Up @@ -1070,7 +1069,7 @@ Model activateFileModel(Model inputModel) throws ModelBuilderException {
profileActivationContext.setUserProperties(profileProps);
}

profileActivationContext.setProjectProperties(inputModel.getProperties());
profileActivationContext.setModel(inputModel);
setSource(inputModel);
List<Profile> activePomProfiles =
profileSelector.getActiveProfiles(inputModel.getProfiles(), profileActivationContext, this);
Expand All @@ -1096,6 +1095,7 @@ private Model readEffectiveModel() throws ModelBuilderException {
throw newModelBuilderException();
}

// TODO: this does not seem to be needed anymore
inputModel = activateFileModel(inputModel);

setRootModel(inputModel);
Expand Down Expand Up @@ -1131,7 +1131,7 @@ private Model readEffectiveModel() throws ModelBuilderException {
model = modelNormalizer.mergeDuplicates(model, request, this);

// profile activation
profileActivationContext.setProjectProperties(model.getProperties());
profileActivationContext.setModel(model);

List<Profile> interpolatedProfiles =
interpolateActivations(model.getProfiles(), profileActivationContext, this);
Expand Down Expand Up @@ -1687,6 +1687,13 @@ public Profile apply(Profile p) {
.build();
}

@Override
protected Activation.Builder transformActivation_Condition(
Supplier<? extends Activation.Builder> creator, Activation.Builder builder, Activation target) {
// do not interpolate the condition activation
return builder;
}

@Override
protected ActivationFile.Builder transformActivationFile_Missing(
Supplier<? extends ActivationFile.Builder> creator,
Expand Down Expand Up @@ -1765,13 +1772,8 @@ private DefaultProfileActivationContext getProfileActivationContext(ModelBuilder
context.setActiveProfileIds(request.getActiveProfileIds());
context.setInactiveProfileIds(request.getInactiveProfileIds());
context.setSystemProperties(request.getSystemProperties());
// enrich user properties with project packaging
Map<String, String> userProperties = new HashMap<>(request.getUserProperties());
if (!userProperties.containsKey(ProfileActivationContext.PROPERTY_NAME_PACKAGING)) {
userProperties.put(ProfileActivationContext.PROPERTY_NAME_PACKAGING, model.getPackaging());
}
context.setUserProperties(userProperties);
context.setProjectDirectory(model.getProjectDirectory());
context.setUserProperties(request.getUserProperties());
context.setModel(model);

return context;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -281,6 +281,17 @@ protected ActivationProperty.Builder transformActivationProperty_Value(
stk.pop();
}
}

@Override
protected Activation.Builder transformActivation_Condition(
Supplier<? extends Activation.Builder> creator, Activation.Builder builder, Activation target) {
stk.push(nextFrame("condition"));
try {
return super.transformActivation_Condition(creator, builder, target);
} finally {
stk.pop();
}
}
}

private final Set<String> validCoordinatesIds = new HashSet<>();
Expand Down Expand Up @@ -451,6 +462,8 @@ public void validateFileModel(
}
}

validateStringNoExpression("packaging", problems, Severity.WARNING, Version.V20, m.getPackaging(), m);

validate20RawDependencies(
problems, m.getDependencies(), "dependencies.dependency.", EMPTY, validationLevel, request);

Expand Down
Loading
Loading