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("&", "&amp;")
-                            .replace("<", "&lt;")
-                            .replace(">", "&gt;");
-                    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("&", "&amp;")
+                                .replace("<", "&lt;")
+                                .replace(">", "&gt;");
+                        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>