diff --git a/maven-model-builder/src/main/java/org/apache/maven/model/building/DefaultModelBuilder.java b/maven-model-builder/src/main/java/org/apache/maven/model/building/DefaultModelBuilder.java index a9b9399209bd..4b46ea24021c 100644 --- a/maven-model-builder/src/main/java/org/apache/maven/model/building/DefaultModelBuilder.java +++ b/maven-model-builder/src/main/java/org/apache/maven/model/building/DefaultModelBuilder.java @@ -506,15 +506,19 @@ void performFor(String value, String locationKey, Consumer mutator) { /** * Determines whether the given request builds a model to resolve a dependency, i.e. a POM * read from a remote repository (a dependency POM, one of its parents, or an imported BOM) - * rather than a POM belonging to the project being built. Such requests use + * rather than a POM belonging to the project being built. Such requests have no POM file + * (see {@link ModelBuildingRequest#setPomFile(File)}) and use * {@link ModelBuildingRequest#VALIDATION_LEVEL_MINIMAL}, see for instance * {@code DefaultArtifactDescriptorReader#loadPom}; a project build uses - * {@link ModelBuildingRequest#VALIDATION_LEVEL_MAVEN_2_0} or higher. A request that sets + * {@link ModelBuildingRequest#VALIDATION_LEVEL_MAVEN_2_0} or higher. A request with a POM file is in project + * mode whatever its validation level, so an embedder that builds its own project at + * {@code VALIDATION_LEVEL_MINIMAL} keeps full profile activation. A request that sets * {@value AbstractStringBasedModelInterpolator#FULL_EXTERNAL_INTERPOLATION_PROPERTY} is never external, so * that tools building their own projects at a low validation level can opt out. */ private static boolean isExternalModelBuildingRequest(ModelBuildingRequest request) { - return request.getValidationLevel() < ModelBuildingRequest.VALIDATION_LEVEL_MAVEN_2_0 + return request.getPomFile() == null + && request.getValidationLevel() < ModelBuildingRequest.VALIDATION_LEVEL_MAVEN_2_0 && !AbstractStringBasedModelInterpolator.isFullExternalModelBuild(request); } diff --git a/maven-model-builder/src/main/java/org/apache/maven/model/interpolation/AbstractStringBasedModelInterpolator.java b/maven-model-builder/src/main/java/org/apache/maven/model/interpolation/AbstractStringBasedModelInterpolator.java index 45037908716c..2088f8d933d0 100644 --- a/maven-model-builder/src/main/java/org/apache/maven/model/interpolation/AbstractStringBasedModelInterpolator.java +++ b/maven-model-builder/src/main/java/org/apache/maven/model/interpolation/AbstractStringBasedModelInterpolator.java @@ -196,12 +196,13 @@ public Object getValue(String expression) { valueSources.add(modelValueSource1); - // Models built at VALIDATION_LEVEL_MINIMAL are the models Maven builds while resolving - // dependency, parent and BOM-import POMs from a repository, not the operator's own - // project. Such models interpolate only against their own properties and a small set - // of environment-independent expressions; everything else in the user/system property - // space stays uninterpolated. Operator project builds use a higher validation level and - // keep the full set of value sources, unchanged from previous behavior. + // Models built without a POM file at VALIDATION_LEVEL_MINIMAL are the models Maven builds + // while resolving dependency, parent and BOM-import POMs from a repository, not the + // operator's own project. Such models interpolate only against their own properties and a + // small set of environment-independent expressions; everything else in the user/system + // property space stays uninterpolated. Operator project builds have a POM file or use a + // higher validation level and keep the full set of value sources, unchanged from previous + // behavior. boolean restricted = restrictExternalModelInterpolation(config); ValueSource userPropertiesValueSource = new MapBasedValueSource(config.getUserProperties()); @@ -234,7 +235,8 @@ public Object getValue(String expression) { } private static boolean restrictExternalModelInterpolation(ModelBuildingRequest config) { - return config.getValidationLevel() < ModelBuildingRequest.VALIDATION_LEVEL_MAVEN_2_0 + return config.getPomFile() == null + && config.getValidationLevel() < ModelBuildingRequest.VALIDATION_LEVEL_MAVEN_2_0 && !isFullExternalModelBuild(config); } diff --git a/maven-model-builder/src/test/java/org/apache/maven/model/building/ResolvedDependencyProfileActivationTest.java b/maven-model-builder/src/test/java/org/apache/maven/model/building/ResolvedDependencyProfileActivationTest.java index faa53153ed86..5978838d140f 100644 --- a/maven-model-builder/src/test/java/org/apache/maven/model/building/ResolvedDependencyProfileActivationTest.java +++ b/maven-model-builder/src/test/java/org/apache/maven/model/building/ResolvedDependencyProfileActivationTest.java @@ -18,11 +18,16 @@ */ package org.apache.maven.model.building; +import java.io.File; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; import java.util.Properties; import org.apache.maven.model.Model; import org.apache.maven.model.interpolation.AbstractStringBasedModelInterpolator; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNull; @@ -168,6 +173,44 @@ private Model build(int validationLevel, Properties systemProperties, Properties return builder.build(request).getEffectiveModel(); } + private Model buildProjectAtMinimal(File pomFile, Properties systemProperties, Properties userProperties) + throws Exception { + ModelBuilder builder = new DefaultModelBuilderFactory().newInstance(); + + DefaultModelBuildingRequest request = new DefaultModelBuildingRequest(); + request.setPomFile(pomFile); + request.setValidationLevel(ModelBuildingRequest.VALIDATION_LEVEL_MINIMAL); + request.setSystemProperties(systemProperties); + request.setUserProperties(userProperties); + request.setModelResolver(new DefaultModelBuilderTest.BaseModelResolver()); + + return builder.build(request).getEffectiveModel(); + } + + private Model buildFromRepositoryAtMinimal(File pomFile, Properties systemProperties, Properties userProperties) + throws Exception { + ModelBuilder builder = new DefaultModelBuilderFactory().newInstance(); + + DefaultModelBuildingRequest request = new DefaultModelBuildingRequest(); + request.setModelSource(new FileModelSource(pomFile)); + request.setValidationLevel(ModelBuildingRequest.VALIDATION_LEVEL_MINIMAL); + request.setSystemProperties(systemProperties); + request.setUserProperties(userProperties); + request.setModelResolver(new DefaultModelBuilderTest.BaseModelResolver()); + + return builder.build(request).getEffectiveModel(); + } + + private File writePom(Path dir) throws Exception { + String pom = POM.replace( + "hello", + "hello\n" + + " ${user.gating.prop}"); + Path file = dir.resolve("pom.xml"); + Files.write(file, pom.getBytes(StandardCharsets.UTF_8)); + return file.toFile(); + } + private Properties systemPropertiesWithTestValues() { Properties sp = new Properties(); sp.putAll(System.getProperties()); @@ -213,6 +256,42 @@ void testProjectBuildEvaluatesAllActivators() throws Exception { "profile repository must be present in project build"); } + @Test + void testProjectModeAtMinimalValidationEvaluatesUserPropertyAndFileActivators(@TempDir Path dir) throws Exception { + // An embedder (an IDE) builds the operator's own project from its POM file at + // VALIDATION_LEVEL_MINIMAL for leniency. A POM file means project mode, so the + // repository-model sandbox must not apply. + Model model = + buildProjectAtMinimal(writePom(dir), systemPropertiesWithTestValues(), userPropertiesWithTestValues()); + + assertEquals( + "activated", + model.getProperties().get("profile.user.property"), + "user-property profile must fire for a project built from its POM file"); + assertEquals( + "activated", + model.getProperties().get("profile.file"), + "file profile must fire for a project built from its POM file"); + assertEquals( + "true", + model.getProperties().get("interpolated.user.property"), + "user properties must be interpolated for a project built from its POM file"); + } + + @Test + void testSamePomFromRepositoryAtMinimalValidationStaysSandboxed(@TempDir Path dir) throws Exception { + // The same POM loaded as a repository model (model source, no POM file) keeps the sandbox. + Model model = buildFromRepositoryAtMinimal( + writePom(dir), systemPropertiesWithTestValues(), userPropertiesWithTestValues()); + + assertNull(model.getProperties().get("profile.user.property"), "user-property profile must NOT fire"); + assertNull(model.getProperties().get("profile.file"), "file profile must NOT fire"); + assertEquals( + "${user.gating.prop}", + model.getProperties().get("interpolated.user.property"), + "user properties must stay literal in a repository model"); + } + @Test void testExternalModelFileActivationIsSuppressed() throws Exception { // File activation must be suppressed in external model builds regardless of whether the