[MNG-8081] interpolate available properties during default profile selection (Maven 4.x) (#1446)

Co-authored-by: Guillaume Nodet <gnodet@gmail.com>
This commit is contained in:
Matt Benson 2024-05-02 08:13:07 -05:00 committed by GitHub
parent 6814ba386d
commit 399f8b4ffc
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 717 additions and 137 deletions

View File

@ -35,7 +35,9 @@ import java.util.concurrent.Callable;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.BiConsumer;
import java.util.function.Supplier;
import java.util.function.UnaryOperator;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.apache.maven.api.VersionRange;
import org.apache.maven.api.annotations.Nullable;
@ -50,6 +52,7 @@ import org.apache.maven.api.model.Dependency;
import org.apache.maven.api.model.DependencyManagement;
import org.apache.maven.api.model.Exclusion;
import org.apache.maven.api.model.InputLocation;
import org.apache.maven.api.model.InputLocationTracker;
import org.apache.maven.api.model.InputSource;
import org.apache.maven.api.model.Model;
import org.apache.maven.api.model.Parent;
@ -80,8 +83,11 @@ import org.apache.maven.api.services.xml.XmlReaderRequest;
import org.apache.maven.internal.impl.InternalSession;
import org.apache.maven.internal.impl.resolver.DefaultModelCache;
import org.apache.maven.internal.impl.resolver.DefaultModelResolver;
import org.apache.maven.model.v4.MavenTransformer;
import org.codehaus.plexus.interpolation.InterpolationException;
import org.codehaus.plexus.interpolation.Interpolator;
import org.codehaus.plexus.interpolation.MapBasedValueSource;
import org.codehaus.plexus.interpolation.RegexBasedInterpolator;
import org.codehaus.plexus.interpolation.StringSearchInterpolator;
import org.eclipse.aether.impl.RemoteRepositoryManager;
@ -377,54 +383,75 @@ public class DefaultModelBuilder implements ModelBuilder {
private List<Profile> interpolateActivations(
List<Profile> profiles, DefaultProfileActivationContext context, DefaultModelProblemCollector problems) {
List<Profile> newProfiles = null;
for (int index = 0; index < profiles.size(); index++) {
Profile profile = profiles.get(index);
Activation activation = profile.getActivation();
if (activation != null) {
ActivationFile file = activation.getFile();
if (file != null) {
String oldExists = file.getExists();
if (isNotEmpty(oldExists)) {
if (profiles.stream()
.map(org.apache.maven.api.model.Profile::getActivation)
.noneMatch(Objects::nonNull)) {
return profiles;
}
final Interpolator xform = new RegexBasedInterpolator();
xform.setCacheAnswers(true);
Stream.of(context.getUserProperties(), context.getSystemProperties())
.map(MapBasedValueSource::new)
.forEach(xform::addValueSource);
class ProfileInterpolator extends MavenTransformer implements UnaryOperator<Profile> {
ProfileInterpolator() {
super(s -> {
if (isNotEmpty(s)) {
try {
String newExists = interpolate(oldExists, context);
if (!Objects.equals(oldExists, newExists)) {
if (newProfiles == null) {
newProfiles = new ArrayList<>(profiles);
}
newProfiles.set(
index, profile.withActivation(activation.withFile(file.withExists(newExists))));
}
return xform.interpolate(s);
} catch (InterpolationException e) {
addInterpolationProblem(problems, file, oldExists, e, "exists");
}
} else {
String oldMissing = file.getMissing();
if (isNotEmpty(oldMissing)) {
try {
String newMissing = interpolate(oldMissing, context);
if (!Objects.equals(oldMissing, newMissing)) {
if (newProfiles == null) {
newProfiles = new ArrayList<>(profiles);
}
newProfiles.set(
index,
profile.withActivation(activation.withFile(file.withMissing(newMissing))));
}
} catch (InterpolationException e) {
addInterpolationProblem(problems, file, oldMissing, e, "missing");
}
problems.add(Severity.ERROR, ModelProblem.Version.BASE, e.getMessage(), e);
}
}
return s;
});
}
@Override
public Profile apply(Profile p) {
return Profile.newBuilder(p)
.activation(transformActivation(p.getActivation()))
.build();
}
@Override
protected ActivationFile.Builder transformActivationFile_Missing(
Supplier<? extends ActivationFile.Builder> 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<? extends ActivationFile.Builder> 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) {
if (isNotEmpty(path)) {
try {
return profileActivationFilePathInterpolator.interpolate(path, context);
} catch (InterpolationException e) {
addInterpolationProblem(problems, target, path, e, locationKey);
}
}
return path;
}
}
return newProfiles != null ? newProfiles : profiles;
return profiles.stream().map(new ProfileInterpolator()).toList();
}
private static void addInterpolationProblem(
DefaultModelProblemCollector problems,
ActivationFile file,
InputLocationTracker target,
String path,
InterpolationException e,
String locationKey) {
@ -432,14 +459,10 @@ public class DefaultModelBuilder implements ModelBuilder {
Severity.ERROR,
ModelProblem.Version.BASE,
"Failed to interpolate file location " + path + ": " + e.getMessage(),
file.getLocation(locationKey),
target.getLocation(locationKey),
e);
}
private String interpolate(String path, ProfileActivationContext context) throws InterpolationException {
return isNotEmpty(path) ? profileActivationFilePathInterpolator.interpolate(path, context) : path;
}
private static boolean isNotEmpty(String string) {
return string != null && !string.isEmpty();
}

View File

@ -21,20 +21,31 @@ package org.apache.maven.internal.impl.model;
import java.io.File;
import java.util.Arrays;
import java.util.Collections;
import java.util.Deque;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
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;
import java.util.stream.StreamSupport;
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.ActivationOS;
import org.apache.maven.api.model.ActivationProperty;
import org.apache.maven.api.model.Build;
import org.apache.maven.api.model.BuildBase;
import org.apache.maven.api.model.Dependency;
@ -59,6 +70,7 @@ import org.apache.maven.api.services.ModelProblem.Version;
import org.apache.maven.api.services.ModelProblemCollector;
import org.apache.maven.api.services.model.*;
import org.apache.maven.model.v4.MavenModelVersion;
import org.apache.maven.model.v4.MavenTransformer;
/**
*/
@ -80,6 +92,196 @@ public class DefaultModelValidator implements ModelValidator {
private static final String EMPTY = "";
private record ActivationFrame(String location, Optional<? extends InputLocationTracker> parent) {}
private static class ActivationWalker extends MavenTransformer {
private final Deque<ActivationFrame> stk;
ActivationWalker(Deque<ActivationFrame> stk, UnaryOperator<String> transformer) {
super(transformer);
this.stk = stk;
}
private ActivationFrame nextFrame(String property) {
return new ActivationFrame(property, Optional.empty());
}
private <P> ActivationFrame nextFrame(String property, Function<P, InputLocationTracker> child) {
@SuppressWarnings("unchecked")
final Optional<P> parent = (Optional<P>) stk.peek().parent;
return new ActivationFrame(property, parent.map(child));
}
@Override
public Activation transformActivation(Activation target) {
stk.push(new ActivationFrame("activation", Optional.of(target)));
try {
return super.transformActivation(target);
} finally {
stk.pop();
}
}
@Override
protected Activation.Builder transformActivation_ActiveByDefault(
Supplier<? extends Activation.Builder> creator, Activation.Builder builder, Activation target) {
return builder;
}
@Override
protected Activation.Builder transformActivation_File(
Supplier<? extends Activation.Builder> creator, Activation.Builder builder, Activation target) {
stk.push(nextFrame("file", Activation::getFile));
Optional.ofNullable(target.getFile());
try {
return super.transformActivation_File(creator, builder, target);
} finally {
stk.pop();
}
}
@Override
protected ActivationFile.Builder transformActivationFile_Exists(
Supplier<? extends ActivationFile.Builder> creator,
ActivationFile.Builder builder,
ActivationFile target) {
stk.push(nextFrame("exists"));
try {
return super.transformActivationFile_Exists(creator, builder, target);
} finally {
stk.pop();
}
}
@Override
protected ActivationFile.Builder transformActivationFile_Missing(
Supplier<? extends ActivationFile.Builder> creator,
ActivationFile.Builder builder,
ActivationFile target) {
stk.push(nextFrame("missing"));
try {
return super.transformActivationFile_Missing(creator, builder, target);
} finally {
stk.pop();
}
}
@Override
protected Activation.Builder transformActivation_Jdk(
Supplier<? extends Activation.Builder> creator, Activation.Builder builder, Activation target) {
stk.push(nextFrame("jdk"));
try {
return super.transformActivation_Jdk(creator, builder, target);
} finally {
stk.pop();
}
}
@Override
protected Activation.Builder transformActivation_Os(
Supplier<? extends Activation.Builder> creator, Activation.Builder builder, Activation target) {
stk.push(nextFrame("os", Activation::getOs));
try {
return super.transformActivation_Os(creator, builder, target);
} finally {
stk.pop();
}
}
@Override
protected ActivationOS.Builder transformActivationOS_Arch(
Supplier<? extends ActivationOS.Builder> creator, ActivationOS.Builder builder, ActivationOS target) {
stk.push(nextFrame("arch"));
try {
return super.transformActivationOS_Arch(creator, builder, target);
} finally {
stk.pop();
}
}
@Override
protected ActivationOS.Builder transformActivationOS_Family(
Supplier<? extends ActivationOS.Builder> creator, ActivationOS.Builder builder, ActivationOS target) {
stk.push(nextFrame("family"));
try {
return super.transformActivationOS_Family(creator, builder, target);
} finally {
stk.pop();
}
}
@Override
protected ActivationOS.Builder transformActivationOS_Name(
Supplier<? extends ActivationOS.Builder> creator, ActivationOS.Builder builder, ActivationOS target) {
stk.push(nextFrame("name"));
try {
return super.transformActivationOS_Name(creator, builder, target);
} finally {
stk.pop();
}
}
@Override
protected ActivationOS.Builder transformActivationOS_Version(
Supplier<? extends ActivationOS.Builder> creator, ActivationOS.Builder builder, ActivationOS target) {
stk.push(nextFrame("version"));
try {
return super.transformActivationOS_Version(creator, builder, target);
} finally {
stk.pop();
}
}
@Override
protected Activation.Builder transformActivation_Packaging(
Supplier<? extends Activation.Builder> creator, Activation.Builder builder, Activation target) {
stk.push(nextFrame("packaging"));
try {
return super.transformActivation_Packaging(creator, builder, target);
} finally {
stk.pop();
}
}
@Override
protected Activation.Builder transformActivation_Property(
Supplier<? extends Activation.Builder> creator, Activation.Builder builder, Activation target) {
stk.push(nextFrame("property", Activation::getProperty));
try {
return super.transformActivation_Property(creator, builder, target);
} finally {
stk.pop();
}
}
@Override
protected ActivationProperty.Builder transformActivationProperty_Name(
Supplier<? extends ActivationProperty.Builder> creator,
ActivationProperty.Builder builder,
ActivationProperty target) {
stk.push(nextFrame("name"));
try {
return super.transformActivationProperty_Name(creator, builder, target);
} finally {
stk.pop();
}
}
@Override
protected ActivationProperty.Builder transformActivationProperty_Value(
Supplier<? extends ActivationProperty.Builder> creator,
ActivationProperty.Builder builder,
ActivationProperty target) {
stk.push(nextFrame("value"));
try {
return super.transformActivationProperty_Value(creator, builder, target);
} finally {
stk.pop();
}
}
}
private final Set<String> validCoordinateIds = new HashSet<>();
private final Set<String> validProfileIds = new HashSet<>();
@ -282,42 +484,53 @@ public class DefaultModelValidator implements ModelValidator {
}
private void validate30RawProfileActivation(ModelProblemCollector problems, Activation activation, String prefix) {
if (activation == null || activation.getFile() == null) {
if (activation == null) {
return;
}
ActivationFile file = activation.getFile();
final Deque<ActivationFrame> stk = new LinkedList<>();
String path;
String location;
final Supplier<String> pathSupplier = () -> {
final boolean parallel = false;
return StreamSupport.stream(((Iterable<ActivationFrame>) stk::descendingIterator).spliterator(), parallel)
.map(ActivationFrame::location)
.collect(Collectors.joining("."));
};
final Supplier<InputLocation> locationSupplier = () -> {
if (stk.size() < 2) {
return null;
}
Iterator<ActivationFrame> f = stk.iterator();
if (file.getExists() != null && !file.getExists().isEmpty()) {
path = file.getExists();
location = "exists";
} else if (file.getMissing() != null && !file.getMissing().isEmpty()) {
path = file.getMissing();
location = "missing";
} else {
return;
}
String location = f.next().location;
ActivationFrame parent = f.next();
if (hasProjectExpression(path)) {
Matcher matcher = EXPRESSION_PROJECT_NAME_PATTERN.matcher(path);
while (matcher.find()) {
String propertyName = matcher.group(0);
if (!"${project.basedir}".equals(propertyName)) {
return parent.parent.map(p -> p.getLocation(location)).orElse(null);
};
final UnaryOperator<String> transformer = s -> {
if (hasProjectExpression(s)) {
String path = pathSupplier.get();
Matcher matcher = EXPRESSION_PROJECT_NAME_PATTERN.matcher(s);
while (matcher.find()) {
String propertyName = matcher.group(0);
if (path.startsWith("activation.file.") && "${project.basedir}".equals(propertyName)) {
continue;
}
addViolation(
problems,
Severity.WARNING,
Version.V30,
prefix + "activation.file." + location,
prefix + path,
null,
"Failed to interpolate file location " + path + ": " + propertyName
"Failed to interpolate profile activation property " + s + ": " + propertyName
+ " expressions are not supported during profile activation.",
file.getLocation(location));
locationSupplier.get());
}
}
}
return s;
};
new ActivationWalker(stk, transformer).transformActivation(activation);
}
private void validate20RawPlugins(

View File

@ -38,10 +38,13 @@ import java.util.Optional;
import java.util.Properties;
import java.util.concurrent.Callable;
import java.util.function.Supplier;
import java.util.function.UnaryOperator;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.apache.maven.api.VersionRange;
import org.apache.maven.api.feature.Features;
import org.apache.maven.api.model.ActivationFile;
import org.apache.maven.api.model.Exclusion;
import org.apache.maven.api.model.InputSource;
import org.apache.maven.api.services.VersionParserException;
@ -57,6 +60,7 @@ import org.apache.maven.model.Plugin;
import org.apache.maven.model.PluginManagement;
import org.apache.maven.model.Profile;
import org.apache.maven.model.building.ModelProblem.Severity;
import org.apache.maven.model.building.ModelProblem.Version;
import org.apache.maven.model.composition.DependencyManagementImporter;
import org.apache.maven.model.inheritance.InheritanceAssembler;
import org.apache.maven.model.interpolation.ModelInterpolator;
@ -82,11 +86,14 @@ import org.apache.maven.model.resolution.ModelResolver;
import org.apache.maven.model.resolution.UnresolvableModelException;
import org.apache.maven.model.resolution.WorkspaceModelResolver;
import org.apache.maven.model.superpom.SuperPomProvider;
import org.apache.maven.model.v4.MavenTransformer;
import org.apache.maven.model.validation.DefaultModelValidator;
import org.apache.maven.model.validation.ModelValidator;
import org.apache.maven.model.version.ModelVersionParser;
import org.codehaus.plexus.interpolation.InterpolationException;
import org.codehaus.plexus.interpolation.Interpolator;
import org.codehaus.plexus.interpolation.MapBasedValueSource;
import org.codehaus.plexus.interpolation.RegexBasedInterpolator;
import org.codehaus.plexus.interpolation.StringSearchInterpolator;
import org.eclipse.sisu.Nullable;
@ -897,69 +904,89 @@ public class DefaultModelBuilder implements ModelBuilder {
List<org.apache.maven.api.model.Profile> profiles,
DefaultProfileActivationContext context,
DefaultModelProblemCollector problems) {
List<org.apache.maven.api.model.Profile> newProfiles = null;
for (int index = 0; index < profiles.size(); index++) {
org.apache.maven.api.model.Profile profile = profiles.get(index);
org.apache.maven.api.model.Activation activation = profile.getActivation();
if (activation != null) {
org.apache.maven.api.model.ActivationFile file = activation.getFile();
if (file != null) {
String oldExists = file.getExists();
if (isNotEmpty(oldExists)) {
if (profiles.stream()
.map(org.apache.maven.api.model.Profile::getActivation)
.noneMatch(Objects::nonNull)) {
return profiles;
}
final Interpolator xform = new RegexBasedInterpolator();
xform.setCacheAnswers(true);
Stream.of(context.getUserProperties(), context.getSystemProperties())
.map(MapBasedValueSource::new)
.forEach(xform::addValueSource);
class ProfileInterpolator extends MavenTransformer
implements UnaryOperator<org.apache.maven.api.model.Profile> {
ProfileInterpolator() {
super(s -> {
if (isNotEmpty(s)) {
try {
String newExists = interpolate(oldExists, context);
if (!Objects.equals(oldExists, newExists)) {
if (newProfiles == null) {
newProfiles = new ArrayList<>(profiles);
}
newProfiles.set(
index, profile.withActivation(activation.withFile(file.withExists(newExists))));
}
return xform.interpolate(s);
} catch (InterpolationException e) {
addInterpolationProblem(problems, file, oldExists, e, "exists");
}
} else {
String oldMissing = file.getMissing();
if (isNotEmpty(oldMissing)) {
try {
String newMissing = interpolate(oldMissing, context);
if (!Objects.equals(oldMissing, newMissing)) {
if (newProfiles == null) {
newProfiles = new ArrayList<>(profiles);
}
newProfiles.set(
index,
profile.withActivation(activation.withFile(file.withMissing(newMissing))));
}
} catch (InterpolationException e) {
addInterpolationProblem(problems, file, oldMissing, e, "missing");
}
problems.add(new ModelProblemCollectorRequest(Severity.ERROR, Version.BASE)
.setMessage(e.getMessage())
.setException(e));
}
}
return s;
});
}
@Override
public org.apache.maven.api.model.Profile apply(org.apache.maven.api.model.Profile p) {
return org.apache.maven.api.model.Profile.newBuilder(p)
.activation(transformActivation(p.getActivation()))
.build();
}
@Override
protected ActivationFile.Builder transformActivationFile_Missing(
Supplier<? extends ActivationFile.Builder> creator,
ActivationFile.Builder builder,
ActivationFile target) {
final String path = target.getMissing();
final String xformed = transformPath(path, target, "missing");
return xformed != path ? (builder != null ? builder : creator.get()).missing(xformed) : builder;
}
@Override
protected ActivationFile.Builder transformActivationFile_Exists(
Supplier<? extends ActivationFile.Builder> 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) {
if (isNotEmpty(path)) {
try {
return profileActivationFilePathInterpolator.interpolate(path, context);
} catch (InterpolationException e) {
addInterpolationProblem(problems, target, path, e, locationKey);
}
}
return path;
}
}
return newProfiles != null ? newProfiles : profiles;
return profiles.stream().map(new ProfileInterpolator()).toList();
}
private static void addInterpolationProblem(
DefaultModelProblemCollector problems,
org.apache.maven.api.model.ActivationFile file,
org.apache.maven.api.model.InputLocationTracker target,
String path,
InterpolationException e,
String locationKey) {
problems.add(new ModelProblemCollectorRequest(Severity.ERROR, ModelProblem.Version.BASE)
.setMessage("Failed to interpolate file location " + path + ": " + e.getMessage())
.setLocation(Optional.ofNullable(file.getLocation(locationKey))
.setLocation(Optional.ofNullable(target.getLocation(locationKey))
.map(InputLocation::new)
.orElse(null))
.setException(e));
}
private String interpolate(String path, ProfileActivationContext context) throws InterpolationException {
return isNotEmpty(path) ? profileActivationFilePathInterpolator.interpolate(path, context) : path;
}
private static boolean isNotEmpty(String string) {
return string != null && !string.isEmpty();
}

View File

@ -25,17 +25,28 @@ import javax.inject.Singleton;
import java.io.File;
import java.util.Arrays;
import java.util.Collections;
import java.util.Deque;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
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;
import java.util.stream.StreamSupport;
import org.apache.maven.api.model.Activation;
import org.apache.maven.api.model.ActivationFile;
import org.apache.maven.api.model.ActivationOS;
import org.apache.maven.api.model.ActivationProperty;
import org.apache.maven.api.model.Build;
import org.apache.maven.api.model.BuildBase;
import org.apache.maven.api.model.Dependency;
@ -61,6 +72,7 @@ import org.apache.maven.model.building.ModelProblemCollector;
import org.apache.maven.model.building.ModelProblemCollectorRequest;
import org.apache.maven.model.interpolation.ModelVersionProcessor;
import org.apache.maven.model.v4.MavenModelVersion;
import org.apache.maven.model.v4.MavenTransformer;
/**
*/
@ -82,6 +94,196 @@ public class DefaultModelValidator implements ModelValidator {
private static final String EMPTY = "";
private record ActivationFrame(String location, Optional<? extends InputLocationTracker> parent) {}
private static class ActivationWalker extends MavenTransformer {
private final Deque<ActivationFrame> stk;
ActivationWalker(Deque<ActivationFrame> stk, UnaryOperator<String> transformer) {
super(transformer);
this.stk = stk;
}
private ActivationFrame nextFrame(String property) {
return new ActivationFrame(property, Optional.empty());
}
private <P> ActivationFrame nextFrame(String property, Function<P, InputLocationTracker> child) {
@SuppressWarnings("unchecked")
final Optional<P> parent = (Optional<P>) stk.peek().parent;
return new ActivationFrame(property, parent.map(child));
}
@Override
public Activation transformActivation(Activation target) {
stk.push(new ActivationFrame("activation", Optional.of(target)));
try {
return super.transformActivation(target);
} finally {
stk.pop();
}
}
@Override
protected Activation.Builder transformActivation_ActiveByDefault(
Supplier<? extends Activation.Builder> creator, Activation.Builder builder, Activation target) {
return builder;
}
@Override
protected Activation.Builder transformActivation_File(
Supplier<? extends Activation.Builder> creator, Activation.Builder builder, Activation target) {
stk.push(nextFrame("file", Activation::getFile));
Optional.ofNullable(target.getFile());
try {
return super.transformActivation_File(creator, builder, target);
} finally {
stk.pop();
}
}
@Override
protected ActivationFile.Builder transformActivationFile_Exists(
Supplier<? extends ActivationFile.Builder> creator,
ActivationFile.Builder builder,
ActivationFile target) {
stk.push(nextFrame("exists"));
try {
return super.transformActivationFile_Exists(creator, builder, target);
} finally {
stk.pop();
}
}
@Override
protected ActivationFile.Builder transformActivationFile_Missing(
Supplier<? extends ActivationFile.Builder> creator,
ActivationFile.Builder builder,
ActivationFile target) {
stk.push(nextFrame("missing"));
try {
return super.transformActivationFile_Missing(creator, builder, target);
} finally {
stk.pop();
}
}
@Override
protected Activation.Builder transformActivation_Jdk(
Supplier<? extends Activation.Builder> creator, Activation.Builder builder, Activation target) {
stk.push(nextFrame("jdk"));
try {
return super.transformActivation_Jdk(creator, builder, target);
} finally {
stk.pop();
}
}
@Override
protected Activation.Builder transformActivation_Os(
Supplier<? extends Activation.Builder> creator, Activation.Builder builder, Activation target) {
stk.push(nextFrame("os", Activation::getOs));
try {
return super.transformActivation_Os(creator, builder, target);
} finally {
stk.pop();
}
}
@Override
protected ActivationOS.Builder transformActivationOS_Arch(
Supplier<? extends ActivationOS.Builder> creator, ActivationOS.Builder builder, ActivationOS target) {
stk.push(nextFrame("arch"));
try {
return super.transformActivationOS_Arch(creator, builder, target);
} finally {
stk.pop();
}
}
@Override
protected ActivationOS.Builder transformActivationOS_Family(
Supplier<? extends ActivationOS.Builder> creator, ActivationOS.Builder builder, ActivationOS target) {
stk.push(nextFrame("family"));
try {
return super.transformActivationOS_Family(creator, builder, target);
} finally {
stk.pop();
}
}
@Override
protected ActivationOS.Builder transformActivationOS_Name(
Supplier<? extends ActivationOS.Builder> creator, ActivationOS.Builder builder, ActivationOS target) {
stk.push(nextFrame("name"));
try {
return super.transformActivationOS_Name(creator, builder, target);
} finally {
stk.pop();
}
}
@Override
protected ActivationOS.Builder transformActivationOS_Version(
Supplier<? extends ActivationOS.Builder> creator, ActivationOS.Builder builder, ActivationOS target) {
stk.push(nextFrame("version"));
try {
return super.transformActivationOS_Version(creator, builder, target);
} finally {
stk.pop();
}
}
@Override
protected Activation.Builder transformActivation_Packaging(
Supplier<? extends Activation.Builder> creator, Activation.Builder builder, Activation target) {
stk.push(nextFrame("packaging"));
try {
return super.transformActivation_Packaging(creator, builder, target);
} finally {
stk.pop();
}
}
@Override
protected Activation.Builder transformActivation_Property(
Supplier<? extends Activation.Builder> creator, Activation.Builder builder, Activation target) {
stk.push(nextFrame("property", Activation::getProperty));
try {
return super.transformActivation_Property(creator, builder, target);
} finally {
stk.pop();
}
}
@Override
protected ActivationProperty.Builder transformActivationProperty_Name(
Supplier<? extends ActivationProperty.Builder> creator,
ActivationProperty.Builder builder,
ActivationProperty target) {
stk.push(nextFrame("name"));
try {
return super.transformActivationProperty_Name(creator, builder, target);
} finally {
stk.pop();
}
}
@Override
protected ActivationProperty.Builder transformActivationProperty_Value(
Supplier<? extends ActivationProperty.Builder> creator,
ActivationProperty.Builder builder,
ActivationProperty target) {
stk.push(nextFrame("value"));
try {
return super.transformActivationProperty_Value(creator, builder, target);
} finally {
stk.pop();
}
}
}
private final Set<String> validCoordinateIds = new HashSet<>();
private final Set<String> validProfileIds = new HashSet<>();
@ -288,42 +490,52 @@ public class DefaultModelValidator implements ModelValidator {
}
private void validate30RawProfileActivation(ModelProblemCollector problems, Activation activation, String prefix) {
if (activation == null || activation.getFile() == null) {
if (activation == null) {
return;
}
final Deque<ActivationFrame> stk = new LinkedList<>();
ActivationFile file = activation.getFile();
final Supplier<String> pathSupplier = () -> {
final boolean parallel = false;
return StreamSupport.stream(((Iterable<ActivationFrame>) stk::descendingIterator).spliterator(), parallel)
.map(ActivationFrame::location)
.collect(Collectors.joining("."));
};
final Supplier<InputLocation> locationSupplier = () -> {
if (stk.size() < 2) {
return null;
}
Iterator<ActivationFrame> f = stk.iterator();
String path;
String location;
String location = f.next().location;
ActivationFrame parent = f.next();
if (file.getExists() != null && !file.getExists().isEmpty()) {
path = file.getExists();
location = "exists";
} else if (file.getMissing() != null && !file.getMissing().isEmpty()) {
path = file.getMissing();
location = "missing";
} else {
return;
}
return parent.parent.map(p -> p.getLocation(location)).orElse(null);
};
final UnaryOperator<String> transformer = s -> {
if (hasProjectExpression(s)) {
String path = pathSupplier.get();
Matcher matcher = EXPRESSION_PROJECT_NAME_PATTERN.matcher(s);
while (matcher.find()) {
String propertyName = matcher.group(0);
if (hasProjectExpression(path)) {
Matcher matcher = EXPRESSION_PROJECT_NAME_PATTERN.matcher(path);
while (matcher.find()) {
String propertyName = matcher.group(0);
if (!"${project.basedir}".equals(propertyName)) {
if (path.startsWith("activation.file.") && "${project.basedir}".equals(propertyName)) {
continue;
}
addViolation(
problems,
Severity.WARNING,
Version.V30,
prefix + "activation.file." + location,
prefix + path,
null,
"Failed to interpolate file location " + path + ": " + propertyName
"Failed to interpolate profile activation property " + s + ": " + propertyName
+ " expressions are not supported during profile activation.",
file.getLocation(location));
locationSupplier.get());
}
}
}
return s;
};
new ActivationWalker(stk, transformer).transformActivation(activation);
}
private void validate20RawPlugins(

View File

@ -20,6 +20,8 @@ package org.apache.maven.model.validation;
import java.io.InputStream;
import java.util.List;
import java.util.Properties;
import java.util.function.UnaryOperator;
import org.apache.maven.model.Model;
import org.apache.maven.model.building.DefaultModelBuildingRequest;
@ -50,32 +52,40 @@ class DefaultModelValidatorTest {
}
private SimpleProblemCollector validate(String pom) throws Exception {
return validateEffective(pom, ModelBuildingRequest.VALIDATION_LEVEL_STRICT);
return validateEffective(pom, UnaryOperator.identity());
}
private SimpleProblemCollector validateRaw(String pom) throws Exception {
return validateRaw(pom, ModelBuildingRequest.VALIDATION_LEVEL_STRICT);
return validateRaw(pom, UnaryOperator.identity());
}
private SimpleProblemCollector validateEffective(String pom, int level) throws Exception {
ModelBuildingRequest request = new DefaultModelBuildingRequest().setValidationLevel(level);
return validateEffective(pom, mbr -> mbr.setValidationLevel(level));
}
private SimpleProblemCollector validateEffective(String pom, UnaryOperator<ModelBuildingRequest> requestConfigurer)
throws Exception {
Model model = read(pom);
SimpleProblemCollector problems = new SimpleProblemCollector(model);
validator.validateEffectiveModel(model, request, problems);
validator.validateEffectiveModel(model, requestConfigurer.apply(new DefaultModelBuildingRequest()), problems);
return problems;
}
private SimpleProblemCollector validateRaw(String pom, int level) throws Exception {
ModelBuildingRequest request = new DefaultModelBuildingRequest().setValidationLevel(level);
return validateRaw(pom, mbr -> mbr.setValidationLevel(level));
}
private SimpleProblemCollector validateRaw(String pom, UnaryOperator<ModelBuildingRequest> requestConfigurer)
throws Exception {
Model model = read(pom);
SimpleProblemCollector problems = new SimpleProblemCollector(model);
ModelBuildingRequest request = requestConfigurer.apply(new DefaultModelBuildingRequest());
validator.validateFileModel(model, request, problems);
validator.validateRawModel(model, request, problems);
@ -818,24 +828,52 @@ class DefaultModelValidatorTest {
@Test
void profileActivationWithAllowedExpression() throws Exception {
SimpleProblemCollector result = validateRaw("raw-model/profile-activation-file-with-allowed-expressions.xml");
SimpleProblemCollector result = validateRaw(
"raw-model/profile-activation-file-with-allowed-expressions.xml",
mbr -> mbr.setUserProperties(new Properties() {
private static final long serialVersionUID = 1L;
{
setProperty("foo", "foo");
setProperty("bar", "foo");
}
}));
assertViolations(result, 0, 0, 0);
}
@Test
void profileActivationWithProjectExpression() throws Exception {
void profileActivationFileWithProjectExpression() throws Exception {
SimpleProblemCollector result = validateRaw("raw-model/profile-activation-file-with-project-expressions.xml");
assertViolations(result, 0, 0, 2);
assertEquals(
"'profiles.profile[exists-project-version].activation.file.exists' "
+ "Failed to interpolate file location ${project.version}/test.txt: "
+ "Failed to interpolate profile activation property ${project.version}/test.txt: "
+ "${project.version} expressions are not supported during profile activation.",
result.getWarnings().get(0));
assertEquals(
"'profiles.profile[missing-project-version].activation.file.missing' "
+ "Failed to interpolate file location ${project.version}/test.txt: "
+ "Failed to interpolate profile activation property ${project.version}/test.txt: "
+ "${project.version} expressions are not supported during profile activation.",
result.getWarnings().get(1));
}
@Test
void profileActivationPropertyWithProjectExpression() throws Exception {
SimpleProblemCollector result =
validateRaw("raw-model/profile-activation-property-with-project-expressions.xml");
assertViolations(result, 0, 0, 2);
assertEquals(
"'profiles.profile[property-name-project-version].activation.property.name' "
+ "Failed to interpolate profile activation property ${project.version}: "
+ "${project.version} expressions are not supported during profile activation.",
result.getWarnings().get(0));
assertEquals(
"'profiles.profile[property-value-project-version].activation.property.value' "
+ "Failed to interpolate profile activation property ${project.version}: "
+ "${project.version} expressions are not supported during profile activation.",
result.getWarnings().get(1));
}

View File

@ -60,5 +60,23 @@ under the License.
</activation>
</profile>
<profile>
<id>dynamic-property-available</id>
<activation>
<property>
<name>${activationProperty}</name>
</property>
</activation>
</profile>
<profile>
<id>matches-another-property</id>
<activation>
<property>
<name>foo</name>
<value>${bar}</value>
</property>
</activation>
</profile>
</profiles>
</project>

View File

@ -0,0 +1,49 @@
<!--
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
-->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<artifactId>aid</artifactId>
<groupId>gid</groupId>
<version>0.1</version>
<packaging>pom</packaging>
<profiles>
<profile>
<id>property-name-project-version</id>
<activation>
<property>
<name>${project.version}</name>
</property>
</activation>
</profile>
<profile>
<id>property-value-project-version</id>
<activation>
<property>
<name>project.version</name>
<value>${project.version}</value>
</property>
</activation>
</profile>
</profiles>
</project>