[MNG-8391] Wrong effective model when conflicting values come from parents and profiles

This commit is contained in:
Guillaume Nodet 2024-11-28 10:45:14 +01:00
parent 175e219a56
commit a58551d9ab
21 changed files with 686 additions and 358 deletions

View File

@ -18,11 +18,11 @@
*/ */
package org.apache.maven.api.services.model; package org.apache.maven.api.services.model;
import java.util.List;
import java.util.Map;
import org.apache.maven.api.annotations.Nonnull; import org.apache.maven.api.annotations.Nonnull;
import org.apache.maven.api.annotations.Nullable;
import org.apache.maven.api.model.Model; import org.apache.maven.api.model.Model;
import org.apache.maven.api.services.InterpolatorException;
import org.apache.maven.api.services.ModelBuilderException;
/** /**
* Describes the environmental context used to determine the activation status of profiles. * Describes the environmental context used to determine the activation status of profiles.
@ -34,45 +34,100 @@ import org.apache.maven.api.model.Model;
public interface ProfileActivationContext { public interface ProfileActivationContext {
/** /**
* Gets the identifiers of those profiles that should be activated by explicit demand. * Checks if the specified profile has been explicitly activated.
* *
* @return The identifiers of those profiles to activate, never {@code null}. * @param profileId the profile id
* @return whether the profile has been activated
*/ */
@Nonnull boolean isProfileActive(@Nonnull String profileId);
List<String> getActiveProfileIds();
/** /**
* Gets the identifiers of those profiles that should be deactivated by explicit demand. * Checks if the specified profile has been explicitly deactivated.
* *
* @return The identifiers of those profiles to deactivate, never {@code null}. * @param profileId the profile id
* @return whether the profile has been deactivated
*/ */
@Nonnull boolean isProfileInactive(@Nonnull String profileId);
List<String> getInactiveProfileIds();
/** /**
* Gets the system properties to use for interpolation and profile activation. The system properties are collected * Gets the system property to use for interpolation and profile activation. The system properties are collected
* from the runtime environment like {@link System#getProperties()} and environment variables. * from the runtime environment like {@link System#getProperties()} and environment variables.
* *
* @return The execution properties, never {@code null}. * @param key the name of the system property
* @return the system property for the specified key, or {@code null}
*/ */
@Nonnull @Nullable
Map<String, String> getSystemProperties(); String getSystemProperty(@Nonnull String key);
/** /**
* Gets the user properties to use for interpolation and profile activation. The user properties have been * Gets the user property to use for interpolation and profile activation. The user properties have been
* configured directly by the user on his discretion, e.g. via the {@code -Dkey=value} parameter on the command * configured directly by the user on his discretion, e.g. via the {@code -Dkey=value} parameter on the command line.
* line.
* *
* @return The user properties, never {@code null}. * @param key the name of the user property
* @return The user property for the specified key, or {@code null}.
*/ */
@Nonnull @Nullable
Map<String, String> getUserProperties(); String getUserProperty(@Nonnull String key);
/** /**
* Gets the model which is being activated. * Gets the model property to use for interpolation and profile activation.
* *
* @return The project model, never {@code null}. * @param key the name of the model property
* @return The model property for the specified key, or {@code null};
*/ */
@Nonnull @Nullable
Model getModel(); String getModelProperty(@Nonnull String key);
/**
* Gets the artifactId from the current model.
*
* @return The artifactId of the current model, or {@code null} if not set.
*/
@Nullable
String getModelArtifactId();
/**
* Gets the packaging type from the current model.
*
* @return The packaging type of the current model, or {@code null} if not set.
*/
@Nullable
String getModelPackaging();
/**
* Gets the root directory of the current model.
*
* @return The root directory path of the current model, or {@code null} if not set.
*/
@Nullable
String getModelRootDirectory();
/**
* Gets the base directory of the current model.
*
* @return The base directory path of the current model, or {@code null} if not set.
*/
@Nullable
String getModelBaseDirectory();
/**
* Interpolates the given path string using the current context's properties.
*
* @param path The path string to interpolate
* @return The interpolated path string
* @throws InterpolatorException if an error occurs during interpolation
*/
@Nullable
String interpolatePath(@Nullable String path);
/**
* Checks if a file or directory matching the given glob pattern exists at the specified path.
*
* @param path the base path to check
* @param glob whether the path can be a glob expression
* @return {@code true} if a matching file exists, {@code false} otherwise
* @throws ModelBuilderException if an error occurs while checking the path
* @throws InterpolatorException if an error occurs during interpolation
*/
boolean exists(@Nullable String path, boolean glob);
} }

View File

@ -99,6 +99,7 @@ import org.apache.maven.api.services.model.ModelResolverException;
import org.apache.maven.api.services.model.ModelUrlNormalizer; import org.apache.maven.api.services.model.ModelUrlNormalizer;
import org.apache.maven.api.services.model.ModelValidator; import org.apache.maven.api.services.model.ModelValidator;
import org.apache.maven.api.services.model.ModelVersionParser; import org.apache.maven.api.services.model.ModelVersionParser;
import org.apache.maven.api.services.model.PathTranslator;
import org.apache.maven.api.services.model.PluginConfigurationExpander; import org.apache.maven.api.services.model.PluginConfigurationExpander;
import org.apache.maven.api.services.model.PluginManagementInjector; import org.apache.maven.api.services.model.PluginManagementInjector;
import org.apache.maven.api.services.model.ProfileInjector; import org.apache.maven.api.services.model.ProfileInjector;
@ -145,12 +146,13 @@ public class DefaultModelBuilder implements ModelBuilder {
private final DependencyManagementInjector dependencyManagementInjector; private final DependencyManagementInjector dependencyManagementInjector;
private final DependencyManagementImporter dependencyManagementImporter; private final DependencyManagementImporter dependencyManagementImporter;
private final PluginConfigurationExpander pluginConfigurationExpander; private final PluginConfigurationExpander pluginConfigurationExpander;
private final ProfileActivationFilePathInterpolator profileActivationFilePathInterpolator;
private final ModelVersionParser versionParser; private final ModelVersionParser versionParser;
private final List<ModelTransformer> transformers; private final List<ModelTransformer> transformers;
private final ModelCacheFactory modelCacheFactory; private final ModelCacheFactory modelCacheFactory;
private final ModelResolver modelResolver; private final ModelResolver modelResolver;
private final Interpolator interpolator; private final Interpolator interpolator;
private final PathTranslator pathTranslator;
private final RootLocator rootLocator;
@SuppressWarnings("checkstyle:ParameterNumber") @SuppressWarnings("checkstyle:ParameterNumber")
@Inject @Inject
@ -169,12 +171,13 @@ public class DefaultModelBuilder implements ModelBuilder {
DependencyManagementInjector dependencyManagementInjector, DependencyManagementInjector dependencyManagementInjector,
DependencyManagementImporter dependencyManagementImporter, DependencyManagementImporter dependencyManagementImporter,
PluginConfigurationExpander pluginConfigurationExpander, PluginConfigurationExpander pluginConfigurationExpander,
ProfileActivationFilePathInterpolator profileActivationFilePathInterpolator,
ModelVersionParser versionParser, ModelVersionParser versionParser,
List<ModelTransformer> transformers, List<ModelTransformer> transformers,
ModelCacheFactory modelCacheFactory, ModelCacheFactory modelCacheFactory,
ModelResolver modelResolver, ModelResolver modelResolver,
Interpolator interpolator) { Interpolator interpolator,
PathTranslator pathTranslator,
RootLocator rootLocator) {
this.modelProcessor = modelProcessor; this.modelProcessor = modelProcessor;
this.modelValidator = modelValidator; this.modelValidator = modelValidator;
this.modelNormalizer = modelNormalizer; this.modelNormalizer = modelNormalizer;
@ -189,12 +192,13 @@ public class DefaultModelBuilder implements ModelBuilder {
this.dependencyManagementInjector = dependencyManagementInjector; this.dependencyManagementInjector = dependencyManagementInjector;
this.dependencyManagementImporter = dependencyManagementImporter; this.dependencyManagementImporter = dependencyManagementImporter;
this.pluginConfigurationExpander = pluginConfigurationExpander; this.pluginConfigurationExpander = pluginConfigurationExpander;
this.profileActivationFilePathInterpolator = profileActivationFilePathInterpolator;
this.versionParser = versionParser; this.versionParser = versionParser;
this.transformers = transformers; this.transformers = transformers;
this.modelCacheFactory = modelCacheFactory; this.modelCacheFactory = modelCacheFactory;
this.modelResolver = modelResolver; this.modelResolver = modelResolver;
this.interpolator = interpolator; this.interpolator = interpolator;
this.pathTranslator = pathTranslator;
this.rootLocator = rootLocator;
} }
public ModelBuilderSession newSession() { public ModelBuilderSession newSession() {
@ -862,12 +866,12 @@ public class DefaultModelBuilder implements ModelBuilder {
} }
} }
Model readParent(Model childModel) throws ModelBuilderException { Model readParent(Model childModel, DefaultProfileActivationContext profileActivationContext) {
Model parentModel; Model parentModel;
Parent parent = childModel.getParent(); Parent parent = childModel.getParent();
if (parent != null) { if (parent != null) {
parentModel = resolveParent(childModel); parentModel = resolveParent(childModel, profileActivationContext);
if (!"pom".equals(parentModel.getPackaging())) { if (!"pom".equals(parentModel.getPackaging())) {
add( add(
@ -892,18 +896,20 @@ public class DefaultModelBuilder implements ModelBuilder {
return parentModel; return parentModel;
} }
private Model resolveParent(Model childModel) throws ModelBuilderException { private Model resolveParent(Model childModel, DefaultProfileActivationContext profileActivationContext)
throws ModelBuilderException {
Model parentModel = null; Model parentModel = null;
if (isBuildRequest()) { if (isBuildRequest()) {
parentModel = readParentLocally(childModel); parentModel = readParentLocally(childModel, profileActivationContext);
} }
if (parentModel == null) { if (parentModel == null) {
parentModel = resolveAndReadParentExternally(childModel); parentModel = resolveAndReadParentExternally(childModel, profileActivationContext);
} }
return parentModel; return parentModel;
} }
private Model readParentLocally(Model childModel) throws ModelBuilderException { private Model readParentLocally(Model childModel, DefaultProfileActivationContext profileActivationContext)
throws ModelBuilderException {
ModelSource candidateSource; ModelSource candidateSource;
Parent parent = childModel.getParent(); Parent parent = childModel.getParent();
@ -938,7 +944,9 @@ public class DefaultModelBuilder implements ModelBuilder {
return null; return null;
} }
Model candidateModel = derive(candidateSource).readAsParentModel(); ModelBuilderSessionState derived = derive(candidateSource);
Model candidateModel = derived.readAsParentModel(profileActivationContext);
addActivePomProfiles(derived.result.getActivePomProfiles());
String groupId = getGroupId(candidateModel); String groupId = getGroupId(candidateModel);
String artifactId = candidateModel.getArtifactId(); String artifactId = candidateModel.getArtifactId();
@ -1020,7 +1028,8 @@ public class DefaultModelBuilder implements ModelBuilder {
add(Severity.FATAL, Version.BASE, buffer.toString(), parent.getLocation("")); add(Severity.FATAL, Version.BASE, buffer.toString(), parent.getLocation(""));
} }
Model resolveAndReadParentExternally(Model childModel) throws ModelBuilderException { Model resolveAndReadParentExternally(Model childModel, DefaultProfileActivationContext profileActivationContext)
throws ModelBuilderException {
ModelBuilderRequest request = this.request; ModelBuilderRequest request = this.request;
setSource(childModel); setSource(childModel);
@ -1078,7 +1087,7 @@ public class DefaultModelBuilder implements ModelBuilder {
.source(modelSource) .source(modelSource)
.build(); .build();
Model parentModel = derive(lenientRequest).readAsParentModel(); Model parentModel = derive(lenientRequest).readAsParentModel(profileActivationContext);
if (!parent.getVersion().equals(version)) { if (!parent.getVersion().equals(version)) {
String rawChildModelVersion = childModel.getVersion(); String rawChildModelVersion = childModel.getVersion();
@ -1119,7 +1128,7 @@ public class DefaultModelBuilder implements ModelBuilder {
for (Profile profile : activeExternalProfiles) { for (Profile profile : activeExternalProfiles) {
profileProps.putAll(profile.getProperties()); profileProps.putAll(profile.getProperties());
} }
profileProps.putAll(profileActivationContext.getUserProperties()); profileProps.putAll(request.getUserProperties());
profileActivationContext.setUserProperties(profileProps); profileActivationContext.setUserProperties(profileProps);
} }
@ -1163,11 +1172,12 @@ public class DefaultModelBuilder implements ModelBuilder {
for (Profile profile : activeExternalProfiles) { for (Profile profile : activeExternalProfiles) {
profileProps.putAll(profile.getProperties()); profileProps.putAll(profile.getProperties());
} }
profileProps.putAll(profileActivationContext.getUserProperties()); profileProps.putAll(request.getUserProperties());
profileActivationContext.setUserProperties(profileProps); profileActivationContext.setUserProperties(profileProps);
} }
Model parentModel = readParent(activatedFileModel); Model parentModel = readParent(activatedFileModel, profileActivationContext);
// Now that we have read the parent, we can set the relative // Now that we have read the parent, we can set the relative
// path correctly if it was not set in the input model // path correctly if it was not set in the input model
if (inputModel.getParent() != null && inputModel.getParent().getRelativePath() == null) { if (inputModel.getParent() != null && inputModel.getParent().getRelativePath() == null) {
@ -1186,16 +1196,7 @@ public class DefaultModelBuilder implements ModelBuilder {
inputModel = inputModel.withParent(inputModel.getParent().withRelativePath(relPath)); inputModel = inputModel.withParent(inputModel.getParent().withRelativePath(relPath));
} }
List<Profile> parentInterpolatedProfiles = Model model = inheritanceAssembler.assembleModelInheritance(inputModel, parentModel, request, this);
interpolateActivations(parentModel.getProfiles(), profileActivationContext, this);
// profile injection
List<Profile> parentActivePomProfiles =
getActiveProfiles(parentInterpolatedProfiles, profileActivationContext);
Model injectedParentModel = profileInjector
.injectProfiles(parentModel, parentActivePomProfiles, request, this)
.withProfiles(List.of());
Model model = inheritanceAssembler.assembleModelInheritance(inputModel, injectedParentModel, request, this);
// model normalization // model normalization
model = modelNormalizer.mergeDuplicates(model, request, this); model = modelNormalizer.mergeDuplicates(model, request, this);
@ -1211,10 +1212,7 @@ public class DefaultModelBuilder implements ModelBuilder {
model = profileInjector.injectProfiles(model, activePomProfiles, request, this); model = profileInjector.injectProfiles(model, activePomProfiles, request, this);
model = profileInjector.injectProfiles(model, activeExternalProfiles, request, this); model = profileInjector.injectProfiles(model, activeExternalProfiles, request, this);
List<Profile> allProfiles = new ArrayList<>(parentActivePomProfiles.size() + activePomProfiles.size()); addActivePomProfiles(activePomProfiles);
allProfiles.addAll(parentActivePomProfiles);
allProfiles.addAll(activePomProfiles);
result.setActivePomProfiles(allProfiles);
// model interpolation // model interpolation
Model resultModel = model; Model resultModel = model;
@ -1239,6 +1237,15 @@ public class DefaultModelBuilder implements ModelBuilder {
return resultModel; return resultModel;
} }
private void addActivePomProfiles(List<Profile> activePomProfiles) {
if (activePomProfiles != null) {
if (result.getActivePomProfiles() == null) {
result.setActivePomProfiles(new ArrayList<>());
}
result.getActivePomProfiles().addAll(activePomProfiles);
}
}
private List<Profile> getActiveProfiles( private List<Profile> getActiveProfiles(
Collection<Profile> interpolatedProfiles, DefaultProfileActivationContext profileActivationContext) { Collection<Profile> interpolatedProfiles, DefaultProfileActivationContext profileActivationContext) {
if (isBuildRequestWithActivation()) { if (isBuildRequestWithActivation()) {
@ -1491,13 +1498,25 @@ public class DefaultModelBuilder implements ModelBuilder {
/** /**
* Reads the request source's parent. * Reads the request source's parent.
*/ */
Model readAsParentModel() throws ModelBuilderException { Model readAsParentModel(DefaultProfileActivationContext profileActivationContext) throws ModelBuilderException {
return cache(request.getSource(), PARENT, this::doReadAsParentModel); Map<DefaultProfileActivationContext.Record, Model> parentsPerContext =
cache(request.getSource(), PARENT, ConcurrentHashMap::new);
for (Map.Entry<DefaultProfileActivationContext.Record, Model> e : parentsPerContext.entrySet()) {
if (e.getKey().matches(profileActivationContext)) {
return e.getValue();
}
}
DefaultProfileActivationContext.Record prev = profileActivationContext.start();
Model model = doReadAsParentModel(profileActivationContext);
DefaultProfileActivationContext.Record record = profileActivationContext.stop(prev);
parentsPerContext.put(record, model);
return model;
} }
private Model doReadAsParentModel() throws ModelBuilderException { private Model doReadAsParentModel(DefaultProfileActivationContext profileActivationContext)
throws ModelBuilderException {
Model raw = readRawModel(); Model raw = readRawModel();
Model parentData = readParent(raw); Model parentData = readParent(raw, profileActivationContext);
Model parent = new DefaultInheritanceAssembler(new DefaultInheritanceAssembler.InheritanceModelMerger() { Model parent = new DefaultInheritanceAssembler(new DefaultInheritanceAssembler.InheritanceModelMerger() {
@Override @Override
protected void mergeModel_Modules( protected void mergeModel_Modules(
@ -1514,23 +1533,21 @@ public class DefaultModelBuilder implements ModelBuilder {
Model source, Model source,
boolean sourceDominant, boolean sourceDominant,
Map<Object, Object> context) {} Map<Object, Object> context) {}
@SuppressWarnings("deprecation")
@Override
protected void mergeModel_Profiles(
Model.Builder builder,
Model target,
Model source,
boolean sourceDominant,
Map<Object, Object> context) {
builder.profiles(Stream.concat(source.getProfiles().stream(), target.getProfiles().stream())
.map(p -> p.withModules(null).withSubprojects(null))
.toList());
}
}) })
.assembleModelInheritance(raw, parentData, request, this); .assembleModelInheritance(raw, parentData, request, this);
return parent.withParent(null); // activate profiles
List<Profile> parentInterpolatedProfiles =
interpolateActivations(parent.getProfiles(), profileActivationContext, this);
// profile injection
List<Profile> parentActivePomProfiles =
getActiveProfiles(parentInterpolatedProfiles, profileActivationContext);
Model injectedParentModel = profileInjector
.injectProfiles(parent, parentActivePomProfiles, request, this)
.withProfiles(List.of());
addActivePomProfiles(parentActivePomProfiles);
return injectedParentModel.withParent(null);
} }
private Model importDependencyManagement(Model model, Collection<String> importIds) { private Model importDependencyManagement(Model model, Collection<String> importIds) {
@ -1763,9 +1780,8 @@ public class DefaultModelBuilder implements ModelBuilder {
ProfileInterpolator() { ProfileInterpolator() {
super(s -> { super(s -> {
try { try {
Map<String, String> map1 = context.getUserProperties(); return interpolator.interpolate(
Map<String, String> map2 = context.getSystemProperties(); s, Interpolator.chain(context::getUserProperty, context::getSystemProperty));
return interpolator.interpolate(s, Interpolator.chain(map1::get, map2::get));
} catch (InterpolatorException e) { } catch (InterpolatorException e) {
problems.add(Severity.ERROR, Version.BASE, e.getMessage(), e); problems.add(Severity.ERROR, Version.BASE, e.getMessage(), e);
} }
@ -1809,7 +1825,7 @@ public class DefaultModelBuilder implements ModelBuilder {
private String transformPath(String path, ActivationFile target, String locationKey) { private String transformPath(String path, ActivationFile target, String locationKey) {
try { try {
return profileActivationFilePathInterpolator.interpolate(path, context); return context.interpolatePath(path);
} catch (InterpolatorException e) { } catch (InterpolatorException e) {
problems.add( problems.add(
Severity.ERROR, Severity.ERROR,
@ -1860,7 +1876,8 @@ public class DefaultModelBuilder implements ModelBuilder {
} }
private DefaultProfileActivationContext getProfileActivationContext(ModelBuilderRequest request, Model model) { private DefaultProfileActivationContext getProfileActivationContext(ModelBuilderRequest request, Model model) {
DefaultProfileActivationContext context = new DefaultProfileActivationContext(); DefaultProfileActivationContext context =
new DefaultProfileActivationContext(pathTranslator, rootLocator, interpolator);
context.setActiveProfileIds(request.getActiveProfileIds()); context.setActiveProfileIds(request.getActiveProfileIds());
context.setInactiveProfileIds(request.getInactiveProfileIds()); context.setInactiveProfileIds(request.getInactiveProfileIds());

View File

@ -18,32 +18,168 @@
*/ */
package org.apache.maven.internal.impl.model; package org.apache.maven.internal.impl.model;
import java.io.File;
import java.io.IOException;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.PathMatcher;
import java.nio.file.Paths;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicBoolean;
import org.apache.maven.api.model.Model; import org.apache.maven.api.model.Model;
import org.apache.maven.api.services.Interpolator;
import org.apache.maven.api.services.InterpolatorException;
import org.apache.maven.api.services.ModelBuilderException;
import org.apache.maven.api.services.ProjectBuilderException;
import org.apache.maven.api.services.model.PathTranslator;
import org.apache.maven.api.services.model.ProfileActivationContext; import org.apache.maven.api.services.model.ProfileActivationContext;
import org.apache.maven.api.services.model.RootLocator;
/** /**
* Describes the environmental context used to determine the activation status of profiles. * Describes the environmental context used to determine the activation status of profiles.
*
*/ */
public class DefaultProfileActivationContext implements ProfileActivationContext { public class DefaultProfileActivationContext implements ProfileActivationContext {
private List<String> activeProfileIds = Collections.emptyList(); record ExistRequest(String path, boolean enableGlob) {}
private List<String> inactiveProfileIds = Collections.emptyList(); enum ModelInfo {
ArtifactId,
Packaging,
BaseDirectory,
RootDirectory
}
private Map<String, String> systemProperties = Collections.emptyMap(); /**
* This class keeps track of information that are used during profile activation.
private Map<String, String> userProperties = Collections.emptyMap(); * This allows to cache the activated parent and check if the result of the
* activation will be the same by verifying that the used keys are the same.
private Model model; */
static class Record {
private final Map<String, Boolean> usedActiveProfiles = new HashMap<>();
private final Map<String, Boolean> usedInactiveProfiles = new HashMap<>();
private final Map<String, String> usedSystemProperties = new HashMap<>();
private final Map<String, String> usedUserProperties = new HashMap<>();
private final Map<String, String> usedModelProperties = new HashMap<>();
private final Map<ModelInfo, String> usedModelInfos = new HashMap<>();
private final Map<ExistRequest, Boolean> usedExists = new HashMap<>();
@Override @Override
public List<String> getActiveProfileIds() { public boolean equals(Object o) {
return activeProfileIds; if (o instanceof Record record) {
return Objects.equals(usedActiveProfiles, record.usedActiveProfiles)
&& Objects.equals(usedInactiveProfiles, record.usedInactiveProfiles)
&& Objects.equals(usedSystemProperties, record.usedSystemProperties)
&& Objects.equals(usedUserProperties, record.usedUserProperties)
&& Objects.equals(usedModelProperties, record.usedModelProperties)
&& Objects.equals(usedModelInfos, record.usedModelInfos)
&& Objects.equals(usedExists, record.usedExists);
}
return false;
}
@Override
public int hashCode() {
return Objects.hash(
usedActiveProfiles,
usedInactiveProfiles,
usedSystemProperties,
usedUserProperties,
usedModelProperties,
usedModelInfos,
usedExists);
}
boolean matches(DefaultProfileActivationContext context) {
return matchesProfiles(usedActiveProfiles, context.activeProfileIds)
&& matchesProfiles(usedInactiveProfiles, context.inactiveProfileIds)
&& matchesProperties(usedSystemProperties, context.systemProperties)
&& matchesProperties(usedUserProperties, context.userProperties)
&& matchesProperties(usedModelProperties, context.model.getProperties())
&& matchesModelInfos(usedModelInfos, context)
&& matchesExists(usedExists, context);
}
private boolean matchesProfiles(Map<String, Boolean> expected, List<String> actual) {
return expected.entrySet().stream()
.allMatch(e -> Objects.equals(e.getValue(), actual.contains(e.getKey())));
}
private boolean matchesProperties(Map<String, String> expected, Map<String, String> actual) {
return expected.entrySet().stream().allMatch(e -> Objects.equals(e.getValue(), actual.get(e.getKey())));
}
private boolean matchesModelInfos(Map<ModelInfo, String> infos, DefaultProfileActivationContext context) {
return infos.entrySet().stream()
.allMatch(e -> Objects.equals(e.getValue(), getModelValue(e.getKey(), context)));
}
private String getModelValue(ModelInfo key, DefaultProfileActivationContext context) {
return switch (key) {
case ArtifactId -> context.model.getArtifactId();
case Packaging -> context.model.getPackaging();
case BaseDirectory -> context.doGetModelBaseDirectory();
case RootDirectory -> context.doGetModelRootDirectory();
};
}
private boolean matchesExists(Map<ExistRequest, Boolean> exists, DefaultProfileActivationContext context) {
return exists.entrySet().stream()
.allMatch(e -> Objects.equals(
e.getValue(),
context.doExists(e.getKey().path(), e.getKey().enableGlob())));
}
}
private final PathTranslator pathTranslator;
private final RootLocator rootLocator;
private final Interpolator interpolator;
private List<String> activeProfileIds = Collections.emptyList();
private List<String> inactiveProfileIds = Collections.emptyList();
private Map<String, String> systemProperties = Collections.emptyMap();
private Map<String, String> userProperties = Collections.emptyMap();
private Model model;
private final ThreadLocal<Record> records = new ThreadLocal<>();
public DefaultProfileActivationContext(
PathTranslator pathTranslator, RootLocator rootLocator, Interpolator interpolator) {
this.pathTranslator = pathTranslator;
this.rootLocator = rootLocator;
this.interpolator = interpolator;
}
Record start() {
Record record = records.get();
records.set(new Record());
return record;
}
Record stop(Record previous) {
Record record = records.get();
records.set(previous);
// only keep keys for which the value is `true`
record.usedActiveProfiles.values().removeIf(value -> !value);
record.usedInactiveProfiles.values().removeIf(value -> !value);
return record;
}
@Override
public boolean isProfileActive(String profileId) {
Record record = records.get();
if (record != null) {
return record.usedActiveProfiles.computeIfAbsent(profileId, activeProfileIds::contains);
} else {
return activeProfileIds.contains(profileId);
}
} }
/** /**
@ -58,8 +194,13 @@ public class DefaultProfileActivationContext implements ProfileActivationContext
} }
@Override @Override
public List<String> getInactiveProfileIds() { public boolean isProfileInactive(String profileId) {
return inactiveProfileIds; Record record = records.get();
if (record != null) {
return record.usedInactiveProfiles.computeIfAbsent(profileId, inactiveProfileIds::contains);
} else {
return inactiveProfileIds.contains(profileId);
}
} }
/** /**
@ -74,8 +215,13 @@ public class DefaultProfileActivationContext implements ProfileActivationContext
} }
@Override @Override
public Map<String, String> getSystemProperties() { public String getSystemProperty(String key) {
return systemProperties; Record record = records.get();
if (record != null) {
return record.usedSystemProperties.computeIfAbsent(key, systemProperties::get);
} else {
return systemProperties.get(key);
}
} }
/** /**
@ -91,8 +237,13 @@ public class DefaultProfileActivationContext implements ProfileActivationContext
} }
@Override @Override
public Map<String, String> getUserProperties() { public String getUserProperty(String key) {
return userProperties; Record record = records.get();
if (record != null) {
return record.usedUserProperties.computeIfAbsent(key, userProperties::get);
} else {
return userProperties.get(key);
}
} }
/** /**
@ -109,16 +260,164 @@ public class DefaultProfileActivationContext implements ProfileActivationContext
} }
@Override @Override
public Model getModel() { public String getModelArtifactId() {
return model; Record record = records.get();
if (record != null) {
return record.usedModelInfos.computeIfAbsent(ModelInfo.ArtifactId, k -> model.getArtifactId());
} else {
return model.getArtifactId();
}
}
@Override
public String getModelPackaging() {
Record record = records.get();
if (record != null) {
return record.usedModelInfos.computeIfAbsent(ModelInfo.Packaging, k -> model.getPackaging());
} else {
return model.getPackaging();
}
}
@Override
public String getModelProperty(String key) {
Record record = records.get();
if (record != null) {
return record.usedModelProperties.computeIfAbsent(
key, k -> model.getProperties().get(k));
} else {
return model.getProperties().get(key);
}
}
@Override
public String getModelBaseDirectory() {
Record record = records.get();
if (record != null) {
return record.usedModelInfos.computeIfAbsent(ModelInfo.BaseDirectory, k -> doGetModelBaseDirectory());
} else {
return doGetModelBaseDirectory();
}
}
private String doGetModelBaseDirectory() {
Path basedir = model.getProjectDirectory();
return basedir != null ? basedir.toAbsolutePath().toString() : null;
}
@Override
public String getModelRootDirectory() {
Record record = records.get();
if (record != null) {
return record.usedModelInfos.computeIfAbsent(ModelInfo.RootDirectory, k -> doGetModelRootDirectory());
} else {
return doGetModelRootDirectory();
}
}
private String doGetModelRootDirectory() {
Path basedir = model != null ? model.getProjectDirectory() : null;
Path rootdir = rootLocator != null ? rootLocator.findRoot(basedir) : null;
return rootdir != null ? rootdir.toAbsolutePath().toString() : null;
} }
public DefaultProfileActivationContext setModel(Model model) { public DefaultProfileActivationContext setModel(Model model) {
this.model = model; this.model = model;
return this; return this;
} }
@Override
public String interpolatePath(String path) throws InterpolatorException {
if (path == null) {
return null;
}
String absolutePath = interpolator.interpolate(path, s -> {
if ("basedir".equals(s) || "project.basedir".equals(s)) {
return getModelBaseDirectory();
}
if ("project.rootDirectory".equals(s)) {
return getModelRootDirectory();
}
String r = getModelProperty(s);
if (r == null) {
r = getUserProperty(s);
}
if (r == null) {
r = getSystemProperty(s);
}
return r;
});
return pathTranslator.alignToBaseDirectory(absolutePath, model.getProjectDirectory());
}
@Override
public boolean exists(String path, boolean enableGlob) throws ModelBuilderException {
Record record = records.get();
if (record != null) {
return record.usedExists.computeIfAbsent(
new ExistRequest(path, enableGlob), r -> doExists(r.path, r.enableGlob));
} else {
return doExists(path, enableGlob);
}
}
private boolean doExists(String path, boolean enableGlob) throws ModelBuilderException {
String pattern = interpolatePath(path);
String fixed, glob;
if (enableGlob) {
int asteriskIndex = pattern.indexOf('*');
int questionMarkIndex = pattern.indexOf('?');
int firstWildcardIndex = questionMarkIndex < 0
? asteriskIndex
: asteriskIndex < 0 ? questionMarkIndex : Math.min(asteriskIndex, questionMarkIndex);
if (firstWildcardIndex < 0) {
fixed = pattern;
glob = "";
} else {
int lastSep = pattern.substring(0, firstWildcardIndex).lastIndexOf(File.separatorChar);
if (lastSep < 0) {
fixed = "";
glob = pattern;
} else {
fixed = pattern.substring(0, lastSep);
glob = pattern.substring(lastSep + 1);
}
}
} else {
fixed = pattern;
glob = "";
}
Path fixedPath = Paths.get(fixed);
return doExists(fixedPath, glob);
}
private static Boolean doExists(Path fixedPath, String glob) {
if (fixedPath == null || !Files.exists(fixedPath)) {
return false;
}
if (glob != null && !glob.isEmpty()) {
try {
PathMatcher matcher = fixedPath.getFileSystem().getPathMatcher("glob:" + glob);
AtomicBoolean found = new AtomicBoolean(false);
Files.walkFileTree(fixedPath, new SimpleFileVisitor<>() {
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) {
if (found.get() || matcher.matches(fixedPath.relativize(file))) {
found.set(true);
return FileVisitResult.TERMINATE;
}
return FileVisitResult.CONTINUE;
}
});
return found.get();
} catch (IOException e) {
throw new ProjectBuilderException(
"Unable to verify file existence for '" + glob + "' inside '" + fixedPath + "'", e);
}
}
return true;
}
private static List<String> unmodifiable(List<String> list) { private static List<String> unmodifiable(List<String> list) {
return list != null ? Collections.unmodifiableList(list) : Collections.emptyList(); return list != null ? Collections.unmodifiableList(list) : Collections.emptyList();
} }

View File

@ -20,7 +20,6 @@ package org.apache.maven.internal.impl.model;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.HashSet;
import java.util.List; import java.util.List;
import org.apache.maven.api.di.Inject; import org.apache.maven.api.di.Inject;
@ -64,16 +63,13 @@ public class DefaultProfileSelector implements ProfileSelector {
@Override @Override
public List<Profile> getActiveProfiles( public List<Profile> getActiveProfiles(
Collection<Profile> profiles, ProfileActivationContext context, ModelProblemCollector problems) { Collection<Profile> profiles, ProfileActivationContext context, ModelProblemCollector problems) {
Collection<String> activatedIds = new HashSet<>(context.getActiveProfileIds());
Collection<String> deactivatedIds = new HashSet<>(context.getInactiveProfileIds());
List<Profile> activeProfiles = new ArrayList<>(profiles.size()); List<Profile> activeProfiles = new ArrayList<>(profiles.size());
List<Profile> activePomProfilesByDefault = new ArrayList<>(); List<Profile> activePomProfilesByDefault = new ArrayList<>();
boolean activatedPomProfileNotByDefault = false; boolean activatedPomProfileNotByDefault = false;
for (Profile profile : profiles) { for (Profile profile : profiles) {
if (!deactivatedIds.contains(profile.getId())) { if (!context.isProfileInactive(profile.getId())) {
if (activatedIds.contains(profile.getId()) || isActive(profile, context, problems)) { if (context.isProfileActive(profile.getId()) || isActive(profile, context, problems)) {
activeProfiles.add(profile); activeProfiles.add(profile);
if (Profile.SOURCE_POM.equals(profile.getSource())) { if (Profile.SOURCE_POM.equals(profile.getSource())) {
activatedPomProfileNotByDefault = true; activatedPomProfileNotByDefault = true;

View File

@ -1,86 +0,0 @@
/*
* 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.
*/
package org.apache.maven.internal.impl.model;
import java.nio.file.Path;
import org.apache.maven.api.di.Inject;
import org.apache.maven.api.di.Named;
import org.apache.maven.api.di.Singleton;
import org.apache.maven.api.model.ActivationFile;
import org.apache.maven.api.services.Interpolator;
import org.apache.maven.api.services.InterpolatorException;
import org.apache.maven.api.services.model.PathTranslator;
import org.apache.maven.api.services.model.ProfileActivationContext;
import org.apache.maven.api.services.model.RootLocator;
/**
* Finds an absolute path for {@link ActivationFile#getExists()} or {@link ActivationFile#getMissing()}
*/
@Named
@Singleton
public class ProfileActivationFilePathInterpolator {
private final PathTranslator pathTranslator;
private final RootLocator rootLocator;
private final Interpolator interpolator;
@Inject
public ProfileActivationFilePathInterpolator(
PathTranslator pathTranslator, RootLocator rootLocator, Interpolator interpolator) {
this.pathTranslator = pathTranslator;
this.rootLocator = rootLocator;
this.interpolator = interpolator;
}
/**
* Interpolates given {@code path}.
*
* @return absolute path or {@code null} if the input was {@code null}
*/
public String interpolate(String path, ProfileActivationContext context) throws InterpolatorException {
if (path == null) {
return null;
}
Path basedir = context.getModel().getProjectDirectory();
String absolutePath = interpolator.interpolate(path, s -> {
if ("basedir".equals(s) || "project.basedir".equals(s)) {
return basedir != null ? basedir.toFile().getAbsolutePath() : null;
}
if ("project.rootDirectory".equals(s)) {
Path root = rootLocator.findMandatoryRoot(basedir);
return root.toFile().getAbsolutePath();
}
String r = context.getModel().getProperties().get(s);
if (r == null) {
r = context.getUserProperties().get(s);
}
if (r == null) {
r = context.getSystemProperties().get(s);
}
return r;
});
return pathTranslator.alignToBaseDirectory(absolutePath, basedir);
}
}

View File

@ -18,21 +18,12 @@
*/ */
package org.apache.maven.internal.impl.model.profile; package org.apache.maven.internal.impl.model.profile;
import java.io.File;
import java.io.IOException;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.PathMatcher;
import java.nio.file.Paths;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.List; import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import org.apache.maven.api.services.InterpolatorException;
import org.apache.maven.api.services.ModelBuilderException;
import org.apache.maven.api.services.VersionParser; import org.apache.maven.api.services.VersionParser;
import org.apache.maven.api.services.model.ProfileActivationContext; import org.apache.maven.api.services.model.ProfileActivationContext;
import org.apache.maven.internal.impl.model.ProfileActivationFilePathInterpolator;
import static org.apache.maven.internal.impl.model.profile.ConditionParser.toInt; import static org.apache.maven.internal.impl.model.profile.ConditionParser.toInt;
@ -45,22 +36,16 @@ import static org.apache.maven.internal.impl.model.profile.ConditionParser.toInt
public class ConditionFunctions { public class ConditionFunctions {
private final ProfileActivationContext context; private final ProfileActivationContext context;
private final VersionParser versionParser; private final VersionParser versionParser;
private final ProfileActivationFilePathInterpolator interpolator;
/** /**
* Constructs a new ConditionFunctions instance. * Constructs a new ConditionFunctions instance.
* *
* @param context The profile activation context * @param context The profile activation context
* @param versionParser The version parser for comparing versions * @param versionParser The version parser for comparing versions
* @param interpolator The interpolator for resolving file paths
*/ */
public ConditionFunctions( public ConditionFunctions(ProfileActivationContext context, VersionParser versionParser) {
ProfileActivationContext context,
VersionParser versionParser,
ProfileActivationFilePathInterpolator interpolator) {
this.context = context; this.context = context;
this.versionParser = versionParser; this.versionParser = versionParser;
this.interpolator = interpolator;
} }
/** /**
@ -207,75 +192,34 @@ public class ConditionFunctions {
* Checks if a file or directory exists at the given path. * Checks if a file or directory exists at the given path.
* *
* @param args A list containing a single string argument representing the path * @param args A list containing a single string argument representing the path
* @return true if the file or directory exists, false otherwise * @return {@code true} if the file or directory exists, {@code false} otherwise
* @throws IllegalArgumentException if the number of arguments is not exactly one * @throws IllegalArgumentException if the number of arguments is not exactly one
* @throws IOException if a problem occurs while walking the file system * @throws ModelBuilderException if a problem occurs while walking the file system
* @throws InterpolatorException if an error occurs during interpolation
*/ */
public Object exists(List<Object> args) throws IOException { public Object exists(List<Object> args) {
if (args.size() != 1) { if (args.size() != 1) {
throw new IllegalArgumentException("exists function requires exactly one argument"); throw new IllegalArgumentException("exists function requires exactly one argument");
} }
String path = ConditionParser.toString(args.get(0)); String path = ConditionParser.toString(args.get(0));
return fileExists(path); return context.exists(path, true);
} }
/** /**
* Checks if a file or directory is missing at the given path. * Checks if a file or directory is missing at the given path.
* *
* @param args A list containing a single string argument representing the path * @param args A list containing a single string argument representing the path
* @return true if the file or directory does not exist, false otherwise * @return {@code true} if the file or directory does not exist, {@code false} otherwise
* @throws IllegalArgumentException if the number of arguments is not exactly one * @throws IllegalArgumentException if the number of arguments is not exactly one
* @throws IOException if a problem occurs while walking the file system * @throws ModelBuilderException if a problem occurs while walking the file system
* @throws InterpolatorException if an error occurs during interpolation
*/ */
public Object missing(List<Object> args) throws IOException { public Object missing(List<Object> args) {
if (args.size() != 1) { if (args.size() != 1) {
throw new IllegalArgumentException("missing function requires exactly one argument"); throw new IllegalArgumentException("missing function requires exactly one argument");
} }
String path = ConditionParser.toString(args.get(0)); String path = ConditionParser.toString(args.get(0));
return !fileExists(path); return !context.exists(path, true);
}
private boolean fileExists(String path) throws IOException {
String pattern = interpolator.interpolate(path, context);
int asteriskIndex = pattern.indexOf('*');
int questionMarkIndex = pattern.indexOf('?');
int firstWildcardIndex = questionMarkIndex < 0
? asteriskIndex
: asteriskIndex < 0 ? questionMarkIndex : Math.min(asteriskIndex, questionMarkIndex);
String fixed, glob;
if (firstWildcardIndex < 0) {
fixed = pattern;
glob = "";
} else {
int lastSep = pattern.substring(0, firstWildcardIndex).lastIndexOf(File.separatorChar);
if (lastSep < 0) {
fixed = "";
glob = pattern;
} else {
fixed = pattern.substring(0, lastSep);
glob = pattern.substring(lastSep + 1);
}
}
Path fixedPath = Paths.get(fixed);
if (!Files.exists(fixedPath)) {
return false;
}
if (!glob.isEmpty()) {
PathMatcher matcher = fixedPath.getFileSystem().getPathMatcher("glob:" + glob);
AtomicBoolean found = new AtomicBoolean(false);
Files.walkFileTree(fixedPath, new SimpleFileVisitor<>() {
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) {
if (found.get() || matcher.matches(fixedPath.relativize(file))) {
found.set(true);
return FileVisitResult.TERMINATE;
}
return FileVisitResult.CONTINUE;
}
});
return found.get();
}
return true;
} }
/** /**

View File

@ -18,7 +18,6 @@
*/ */
package org.apache.maven.internal.impl.model.profile; package org.apache.maven.internal.impl.model.profile;
import java.nio.file.Path;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.function.Function; import java.util.function.Function;
@ -34,9 +33,7 @@ import org.apache.maven.api.services.ModelProblemCollector;
import org.apache.maven.api.services.VersionParser; import org.apache.maven.api.services.VersionParser;
import org.apache.maven.api.services.model.ProfileActivationContext; import org.apache.maven.api.services.model.ProfileActivationContext;
import org.apache.maven.api.services.model.ProfileActivator; import org.apache.maven.api.services.model.ProfileActivator;
import org.apache.maven.api.services.model.RootLocator;
import org.apache.maven.internal.impl.model.DefaultInterpolator; import org.apache.maven.internal.impl.model.DefaultInterpolator;
import org.apache.maven.internal.impl.model.ProfileActivationFilePathInterpolator;
import static org.apache.maven.internal.impl.model.profile.ConditionParser.toBoolean; import static org.apache.maven.internal.impl.model.profile.ConditionParser.toBoolean;
@ -49,22 +46,15 @@ import static org.apache.maven.internal.impl.model.profile.ConditionParser.toBoo
public class ConditionProfileActivator implements ProfileActivator { public class ConditionProfileActivator implements ProfileActivator {
private final VersionParser versionParser; private final VersionParser versionParser;
private final ProfileActivationFilePathInterpolator interpolator;
private final RootLocator rootLocator;
/** /**
* Constructs a new ConditionProfileActivator with the necessary dependencies. * Constructs a new ConditionProfileActivator with the necessary dependencies.
* *
* @param versionParser The parser for handling version comparisons * @param versionParser The parser for handling version comparisons
* @param interpolator The interpolator for resolving file paths
* @param rootLocator The locator for finding the project root directory
*/ */
@Inject @Inject
public ConditionProfileActivator( public ConditionProfileActivator(VersionParser versionParser) {
VersionParser versionParser, ProfileActivationFilePathInterpolator interpolator, RootLocator rootLocator) {
this.versionParser = versionParser; this.versionParser = versionParser;
this.interpolator = interpolator;
this.rootLocator = rootLocator;
} }
/** /**
@ -82,9 +72,8 @@ public class ConditionProfileActivator implements ProfileActivator {
} }
String condition = profile.getActivation().getCondition(); String condition = profile.getActivation().getCondition();
try { try {
Map<String, ConditionParser.ExpressionFunction> functions = Map<String, ConditionParser.ExpressionFunction> functions = registerFunctions(context, versionParser);
registerFunctions(context, versionParser, interpolator); Function<String, String> propertyResolver = s -> property(context, s);
Function<String, String> propertyResolver = s -> property(context, rootLocator, s);
return toBoolean(new ConditionParser(functions, propertyResolver).parse(condition)); return toBoolean(new ConditionParser(functions, propertyResolver).parse(condition));
} catch (Exception e) { } catch (Exception e) {
problems.add( problems.add(
@ -115,16 +104,13 @@ public class ConditionProfileActivator implements ProfileActivator {
* *
* @param context The profile activation context * @param context The profile activation context
* @param versionParser The parser for handling version comparisons * @param versionParser The parser for handling version comparisons
* @param interpolator The interpolator for resolving file paths
* @return A map of function names to their implementations * @return A map of function names to their implementations
*/ */
public static Map<String, ConditionParser.ExpressionFunction> registerFunctions( public static Map<String, ConditionParser.ExpressionFunction> registerFunctions(
ProfileActivationContext context, ProfileActivationContext context, VersionParser versionParser) {
VersionParser versionParser,
ProfileActivationFilePathInterpolator interpolator) {
Map<String, ConditionParser.ExpressionFunction> functions = new HashMap<>(); Map<String, ConditionParser.ExpressionFunction> functions = new HashMap<>();
ConditionFunctions conditionFunctions = new ConditionFunctions(context, versionParser, interpolator); ConditionFunctions conditionFunctions = new ConditionFunctions(context, versionParser);
for (java.lang.reflect.Method method : ConditionFunctions.class.getDeclaredMethods()) { for (java.lang.reflect.Method method : ConditionFunctions.class.getDeclaredMethods()) {
String methodName = method.getName(); String methodName = method.getName();
@ -170,43 +156,37 @@ public class ConditionProfileActivator implements ProfileActivator {
* @return The value of the property, or null if not found * @return The value of the property, or null if not found
* @throws IllegalArgumentException if the number of arguments is not exactly one * @throws IllegalArgumentException if the number of arguments is not exactly one
*/ */
static String property(ProfileActivationContext context, RootLocator rootLocator, String name) { static String property(ProfileActivationContext context, String name) {
String value = doGetProperty(context, rootLocator, name); String value = doGetProperty(context, name);
return new DefaultInterpolator().interpolate(value, s -> doGetProperty(context, rootLocator, s)); return new DefaultInterpolator().interpolate(value, s -> doGetProperty(context, s));
} }
static String doGetProperty(ProfileActivationContext context, RootLocator rootLocator, String name) { static String doGetProperty(ProfileActivationContext context, String name) {
// Handle special project-related properties // Handle special project-related properties
if ("project.basedir".equals(name)) { if ("project.basedir".equals(name)) {
Path basedir = context.getModel().getProjectDirectory(); return context.getModelBaseDirectory();
return basedir != null ? basedir.toFile().getAbsolutePath() : null;
} }
if ("project.rootDirectory".equals(name)) { if ("project.rootDirectory".equals(name)) {
Path basedir = context.getModel().getProjectDirectory(); return context.getModelRootDirectory();
if (basedir != null) {
Path root = rootLocator.findMandatoryRoot(basedir);
return root.toFile().getAbsolutePath();
}
return null;
} }
if ("project.artifactId".equals(name)) { if ("project.artifactId".equals(name)) {
return context.getModel().getArtifactId(); return context.getModelArtifactId();
} }
if ("project.packaging".equals(name)) { if ("project.packaging".equals(name)) {
return context.getModel().getPackaging(); return context.getModelPackaging();
} }
// Check user properties // Check user properties
String v = context.getUserProperties().get(name); String v = context.getUserProperty(name);
if (v == null) { if (v == null) {
// Check project properties // Check project properties
// TODO: this may leads to instability between file model activation and effective model activation // TODO: this may leads to instability between file model activation and effective model activation
// as the effective model properties may be different from the file model // as the effective model properties may be different from the file model
v = context.getModel().getProperties().get(name); v = context.getModelProperty(name);
} }
if (v == null) { if (v == null) {
// Check system properties // Check system properties
v = context.getSystemProperties().get(name); v = context.getSystemProperty(name);
} }
return v; return v;
} }

View File

@ -18,21 +18,17 @@
*/ */
package org.apache.maven.internal.impl.model.profile; package org.apache.maven.internal.impl.model.profile;
import java.io.File;
import org.apache.maven.api.di.Inject;
import org.apache.maven.api.di.Named; import org.apache.maven.api.di.Named;
import org.apache.maven.api.di.Singleton; import org.apache.maven.api.di.Singleton;
import org.apache.maven.api.model.Activation; import org.apache.maven.api.model.Activation;
import org.apache.maven.api.model.ActivationFile; import org.apache.maven.api.model.ActivationFile;
import org.apache.maven.api.model.Profile; import org.apache.maven.api.model.Profile;
import org.apache.maven.api.services.BuilderProblem; import org.apache.maven.api.services.BuilderProblem;
import org.apache.maven.api.services.InterpolatorException; import org.apache.maven.api.services.MavenException;
import org.apache.maven.api.services.ModelProblem; import org.apache.maven.api.services.ModelProblem;
import org.apache.maven.api.services.ModelProblemCollector; import org.apache.maven.api.services.ModelProblemCollector;
import org.apache.maven.api.services.model.ProfileActivationContext; import org.apache.maven.api.services.model.ProfileActivationContext;
import org.apache.maven.api.services.model.ProfileActivator; import org.apache.maven.api.services.model.ProfileActivator;
import org.apache.maven.internal.impl.model.ProfileActivationFilePathInterpolator;
/** /**
* Determines profile activation based on the existence/absence of some file. * Determines profile activation based on the existence/absence of some file.
@ -46,13 +42,6 @@ import org.apache.maven.internal.impl.model.ProfileActivationFilePathInterpolato
@Singleton @Singleton
public class FileProfileActivator implements ProfileActivator { public class FileProfileActivator implements ProfileActivator {
private final ProfileActivationFilePathInterpolator profileActivationFilePathInterpolator;
@Inject
public FileProfileActivator(ProfileActivationFilePathInterpolator profileActivationFilePathInterpolator) {
this.profileActivationFilePathInterpolator = profileActivationFilePathInterpolator;
}
@Override @Override
public boolean isActive(Profile profile, ProfileActivationContext context, ModelProblemCollector problems) { public boolean isActive(Profile profile, ProfileActivationContext context, ModelProblemCollector problems) {
Activation activation = profile.getActivation(); Activation activation = profile.getActivation();
@ -92,31 +81,20 @@ public class FileProfileActivator implements ProfileActivator {
return false; return false;
} }
boolean fileExists;
try { try {
path = profileActivationFilePathInterpolator.interpolate(path, context); fileExists = context.exists(path, false);
} catch (InterpolatorException e) { } catch (MavenException e) {
problems.add( problems.add(
BuilderProblem.Severity.ERROR, BuilderProblem.Severity.ERROR,
ModelProblem.Version.BASE, ModelProblem.Version.BASE,
"Failed to interpolate file location " + path + " for profile " + profile.getId() + ": " "Failed to check file existence " + path + " for profile " + profile.getId() + ": "
+ e.getMessage(), + e.getMessage(),
file.getLocation(missing ? "missing" : "exists"), file.getLocation(missing ? "missing" : "exists"),
e); e);
return false; return false;
} }
if (path == null) {
return false;
}
File f = new File(path);
if (!f.isAbsolute()) {
return false;
}
boolean fileExists = f.exists();
return missing != fileExists; return missing != fileExists;
} }

View File

@ -60,7 +60,7 @@ public class JdkVersionProfileActivator implements ProfileActivator {
return false; return false;
} }
String version = context.getSystemProperties().get("java.version"); String version = context.getSystemProperty("java.version");
if (version == null || version.isEmpty()) { if (version == null || version.isEmpty()) {
problems.add( problems.add(

View File

@ -56,15 +56,10 @@ public class OperatingSystemProfileActivator implements ProfileActivator {
boolean active = ensureAtLeastOneNonNull(os); boolean active = ensureAtLeastOneNonNull(os);
String actualOsName = context.getSystemProperties() String actualOsName = getSystemProperty(context, "os.name", Os.OS_NAME).toLowerCase(Locale.ENGLISH);
.getOrDefault("os.name", Os.OS_NAME) String actualOsArch = getSystemProperty(context, "os.arch", Os.OS_ARCH).toLowerCase(Locale.ENGLISH);
.toLowerCase(Locale.ENGLISH); String actualOsVersion =
String actualOsArch = context.getSystemProperties() getSystemProperty(context, "os.version", Os.OS_VERSION).toLowerCase(Locale.ENGLISH);
.getOrDefault("os.arch", Os.OS_ARCH)
.toLowerCase(Locale.ENGLISH);
String actualOsVersion = context.getSystemProperties()
.getOrDefault("os.version", Os.OS_VERSION)
.toLowerCase(Locale.ENGLISH);
if (active && os.getFamily() != null) { if (active && os.getFamily() != null) {
active = determineFamilyMatch(os.getFamily(), actualOsName); active = determineFamilyMatch(os.getFamily(), actualOsName);
@ -82,6 +77,11 @@ public class OperatingSystemProfileActivator implements ProfileActivator {
return active; return active;
} }
private String getSystemProperty(ProfileActivationContext context, String key, String defValue) {
String val = context.getSystemProperty(key);
return val != null ? val : defValue;
}
@Override @Override
public boolean presentInConfig(Profile profile, ProfileActivationContext context, ModelProblemCollector problems) { public boolean presentInConfig(Profile profile, ProfileActivationContext context, ModelProblemCollector problems) {
Activation activation = profile.getActivation(); Activation activation = profile.getActivation();

View File

@ -47,7 +47,7 @@ public class PackagingProfileActivator implements ProfileActivator {
} }
private static boolean isPackaging(ProfileActivationContext context, String p) { private static boolean isPackaging(ProfileActivationContext context, String p) {
String packaging = context.getModel().getPackaging(); String packaging = context.getModelPackaging();
return Objects.equals(p, packaging); return Objects.equals(p, packaging);
} }

View File

@ -69,12 +69,12 @@ public class PropertyProfileActivator implements ProfileActivator {
return false; return false;
} }
String sysValue = context.getUserProperties().get(name); String sysValue = context.getUserProperty(name);
if (sysValue == null && "packaging".equals(name)) { if (sysValue == null && "packaging".equals(name)) {
sysValue = context.getModel().getPackaging(); sysValue = context.getModelPackaging();
} }
if (sysValue == null) { if (sysValue == null) {
sysValue = context.getSystemProperties().get(name); sysValue = context.getSystemProperty(name);
} }
String propValue = property.getValue(); String propValue = property.getValue();

View File

@ -0,0 +1,66 @@
/*
* 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.
*/
package org.apache.maven.internal.impl.model;
import java.nio.file.Path;
import java.nio.file.Paths;
import org.apache.maven.api.Session;
import org.apache.maven.api.services.ModelBuilder;
import org.apache.maven.api.services.ModelBuilderRequest;
import org.apache.maven.api.services.ModelBuilderResult;
import org.apache.maven.api.services.ModelSource;
import org.apache.maven.internal.impl.standalone.ApiRunner;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
/**
*
*/
class DefaultModelBuilderTest {
Session session;
ModelBuilder builder;
@BeforeEach
void setup() {
session = ApiRunner.createSession();
builder = session.getService(ModelBuilder.class);
assertNotNull(builder);
}
@Test
public void testPropertiesAndProfiles() {
ModelBuilderRequest request = ModelBuilderRequest.builder()
.session(session)
.requestType(ModelBuilderRequest.RequestType.BUILD_PROJECT)
.source(ModelSource.fromPath(getPom("props-and-profiles")))
.build();
ModelBuilderResult result = builder.newSession().build(request);
assertNotNull(result);
assertEquals("21", result.getEffectiveModel().getProperties().get("maven.compiler.release"));
}
private Path getPom(String name) {
return Paths.get("src/test/resources/poms/factory/" + name + ".xml").toAbsolutePath();
}
}

View File

@ -25,6 +25,8 @@ import org.apache.maven.api.model.Model;
import org.apache.maven.api.model.Profile; import org.apache.maven.api.model.Profile;
import org.apache.maven.api.services.model.ProfileActivationContext; import org.apache.maven.api.services.model.ProfileActivationContext;
import org.apache.maven.api.services.model.ProfileActivator; import org.apache.maven.api.services.model.ProfileActivator;
import org.apache.maven.internal.impl.model.DefaultInterpolator;
import org.apache.maven.internal.impl.model.DefaultPathTranslator;
import org.apache.maven.internal.impl.model.DefaultProfileActivationContext; import org.apache.maven.internal.impl.model.DefaultProfileActivationContext;
import org.apache.maven.internal.impl.model.rootlocator.DefaultRootLocator; import org.apache.maven.internal.impl.model.rootlocator.DefaultRootLocator;
import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.AfterEach;
@ -55,7 +57,8 @@ public abstract class AbstractProfileActivatorTest<T extends ProfileActivator> {
} }
protected DefaultProfileActivationContext newContext() { protected DefaultProfileActivationContext newContext() {
return new DefaultProfileActivationContext(); return new DefaultProfileActivationContext(
new DefaultPathTranslator(), new FakeRootLocator(), new DefaultInterpolator());
} }
protected ProfileActivationContext newContext( protected ProfileActivationContext newContext(

View File

@ -28,7 +28,6 @@ import org.apache.maven.internal.impl.DefaultVersionParser;
import org.apache.maven.internal.impl.model.DefaultInterpolator; import org.apache.maven.internal.impl.model.DefaultInterpolator;
import org.apache.maven.internal.impl.model.DefaultPathTranslator; import org.apache.maven.internal.impl.model.DefaultPathTranslator;
import org.apache.maven.internal.impl.model.DefaultProfileActivationContext; import org.apache.maven.internal.impl.model.DefaultProfileActivationContext;
import org.apache.maven.internal.impl.model.ProfileActivationFilePathInterpolator;
import org.apache.maven.internal.impl.model.profile.ConditionParser.ExpressionFunction; import org.apache.maven.internal.impl.model.profile.ConditionParser.ExpressionFunction;
import org.apache.maven.internal.impl.model.rootlocator.DefaultRootLocator; import org.apache.maven.internal.impl.model.rootlocator.DefaultRootLocator;
import org.eclipse.aether.util.version.GenericVersionScheme; import org.eclipse.aether.util.version.GenericVersionScheme;
@ -51,19 +50,18 @@ class ConditionParserTest {
ProfileActivationContext context = createMockContext(); ProfileActivationContext context = createMockContext();
DefaultVersionParser versionParser = DefaultVersionParser versionParser =
new DefaultVersionParser(new DefaultModelVersionParser(new GenericVersionScheme())); new DefaultVersionParser(new DefaultModelVersionParser(new GenericVersionScheme()));
ProfileActivationFilePathInterpolator interpolator = new ProfileActivationFilePathInterpolator(
new DefaultPathTranslator(),
new AbstractProfileActivatorTest.FakeRootLocator(),
new DefaultInterpolator());
DefaultRootLocator rootLocator = new DefaultRootLocator(); DefaultRootLocator rootLocator = new DefaultRootLocator();
functions = ConditionProfileActivator.registerFunctions(context, versionParser, interpolator); functions = ConditionProfileActivator.registerFunctions(context, versionParser);
propertyResolver = s -> ConditionProfileActivator.property(context, rootLocator, s); propertyResolver = s -> ConditionProfileActivator.property(context, s);
parser = new ConditionParser(functions, propertyResolver); parser = new ConditionParser(functions, propertyResolver);
} }
private ProfileActivationContext createMockContext() { private ProfileActivationContext createMockContext() {
DefaultProfileActivationContext context = new DefaultProfileActivationContext(); DefaultProfileActivationContext context = new DefaultProfileActivationContext(
new DefaultPathTranslator(),
new AbstractProfileActivatorTest.FakeRootLocator(),
new DefaultInterpolator());
context.setSystemProperties(Map.of( context.setSystemProperties(Map.of(
"os.name", "windows", "os.name", "windows",
"os.arch", "amd64", "os.arch", "amd64",

View File

@ -33,8 +33,6 @@ import org.apache.maven.internal.impl.DefaultVersionParser;
import org.apache.maven.internal.impl.model.DefaultInterpolator; import org.apache.maven.internal.impl.model.DefaultInterpolator;
import org.apache.maven.internal.impl.model.DefaultPathTranslator; import org.apache.maven.internal.impl.model.DefaultPathTranslator;
import org.apache.maven.internal.impl.model.DefaultProfileActivationContext; import org.apache.maven.internal.impl.model.DefaultProfileActivationContext;
import org.apache.maven.internal.impl.model.ProfileActivationFilePathInterpolator;
import org.apache.maven.internal.impl.model.rootlocator.DefaultRootLocator;
import org.eclipse.aether.util.version.GenericVersionScheme; import org.eclipse.aether.util.version.GenericVersionScheme;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Disabled;
@ -55,10 +53,7 @@ public class ConditionProfileActivatorTest extends AbstractProfileActivatorTest<
@Override @Override
void setUp() throws Exception { void setUp() throws Exception {
activator = new ConditionProfileActivator( activator = new ConditionProfileActivator(
new DefaultVersionParser(new DefaultModelVersionParser(new GenericVersionScheme())), new DefaultVersionParser(new DefaultModelVersionParser(new GenericVersionScheme())));
new ProfileActivationFilePathInterpolator(
new DefaultPathTranslator(), new FakeRootLocator(), new DefaultInterpolator()),
new DefaultRootLocator());
Path file = tempDir.resolve("file.txt"); Path file = tempDir.resolve("file.txt");
Files.createFile(file); Files.createFile(file);
@ -484,7 +479,9 @@ public class ConditionProfileActivatorTest extends AbstractProfileActivatorTest<
} }
protected ProfileActivationContext newFileContext(Path path) { protected ProfileActivationContext newFileContext(Path path) {
DefaultProfileActivationContext context = new DefaultProfileActivationContext(); DefaultProfileActivationContext context = new DefaultProfileActivationContext(
new DefaultPathTranslator(), new FakeRootLocator(), new DefaultInterpolator());
context.setModel(Model.newBuilder().pomFile(path.resolve("pom.xml")).build()); context.setModel(Model.newBuilder().pomFile(path.resolve("pom.xml")).build());
return context; return context;
} }

View File

@ -27,10 +27,7 @@ import org.apache.maven.api.model.ActivationFile;
import org.apache.maven.api.model.Model; import org.apache.maven.api.model.Model;
import org.apache.maven.api.model.Profile; import org.apache.maven.api.model.Profile;
import org.apache.maven.api.services.model.RootLocator; import org.apache.maven.api.services.model.RootLocator;
import org.apache.maven.internal.impl.model.DefaultInterpolator;
import org.apache.maven.internal.impl.model.DefaultPathTranslator;
import org.apache.maven.internal.impl.model.DefaultProfileActivationContext; import org.apache.maven.internal.impl.model.DefaultProfileActivationContext;
import org.apache.maven.internal.impl.model.ProfileActivationFilePathInterpolator;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir; import org.junit.jupiter.api.io.TempDir;
@ -47,13 +44,12 @@ class FileProfileActivatorTest extends AbstractProfileActivatorTest<FileProfileA
@TempDir @TempDir
Path tempDir; Path tempDir;
private final DefaultProfileActivationContext context = new DefaultProfileActivationContext(); private final DefaultProfileActivationContext context = newContext();
@BeforeEach @BeforeEach
@Override @Override
void setUp() throws Exception { void setUp() throws Exception {
activator = new FileProfileActivator(new ProfileActivationFilePathInterpolator( activator = new FileProfileActivator();
new DefaultPathTranslator(), new FakeRootLocator(), new DefaultInterpolator()));
context.setModel(Model.newBuilder().pomFile(tempDir.resolve("pom.xml")).build()); context.setModel(Model.newBuilder().pomFile(tempDir.resolve("pom.xml")).build());

View File

@ -47,7 +47,6 @@ import org.apache.maven.internal.impl.model.DefaultPathTranslator;
import org.apache.maven.internal.impl.model.DefaultPluginManagementInjector; import org.apache.maven.internal.impl.model.DefaultPluginManagementInjector;
import org.apache.maven.internal.impl.model.DefaultProfileInjector; import org.apache.maven.internal.impl.model.DefaultProfileInjector;
import org.apache.maven.internal.impl.model.DefaultProfileSelector; import org.apache.maven.internal.impl.model.DefaultProfileSelector;
import org.apache.maven.internal.impl.model.ProfileActivationFilePathInterpolator;
import org.apache.maven.internal.impl.model.rootlocator.DefaultRootLocator; import org.apache.maven.internal.impl.model.rootlocator.DefaultRootLocator;
import org.apache.maven.internal.impl.resolver.DefaultArtifactDescriptorReader; import org.apache.maven.internal.impl.resolver.DefaultArtifactDescriptorReader;
import org.apache.maven.internal.impl.resolver.DefaultModelResolver; import org.apache.maven.internal.impl.resolver.DefaultModelResolver;
@ -1058,13 +1057,13 @@ public class RepositorySystemSupplier implements Supplier<RepositorySystem> {
new DefaultDependencyManagementInjector(), new DefaultDependencyManagementInjector(),
new DefaultDependencyManagementImporter(), new DefaultDependencyManagementImporter(),
new DefaultPluginConfigurationExpander(), new DefaultPluginConfigurationExpander(),
new ProfileActivationFilePathInterpolator(
new DefaultPathTranslator(), new DefaultRootLocator(), new DefaultInterpolator()),
new DefaultModelVersionParser(getVersionScheme()), new DefaultModelVersionParser(getVersionScheme()),
List.of(), List.of(),
new DefaultModelCacheFactory(), new DefaultModelCacheFactory(),
new DefaultModelResolver(), new DefaultModelResolver(),
new DefaultInterpolator()); new DefaultInterpolator(),
new DefaultPathTranslator(),
new DefaultRootLocator());
} }
private RepositorySystem repositorySystem; private RepositorySystem repositorySystem;

View File

@ -0,0 +1,34 @@
<?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>props-and-profiles-grand-parent</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>pom</packaging>
<profiles>
<profile>
<id>jdk9+</id>
<activation>
<activeByDefault>true</activeByDefault>
</activation>
<properties>
<maven.compiler.release>17</maven.compiler.release>
</properties>
</profile>
</profiles>
</project>

View File

@ -0,0 +1,28 @@
<?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>
<relativePath>props-and-profiles-grand-parent.xml</relativePath>
</parent>
<artifactId>props-and-profiles</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>pom</packaging>
<properties>
<maven.compiler.release>21</maven.compiler.release>
</properties>
</project>

View File

@ -0,0 +1,24 @@
<?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>
<relativePath>props-and-profiles-parent.xml</relativePath>
</parent>
<artifactId>props-and-profiles</artifactId>
<version>1.0-SNAPSHOT</version>
</project>