Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

[MNG-8081] Interpolate available properties during default profile selection (Maven 3.9.x) #1447

Merged
merged 4 commits into from
May 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -26,23 +26,28 @@
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Properties;
import java.util.Set;
import java.util.function.Consumer;
import java.util.stream.Collectors;

import org.apache.maven.artifact.versioning.DefaultArtifactVersion;
import org.apache.maven.artifact.versioning.InvalidVersionSpecificationException;
import org.apache.maven.artifact.versioning.VersionRange;
import org.apache.maven.model.Activation;
import org.apache.maven.model.ActivationFile;
import org.apache.maven.model.Build;
import org.apache.maven.model.Dependency;
import org.apache.maven.model.DependencyManagement;
import org.apache.maven.model.InputLocation;
import org.apache.maven.model.InputLocationTracker;
import org.apache.maven.model.InputSource;
import org.apache.maven.model.Model;
import org.apache.maven.model.Parent;
Expand Down Expand Up @@ -78,6 +83,7 @@
import org.apache.maven.model.validation.ModelValidator;
import org.codehaus.plexus.interpolation.InterpolationException;
import org.codehaus.plexus.interpolation.MapBasedValueSource;
import org.codehaus.plexus.interpolation.RegexBasedInterpolator;
import org.codehaus.plexus.interpolation.StringSearchInterpolator;
import org.codehaus.plexus.util.StringUtils;
import org.eclipse.sisu.Nullable;
Expand Down Expand Up @@ -299,14 +305,19 @@ protected ModelBuildingResult build(ModelBuildingRequest request, Collection<Str

profileActivationContext.setProjectProperties(tmpModel.getProperties());

List<Profile> activePomProfiles =
profileSelector.getActiveProfiles(rawModel.getProfiles(), profileActivationContext, problems);
currentData.setActiveProfiles(activePomProfiles);

Map<String, Activation> interpolatedActivations =
getInterpolatedActivations(rawModel, profileActivationContext, problems);
injectProfileActivations(tmpModel, interpolatedActivations);

List<Profile> activePomProfiles =
profileSelector.getActiveProfiles(tmpModel.getProfiles(), profileActivationContext, problems);

Set<String> activeProfileIds =
activePomProfiles.stream().map(Profile::getId).collect(Collectors.toSet());
currentData.setActiveProfiles(rawModel.getProfiles().stream()
.filter(p -> activeProfileIds.contains(p.getId()))
.collect(Collectors.toList()));

// profile injection
for (Profile activeProfile : activePomProfiles) {
profileInjector.injectProfile(tmpModel, activeProfile, request, problems);
Expand Down Expand Up @@ -413,40 +424,72 @@ protected ModelBuildingResult build(ModelBuildingRequest request, Collection<Str
return result;
}

@FunctionalInterface
private interface InterpolateString {
String apply(String s) throws InterpolationException;
}

private Map<String, Activation> getInterpolatedActivations(
Model rawModel, DefaultProfileActivationContext context, DefaultModelProblemCollector problems) {
Map<String, Activation> interpolatedActivations = getProfileActivations(rawModel, true);
for (Activation activation : interpolatedActivations.values()) {
if (activation.getFile() != null) {
replaceWithInterpolatedValue(activation.getFile(), context, problems);
}

if (interpolatedActivations.isEmpty()) {
return Collections.emptyMap();
}
return interpolatedActivations;
}
RegexBasedInterpolator interpolator = new RegexBasedInterpolator();

private void replaceWithInterpolatedValue(
ActivationFile activationFile, ProfileActivationContext context, DefaultModelProblemCollector problems) {
try {
if (StringUtils.isNotEmpty(activationFile.getExists())) {
String path = activationFile.getExists();
String absolutePath = profileActivationFilePathInterpolator.interpolate(path, context);
activationFile.setExists(absolutePath);
} else if (StringUtils.isNotEmpty(activationFile.getMissing())) {
String path = activationFile.getMissing();
String absolutePath = profileActivationFilePathInterpolator.interpolate(path, context);
activationFile.setMissing(absolutePath);
interpolator.addValueSource(new MapBasedValueSource(context.getProjectProperties()));
interpolator.addValueSource(new MapBasedValueSource(context.getUserProperties()));
interpolator.addValueSource(new MapBasedValueSource(context.getSystemProperties()));

class Interpolation {
final InputLocationTracker target;

final InterpolateString impl;

Interpolation(InputLocationTracker target, InterpolateString impl) {
this.target = target;
this.impl = impl;
}

void performFor(String value, String locationKey, Consumer<String> mutator) {
if (StringUtils.isEmpty(value)) {
return;
}
try {
mutator.accept(impl.apply(value));
} catch (InterpolationException e) {
problems.add(new ModelProblemCollectorRequest(Severity.ERROR, Version.BASE)
.setMessage("Failed to interpolate value " + value + ": " + e.getMessage())
.setLocation(target.getLocation(locationKey))
.setException(e));
}
}
} catch (InterpolationException e) {
String path = StringUtils.isNotEmpty(activationFile.getExists())
? activationFile.getExists()
: activationFile.getMissing();

problems.add(new ModelProblemCollectorRequest(Severity.ERROR, Version.BASE)
.setMessage("Failed to interpolate file location " + path + ": " + e.getMessage())
.setLocation(activationFile.getLocation(
StringUtils.isNotEmpty(activationFile.getExists()) ? "exists" : "missing"))
.setException(e));
}
for (Activation activation : interpolatedActivations.values()) {
Optional<Activation> a = Optional.of(activation);
a.map(Activation::getFile).ifPresent(fa -> {
Interpolation nt =
new Interpolation(fa, s -> profileActivationFilePathInterpolator.interpolate(s, context));
nt.performFor(fa.getExists(), "exists", fa::setExists);
nt.performFor(fa.getMissing(), "missing", fa::setMissing);
});
a.map(Activation::getOs).ifPresent(oa -> {
Interpolation nt = new Interpolation(oa, interpolator::interpolate);
nt.performFor(oa.getArch(), "arch", oa::setArch);
nt.performFor(oa.getFamily(), "family", oa::setFamily);
nt.performFor(oa.getName(), "name", oa::setName);
nt.performFor(oa.getVersion(), "version", oa::setVersion);
});
a.map(Activation::getProperty).ifPresent(pa -> {
Interpolation nt = new Interpolation(pa, interpolator::interpolate);
nt.performFor(pa.getName(), "name", pa::setName);
nt.performFor(pa.getValue(), "value", pa::setValue);
});
a.map(Activation::getJdk).ifPresent(ja -> new Interpolation(activation, interpolator::interpolate)
.performFor(ja, "jdk", activation::setJdk));
}
return interpolatedActivations;
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,17 +24,24 @@

import java.io.File;
import java.util.Arrays;
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.Consumer;
import java.util.function.Supplier;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;

import org.apache.maven.model.Activation;
import org.apache.maven.model.ActivationFile;
import org.apache.maven.model.Build;
import org.apache.maven.model.BuildBase;
import org.apache.maven.model.Dependency;
Expand Down Expand Up @@ -236,42 +243,97 @@ public void validateRawModel(Model m, ModelBuildingRequest request, ModelProblem
}

private void validate30RawProfileActivation(ModelProblemCollector problems, Activation activation, String prefix) {
if (activation == null || activation.getFile() == null) {
if (activation == null) {
return;
}
class ActivationFrame {
String location;
Optional<? extends InputLocationTracker> parent;

ActivationFile file = activation.getFile();

String path;
String location;

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;
ActivationFrame(String location, Optional<? extends InputLocationTracker> parent) {
this.location = location;
this.parent = parent;
}
}

if (hasProjectExpression(path)) {
Matcher matcher = EXPRESSION_PROJECT_NAME_PATTERN.matcher(path);
while (matcher.find()) {
String propertyName = matcher.group(0);
if (!"${project.basedir}".equals(propertyName)) {
final Deque<ActivationFrame> stk = new LinkedList<>();

final Supplier<String> pathSupplier = () -> {
final boolean parallel = false;
return StreamSupport.stream(((Iterable<ActivationFrame>) stk::descendingIterator).spliterator(), parallel)
.map(f -> f.location)
.collect(Collectors.joining("."));
};
final Supplier<InputLocation> locationSupplier = () -> {
if (stk.size() < 2) {
return null;
}
Iterator<ActivationFrame> f = stk.iterator();

String location = f.next().location;
ActivationFrame parent = f.next();

return parent.parent.map(p -> p.getLocation(location)).orElse(null);
};
final Consumer<String> validator = 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());
}
}
}
};
Optional<Activation> root = Optional.of(activation);
stk.push(new ActivationFrame("activation", root));
root.map(Activation::getFile).ifPresent(fa -> {
stk.push(new ActivationFrame("file", Optional.of(fa)));
stk.push(new ActivationFrame("exists", Optional.empty()));
validator.accept(fa.getExists());
stk.peek().location = "missing";
validator.accept(fa.getMissing());
stk.pop();
stk.pop();
});
root.map(Activation::getOs).ifPresent(oa -> {
stk.push(new ActivationFrame("os", Optional.of(oa)));
stk.push(new ActivationFrame("arch", Optional.empty()));
validator.accept(oa.getArch());
stk.peek().location = "family";
validator.accept(oa.getFamily());
stk.peek().location = "name";
validator.accept(oa.getName());
stk.peek().location = "version";
validator.accept(oa.getVersion());
stk.pop();
stk.pop();
});
root.map(Activation::getProperty).ifPresent(pa -> {
stk.push(new ActivationFrame("property", Optional.of(pa)));
stk.push(new ActivationFrame("name", Optional.empty()));
validator.accept(pa.getName());
stk.peek().location = "value";
validator.accept(pa.getValue());
stk.pop();
stk.pop();
});
root.map(Activation::getJdk).ifPresent(jdk -> {
stk.push(new ActivationFrame("jdk", Optional.empty()));
validator.accept(jdk);
stk.pop();
});
}

private void validate20RawPlugins(
Expand Down
Loading