8000 [MNG-7774] Maven config and command line interpolation (#1098) · apache/maven@79556dd · GitHub
[go: up one dir, main page]

Skip to content

Commit 79556dd

Browse files
authored
[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
1 parent 7cb87a6 commit 79556dd

File tree

3 files changed

+216
-32
lines changed

3 files changed

+216
-32
lines changed

maven-embedder/src/main/java/org/apache/maven/cli/CliRequest.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
package org.apache.maven.cli;
2020

2121
import java.io.File;
22+
import java.nio.file.Path;
2223
import java.util.Properties;
2324

2425
import org.apache.commons.cli.CommandLine;
@@ -40,6 +41,10 @@ public class CliRequest {
4041

4142
File multiModuleProjectDirectory;
4243

44+
Path rootDirectory;
45+
46+
Path topDirectory;
47+
4348
boolean debug;
4449

4550
boolean quiet;

maven-embedder/src/main/java/org/apache/maven/cli/MavenCli.java

Lines changed: 175 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -29,11 +29,14 @@
2929
import java.io.PrintStream;
3030
import java.nio.charset.Charset;
3131
import java.nio.file.Files;
32+
import java.nio.file.Path;
33+
import java.nio.file.Paths;
3234
import java.util.ArrayList;
3335
import java.util.Collections;
3436
import java.util.HashSet;
3537
import java.util.LinkedHashMap;
3638
import java.util.List;
39+
import java.util.ListIterator;
3740
import java.util.Map;
3841
import java.util.Map.Entry;
3942
import java.util.Properties;
@@ -102,6 +105,9 @@
102105
import org.codehaus.plexus.classworlds.realm.ClassRealm;
103106
import org.codehaus.plexus.classworlds.realm.NoSuchRealmException;
104107
import org.codehaus.plexus.component.repository.exception.ComponentLookupException;
108+
import org.codehaus.plexus.interpolation.AbstractValueSource;
109+
import org.codehaus.plexus.interpolation.InterpolationException;
110+
import org.codehaus.plexus.interpolation.StringSearchInterpolator;
105111
import org.codehaus.plexus.logging.LoggerManager;
106112
import org.codehaus.plexus.util.StringUtils;
107113
import org.codehaus.plexus.util.xml.pull.XmlPullParserException;
@@ -140,9 +146,14 @@ public class MavenCli {
140146

141147
private static final String EXT_CLASS_PATH = "maven.ext.class.path";
142148

143-
private static final String EXTENSIONS_FILENAME = ".mvn/extensions.xml";
149+
private static final String DOT_MVN = ".mvn";
144150

145-
private static final String MVN_MAVEN_CONFIG = ".mvn/maven.config";
151+
private static final String UNABLE_TO_FIND_ROOT_PROJECT_MESSAGE = "Unable to find the root directory. Create a "
152+
+ DOT_MVN + " directory in the project root directory to identify it.";
153+
154+
private static final String EXTENSIONS_FILENAME = DOT_MVN + "/extensions.xml";
155+
156+
private static final String MVN_MAVEN_CONFIG = DOT_MVN + "/maven.config";
146157

147158
public static final String STYLE_COLOR_PROPERTY = "style.color";
148159

@@ -309,6 +320,47 @@ void initialize(CliRequest cliRequest) throws ExitException {
309320
}
310321
}
311322

323+
// We need to locate the top level project which may be pointed at using
324+
// the -f/--file option. However, the command line isn't parsed yet, so
325+
// we need to iterate through the args to find it and act upon it.
326+
Path topDirectory = Paths.get(cliRequest.workingDirectory);
327+
boolean isAltFile = false;
328+
for (String arg : cliRequest.args) {
329+
if (isAltFile) {
330+
// this is the argument following -f/--file
331+
Path path = topDirectory.resolve(arg);
332+
if (Files.isDirectory(path)) {
333+
topDirectory = path;
334+
} else if (Files.isRegularFile(path)) {
335+
topDirectory = path.getParent();
336+
if (!Files.isDirectory(topDirectory)) {
337+
System.err.println("Directory " + topDirectory
338+
+ " extracted from the -f/--file command-line argument " + arg + " does not exist");
339+
throw new ExitException(1);
340+
}
341+
} else {
342+
System.err.println(
343+
"POM file " + arg + " specified with the -f/--file command line argument does not exist");
344+
throw new ExitException(1);
345+
}
346+
break;
347+
} else {
348+
// Check if this is the -f/--file option
349+
isAltFile = arg.equals(String.valueOf(CLIManager.ALTERNATE_POM_FILE)) || arg.equals("file");
350+
}
351+
}
352+
try {
353+
topDirectory = topDirectory.toAbsolutePath().toRealPath();
354+
} catch (IOException e) {
355+
System.err.println("Error computing real path from " + topDirectory + ": " + e.getMessage());
356+
throw new ExitException(1);
357+
}
358+
cliRequest.topDirectory = topDirectory;
359+
// We're very early in the process and we don't have the container set up yet,
360+
// so we on searchAcceptableRootDirectory method to find us acceptable directory.
361+
// The method may return null if nothing acceptable found.
362+
cliRequest.rootDirectory = searchAcceptableRootDirectory(topDirectory);
363+
312364
//
313365
// Make sure the Maven home directory is an absolute path to save us from confusion with say drive-relative
314366
// Windows paths.
@@ -526,8 +578,39 @@ private void commands(CliRequest cliRequest) {
526578

527579
// Needed to make this method package visible to make writing a unit test possible
528580
// Maybe it's better to move some of those methods to separate class (SoC).
529-
void properties(CliRequest cliRequest) {
530-
populateProperties(cliRequest.commandLine, cliRequest.systemProperties, cliRequest.userProperties);
581+
void properties(CliRequest cliRequest) throws ExitException {
582+
try {
583+
populateProperties(cliRequest, cliRequest.systemProperties, cliRequest.userProperties);
584+
585+
StringSearchInterpolator interpolator =
586+
createInterpolator(cliRequest, cliRequest.systemProperties, cliRequest.userProperties);
587+
CommandLine.Builder commandLineBuilder = new CommandLine.Builder();
588+
for (Option option : cliRequest.commandLine.getOptions()) {
589+
if (!String.valueOf(CLIManager.SET_USER_PROPERTY).equals(option.getOpt())) {
590+
List<String> values = option.getValuesList();
591+
for (ListIterator<String> it = values.listIterator(); it.hasNext(); ) {
592+
it.set(interpolator.interpolate(it.next()));
593+
}
594+
}
595+
commandLineBuilder.addOption(option);
596+
}
597+
for (String arg : cliRequest.commandLine.getArgList()) {
598+
commandLineBuilder.addArg(interpolator.interpolate(arg));
599+
}
600+
cliRequest.commandLine = commandLineBuilder.build();
601+
} catch (InterpolationException e) {
602+
String message = "ERROR: Could not interpolate properties and/or arguments: " + e.getMessage();
603+
System.err.println(message);
604+
throw new ExitException(1); // user error
605+
} catch (IllegalUseOfUndefinedProperty e) {
606+
String message = "ERROR: Illegal use of undefined property: " + e.property;
607+
System.err.println(message);
608+
if (cliRequest.rootDirectory == null) {
609+
System.err.println();
610+
System.err.println(UNABLE_TO_FIND_ROOT_PROJECT_MESSAGE);
611+
}
612+
throw new ExitException(1); // user error
613+
}
531614
}
532615

533616
PlexusContainer container(CliRequest cliRequest) throws Exception {
@@ -1405,27 +1488,54 @@ int calculateDegreeOfConcurrency(String threadConfiguration) {
14051488
// Properties handling
14061489
// ----------------------------------------------------------------------
14071490

1408-
static void populateProperties(CommandLine commandLine, Properties systemProperties, Properties userProperties) {
1409-
EnvironmentUtils.addEnvVars(systemProperties);
1491+
static void populateProperties(CliRequest cliRequest, Properties systemProperties, Properties userProperties)
1492+
throws InterpolationException {
14101493

14111494
// ----------------------------------------------------------------------
14121495
// Options that are set on the command line become system properties
14131496
// and therefore are set in the session properties. System properties
14141497
// are most dominant.
14151498
// ----------------------------------------------------------------------
14161499

1417-
if (commandLine.hasOption(CLIManager.SET_USER_PROPERTY)) {
1418-
String[] defStrs = commandLine.getOptionValues(CLIManager.SET_USER_PROPERTY);
1500+
Properties cliProperties = new Properties();
1501+
if (cliRequest.commandLine.hasOption(CLIManager.SET_USER_PROPERTY)) {
1502+
String[] defStrs = cliRequest.commandLine.getOptionValues(CLIManager.SET_USER_PROPERTY);
14191503

14201504
if (defStrs != null) {
1421-
for (String defStr : defStrs) {
1422-
setCliProperty(defStr, userProperties);
1505+
String name;
1506+
String value;
1507+
for (String property : defStrs) {
1508+
int i = property.indexOf('=');
1509+
if (i <= 0) {
1510+
name = property.trim();
1511+
value = "true";
1512+
} else {
1513+
name = property.substring(0, i).trim();
1514+
value = property.substring(i + 1);
1515+
}
1516+
cliProperties.setProperty(name, value);
14231517
}
14241518
}
14251519
}
14261520

1521+
EnvironmentUtils.addEnvVars(systemProperties);
14271522
SystemProperties.addSystemProperties(systemProperties);
14281523

1524+
StringSearchInterpolator interpolator = createInterpolator(cliRequest, cliProperties, systemProperties);
1525+
for (Map.Entry<Object, Object> e : cliProperties.entrySet()) {
1526+
String name = (String) e.getKey();
1527+
String value = interpolator.interpolate((String) e.getValue());
1528+
userProperties.setProperty(name, value);
1529+
}
1530+
1531+
systemProperties.putAll(userProperties);
1532+
1533+
// ----------------------------------------------------------------------
1534+
// I'm leaving the setting of system properties here as not to break
1535+
// the SystemPropertyProfileActivator. This won't harm embedding. jvz.
1536+
// ----------------------------------------------------------------------
1537+
userProperties.forEach((k, v) -> System.setProperty((String) k, (String) v));
1538+
14291539
// ----------------------------------------------------------------------
14301540
// Properties containing info about the currently running version of Maven
14311541
// These override any corresponding properties set on the command line
@@ -1440,31 +1550,56 @@ static void populateProperties(CommandLine commandLine, Properties systemPropert
14401550
systemProperties.setProperty("maven.build.version", mavenBuildVersion);
14411551
}
14421552

< F438 /td>
1443-
private static void setCliProperty(String property, Properties properties) {
1444-
String name;
1445-
1446-
String value;
1447-
1448-
int i = property.indexOf('=');
1449-
1450-
if (i <= 0) {
1451-
name = property.trim();
1452-
1453-
value = "true";
1454-
} else {
1455-
name = property.substring(0, i).trim();
1553+
protected boolean isAcceptableRootDirectory(Path path) {
1554+
return path != null && Files.isDirectory(path.resolve(DOT_MVN));
1555+
}
14561556

1457-
value = property.substring(i + 1);
1557+
protected Path searchAcceptableRootDirectory(Path path) {
1558+
if (path == null) {
1559+
return null;
14581560
}
1561+
if (isAcceptableRootDirectory(path)) {
1562+
return path;
1563+
}
1564+
return searchAcceptableRootDirectory(path.getParent());
1565+
}
14591566

1460-
properties.setProperty(name, value);
1461-
1462-
// ----------------------------------------------------------------------
1463-
// I'm leaving the setting of system properties here as not to break
1464-
// the SystemPropertyProfileActivator. This won't harm embedding. jvz.
1465-
// ----------------------------------------------------------------------
1466-
1467-
System.setProperty(name, value);
1567+
protected static StringSearchInterpolator createInterpolator(CliRequest cliRequest, Properties... properties) {
1568+
StringSearchInterpolator interpolator = new StringSearchInterpolator();
1569+
interpolator.addValueSource(new AbstractValueSource(false) {
1570+
@Override
1571+
public Object getValue(String expression) {
1572+
if ("session.topDirectory".equals(expression)) {
1573+
Path topDirectory = cliRequest.topDirectory;
1574+
if (topDirectory != null) {
1575+
return topDirectory.toString();
1576+
} else {
1577+
throw new IllegalUseOfUndefinedProperty(expression);
1578+
}
1579+
} else if ("session.rootDirectory".equals(expression)) {
1580+
Path rootDirectory = cliRequest.rootDirectory;
1581+
if (rootDirectory != null) {
1582+
return rootDirectory.toString();
1583+
} else {
1584+
throw new IllegalUseOfUndefinedProperty(expression);
1585+
}
1586+
}
1587+
return null;
1588+
}
1589+
});
1590+
interpolator.addValueSource(new AbstractValueSource(false) {
1591+
@Override
1592+
public Object getValue(String expression) {
1593+
for (Properties props : properties) {
1594+
Object val = props.getProperty(expression);
1595+
if (val != null) {
1596+
return val;
1597+
}
1598+
}
1599+
return null;
1600+
}
1601+
});
1602+
return interpolator;
14681603
}
14691604

14701605
static class ExitException extends Exception {
@@ -1475,6 +1610,14 @@ static class ExitException extends Exception {
14751610
}
14761611
}
14771612

1613+
static class IllegalUseOfUndefinedProperty extends IllegalArgumentException {
1614+
final String property;
1615+
1616+
IllegalUseOfUndefinedProperty(String property) {
1617+
this.property = property;
1618+
}
1619+
}
1620+
14781621
//
14791622
// Customizations available via the CLI
14801623
//

maven-embedder/src/test/java/org/apache/maven/cli/MavenCliTest.java

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import java.io.File;
2323
import java.io.PrintStream;
2424
import java.nio.charset.StandardCharsets;
25+
import java.nio.file.Paths;
2526

2627
import org.apache.commons.cli.ParseException;
2728
import org.apache.maven.Maven;
@@ -36,6 +37,9 @@
3637
import org.junit.function.ThrowingRunnable;
3738
import org.mockito.InOrder;
3839

40+
import static org.hamcrest.MatcherAssert.assertThat;
41+
import static org.hamcrest.Matchers.equalTo;
42+
import static org.hamcrest.Matchers.is;
3943
import static org.junit.Assert.assertEquals;
4044
import static org.junit.Assert.assertFalse;
4145
import static org.junit.Assert.assertThrows;
@@ -364,6 +368,38 @@ public void testVersionStringWithoutAnsi() throws Exception {
364368
assertEquals(MessageUtils.stripAnsiCodes(versionOut), versionOut);
365369
}
366370

371+
@Test
372+
public void testPropertiesInterpolation() throws Exception {
373+
// Arrange
374+
CliRequest request = new CliRequest(
375+
new String[] {
376+
"-Dfoo=bar",
377+
"-DvalFound=s${foo}i",
378+
"-DvalNotFound=s${foz}i",
379+
"-DvalRootDirectory=${session.rootDirectory}/.mvn/foo",
380+
"-DvalTopDirectory=${session.topDirectory}/pom.xml",
381+
"-f",
382+
"${session.rootDirectory}/my-child",
383+
"prefix:3.0.0:${foo}",
384+
"validate"
385+
},
386+
null);
387+
request.rootDirectory = Paths.get("myRootDirectory");
388+
request.topDirectory = Paths.get("myTopDirectory");
389+
390+
// Act
391+
cli.cli(request);
392+
cli.properties(request);
393+
394+
// Assert
395+
assertThat(request.getUserProperties().getProperty("valFound"), is("sbari"));
396+
assertThat(request.getUserProperties().getProperty("valNotFound"), is("s${foz}i"));
397+
assertThat(request.getUserProperties().getProperty("valRootDirectory"), is("myRootDirectory/.mvn/foo"));
398+
assertThat(request.getUserProperties().getProperty("valTopDirectory"), is("myTopDirectory/pom.xml"));
399+
assertThat(request.getCommandLine().getOptionValue('f'), is("myRootDirectory/my-child"));
400+
assertThat(request.getCommandLine().getArgs(), equalTo(new String[] {"prefix:3.0.0:bar", "validate"}));
401+
}
402+
367403
class ConcurrencyCalculator implements ThrowingRunnable {
368404

369405
private final String value;

0 commit comments

Comments
 (0)
0