You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@maven.apache.org by cs...@apache.org on 2026/04/08 15:00:49 UTC
(maven) branch maven-3.10.x updated: Bug: Settings interpolation and strict (#11776)
This is an automated email from the ASF dual-hosted git repository.
cstamas pushed a commit to branch maven-3.10.x
in repository https://gitbox.apache.org/repos/asf/maven.git
The following commit(s) were added to refs/heads/maven-3.10.x by this push:
new 24deab9b74 Bug: Settings interpolation and strict (#11776)
24deab9b74 is described below
commit 24deab9b74ca3144b9981a38a59c6c551d71de2d
Author: Tamas Cservenak <ta...@cservenak.net>
AuthorDate: Wed Apr 8 17:00:44 2026 +0200
Bug: Settings interpolation and strict (#11776)
Problem: today in none of Maven 3 is possible to use placeholders and apply interpolation to non-String fields (ie `proxy.active` or `proxy.port`). Moreover, given Maven is "forgiving" in settings parsing, and only WARNs, it is very easy for user to miss information.
This PR:
* fixes interpolation for whole settings
* allows users to apply "strict" settings parsing that makes Maven FAIL if settings was not properly parsed or interpolated by setting `-Dmaven.settings.strictParsing` in user or system properties.
Fixes #11011
---
.../settings/building/DefaultSettingsBuilder.java | 155 ++++++++++++---------
.../DefaultSettingsBuilderFactoryTest.java | 62 +++++++++
.../src/test/resources/settings/factory/proxy.xml | 32 +++++
3 files changed, 181 insertions(+), 68 deletions(-)
diff --git a/maven-settings-builder/src/main/java/org/apache/maven/settings/building/DefaultSettingsBuilder.java b/maven-settings-builder/src/main/java/org/apache/maven/settings/building/DefaultSettingsBuilder.java
index 3addf78ea5..95d4116047 100644
--- a/maven-settings-builder/src/main/java/org/apache/maven/settings/building/DefaultSettingsBuilder.java
+++ b/maven-settings-builder/src/main/java/org/apache/maven/settings/building/DefaultSettingsBuilder.java
@@ -24,8 +24,9 @@
import java.io.File;
import java.io.IOException;
+import java.io.InputStream;
import java.io.StringReader;
-import java.io.StringWriter;
+import java.nio.charset.StandardCharsets;
import java.util.Collections;
import java.util.List;
import java.util.Map;
@@ -44,6 +45,7 @@
import org.codehaus.plexus.interpolation.InterpolationPostProcessor;
import org.codehaus.plexus.interpolation.PropertiesBasedValueSource;
import org.codehaus.plexus.interpolation.RegexBasedInterpolator;
+import org.codehaus.plexus.util.IOUtil;
/**
* Builds the effective settings from a user settings file and/or a global settings file.
@@ -53,6 +55,7 @@
@Named
@Singleton
public class DefaultSettingsBuilder implements SettingsBuilder {
+ private static final String MAVEN_SETTINGS_STRICT = "maven.settings.strictParsing";
private SettingsReader settingsReader;
@@ -89,22 +92,35 @@ public DefaultSettingsBuilder setSettingsValidator(SettingsValidator settingsVal
public SettingsBuildingResult build(SettingsBuildingRequest request) throws SettingsBuildingException {
DefaultSettingsProblemCollector problems = new DefaultSettingsProblemCollector(null);
+ boolean strict = Boolean.parseBoolean(request.getUserProperties()
+ .getProperty(
+ MAVEN_SETTINGS_STRICT,
+ request.getSystemProperties().getProperty(MAVEN_SETTINGS_STRICT, Boolean.FALSE.toString())));
+
Source globalSettingsSource =
getSettingsSource(request.getGlobalSettingsFile(), request.getGlobalSettingsSource());
- Settings globalSettings = readSettings(globalSettingsSource, request, problems);
+ Settings globalSettings = readSettingsFromString(
+ globalSettingsSource,
+ interpolateFromSourceToString(globalSettingsSource, request, problems),
+ request,
+ problems,
+ strict);
Source userSettingsSource = getSettingsSource(request.getUserSettingsFile(), request.getUserSettingsSource());
- Settings userSettings = readSettings(userSettingsSource, request, problems);
+ Settings userSettings = readSettingsFromString(
+ userSettingsSource,
+ interpolateFromSourceToString(userSettingsSource, request, problems),
+ request,
+ problems,
+ strict);
settingsMerger.merge(userSettings, globalSettings, TrackableBase.GLOBAL_LEVEL);
problems.setSource("");
- userSettings = interpolate(userSettings, request, problems);
-
// for the special case of a drive-relative Windows path, make sure it's absolute to save plugins from trouble
String localRepository = userSettings.getLocalRepository();
- if (localRepository != null && localRepository.length() > 0) {
+ if (localRepository != null && !localRepository.isEmpty()) {
File file = new File(localRepository);
if (!file.isAbsolute() && file.getPath().startsWith(File.separator)) {
userSettings.setLocalRepository(file.getAbsolutePath());
@@ -139,8 +155,12 @@ private Source getSettingsSource(File settingsFile, Source settingsSource) {
return null;
}
- private Settings readSettings(
- Source settingsSource, SettingsBuildingRequest request, DefaultSettingsProblemCollector problems) {
+ private Settings readSettingsFromString(
+ Source settingsSource,
+ String settingsString,
+ SettingsBuildingRequest request,
+ DefaultSettingsProblemCollector problems,
+ boolean strict) {
if (settingsSource == null) {
return new Settings();
}
@@ -153,14 +173,22 @@ private Settings readSettings(
Map<String, ?> options = Collections.singletonMap(SettingsReader.IS_STRICT, Boolean.TRUE);
try {
- settings = settingsReader.read(settingsSource.getInputStream(), options);
+ settings = settingsReader.read(new StringReader(settingsString), options);
} catch (SettingsParseException e) {
- options = Collections.singletonMap(SettingsReader.IS_STRICT, Boolean.FALSE);
-
- settings = settingsReader.read(settingsSource.getInputStream(), options);
-
- problems.add(
- SettingsProblem.Severity.WARNING, e.getMessage(), e.getLineNumber(), e.getColumnNumber(), e);
+ if (strict) {
+ problems.add(
+ SettingsProblem.Severity.FATAL, e.getMessage(), e.getLineNumber(), e.getColumnNumber(), e);
+ return new Settings();
+ } else {
+ options = Collections.singletonMap(SettingsReader.IS_STRICT, Boolean.FALSE);
+ settings = settingsReader.read(new StringReader(settingsString), options);
+ problems.add(
+ SettingsProblem.Severity.WARNING,
+ e.getMessage(),
+ e.getLineNumber(),
+ e.getColumnNumber(),
+ e);
+ }
}
} catch (SettingsParseException e) {
problems.add(
@@ -185,69 +213,60 @@ private Settings readSettings(
return settings;
}
- private Settings interpolate(
- Settings settings, SettingsBuildingRequest request, SettingsProblemCollector problems) {
- StringWriter writer = new StringWriter(1024 * 4);
-
- try {
- settingsWriter.write(writer, null, settings);
- } catch (IOException e) {
- throw new IllegalStateException("Failed to serialize settings to memory", e);
+ private String interpolateFromSourceToString(
+ Source settingsSource, SettingsBuildingRequest request, SettingsProblemCollector problems) {
+ String serializedSettings = null;
+ if (settingsSource == null) {
+ return "<settings></settings>";
}
-
- String serializedSettings = writer.toString();
-
- RegexBasedInterpolator interpolator = new RegexBasedInterpolator();
-
- interpolator.addValueSource(new PropertiesBasedValueSource(request.getUserProperties()));
-
- interpolator.addValueSource(new PropertiesBasedValueSource(request.getSystemProperties()));
-
try {
- interpolator.addValueSource(new EnvarBasedValueSource());
- } catch (IOException e) {
- problems.add(
- SettingsProblem.Severity.WARNING,
- "Failed to use environment variables for interpolation: " + e.getMessage(),
- -1,
- -1,
- e);
- }
+ try (InputStream inputStream = settingsSource.getInputStream()) {
+ serializedSettings = IOUtil.toString(inputStream, StandardCharsets.UTF_8.name());
+ }
+ RegexBasedInterpolator interpolator = new RegexBasedInterpolator();
+ interpolator.addValueSource(new PropertiesBasedValueSource(request.getUserProperties()));
+ interpolator.addValueSource(new PropertiesBasedValueSource(request.getSystemProperties()));
- interpolator.addPostProcessor(new InterpolationPostProcessor() {
- @Override
- public Object execute(String expression, Object value) {
- if (value != null) {
- // we're going to parse this back in as XML so we need to escape XML markup
- value = value.toString()
- .replace("&", "&")
- .replace("<", "<")
- .replace(">", ">");
- return value;
- }
- return null;
+ try {
+ interpolator.addValueSource(new EnvarBasedValueSource());
+ } catch (IOException e) {
+ problems.add(
+ SettingsProblem.Severity.WARNING,
+ "Failed to use environment variables for interpolation: " + e.getMessage(),
+ -1,
+ -1,
+ e);
}
- });
- try {
- serializedSettings = interpolator.interpolate(serializedSettings, "settings");
- } catch (InterpolationException e) {
- problems.add(
- SettingsProblem.Severity.ERROR, "Failed to interpolate settings: " + e.getMessage(), -1, -1, e);
+ interpolator.addPostProcessor(new InterpolationPostProcessor() {
+ @Override
+ public Object execute(String expression, Object value) {
+ if (value != null) {
+ // we're going to parse this back in as XML so we need to escape XML markup
+ value = value.toString()
+ .replace("&", "&")
+ .replace("<", "<")
+ .replace(">", ">");
+ return value;
+ }
+ return null;
+ }
+ });
- return settings;
- }
+ try {
+ serializedSettings = interpolator.interpolate(serializedSettings, "settings");
+ } catch (InterpolationException e) {
+ problems.add(
+ SettingsProblem.Severity.ERROR, "Failed to interpolate settings: " + e.getMessage(), -1, -1, e);
- Settings result;
- try {
- Map<String, ?> options = Collections.singletonMap(SettingsReader.IS_STRICT, Boolean.FALSE);
- result = settingsReader.read(new StringReader(serializedSettings), options);
+ return serializedSettings;
+ }
+
+ return serializedSettings;
} catch (IOException e) {
problems.add(
SettingsProblem.Severity.ERROR, "Failed to interpolate settings: " + e.getMessage(), -1, -1, e);
- return settings;
+ return serializedSettings;
}
-
- return result;
}
}
diff --git a/maven-settings-builder/src/test/java/org/apache/maven/settings/building/DefaultSettingsBuilderFactoryTest.java b/maven-settings-builder/src/test/java/org/apache/maven/settings/building/DefaultSettingsBuilderFactoryTest.java
index 7aadfcb070..2c51001228 100644
--- a/maven-settings-builder/src/test/java/org/apache/maven/settings/building/DefaultSettingsBuilderFactoryTest.java
+++ b/maven-settings-builder/src/test/java/org/apache/maven/settings/building/DefaultSettingsBuilderFactoryTest.java
@@ -19,10 +19,13 @@
package org.apache.maven.settings.building;
import java.io.File;
+import java.util.Properties;
import org.junit.jupiter.api.Test;
+import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
/**
* @author Benjamin Bentmann
@@ -46,4 +49,63 @@ public void testCompleteWiring() throws Exception {
assertNotNull(result);
assertNotNull(result.getEffectiveSettings());
}
+
+ @Test
+ public void testNonStringInterpolationHappyPath() throws Exception {
+ SettingsBuilder builder = new DefaultSettingsBuilderFactory().newInstance();
+ assertNotNull(builder);
+
+ boolean testActive = true;
+ int testPort = 2026;
+ Properties userProperties = new Properties();
+ userProperties.setProperty("test.active", Boolean.toString(testActive));
+ userProperties.setProperty("test.port", Integer.toString(testPort));
+ userProperties.setProperty("maven.settings.strictParsing", Boolean.TRUE.toString());
+
+ DefaultSettingsBuildingRequest request = new DefaultSettingsBuildingRequest();
+ request.setUserProperties(userProperties);
+ request.setSystemProperties(System.getProperties());
+ request.setUserSettingsFile(getSettings("proxy"));
+
+ SettingsBuildingResult result = builder.build(request);
+ assertNotNull(result);
+ assertNotNull(result.getEffectiveSettings());
+ assertEquals(
+ testActive, result.getEffectiveSettings().getProxies().get(0).isActive());
+ assertEquals(testPort, result.getEffectiveSettings().getProxies().get(0).getPort());
+ }
+
+ @Test
+ public void testNonStringInterpolationNonHappyPath() {
+ SettingsBuilder builder = new DefaultSettingsBuilderFactory().newInstance();
+ assertNotNull(builder);
+
+ Properties userProperties = new Properties();
+ userProperties.setProperty("test.active", "yes");
+ userProperties.setProperty("test.port", "foo");
+ userProperties.setProperty("maven.settings.strictParsing", Boolean.TRUE.toString());
+
+ DefaultSettingsBuildingRequest request = new DefaultSettingsBuildingRequest();
+ request.setUserProperties(userProperties);
+ request.setSystemProperties(System.getProperties());
+ request.setUserSettingsFile(getSettings("proxy"));
+
+ assertThrows(SettingsBuildingException.class, () -> builder.build(request));
+ }
+
+ @Test
+ public void testNonStringInterpolationMissingProperties() {
+ SettingsBuilder builder = new DefaultSettingsBuilderFactory().newInstance();
+ assertNotNull(builder);
+
+ Properties userProperties = new Properties();
+ userProperties.setProperty("maven.settings.strictParsing", Boolean.TRUE.toString());
+
+ DefaultSettingsBuildingRequest request = new DefaultSettingsBuildingRequest();
+ request.setUserProperties(userProperties);
+ request.setSystemProperties(System.getProperties());
+ request.setUserSettingsFile(getSettings("proxy"));
+
+ assertThrows(SettingsBuildingException.class, () -> builder.build(request));
+ }
}
diff --git a/maven-settings-builder/src/test/resources/settings/factory/proxy.xml b/maven-settings-builder/src/test/resources/settings/factory/proxy.xml
new file mode 100644
index 0000000000..0503cb5677
--- /dev/null
+++ b/maven-settings-builder/src/test/resources/settings/factory/proxy.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!--
+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.
+-->
+
+<settings>
+ <localRepository>${user.home}/.m2/repository</localRepository>
+ <proxies>
+ <proxy>
+ <id>test</id>
+ <active>${test.active}</active>
+ <host>somehost.org</host>
+ <port>${test.port}</port>
+ </proxy>
+ </proxies>
+</settings>