From 3d21d5f0ae745917f4c950765d2b32e1b7cb3370 Mon Sep 17 00:00:00 2001 From: Guillaume Nodet Date: Fri, 4 Oct 2024 08:35:42 +0200 Subject: [PATCH] [MNG-3309] Cascading profile activation --- .../invoker/mvn/RepositorySystemSupplier.java | 5 +- .../model/ProfileActivationContext.java | 16 ++ .../api/services/model/ProfileSelector.java | 7 +- .../maven/internal/impl/SettingsUtilsV4.java | 2 +- .../impl/model/DefaultModelBuilder.java | 253 +++--------------- .../DefaultProfileActivationContext.java | 20 +- .../impl/model/DefaultProfileSelector.java | 214 +++++++++++++-- ...ProfileActivationFilePathInterpolator.java | 2 +- .../profile/ConditionProfileActivator.java | 2 +- .../profile/PropertyProfileActivator.java | 3 + .../standalone/RepositorySystemSupplier.java | 7 +- 11 files changed, 279 insertions(+), 252 deletions(-) diff --git a/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvn/RepositorySystemSupplier.java b/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvn/RepositorySystemSupplier.java index b4188631ca85..27c2c1086f9c 100644 --- a/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvn/RepositorySystemSupplier.java +++ b/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvn/RepositorySystemSupplier.java @@ -47,7 +47,6 @@ import org.apache.maven.internal.impl.model.DefaultPluginManagementInjector; import org.apache.maven.internal.impl.model.DefaultProfileInjector; 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.resolver.DefaultArtifactDescriptorReader; import org.apache.maven.internal.impl.resolver.DefaultModelResolver; @@ -1049,14 +1048,12 @@ protected ModelBuilder createModelBuilder() { new DefaultModelUrlNormalizer(new DefaultUrlNormalizer()), new DefaultSuperPomProvider(modelProcessor), new DefaultInheritanceAssembler(), - new DefaultProfileSelector(), + new DefaultProfileSelector(null, null), new DefaultProfileInjector(), new DefaultPluginManagementInjector(), new DefaultDependencyManagementInjector(), new DefaultDependencyManagementImporter(), new DefaultPluginConfigurationExpander(), - new ProfileActivationFilePathInterpolator( - new DefaultPathTranslator(), new DefaultRootLocator(), new DefaultInterpolator()), new DefaultModelVersionParser(getVersionScheme()), List.of(), new DefaultModelCacheFactory(), diff --git a/impl/maven-impl/src/main/java/org/apache/maven/api/services/model/ProfileActivationContext.java b/impl/maven-impl/src/main/java/org/apache/maven/api/services/model/ProfileActivationContext.java index 1cc7f3c46fd2..be1818524b5e 100644 --- a/impl/maven-impl/src/main/java/org/apache/maven/api/services/model/ProfileActivationContext.java +++ b/impl/maven-impl/src/main/java/org/apache/maven/api/services/model/ProfileActivationContext.java @@ -18,11 +18,13 @@ */ package org.apache.maven.api.services.model; +import java.util.Collection; import java.util.List; import java.util.Map; import org.apache.maven.api.annotations.Nonnull; import org.apache.maven.api.model.Model; +import org.apache.maven.api.model.Profile; /** * Describes the environmental context used to determine the activation status of profiles. @@ -68,6 +70,20 @@ public interface ProfileActivationContext { @Nonnull Map getUserProperties(); + /** + * Gets current calculated project properties + * + * @return The project properties, never {@code null}. + */ + Map getProjectProperties(); + + /** + * Inject properties from newly activated profiles in order to trigger the cascading mechanism. + * + * @param activatedProfiles The collection of profiles that have been activated that may trigger the cascading effect. + */ + void addProfileProperties(Collection activatedProfiles); + /** * Gets the model which is being activated. * diff --git a/impl/maven-impl/src/main/java/org/apache/maven/api/services/model/ProfileSelector.java b/impl/maven-impl/src/main/java/org/apache/maven/api/services/model/ProfileSelector.java index b85cdd43eb5c..caea8bc98e47 100644 --- a/impl/maven-impl/src/main/java/org/apache/maven/api/services/model/ProfileSelector.java +++ b/impl/maven-impl/src/main/java/org/apache/maven/api/services/model/ProfileSelector.java @@ -38,8 +38,13 @@ public interface ProfileSelector { * @param context The environmental context used to determine the activation status of a profile, must not be * {@code null}. * @param problems The container used to collect problems that were encountered, must not be {@code null}. + * @param cascade Indicates whether profile activation should cascade, i.e. properties from an activated profile may + * trigger the activation of other profiles. * @return The profiles that have been activated, never {@code null}. */ List getActiveProfiles( - Collection profiles, ProfileActivationContext context, ModelProblemCollector problems); + Collection profiles, + ProfileActivationContext context, + ModelProblemCollector problems, + boolean cascade); } diff --git a/impl/maven-impl/src/main/java/org/apache/maven/internal/impl/SettingsUtilsV4.java b/impl/maven-impl/src/main/java/org/apache/maven/internal/impl/SettingsUtilsV4.java index 9c2a33482d72..2b3384e870b0 100644 --- a/impl/maven-impl/src/main/java/org/apache/maven/internal/impl/SettingsUtilsV4.java +++ b/impl/maven-impl/src/main/java/org/apache/maven/internal/impl/SettingsUtilsV4.java @@ -237,7 +237,7 @@ public static org.apache.maven.api.model.Profile convertFromSettingsProfile(Prof } org.apache.maven.api.model.Profile value = profile.build(); - value.setSource("settings.xml"); + value.setSource(org.apache.maven.api.model.Profile.SOURCE_SETTINGS); return value; } diff --git a/impl/maven-impl/src/main/java/org/apache/maven/internal/impl/model/DefaultModelBuilder.java b/impl/maven-impl/src/main/java/org/apache/maven/internal/impl/model/DefaultModelBuilder.java index 42e2eca8cd2f..bbc4362c3cc9 100644 --- a/impl/maven-impl/src/main/java/org/apache/maven/internal/impl/model/DefaultModelBuilder.java +++ b/impl/maven-impl/src/main/java/org/apache/maven/internal/impl/model/DefaultModelBuilder.java @@ -34,7 +34,6 @@ import java.util.List; import java.util.Map; import java.util.Objects; -import java.util.Properties; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CopyOnWriteArrayList; @@ -43,7 +42,6 @@ import java.util.concurrent.atomic.AtomicReference; import java.util.function.Function; import java.util.function.Supplier; -import java.util.function.UnaryOperator; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.stream.Collectors; @@ -58,8 +56,6 @@ 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.Activation; -import org.apache.maven.api.model.ActivationFile; import org.apache.maven.api.model.Dependency; import org.apache.maven.api.model.DependencyManagement; import org.apache.maven.api.model.Exclusion; @@ -72,7 +68,6 @@ import org.apache.maven.api.services.BuilderProblem; import org.apache.maven.api.services.BuilderProblem.Severity; import org.apache.maven.api.services.Interpolator; -import org.apache.maven.api.services.InterpolatorException; import org.apache.maven.api.services.MavenException; import org.apache.maven.api.services.ModelBuilder; import org.apache.maven.api.services.ModelBuilderException; @@ -110,7 +105,6 @@ import org.apache.maven.api.spi.ModelParserException; import org.apache.maven.api.spi.ModelTransformer; import org.apache.maven.internal.impl.util.PhasingExecutor; -import org.apache.maven.model.v4.MavenTransformer; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -146,7 +140,6 @@ public class DefaultModelBuilder implements ModelBuilder { private final DependencyManagementInjector dependencyManagementInjector; private final DependencyManagementImporter dependencyManagementImporter; private final PluginConfigurationExpander pluginConfigurationExpander; - private final ProfileActivationFilePathInterpolator profileActivationFilePathInterpolator; private final ModelVersionParser versionParser; private final List transformers; private final ModelCacheFactory modelCacheFactory; @@ -170,7 +163,6 @@ public DefaultModelBuilder( DependencyManagementInjector dependencyManagementInjector, DependencyManagementImporter dependencyManagementImporter, PluginConfigurationExpander pluginConfigurationExpander, - ProfileActivationFilePathInterpolator profileActivationFilePathInterpolator, ModelVersionParser versionParser, List transformers, ModelCacheFactory modelCacheFactory, @@ -190,7 +182,6 @@ public DefaultModelBuilder( this.dependencyManagementInjector = dependencyManagementInjector; this.dependencyManagementImporter = dependencyManagementImporter; this.pluginConfigurationExpander = pluginConfigurationExpander; - this.profileActivationFilePathInterpolator = profileActivationFilePathInterpolator; this.versionParser = versionParser; this.transformers = transformers; this.modelCacheFactory = modelCacheFactory; @@ -733,7 +724,8 @@ private void loadFilePom( // keep all loaded file models in memory, those will be needed // during the raw to build transformation putSource(getGroupId(model), model.getArtifactId(), src); - Model activated = activateFileModel(model); + setRootModel(model); + Model activated = activateModel(model, List.of(), false); for (String subproject : getSubprojects(activated)) { if (subproject == null || subproject.isEmpty()) { continue; @@ -795,12 +787,6 @@ private void loadFilePom( } } - static Set concat(Set a, T b) { - Set result = new HashSet<>(a); - result.add(b); - return Set.copyOf(result); - } - void buildEffectiveModel(Collection importIds) throws ModelBuilderException { Model resultModel = readEffectiveModel(); setSource(resultModel); @@ -1093,42 +1079,38 @@ Model resolveAndReadParentExternally(Model childModel) throws ModelBuilderExcept return parentModel; } - Model activateFileModel(Model inputModel) throws ModelBuilderException { - setRootModel(inputModel); - - // profile activation - DefaultProfileActivationContext profileActivationContext = getProfileActivationContext(request, inputModel); - - setSource("(external profiles)"); - List activeExternalProfiles = getActiveProfiles(request.getProfiles(), profileActivationContext); - - result.setActiveExternalProfiles(activeExternalProfiles); - - if (!activeExternalProfiles.isEmpty()) { - Properties profileProps = new Properties(); - for (Profile profile : activeExternalProfiles) { - profileProps.putAll(profile.getProperties()); + Model activateModel(Model model, List parentProfiles, boolean saveInfo) throws ModelBuilderException { + int nbProfiles = request.getProfiles().size() + + parentProfiles.size() + + model.getProfiles().size(); + if (nbProfiles == 0) { + if (saveInfo) { + result.setActivePomProfiles(List.of()); + result.setActiveExternalProfiles(List.of()); } - profileProps.putAll(profileActivationContext.getUserProperties()); - profileActivationContext.setUserProperties(profileProps); + return model; } - profileActivationContext.setModel(inputModel); - setSource(inputModel); - List activePomProfiles = getActiveProfiles(inputModel.getProfiles(), profileActivationContext); + DefaultProfileActivationContext profileActivationContext = getProfileActivationContext(request, model); - // model normalization - setSource(inputModel); - inputModel = modelNormalizer.mergeDuplicates(inputModel, request, this); + List profiles = new ArrayList<>(nbProfiles); + profiles.addAll(model.getProfiles()); + profiles.addAll(parentProfiles); + profiles.addAll(request.getProfiles()); - Map interpolatedActivations = getProfileActivations(inputModel); - inputModel = injectProfileActivations(inputModel, interpolatedActivations); + boolean cascade = !MODEL_VERSION_4_0_0.equals(model.getModelVersion()); + List activated = + profileSelector.getActiveProfiles(profiles, profileActivationContext, this, cascade); - // profile injection - inputModel = profileInjector.injectProfiles(inputModel, activePomProfiles, request, this); - inputModel = profileInjector.injectProfiles(inputModel, activeExternalProfiles, request, this); + if (saveInfo) { + Map> partitioned = activated.stream() + .collect(Collectors.partitioningBy(p -> Profile.SOURCE_POM.equals(p.getSource()))); + result.setActivePomProfiles(partitioned.get(true)); + result.setActiveExternalProfiles(partitioned.get(false)); + } - return inputModel; + // profile injection + return profileInjector.injectProfiles(model, activated, request, this); } @SuppressWarnings("checkstyle:methodlength") @@ -1140,71 +1122,15 @@ private Model readEffectiveModel() throws ModelBuilderException { setRootModel(inputModel); - Model activatedFileModel = activateFileModel(inputModel); + Model parentModel = readParent(inputModel); - // profile activation - DefaultProfileActivationContext profileActivationContext = - getProfileActivationContext(request, activatedFileModel); - - List activeExternalProfiles = result.getActiveExternalProfiles(); - - if (!activeExternalProfiles.isEmpty()) { - Properties profileProps = new Properties(); - for (Profile profile : activeExternalProfiles) { - profileProps.putAll(profile.getProperties()); - } - profileProps.putAll(profileActivationContext.getUserProperties()); - profileActivationContext.setUserProperties(profileProps); - } - - Model parentModel = readParent(activatedFileModel); - // Now that we have read the parent, we can set the relative - // path correctly if it was not set in the input model - if (inputModel.getParent() != null && inputModel.getParent().getRelativePath() == null) { - String relPath; - if (parentModel.getPomFile() != null - && (request.getRequestType() == ModelBuilderRequest.RequestType.BUILD_POM - || request.getRequestType() == ModelBuilderRequest.RequestType.CONSUMER_POM)) { - relPath = inputModel - .getPomFile() - .getParent() - .relativize(parentModel.getPomFile().getParent()) - .toString(); - } else { - relPath = ".."; - } - inputModel = inputModel.withParent(inputModel.getParent().withRelativePath(relPath)); - } - - List parentInterpolatedProfiles = - interpolateActivations(parentModel.getProfiles(), profileActivationContext, this); - // profile injection - List parentActivePomProfiles = - getActiveProfiles(parentInterpolatedProfiles, profileActivationContext); - Model injectedParentModel = profileInjector - .injectProfiles(parentModel, parentActivePomProfiles, request, this) - .withProfiles(List.of()); - - Model model = inheritanceAssembler.assembleModelInheritance(inputModel, injectedParentModel, request, this); + Model model = inheritanceAssembler.assembleModelInheritance(inputModel, parentModel, request, this); // model normalization model = modelNormalizer.mergeDuplicates(model, request, this); // profile activation - profileActivationContext.setModel(model); - - List interpolatedProfiles = - interpolateActivations(model.getProfiles(), profileActivationContext, this); - - // profile injection - List activePomProfiles = getActiveProfiles(interpolatedProfiles, profileActivationContext); - model = profileInjector.injectProfiles(model, activePomProfiles, request, this); - model = profileInjector.injectProfiles(model, activeExternalProfiles, request, this); - - List allProfiles = new ArrayList<>(parentActivePomProfiles.size() + activePomProfiles.size()); - allProfiles.addAll(parentActivePomProfiles); - allProfiles.addAll(activePomProfiles); - result.setActivePomProfiles(allProfiles); + model = activateModel(model, parentModel.getProfiles(), true); // model interpolation Model resultModel = model; @@ -1229,15 +1155,6 @@ private Model readEffectiveModel() throws ModelBuilderException { return resultModel; } - private List getActiveProfiles( - Collection interpolatedProfiles, DefaultProfileActivationContext profileActivationContext) { - if (request.getRequestType() != ModelBuilderRequest.RequestType.CONSUMER_POM) { - return profileSelector.getActiveProfiles(interpolatedProfiles, profileActivationContext, this); - } else { - return List.of(); - } - } - Model readFileModel() throws ModelBuilderException { Model model = cache(request.getSource(), FILE, this::doReadFileModel); // set the file model in the result outside the cache @@ -1736,80 +1653,6 @@ private T cache(String groupId, String artifactId, String version, String ta private T cache(Source source, String tag, Supplier supplier) { return cache.computeIfAbsent(source, tag, supplier); } - - private List interpolateActivations( - List profiles, DefaultProfileActivationContext context, ModelProblemCollector problems) { - if (profiles.stream() - .map(org.apache.maven.api.model.Profile::getActivation) - .noneMatch(Objects::nonNull)) { - return profiles; - } - Interpolator interpolator = request.getSession().getService(Interpolator.class); - - class ProfileInterpolator extends MavenTransformer implements UnaryOperator { - ProfileInterpolator() { - super(s -> { - try { - Map map1 = context.getUserProperties(); - Map map2 = context.getSystemProperties(); - return interpolator.interpolate(s, Interpolator.chain(map1::get, map2::get)); - } catch (InterpolatorException e) { - problems.add(Severity.ERROR, Version.BASE, e.getMessage(), e); - } - return s; - }); - } - - @Override - public Profile apply(Profile p) { - return Profile.newBuilder(p) - .activation(transformActivation(p.getActivation())) - .build(); - } - - @Override - protected Activation.Builder transformActivation_Condition( - Supplier creator, Activation.Builder builder, Activation target) { - // do not interpolate the condition activation - return builder; - } - - @Override - protected ActivationFile.Builder transformActivationFile_Missing( - Supplier creator, - ActivationFile.Builder builder, - ActivationFile target) { - String path = target.getMissing(); - String xformed = transformPath(path, target, "missing"); - return xformed != path ? (builder != null ? builder : creator.get()).missing(xformed) : builder; - } - - @Override - protected ActivationFile.Builder transformActivationFile_Exists( - Supplier creator, - ActivationFile.Builder builder, - ActivationFile target) { - final String path = target.getExists(); - final String xformed = transformPath(path, target, "exists"); - return xformed != path ? (builder != null ? builder : creator.get()).exists(xformed) : builder; - } - - private String transformPath(String path, ActivationFile target, String locationKey) { - try { - return profileActivationFilePathInterpolator.interpolate(path, context); - } catch (InterpolatorException e) { - problems.add( - Severity.ERROR, - Version.BASE, - "Failed to interpolate file location " + path + ": " + e.getMessage(), - target.getLocation(locationKey), - e); - } - return path; - } - } - return profiles.stream().map(new ProfileInterpolator()).toList(); - } } @SuppressWarnings("deprecation") @@ -1848,37 +1691,14 @@ static String getVersion(Model model) { private DefaultProfileActivationContext getProfileActivationContext(ModelBuilderRequest request, Model model) { DefaultProfileActivationContext context = new DefaultProfileActivationContext(); - context.setActiveProfileIds(request.getActiveProfileIds()); context.setInactiveProfileIds(request.getInactiveProfileIds()); context.setSystemProperties(request.getSystemProperties()); context.setUserProperties(request.getUserProperties()); context.setModel(model); - return context; } - private Map getProfileActivations(Model model) { - return model.getProfiles().stream() - .filter(p -> p.getActivation() != null) - .collect(Collectors.toMap(Profile::getId, Profile::getActivation)); - } - - private Model injectProfileActivations(Model model, Map activations) { - List profiles = new ArrayList<>(); - boolean modified = false; - for (Profile profile : model.getProfiles()) { - Activation activation = profile.getActivation(); - if (activation != null) { - // restore activation - profile = profile.withActivation(activations.get(profile.getId())); - modified = true; - } - profiles.add(profile); - } - return modified ? model.withProfiles(profiles) : model; - } - private Model interpolateModel(Model model, ModelBuilderRequest request, ModelProblemCollector problems) { Model interpolatedModel = modelInterpolator.interpolateModel(model, model.getProjectDirectory(), request, problems); @@ -1919,8 +1739,7 @@ private Model getSuperModel(String modelVersion) { private static org.apache.maven.api.model.Dependency addExclusions( org.apache.maven.api.model.Dependency candidate, List exclusions) { - return candidate.withExclusions(Stream.concat(candidate.getExclusions().stream(), exclusions.stream()) - .toList()); + return candidate.withExclusions(concat(candidate.getExclusions(), exclusions)); } private boolean match(Exclusion exclusion, Dependency candidate) { @@ -1939,5 +1758,15 @@ private boolean containsCoordinates(String message, String groupId, String artif && (version == null || message.contains(version)); } + private static List concat(Collection l1, Collection l2) { + return Stream.concat(l1.stream(), l2.stream()).toList(); + } + + private static Set concat(Set a, T b) { + Set result = new HashSet<>(a); + result.add(b); + return Set.copyOf(result); + } + record GAKey(String groupId, String artifactId) {} } diff --git a/impl/maven-impl/src/main/java/org/apache/maven/internal/impl/model/DefaultProfileActivationContext.java b/impl/maven-impl/src/main/java/org/apache/maven/internal/impl/model/DefaultProfileActivationContext.java index b6c350c1d8b2..4de28e031caa 100644 --- a/impl/maven-impl/src/main/java/org/apache/maven/internal/impl/model/DefaultProfileActivationContext.java +++ b/impl/maven-impl/src/main/java/org/apache/maven/internal/impl/model/DefaultProfileActivationContext.java @@ -18,13 +18,16 @@ */ package org.apache.maven.internal.impl.model; +import java.util.Collection; import java.util.Collections; +import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Properties; import java.util.stream.Collectors; import org.apache.maven.api.model.Model; +import org.apache.maven.api.model.Profile; import org.apache.maven.api.services.model.ProfileActivationContext; /** @@ -41,6 +44,8 @@ public class DefaultProfileActivationContext implements ProfileActivationContext private Map userProperties = Collections.emptyMap(); + private Map projectProperties = Collections.emptyMap(); + private Model model; @Override @@ -142,10 +147,23 @@ public Model getModel() { public DefaultProfileActivationContext setModel(Model model) { this.model = model; - + this.projectProperties = unmodifiable(model.getProperties()); return this; } + @Override + public Map getProjectProperties() { + return projectProperties; + } + + public void addProfileProperties(Collection profiles) { + Map props = new HashMap<>(this.projectProperties); + for (var profile : profiles) { + props.putAll(profile.getProperties()); + } + this.projectProperties = unmodifiable(props); + } + private static List unmodifiable(List list) { return list != null ? Collections.unmodifiableList(list) : Collections.emptyList(); } diff --git a/impl/maven-impl/src/main/java/org/apache/maven/internal/impl/model/DefaultProfileSelector.java b/impl/maven-impl/src/main/java/org/apache/maven/internal/impl/model/DefaultProfileSelector.java index 36dc4a9fcd06..c5dbe498aa4a 100644 --- a/impl/maven-impl/src/main/java/org/apache/maven/internal/impl/model/DefaultProfileSelector.java +++ b/impl/maven-impl/src/main/java/org/apache/maven/internal/impl/model/DefaultProfileSelector.java @@ -22,77 +22,166 @@ import java.util.Collection; import java.util.HashSet; import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.function.Supplier; +import java.util.function.UnaryOperator; 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.Activation; +import org.apache.maven.api.model.ActivationFile; import org.apache.maven.api.model.Profile; import org.apache.maven.api.services.BuilderProblem.Severity; +import org.apache.maven.api.services.Interpolator; +import org.apache.maven.api.services.InterpolatorException; import org.apache.maven.api.services.ModelProblem.Version; import org.apache.maven.api.services.ModelProblemCollector; import org.apache.maven.api.services.model.ProfileActivationContext; import org.apache.maven.api.services.model.ProfileActivator; import org.apache.maven.api.services.model.ProfileSelector; +import org.apache.maven.model.v4.MavenTransformer; /** * Calculates the active profiles among a given collection of profiles. - * */ @Named @Singleton public class DefaultProfileSelector implements ProfileSelector { + private final Interpolator interpolator; + private final ProfileActivationFilePathInterpolator profileActivationFilePathInterpolator; private final List activators; - public DefaultProfileSelector() { - this.activators = new ArrayList<>(); + public DefaultProfileSelector( + Interpolator interpolator, ProfileActivationFilePathInterpolator profileActivationFilePathInterpolator) { + this(interpolator, profileActivationFilePathInterpolator, new ArrayList<>()); } @Inject - public DefaultProfileSelector(List activators) { + public DefaultProfileSelector( + Interpolator interpolator, + ProfileActivationFilePathInterpolator profileActivationFilePathInterpolator, + List activators) { + this.interpolator = interpolator; + this.profileActivationFilePathInterpolator = profileActivationFilePathInterpolator; this.activators = new ArrayList<>(activators); } - public DefaultProfileSelector addProfileActivator(ProfileActivator profileActivator) { - if (profileActivator != null) { - activators.add(profileActivator); + @Override + public List getActiveProfiles( + Collection orgProfiles, + ProfileActivationContext context, + ModelProblemCollector problems, + boolean cascade) { + + if (cascade) { + return getActiveProfilesCascading(orgProfiles, context, problems); + } else { + return getActiveProfilesNonCascading(orgProfiles, context, problems); } - return this; } - @Override - public List getActiveProfiles( + public List getActiveProfilesNonCascading( Collection profiles, ProfileActivationContext context, ModelProblemCollector problems) { + Collection activatedIds = new HashSet<>(context.getActiveProfileIds()); Collection deactivatedIds = new HashSet<>(context.getInactiveProfileIds()); - List activeProfiles = new ArrayList<>(profiles.size()); + List activeSettingsProfiles = new ArrayList<>(); + List activePomProfiles = new ArrayList<>(); List activePomProfilesByDefault = new ArrayList<>(); - boolean activatedPomProfileNotByDefault = false; - - for (Profile profile : profiles) { - if (!deactivatedIds.contains(profile.getId())) { - if (activatedIds.contains(profile.getId()) || isActive(profile, context, problems)) { - activeProfiles.add(profile); - if (Profile.SOURCE_POM.equals(profile.getSource())) { - activatedPomProfileNotByDefault = true; - } - } else if (isActiveByDefault(profile)) { - if (Profile.SOURCE_POM.equals(profile.getSource())) { - activePomProfilesByDefault.add(profile); - } else { - activeProfiles.add(profile); + + ProfileActivationInterpolator activationInterpolator = new ProfileActivationInterpolator(context, problems); + for (String source : List.of(Profile.SOURCE_SETTINGS, Profile.SOURCE_POM)) { + // Iterate over the profiles and check if a given profile is activated + List activatedProfiles = new ArrayList<>(); + for (Profile profile : profiles) { + if (Objects.equals(source, profile.getSource())) { + Profile iprofile = activationInterpolator.apply(profile); + if (!deactivatedIds.contains(iprofile.getId())) { + boolean activated = activatedIds.contains(iprofile.getId()); + boolean active = isActive(iprofile, context, problems); + boolean activeByDefault = isActiveByDefault(iprofile); + if (activated || active || activeByDefault) { + if (Profile.SOURCE_POM.equals(profile.getSource())) { + if (activated || active) { + activePomProfiles.add(profile); + } else { + activePomProfilesByDefault.add(profile); + } + } else { + activeSettingsProfiles.add(profile); + } + activatedProfiles.add(profile); + } } } } + context.addProfileProperties(activatedProfiles); + } + + List allActivated = new ArrayList<>(); + if (activePomProfiles.isEmpty()) { + allActivated.addAll(activePomProfilesByDefault); + } else { + allActivated.addAll(activePomProfiles); } + allActivated.addAll(activeSettingsProfiles); + + return allActivated; + } + + public List getActiveProfilesCascading( + Collection orgProfiles, ProfileActivationContext context, ModelProblemCollector problems) { + + Collection activatedIds = new HashSet<>(context.getActiveProfileIds()); + Collection deactivatedIds = new HashSet<>(context.getInactiveProfileIds()); + + List activeSettingsProfiles = new ArrayList<>(); + List activePomProfiles = new ArrayList<>(); + List activePomProfilesByDefault = new ArrayList<>(); + + List profiles = new ArrayList<>(orgProfiles); + ProfileActivationInterpolator activationInterpolator = new ProfileActivationInterpolator(context, problems); + List activatedProfiles; + do { + // Iterate over the profiles and check if a given profile is activated + activatedProfiles = new ArrayList<>(); + for (Profile profile : List.copyOf(profiles)) { + Profile iprofile = activationInterpolator.apply(profile); + if (!deactivatedIds.contains(iprofile.getId())) { + boolean activated = activatedIds.contains(iprofile.getId()); + boolean active = isActive(iprofile, context, problems); + boolean activeByDefault = isActiveByDefault(iprofile); + if (activated || active || activeByDefault) { + if (Profile.SOURCE_POM.equals(profile.getSource())) { + if (activated || active) { + activePomProfiles.add(profile); + } else { + activePomProfilesByDefault.add(profile); + } + } else { + activeSettingsProfiles.add(profile); + } + profiles.remove(profile); + activatedProfiles.add(profile); + } + } + } + context.addProfileProperties(activatedProfiles); + } while (!activatedProfiles.isEmpty()); - if (!activatedPomProfileNotByDefault) { - activeProfiles.addAll(activePomProfilesByDefault); + List allActivated = new ArrayList<>(); + if (activePomProfiles.isEmpty()) { + allActivated.addAll(activePomProfilesByDefault); + } else { + allActivated.addAll(activePomProfiles); } + allActivated.addAll(activeSettingsProfiles); - return activeProfiles; + return allActivated; } private boolean isActive(Profile profile, ProfileActivationContext context, ModelProblemCollector problems) { @@ -122,4 +211,73 @@ private boolean isActiveByDefault(Profile profile) { Activation activation = profile.getActivation(); return activation != null && activation.isActiveByDefault(); } + + private class ProfileActivationInterpolator extends MavenTransformer implements UnaryOperator { + private final ProfileActivationContext context; + private final ModelProblemCollector problems; + + ProfileActivationInterpolator(ProfileActivationContext context, ModelProblemCollector problems) { + super(s -> { + try { + Map map1 = context.getUserProperties(); + Map map2 = context.getProjectProperties(); + Map map3 = context.getSystemProperties(); + return interpolator.interpolate(s, Interpolator.chain(List.of(map1::get, map2::get, map3::get))); + } catch (InterpolatorException e) { + problems.add(Severity.ERROR, Version.BASE, e.getMessage(), e); + } + return s; + }); + this.context = context; + this.problems = problems; + } + + @Override + public Profile apply(Profile p) { + return Profile.newBuilder(p) + .activation(transformActivation(p.getActivation())) + .build(); + } + + @Override + protected Activation.Builder transformActivation_Condition( + Supplier creator, Activation.Builder builder, Activation target) { + // do not interpolate the condition activation + return builder; + } + + @Override + protected ActivationFile.Builder transformActivationFile_Missing( + Supplier creator, + ActivationFile.Builder builder, + ActivationFile target) { + String path = target.getMissing(); + String xformed = transformPath(path, target, "missing"); + return xformed != path ? (builder != null ? builder : creator.get()).missing(xformed) : builder; + } + + @Override + protected ActivationFile.Builder transformActivationFile_Exists( + Supplier creator, + ActivationFile.Builder builder, + ActivationFile target) { + final String path = target.getExists(); + final String xformed = transformPath(path, target, "exists"); + return xformed != path ? (builder != null ? builder : creator.get()).exists(xformed) : builder; + } + + private String transformPath(String path, ActivationFile target, String locationKey) { + try { + return profileActivationFilePathInterpolator.interpolate(path, context); + } catch (InterpolatorException e) { + problems.add( + Severity.ERROR, + Version.BASE, + "Failed to interpolate file location " + path + ": " + e.getMessage(), + target.getLocation(locationKey), + e); + } + return path; + } + } } diff --git a/impl/maven-impl/src/main/java/org/apache/maven/internal/impl/model/ProfileActivationFilePathInterpolator.java b/impl/maven-impl/src/main/java/org/apache/maven/internal/impl/model/ProfileActivationFilePathInterpolator.java index d512b727ecfc..bdea70347885 100644 --- a/impl/maven-impl/src/main/java/org/apache/maven/internal/impl/model/ProfileActivationFilePathInterpolator.java +++ b/impl/maven-impl/src/main/java/org/apache/maven/internal/impl/model/ProfileActivationFilePathInterpolator.java @@ -71,7 +71,7 @@ public String interpolate(String path, ProfileActivationContext context) throws Path root = rootLocator.findMandatoryRoot(basedir); return root.toFile().getAbsolutePath(); } - String r = context.getModel().getProperties().get(s); + String r = context.getProjectProperties().get(s); if (r == null) { r = context.getUserProperties().get(s); } diff --git a/impl/maven-impl/src/main/java/org/apache/maven/internal/impl/model/profile/ConditionProfileActivator.java b/impl/maven-impl/src/main/java/org/apache/maven/internal/impl/model/profile/ConditionProfileActivator.java index 3c0d4b040fed..e998b5dd35f8 100644 --- a/impl/maven-impl/src/main/java/org/apache/maven/internal/impl/model/profile/ConditionProfileActivator.java +++ b/impl/maven-impl/src/main/java/org/apache/maven/internal/impl/model/profile/ConditionProfileActivator.java @@ -202,7 +202,7 @@ static String doGetProperty(ProfileActivationContext context, RootLocator rootLo // Check project properties // 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 - v = context.getModel().getProperties().get(name); + v = context.getProjectProperties().get(name); } if (v == null) { // Check system properties diff --git a/impl/maven-impl/src/main/java/org/apache/maven/internal/impl/model/profile/PropertyProfileActivator.java b/impl/maven-impl/src/main/java/org/apache/maven/internal/impl/model/profile/PropertyProfileActivator.java index d36ecda8f914..4ec357d199c0 100644 --- a/impl/maven-impl/src/main/java/org/apache/maven/internal/impl/model/profile/PropertyProfileActivator.java +++ b/impl/maven-impl/src/main/java/org/apache/maven/internal/impl/model/profile/PropertyProfileActivator.java @@ -73,6 +73,9 @@ public boolean isActive(Profile profile, ProfileActivationContext context, Model if (sysValue == null && "packaging".equals(name)) { sysValue = context.getModel().getPackaging(); } + if (sysValue == null) { + sysValue = context.getProjectProperties().get(name); + } if (sysValue == null) { sysValue = context.getSystemProperties().get(name); } diff --git a/impl/maven-impl/src/test/java/org/apache/maven/internal/impl/standalone/RepositorySystemSupplier.java b/impl/maven-impl/src/test/java/org/apache/maven/internal/impl/standalone/RepositorySystemSupplier.java index 597cf9a76d45..026fc440a378 100644 --- a/impl/maven-impl/src/test/java/org/apache/maven/internal/impl/standalone/RepositorySystemSupplier.java +++ b/impl/maven-impl/src/test/java/org/apache/maven/internal/impl/standalone/RepositorySystemSupplier.java @@ -1052,14 +1052,15 @@ protected ModelBuilder createModelBuilder() { new DefaultModelUrlNormalizer(new DefaultUrlNormalizer()), new DefaultSuperPomProvider(modelProcessor), new DefaultInheritanceAssembler(), - new DefaultProfileSelector(), + new DefaultProfileSelector( + new DefaultInterpolator(), + new ProfileActivationFilePathInterpolator( + new DefaultPathTranslator(), new DefaultRootLocator(), new DefaultInterpolator())), new DefaultProfileInjector(), new DefaultPluginManagementInjector(), new DefaultDependencyManagementInjector(), new DefaultDependencyManagementImporter(), new DefaultPluginConfigurationExpander(), - new ProfileActivationFilePathInterpolator( - new DefaultPathTranslator(), new DefaultRootLocator(), new DefaultInterpolator()), new DefaultModelVersionParser(getVersionScheme()), List.of(), new DefaultModelCacheFactory(),