Skip to content

Commit

Permalink
[MNG-8081] Interpolate available properties during default profile se…
Browse files Browse the repository at this point in the history
…lection (Maven 3.9.x) (#1447)

Co-authored-by: Guillaume Nodet <gnodet@gmail.com>
  • Loading branch information
mbenson and gnodet committed May 2, 2024
1 parent b4cbda8 commit c1c114d
Show file tree
Hide file tree
Showing 7 changed files with 388 additions and 69 deletions.
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

0 comments on commit c1c114d

Please sign in to comment.