This is an automated email from the ASF dual-hosted git repository.

cstamas pushed a commit to branch maven-3.9.x
in repository https://gitbox.apache.org/repos/asf/maven.git


The following commit(s) were added to refs/heads/maven-3.9.x by this push:
     new 79556ddc6 [MNG-7774] Maven config and command line interpolation 
(#1098)
79556ddc6 is described below

commit 79556ddc6b1a560cc61ca9fe6a81be0b53fd1e09
Author: Tamas Cservenak <[email protected]>
AuthorDate: Fri May 5 14:01:38 2023 +0200

    [MNG-7774] Maven config and command line interpolation (#1098)
    
    Reuse as much as possible from master, but keep existing stuff like 
multiModuleProjectDirectory alone.
    
    Changes:
    * interpolate user properties and arguments
    * introduce session.topDirectory and session.rootDirectory expressions (for 
interpolation only)
    * Maven fails to start if any of the new properties are undefined but their 
use is attempted
    * leave everything else untouched
    
    ---
    
    https://issues.apache.org/jira/browse/MNG-7774
---
 .../main/java/org/apache/maven/cli/CliRequest.java |   5 +
 .../main/java/org/apache/maven/cli/MavenCli.java   | 207 +++++++++++++++++----
 .../java/org/apache/maven/cli/MavenCliTest.java    |  36 ++++
 3 files changed, 216 insertions(+), 32 deletions(-)

diff --git a/maven-embedder/src/main/java/org/apache/maven/cli/CliRequest.java 
b/maven-embedder/src/main/java/org/apache/maven/cli/CliRequest.java
index 15439def8..4c48de392 100644
--- a/maven-embedder/src/main/java/org/apache/maven/cli/CliRequest.java
+++ b/maven-embedder/src/main/java/org/apache/maven/cli/CliRequest.java
@@ -19,6 +19,7 @@
 package org.apache.maven.cli;
 
 import java.io.File;
+import java.nio.file.Path;
 import java.util.Properties;
 
 import org.apache.commons.cli.CommandLine;
@@ -40,6 +41,10 @@ public class CliRequest {
 
     File multiModuleProjectDirectory;
 
+    Path rootDirectory;
+
+    Path topDirectory;
+
     boolean debug;
 
     boolean quiet;
diff --git a/maven-embedder/src/main/java/org/apache/maven/cli/MavenCli.java 
b/maven-embedder/src/main/java/org/apache/maven/cli/MavenCli.java
index fa667e9a0..08187d152 100644
--- a/maven-embedder/src/main/java/org/apache/maven/cli/MavenCli.java
+++ b/maven-embedder/src/main/java/org/apache/maven/cli/MavenCli.java
@@ -29,11 +29,14 @@ import java.io.InputStream;
 import java.io.PrintStream;
 import java.nio.charset.Charset;
 import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.HashSet;
 import java.util.LinkedHashMap;
 import java.util.List;
+import java.util.ListIterator;
 import java.util.Map;
 import java.util.Map.Entry;
 import java.util.Properties;
@@ -102,6 +105,9 @@ import org.codehaus.plexus.classworlds.ClassWorld;
 import org.codehaus.plexus.classworlds.realm.ClassRealm;
 import org.codehaus.plexus.classworlds.realm.NoSuchRealmException;
 import 
org.codehaus.plexus.component.repository.exception.ComponentLookupException;
+import org.codehaus.plexus.interpolation.AbstractValueSource;
+import org.codehaus.plexus.interpolation.InterpolationException;
+import org.codehaus.plexus.interpolation.StringSearchInterpolator;
 import org.codehaus.plexus.logging.LoggerManager;
 import org.codehaus.plexus.util.StringUtils;
 import org.codehaus.plexus.util.xml.pull.XmlPullParserException;
@@ -140,9 +146,14 @@ public class MavenCli {
 
     private static final String EXT_CLASS_PATH = "maven.ext.class.path";
 
-    private static final String EXTENSIONS_FILENAME = ".mvn/extensions.xml";
+    private static final String DOT_MVN = ".mvn";
 
-    private static final String MVN_MAVEN_CONFIG = ".mvn/maven.config";
+    private static final String UNABLE_TO_FIND_ROOT_PROJECT_MESSAGE = "Unable 
to find the root directory. Create a "
+            + DOT_MVN + " directory in the project root directory to identify 
it.";
+
+    private static final String EXTENSIONS_FILENAME = DOT_MVN + 
"/extensions.xml";
+
+    private static final String MVN_MAVEN_CONFIG = DOT_MVN + "/maven.config";
 
     public static final String STYLE_COLOR_PROPERTY = "style.color";
 
@@ -309,6 +320,47 @@ public class MavenCli {
             }
         }
 
+        // We need to locate the top level project which may be pointed at 
using
+        // the -f/--file option.  However, the command line isn't parsed yet, 
so
+        // we need to iterate through the args to find it and act upon it.
+        Path topDirectory = Paths.get(cliRequest.workingDirectory);
+        boolean isAltFile = false;
+        for (String arg : cliRequest.args) {
+            if (isAltFile) {
+                // this is the argument following -f/--file
+                Path path = topDirectory.resolve(arg);
+                if (Files.isDirectory(path)) {
+                    topDirectory = path;
+                } else if (Files.isRegularFile(path)) {
+                    topDirectory = path.getParent();
+                    if (!Files.isDirectory(topDirectory)) {
+                        System.err.println("Directory " + topDirectory
+                                + " extracted from the -f/--file command-line 
argument " + arg + " does not exist");
+                        throw new ExitException(1);
+                    }
+                } else {
+                    System.err.println(
+                            "POM file " + arg + " specified with the -f/--file 
command line argument does not exist");
+                    throw new ExitException(1);
+                }
+                break;
+            } else {
+                // Check if this is the -f/--file option
+                isAltFile = 
arg.equals(String.valueOf(CLIManager.ALTERNATE_POM_FILE)) || arg.equals("file");
+            }
+        }
+        try {
+            topDirectory = topDirectory.toAbsolutePath().toRealPath();
+        } catch (IOException e) {
+            System.err.println("Error computing real path from " + 
topDirectory + ": " + e.getMessage());
+            throw new ExitException(1);
+        }
+        cliRequest.topDirectory = topDirectory;
+        // We're very early in the process and we don't have the container set 
up yet,
+        // so we on searchAcceptableRootDirectory method to find us acceptable 
directory.
+        // The method may return null if nothing acceptable found.
+        cliRequest.rootDirectory = searchAcceptableRootDirectory(topDirectory);
+
         //
         // Make sure the Maven home directory is an absolute path to save us 
from confusion with say drive-relative
         // Windows paths.
@@ -526,8 +578,39 @@ public class MavenCli {
 
     // Needed to make this method package visible to make writing a unit test 
possible
     // Maybe it's better to move some of those methods to separate class (SoC).
-    void properties(CliRequest cliRequest) {
-        populateProperties(cliRequest.commandLine, 
cliRequest.systemProperties, cliRequest.userProperties);
+    void properties(CliRequest cliRequest) throws ExitException {
+        try {
+            populateProperties(cliRequest, cliRequest.systemProperties, 
cliRequest.userProperties);
+
+            StringSearchInterpolator interpolator =
+                    createInterpolator(cliRequest, 
cliRequest.systemProperties, cliRequest.userProperties);
+            CommandLine.Builder commandLineBuilder = new CommandLine.Builder();
+            for (Option option : cliRequest.commandLine.getOptions()) {
+                if 
(!String.valueOf(CLIManager.SET_USER_PROPERTY).equals(option.getOpt())) {
+                    List<String> values = option.getValuesList();
+                    for (ListIterator<String> it = values.listIterator(); 
it.hasNext(); ) {
+                        it.set(interpolator.interpolate(it.next()));
+                    }
+                }
+                commandLineBuilder.addOption(option);
+            }
+            for (String arg : cliRequest.commandLine.getArgList()) {
+                commandLineBuilder.addArg(interpolator.interpolate(arg));
+            }
+            cliRequest.commandLine = commandLineBuilder.build();
+        } catch (InterpolationException e) {
+            String message = "ERROR: Could not interpolate properties and/or 
arguments: " + e.getMessage();
+            System.err.println(message);
+            throw new ExitException(1); // user error
+        } catch (IllegalUseOfUndefinedProperty e) {
+            String message = "ERROR: Illegal use of undefined property: " + 
e.property;
+            System.err.println(message);
+            if (cliRequest.rootDirectory == null) {
+                System.err.println();
+                System.err.println(UNABLE_TO_FIND_ROOT_PROJECT_MESSAGE);
+            }
+            throw new ExitException(1); // user error
+        }
     }
 
     PlexusContainer container(CliRequest cliRequest) throws Exception {
@@ -1405,8 +1488,8 @@ public class MavenCli {
     // Properties handling
     // ----------------------------------------------------------------------
 
-    static void populateProperties(CommandLine commandLine, Properties 
systemProperties, Properties userProperties) {
-        EnvironmentUtils.addEnvVars(systemProperties);
+    static void populateProperties(CliRequest cliRequest, Properties 
systemProperties, Properties userProperties)
+            throws InterpolationException {
 
         // 
----------------------------------------------------------------------
         // Options that are set on the command line become system properties
@@ -1414,18 +1497,45 @@ public class MavenCli {
         // are most dominant.
         // 
----------------------------------------------------------------------
 
-        if (commandLine.hasOption(CLIManager.SET_USER_PROPERTY)) {
-            String[] defStrs = 
commandLine.getOptionValues(CLIManager.SET_USER_PROPERTY);
+        Properties cliProperties = new Properties();
+        if (cliRequest.commandLine.hasOption(CLIManager.SET_USER_PROPERTY)) {
+            String[] defStrs = 
cliRequest.commandLine.getOptionValues(CLIManager.SET_USER_PROPERTY);
 
             if (defStrs != null) {
-                for (String defStr : defStrs) {
-                    setCliProperty(defStr, userProperties);
+                String name;
+                String value;
+                for (String property : defStrs) {
+                    int i = property.indexOf('=');
+                    if (i <= 0) {
+                        name = property.trim();
+                        value = "true";
+                    } else {
+                        name = property.substring(0, i).trim();
+                        value = property.substring(i + 1);
+                    }
+                    cliProperties.setProperty(name, value);
                 }
             }
         }
 
+        EnvironmentUtils.addEnvVars(systemProperties);
         SystemProperties.addSystemProperties(systemProperties);
 
+        StringSearchInterpolator interpolator = createInterpolator(cliRequest, 
cliProperties, systemProperties);
+        for (Map.Entry<Object, Object> e : cliProperties.entrySet()) {
+            String name = (String) e.getKey();
+            String value = interpolator.interpolate((String) e.getValue());
+            userProperties.setProperty(name, value);
+        }
+
+        systemProperties.putAll(userProperties);
+
+        // 
----------------------------------------------------------------------
+        // I'm leaving the setting of system properties here as not to break
+        // the SystemPropertyProfileActivator. This won't harm embedding. jvz.
+        // 
----------------------------------------------------------------------
+        userProperties.forEach((k, v) -> System.setProperty((String) k, 
(String) v));
+
         // 
----------------------------------------------------------------------
         // Properties containing info about the currently running version of 
Maven
         // These override any corresponding properties set on the command line
@@ -1440,31 +1550,56 @@ public class MavenCli {
         systemProperties.setProperty("maven.build.version", mavenBuildVersion);
     }
 
-    private static void setCliProperty(String property, Properties properties) 
{
-        String name;
-
-        String value;
-
-        int i = property.indexOf('=');
-
-        if (i <= 0) {
-            name = property.trim();
-
-            value = "true";
-        } else {
-            name = property.substring(0, i).trim();
+    protected boolean isAcceptableRootDirectory(Path path) {
+        return path != null && Files.isDirectory(path.resolve(DOT_MVN));
+    }
 
-            value = property.substring(i + 1);
+    protected Path searchAcceptableRootDirectory(Path path) {
+        if (path == null) {
+            return null;
         }
+        if (isAcceptableRootDirectory(path)) {
+            return path;
+        }
+        return searchAcceptableRootDirectory(path.getParent());
+    }
 
-        properties.setProperty(name, value);
-
-        // 
----------------------------------------------------------------------
-        // I'm leaving the setting of system properties here as not to break
-        // the SystemPropertyProfileActivator. This won't harm embedding. jvz.
-        // 
----------------------------------------------------------------------
-
-        System.setProperty(name, value);
+    protected static StringSearchInterpolator createInterpolator(CliRequest 
cliRequest, Properties... properties) {
+        StringSearchInterpolator interpolator = new StringSearchInterpolator();
+        interpolator.addValueSource(new AbstractValueSource(false) {
+            @Override
+            public Object getValue(String expression) {
+                if ("session.topDirectory".equals(expression)) {
+                    Path topDirectory = cliRequest.topDirectory;
+                    if (topDirectory != null) {
+                        return topDirectory.toString();
+                    } else {
+                        throw new IllegalUseOfUndefinedProperty(expression);
+                    }
+                } else if ("session.rootDirectory".equals(expression)) {
+                    Path rootDirectory = cliRequest.rootDirectory;
+                    if (rootDirectory != null) {
+                        return rootDirectory.toString();
+                    } else {
+                        throw new IllegalUseOfUndefinedProperty(expression);
+                    }
+                }
+                return null;
+            }
+        });
+        interpolator.addValueSource(new AbstractValueSource(false) {
+            @Override
+            public Object getValue(String expression) {
+                for (Properties props : properties) {
+                    Object val = props.getProperty(expression);
+                    if (val != null) {
+                        return val;
+                    }
+                }
+                return null;
+            }
+        });
+        return interpolator;
     }
 
     static class ExitException extends Exception {
@@ -1475,6 +1610,14 @@ public class MavenCli {
         }
     }
 
+    static class IllegalUseOfUndefinedProperty extends 
IllegalArgumentException {
+        final String property;
+
+        IllegalUseOfUndefinedProperty(String property) {
+            this.property = property;
+        }
+    }
+
     //
     // Customizations available via the CLI
     //
diff --git 
a/maven-embedder/src/test/java/org/apache/maven/cli/MavenCliTest.java 
b/maven-embedder/src/test/java/org/apache/maven/cli/MavenCliTest.java
index 384bf02f3..ddeceba5d 100644
--- a/maven-embedder/src/test/java/org/apache/maven/cli/MavenCliTest.java
+++ b/maven-embedder/src/test/java/org/apache/maven/cli/MavenCliTest.java
@@ -22,6 +22,7 @@ import java.io.ByteArrayOutputStream;
 import java.io.File;
 import java.io.PrintStream;
 import java.nio.charset.StandardCharsets;
+import java.nio.file.Paths;
 
 import org.apache.commons.cli.ParseException;
 import org.apache.maven.Maven;
@@ -36,6 +37,9 @@ import org.junit.Test;
 import org.junit.function.ThrowingRunnable;
 import org.mockito.InOrder;
 
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.equalTo;
+import static org.hamcrest.Matchers.is;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertThrows;
@@ -364,6 +368,38 @@ public class MavenCliTest {
         assertEquals(MessageUtils.stripAnsiCodes(versionOut), versionOut);
     }
 
+    @Test
+    public void testPropertiesInterpolation() throws Exception {
+        // Arrange
+        CliRequest request = new CliRequest(
+                new String[] {
+                    "-Dfoo=bar",
+                    "-DvalFound=s${foo}i",
+                    "-DvalNotFound=s${foz}i",
+                    "-DvalRootDirectory=${session.rootDirectory}/.mvn/foo",
+                    "-DvalTopDirectory=${session.topDirectory}/pom.xml",
+                    "-f",
+                    "${session.rootDirectory}/my-child",
+                    "prefix:3.0.0:${foo}",
+                    "validate"
+                },
+                null);
+        request.rootDirectory = Paths.get("myRootDirectory");
+        request.topDirectory = Paths.get("myTopDirectory");
+
+        // Act
+        cli.cli(request);
+        cli.properties(request);
+
+        // Assert
+        assertThat(request.getUserProperties().getProperty("valFound"), 
is("sbari"));
+        assertThat(request.getUserProperties().getProperty("valNotFound"), 
is("s${foz}i"));
+        
assertThat(request.getUserProperties().getProperty("valRootDirectory"), 
is("myRootDirectory/.mvn/foo"));
+        assertThat(request.getUserProperties().getProperty("valTopDirectory"), 
is("myTopDirectory/pom.xml"));
+        assertThat(request.getCommandLine().getOptionValue('f'), 
is("myRootDirectory/my-child"));
+        assertThat(request.getCommandLine().getArgs(), equalTo(new String[] 
{"prefix:3.0.0:bar", "validate"}));
+    }
+
     class ConcurrencyCalculator implements ThrowingRunnable {
 
         private final String value;

Reply via email to