View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.maven.cli;
20  
21  import java.io.BufferedReader;
22  import java.io.Console;
23  import java.io.File;
24  import java.io.FileNotFoundException;
25  import java.io.FileOutputStream;
26  import java.io.IOException;
27  import java.io.PrintStream;
28  import java.nio.charset.StandardCharsets;
29  import java.nio.file.Files;
30  import java.nio.file.Path;
31  import java.nio.file.Paths;
32  import java.util.ArrayList;
33  import java.util.Collections;
34  import java.util.HashSet;
35  import java.util.LinkedHashMap;
36  import java.util.List;
37  import java.util.ListIterator;
38  import java.util.Map;
39  import java.util.Map.Entry;
40  import java.util.Objects;
41  import java.util.Properties;
42  import java.util.ServiceLoader;
43  import java.util.Set;
44  import java.util.StringTokenizer;
45  import java.util.regex.Matcher;
46  import java.util.regex.Pattern;
47  import java.util.stream.Stream;
48  
49  import com.google.inject.AbstractModule;
50  import org.apache.commons.cli.CommandLine;
51  import org.apache.commons.cli.Option;
52  import org.apache.commons.cli.ParseException;
53  import org.apache.commons.cli.UnrecognizedOptionException;
54  import org.apache.maven.BuildAbort;
55  import org.apache.maven.InternalErrorException;
56  import org.apache.maven.Maven;
57  import org.apache.maven.building.FileSource;
58  import org.apache.maven.building.Problem;
59  import org.apache.maven.building.Source;
60  import org.apache.maven.cli.configuration.ConfigurationProcessor;
61  import org.apache.maven.cli.configuration.SettingsXmlConfigurationProcessor;
62  import org.apache.maven.cli.event.DefaultEventSpyContext;
63  import org.apache.maven.cli.event.ExecutionEventLogger;
64  import org.apache.maven.cli.internal.BootstrapCoreExtensionManager;
65  import org.apache.maven.cli.internal.extension.model.CoreExtension;
66  import org.apache.maven.cli.internal.extension.model.io.xpp3.CoreExtensionsXpp3Reader;
67  import org.apache.maven.cli.logging.Slf4jConfiguration;
68  import org.apache.maven.cli.logging.Slf4jConfigurationFactory;
69  import org.apache.maven.cli.logging.Slf4jLoggerManager;
70  import org.apache.maven.cli.logging.Slf4jStdoutLogger;
71  import org.apache.maven.cli.transfer.ConsoleMavenTransferListener;
72  import org.apache.maven.cli.transfer.QuietMavenTransferListener;
73  import org.apache.maven.cli.transfer.SimplexTransferListener;
74  import org.apache.maven.cli.transfer.Slf4jMavenTransferListener;
75  import org.apache.maven.eventspy.internal.EventSpyDispatcher;
76  import org.apache.maven.exception.DefaultExceptionHandler;
77  import org.apache.maven.exception.ExceptionHandler;
78  import org.apache.maven.exception.ExceptionSummary;
79  import org.apache.maven.execution.DefaultMavenExecutionRequest;
80  import org.apache.maven.execution.ExecutionListener;
81  import org.apache.maven.execution.MavenExecutionRequest;
82  import org.apache.maven.execution.MavenExecutionRequestPopulationException;
83  import org.apache.maven.execution.MavenExecutionRequestPopulator;
84  import org.apache.maven.execution.MavenExecutionResult;
85  import org.apache.maven.execution.scope.internal.MojoExecutionScopeModule;
86  import org.apache.maven.extension.internal.CoreExports;
87  import org.apache.maven.extension.internal.CoreExtensionEntry;
88  import org.apache.maven.jline.MessageUtils;
89  import org.apache.maven.lifecycle.LifecycleExecutionException;
90  import org.apache.maven.message.MessageBuilder;
91  import org.apache.maven.model.building.ModelProcessor;
92  import org.apache.maven.model.interpolation.ModelInterpolator;
93  import org.apache.maven.model.root.RootLocator;
94  import org.apache.maven.project.MavenProject;
95  import org.apache.maven.properties.internal.EnvironmentUtils;
96  import org.apache.maven.properties.internal.SystemProperties;
97  import org.apache.maven.session.scope.internal.SessionScopeModule;
98  import org.apache.maven.toolchain.building.DefaultToolchainsBuildingRequest;
99  import org.apache.maven.toolchain.building.ToolchainsBuilder;
100 import org.apache.maven.toolchain.building.ToolchainsBuildingResult;
101 import org.codehaus.plexus.ContainerConfiguration;
102 import org.codehaus.plexus.DefaultContainerConfiguration;
103 import org.codehaus.plexus.DefaultPlexusContainer;
104 import org.codehaus.plexus.PlexusConstants;
105 import org.codehaus.plexus.PlexusContainer;
106 import org.codehaus.plexus.classworlds.ClassWorld;
107 import org.codehaus.plexus.classworlds.realm.ClassRealm;
108 import org.codehaus.plexus.classworlds.realm.NoSuchRealmException;
109 import org.codehaus.plexus.component.repository.exception.ComponentLookupException;
110 import org.codehaus.plexus.interpolation.AbstractValueSource;
111 import org.codehaus.plexus.interpolation.InterpolationException;
112 import org.codehaus.plexus.interpolation.StringSearchInterpolator;
113 import org.codehaus.plexus.logging.LoggerManager;
114 import org.codehaus.plexus.util.StringUtils;
115 import org.codehaus.plexus.util.xml.pull.XmlPullParserException;
116 import org.eclipse.aether.DefaultRepositoryCache;
117 import org.eclipse.aether.repository.RepositoryPolicy;
118 import org.eclipse.aether.transfer.TransferListener;
119 import org.slf4j.ILoggerFactory;
120 import org.slf4j.Logger;
121 import org.slf4j.LoggerFactory;
122 import org.sonatype.plexus.components.cipher.DefaultPlexusCipher;
123 import org.sonatype.plexus.components.sec.dispatcher.DefaultSecDispatcher;
124 import org.sonatype.plexus.components.sec.dispatcher.SecDispatcher;
125 import org.sonatype.plexus.components.sec.dispatcher.SecUtil;
126 import org.sonatype.plexus.components.sec.dispatcher.model.SettingsSecurity;
127 
128 import static org.apache.maven.cli.CLIManager.COLOR;
129 import static org.apache.maven.cli.ResolveFile.resolveFile;
130 import static org.apache.maven.jline.MessageUtils.builder;
131 
132 // TODO push all common bits back to plexus cli and prepare for transition to Guice. We don't need 50 ways to make CLIs
133 
134 /**
135  * @author Jason van Zyl
136  */
137 public class MavenCli {
138     public static final String LOCAL_REPO_PROPERTY = "maven.repo.local";
139 
140     public static final String MULTIMODULE_PROJECT_DIRECTORY = "maven.multiModuleProjectDirectory";
141 
142     public static final String USER_HOME = System.getProperty("user.home");
143 
144     public static final File USER_MAVEN_CONFIGURATION_HOME = new File(USER_HOME, ".m2");
145 
146     public static final File DEFAULT_USER_TOOLCHAINS_FILE = new File(USER_MAVEN_CONFIGURATION_HOME, "toolchains.xml");
147 
148     public static final File DEFAULT_GLOBAL_TOOLCHAINS_FILE =
149             new File(System.getProperty("maven.conf"), "toolchains.xml");
150 
151     private static final String EXT_CLASS_PATH = "maven.ext.class.path";
152 
153     private static final String DOT_MVN = ".mvn";
154 
155     private static final String PROJECT_EXTENSIONS_FILENAME = DOT_MVN + "/extensions.xml";
156 
157     private static final File USER_EXTENSIONS_FILE = new File(USER_MAVEN_CONFIGURATION_HOME, "extensions.xml");
158 
159     public static final File GLOBAL_EXTENSIONS_FILE = new File(System.getProperty("maven.conf"), "extensions.xml");
160 
161     private static final String MVN_MAVEN_CONFIG = DOT_MVN + "/maven.config";
162 
163     /**
164      * One "distinguished" env variable that we want to make into non-prefixed property, if present.
165      *
166      * @see ModelInterpolator
167      * @since 3.10.0
168      */
169     private static final String MAVEN_REPO_CENTRAL_ENV = "env.MAVEN_REPO_CENTRAL";
170 
171     public static final String STYLE_COLOR_PROPERTY = "style.color";
172 
173     private ClassWorld classWorld;
174 
175     private LoggerManager plexusLoggerManager;
176 
177     private ILoggerFactory slf4jLoggerFactory;
178 
179     private Logger slf4jLogger;
180 
181     private EventSpyDispatcher eventSpyDispatcher;
182 
183     private ModelProcessor modelProcessor;
184 
185     private Maven maven;
186 
187     private MavenExecutionRequestPopulator executionRequestPopulator;
188 
189     private ToolchainsBuilder toolchainsBuilder;
190 
191     private DefaultSecDispatcher dispatcher;
192 
193     private Map<String, ConfigurationProcessor> configurationProcessors;
194 
195     private CLIManager cliManager;
196 
197     private static final Pattern NEXT_LINE = Pattern.compile("\r?\n");
198 
199     public MavenCli() {
200         this(null);
201     }
202 
203     // This supports painless invocation by the Verifier during embedded execution of the core ITs
204     public MavenCli(ClassWorld classWorld) {
205         this.classWorld = classWorld;
206     }
207 
208     public static void main(String[] args) {
209         int result = main(args, null);
210 
211         System.exit(result);
212     }
213 
214     public static int main(String[] args, ClassWorld classWorld) {
215         MavenCli cli = new MavenCli();
216 
217         MessageUtils.systemInstall();
218         MessageUtils.registerShutdownHook();
219         int result = cli.doMain(new CliRequest(args, classWorld));
220         MessageUtils.systemUninstall();
221 
222         return result;
223     }
224 
225     // TODO need to externalize CliRequest
226     public static int doMain(String[] args, ClassWorld classWorld) {
227         MavenCli cli = new MavenCli();
228         return cli.doMain(new CliRequest(args, classWorld));
229     }
230 
231     /**
232      * This supports painless invocation by the Verifier during embedded execution of the core ITs.
233      * See <a href="http://maven.apache.org/shared/maven-verifier/xref/org/apache/maven/it/Embedded3xLauncher.html">
234      * <code>Embedded3xLauncher</code> in <code>maven-verifier</code></a>
235      */
236     public int doMain(String[] args, String workingDirectory, PrintStream stdout, PrintStream stderr) {
237         PrintStream oldout = System.out;
238         PrintStream olderr = System.err;
239 
240         final Set<String> realms;
241         if (classWorld != null) {
242             realms = new HashSet<>();
243             for (ClassRealm realm : classWorld.getRealms()) {
244                 realms.add(realm.getId());
245             }
246         } else {
247             realms = Collections.emptySet();
248         }
249 
250         try {
251             if (stdout != null) {
252                 MessageUtils.awaitTerminalInitialization();
253                 System.setOut(stdout);
254             }
255             if (stderr != null) {
256                 MessageUtils.awaitTerminalInitialization();
257                 System.setErr(stderr);
258             }
259 
260             CliRequest cliRequest = new CliRequest(args, classWorld);
261             cliRequest.workingDirectory = workingDirectory;
262 
263             return doMain(cliRequest);
264         } finally {
265             if (classWorld != null) {
266                 for (ClassRealm realm : new ArrayList<>(classWorld.getRealms())) {
267                     String realmId = realm.getId();
268                     if (!realms.contains(realmId)) {
269                         try {
270                             classWorld.disposeRealm(realmId);
271                         } catch (NoSuchRealmException ignored) {
272                             // can't happen
273                         }
274                     }
275                 }
276             }
277             System.setOut(oldout);
278             System.setErr(olderr);
279         }
280     }
281 
282     // TODO need to externalize CliRequest
283     public int doMain(CliRequest cliRequest) {
284         PlexusContainer localContainer = null;
285         try {
286             initialize(cliRequest);
287             cli(cliRequest);
288             properties(cliRequest);
289             setupGuiceClassLoading();
290             logging(cliRequest);
291             informativeCommands(cliRequest);
292             version(cliRequest);
293             localContainer = container(cliRequest);
294             commands(cliRequest);
295             configure(cliRequest);
296             toolchains(cliRequest);
297             populateRequest(cliRequest);
298             encryption(cliRequest);
299             return execute(cliRequest);
300         } catch (ExitException e) {
301             return e.exitCode;
302         } catch (UnrecognizedOptionException e) {
303             // pure user error, suppress stack trace
304             return 1;
305         } catch (BuildAbort e) {
306             CLIReportingUtils.showError(slf4jLogger, "ABORTED", e, cliRequest.showErrors);
307 
308             return 2;
309         } catch (Exception e) {
310             CLIReportingUtils.showError(slf4jLogger, "Error executing Maven.", e, cliRequest.showErrors);
311 
312             return 1;
313         } finally {
314             if (localContainer != null) {
315                 localContainer.dispose();
316             }
317         }
318     }
319 
320     void initialize(CliRequest cliRequest) throws ExitException {
321         if (cliRequest.workingDirectory == null) {
322             cliRequest.workingDirectory = System.getProperty("user.dir");
323         }
324 
325         if (cliRequest.multiModuleProjectDirectory == null) {
326             String basedirProperty = System.getProperty(MULTIMODULE_PROJECT_DIRECTORY);
327             if (basedirProperty == null) {
328                 System.err.format("-D%s system property is not set.", MULTIMODULE_PROJECT_DIRECTORY);
329                 throw new ExitException(1);
330             }
331             File basedir = basedirProperty != null ? new File(basedirProperty) : new File("");
332             try {
333                 cliRequest.multiModuleProjectDirectory = basedir.getCanonicalFile();
334             } catch (IOException e) {
335                 cliRequest.multiModuleProjectDirectory = basedir.getAbsoluteFile();
336             }
337         }
338 
339         // We need to locate the top level project which may be pointed at using
340         // the -f/--file option.  However, the command line isn't parsed yet, so
341         // we need to iterate through the args to find it and act upon it.
342         Path topDirectory = Paths.get(cliRequest.workingDirectory);
343         boolean isAltFile = false;
344         for (String arg : cliRequest.args) {
345             if (isAltFile) {
346                 // this is the argument following -f/--file
347                 Path path = topDirectory.resolve(stripLeadingAndTrailingQuotes(arg));
348                 if (Files.isDirectory(path)) {
349                     topDirectory = path;
350                 } else if (Files.isRegularFile(path)) {
351                     topDirectory = path.getParent();
352                     if (!Files.isDirectory(topDirectory)) {
353                         System.err.println("Directory " + topDirectory
354                                 + " extracted from the -f/--file command-line argument " + arg + " does not exist");
355                         throw new ExitException(1);
356                     }
357                 } else {
358                     System.err.println(
359                             "POM file " + arg + " specified with the -f/--file command line argument does not exist");
360                     throw new ExitException(1);
361                 }
362                 break;
363             } else {
364                 // Check if this is the -f/--file option
365                 isAltFile = arg.equals("-f") || arg.equals("--file");
366             }
367         }
368         topDirectory = getCanonicalPath(topDirectory);
369         cliRequest.request.setTopDirectory(topDirectory);
370         // We're very early in the process, and we don't have the container set up yet,
371         // so we rely on the JDK services to eventually look up a custom RootLocator.
372         // This is used to compute {@code session.rootDirectory} but all {@code project.rootDirectory}
373         // properties will be computed through the RootLocator found in the container.
374         RootLocator rootLocator =
375                 ServiceLoader.load(RootLocator.class).iterator().next();
376         cliRequest.request.setRootDirectory(rootLocator.findRoot(topDirectory));
377 
378         //
379         // Make sure the Maven home directory is an absolute path to save us from confusion with say drive-relative
380         // Windows paths.
381         //
382         String mavenHome = System.getProperty("maven.home");
383 
384         if (mavenHome != null) {
385             System.setProperty("maven.home", new File(mavenHome).getAbsolutePath());
386         }
387     }
388 
389     void cli(CliRequest cliRequest) throws Exception {
390         //
391         // Parsing errors can happen during the processing of the arguments and we prefer not having to check if
392         // the logger is null and construct this so we can use an SLF4J logger everywhere.
393         //
394         slf4jLogger = new Slf4jStdoutLogger();
395 
396         cliManager = new CLIManager();
397 
398         CommandLine mavenConfig = null;
399         try {
400             File configFile = new File(cliRequest.multiModuleProjectDirectory, MVN_MAVEN_CONFIG);
401 
402             if (configFile.isFile()) {
403                 try (Stream<String> lines = Files.lines(configFile.toPath(), StandardCharsets.UTF_8)) {
404                     String[] args = lines.filter(arg -> !arg.isEmpty() && !arg.startsWith("#"))
405                             .toArray(String[]::new);
406                     mavenConfig = cliManager.parse(args);
407                     List<?> unrecognized = mavenConfig.getArgList();
408                     if (!unrecognized.isEmpty()) {
409                         // This file can only contain options, not args (goals or phases)
410                         throw new ParseException("Unrecognized maven.config file entries: " + unrecognized);
411                     }
412                 }
413             }
414         } catch (ParseException e) {
415             System.err.println("Unable to parse maven.config file options: " + e.getMessage());
416             cliManager.displayHelp(System.out);
417             throw e;
418         }
419 
420         try {
421             CommandLine mavenCli = cliManager.parse(cliRequest.args);
422             if (mavenConfig == null) {
423                 cliRequest.commandLine = mavenCli;
424             } else {
425                 cliRequest.commandLine = cliMerge(mavenConfig, mavenCli);
426             }
427         } catch (ParseException e) {
428             System.err.println("Unable to parse command line options: " + e.getMessage());
429             cliManager.displayHelp(System.out);
430             throw e;
431         }
432 
433         // check for presence of unsupported command line options
434         try {
435             if (cliRequest.commandLine.hasOption("llr")) {
436                 throw new UnrecognizedOptionException("Option '-llr' is not supported starting with Maven 3.9.1");
437             }
438         } catch (ParseException e) {
439             System.err.println("Unsupported options: " + e.getMessage());
440             cliManager.displayHelp(System.out);
441             throw e;
442         }
443     }
444 
445     private void informativeCommands(CliRequest cliRequest) throws ExitException {
446         if (cliRequest.commandLine.hasOption(CLIManager.HELP)) {
447             cliManager.displayHelp(System.out);
448             throw new ExitException(0);
449         }
450 
451         if (cliRequest.commandLine.hasOption(CLIManager.VERSION)) {
452             if (cliRequest.commandLine.hasOption(CLIManager.QUIET)) {
453                 System.out.println(CLIReportingUtils.showVersionMinimal());
454             } else {
455                 System.out.println(CLIReportingUtils.showVersion());
456             }
457             throw new ExitException(0);
458         }
459     }
460 
461     private CommandLine cliMerge(CommandLine mavenConfig, CommandLine mavenCli) {
462         CommandLine.Builder commandLineBuilder = new CommandLine.Builder();
463 
464         // the args are easy, CLI only since maven.config file can only contain options
465         for (String arg : mavenCli.getArgs()) {
466             commandLineBuilder.addArg(arg);
467         }
468 
469         /* Although this looks wrong in terms of order Commons CLI stores the value of options in
470          * an array and when a value is potentionally overriden it is added to the array. The single
471          * arg option value is retrieved and instead of returning values[values.length-1] it returns
472          * values[0] which means that the original value instead of the overridden one is returned
473          * (first wins). With properties values are truely overriden since at the end a map is used
474          * to merge which means last wins.
475          *
476          * TODO Report this behavioral bug with Commons CLI
477          */
478         // now add all options, except for user properties with CLI first then maven.config file
479         List<Option> setPropertyOptions = new ArrayList<>();
480         for (Option opt : mavenCli.getOptions()) {
481             if (String.valueOf(CLIManager.SET_USER_PROPERTY).equals(opt.getOpt())) {
482                 setPropertyOptions.add(opt);
483             } else {
484                 commandLineBuilder.addOption(opt);
485             }
486         }
487         for (Option opt : mavenConfig.getOptions()) {
488             commandLineBuilder.addOption(opt);
489         }
490         // finally add the CLI user properties
491         for (Option opt : setPropertyOptions) {
492             commandLineBuilder.addOption(opt);
493         }
494         return commandLineBuilder.build();
495     }
496 
497     /**
498      * Sets up Guice class loading mode to CHILD, if not already set.
499      * Default Guice class loading mode uses a terminally deprecated JDK memory-access classes.
500      */
501     void setupGuiceClassLoading() {
502         if (System.getProperty("guice_custom_class_loading", "").trim().isEmpty()) {
503             System.setProperty("guice_custom_class_loading", "CHILD");
504         }
505     }
506 
507     /**
508      * configure logging
509      */
510     void logging(CliRequest cliRequest) {
511         // LOG LEVEL
512         cliRequest.debug = cliRequest.commandLine.hasOption(CLIManager.DEBUG);
513         cliRequest.quiet = !cliRequest.debug && cliRequest.commandLine.hasOption(CLIManager.QUIET);
514         cliRequest.showErrors = cliRequest.debug || cliRequest.commandLine.hasOption(CLIManager.ERRORS);
515 
516         slf4jLoggerFactory = LoggerFactory.getILoggerFactory();
517         Slf4jConfiguration slf4jConfiguration = Slf4jConfigurationFactory.getConfiguration(slf4jLoggerFactory);
518 
519         if (cliRequest.debug) {
520             cliRequest.request.setLoggingLevel(MavenExecutionRequest.LOGGING_LEVEL_DEBUG);
521             slf4jConfiguration.setRootLoggerLevel(Slf4jConfiguration.Level.DEBUG);
522         } else if (cliRequest.quiet) {
523             cliRequest.request.setLoggingLevel(MavenExecutionRequest.LOGGING_LEVEL_ERROR);
524             slf4jConfiguration.setRootLoggerLevel(Slf4jConfiguration.Level.ERROR);
525         }
526         // else fall back to default log level specified in conf
527         // see https://issues.apache.org/jira/browse/MNG-2570
528 
529         // LOG COLOR
530         String styleColor = cliRequest.getUserProperties().getProperty(STYLE_COLOR_PROPERTY, "auto");
531         styleColor = cliRequest.commandLine.getOptionValue(COLOR, styleColor);
532         if ("always".equals(styleColor) || "yes".equals(styleColor) || "force".equals(styleColor)) {
533             MessageUtils.setColorEnabled(true);
534         } else if ("never".equals(styleColor) || "no".equals(styleColor) || "none".equals(styleColor)) {
535             MessageUtils.setColorEnabled(false);
536         } else if (!"auto".equals(styleColor) && !"tty".equals(styleColor) && !"if-tty".equals(styleColor)) {
537             throw new IllegalArgumentException(
538                     "Invalid color configuration value '" + styleColor + "'. Supported are 'auto', 'always', 'never'.");
539         } else if (cliRequest.commandLine.hasOption(CLIManager.BATCH_MODE)
540                 || cliRequest.commandLine.hasOption(CLIManager.LOG_FILE)) {
541             MessageUtils.setColorEnabled(false);
542         } else {
543             // if the terminal is detected as dumb terminal, disable colors
544             if (MessageUtils.getTerminal() != null
545                     && MessageUtils.getTerminal().getType().startsWith("dumb")) {
546                 MessageUtils.setColorEnabled(false);
547             }
548         }
549 
550         // LOG STREAMS
551         if (cliRequest.commandLine.hasOption(CLIManager.LOG_FILE)) {
552             File logFile = new File(cliRequest.commandLine.getOptionValue(CLIManager.LOG_FILE));
553             logFile = resolveFile(logFile, cliRequest.workingDirectory);
554 
555             // make sure the terminal has finished installing before we grab the streams, otherwise it
556             // could later overwrite our redirection and logs would leak back to the console
557             // (see https://github.com/apache/maven/issues/12188)
558             MessageUtils.awaitTerminalInitialization();
559 
560             // redirect stdout and stderr to file
561             try {
562                 PrintStream ps = new PrintStream(new FileOutputStream(logFile));
563                 System.setOut(ps);
564                 System.setErr(ps);
565             } catch (FileNotFoundException e) {
566                 //
567                 // Ignore
568                 //
569             }
570         }
571 
572         slf4jConfiguration.activate();
573 
574         plexusLoggerManager = new Slf4jLoggerManager();
575         slf4jLogger = slf4jLoggerFactory.getLogger(this.getClass().getName());
576     }
577 
578     private void version(CliRequest cliRequest) {
579         if (cliRequest.debug || cliRequest.commandLine.hasOption(CLIManager.SHOW_VERSION)) {
580             System.out.println(CLIReportingUtils.showVersion());
581         }
582     }
583 
584     private void commands(CliRequest cliRequest) {
585         if (cliRequest.showErrors) {
586             slf4jLogger.info("Error stacktraces are turned on.");
587         }
588 
589         if (MavenExecutionRequest.CHECKSUM_POLICY_WARN.equals(cliRequest.request.getGlobalChecksumPolicy())) {
590             slf4jLogger.info("Disabling strict checksum verification on all artifact downloads.");
591         } else if (MavenExecutionRequest.CHECKSUM_POLICY_FAIL.equals(cliRequest.request.getGlobalChecksumPolicy())) {
592             slf4jLogger.info("Enabling strict checksum verification on all artifact downloads.");
593         }
594 
595         if (slf4jLogger.isDebugEnabled()) {
596             slf4jLogger.debug("Message scheme: {}", (MessageUtils.isColorEnabled() ? "color" : "plain"));
597             slf4jLogger.debug("Message terminal: {}", MessageUtils.getTerminal());
598             if (MessageUtils.isColorEnabled()) {
599                 MessageBuilder buff = builder();
600                 buff.a("Message styles: ");
601                 buff.a(builder().trace("trace")).a(' ');
602                 buff.a(builder().debug("debug")).a(' ');
603                 buff.a(builder().info("info")).a(' ');
604                 buff.a(builder().warning("warning")).a(' ');
605                 buff.a(builder().error("error")).a(' ');
606                 buff.success("success").a(' ');
607                 buff.failure("failure").a(' ');
608                 buff.strong("strong").a(' ');
609                 buff.mojo("mojo").a(' ');
610                 buff.project("project");
611                 slf4jLogger.debug(buff.toString());
612             }
613         }
614     }
615 
616     // Needed to make this method package visible to make writing a unit test possible
617     // Maybe it's better to move some of those methods to separate class (SoC).
618     void properties(CliRequest cliRequest) throws ExitException {
619         try {
620             populateProperties(cliRequest, cliRequest.systemProperties, cliRequest.userProperties);
621 
622             StringSearchInterpolator interpolator =
623                     createInterpolator(cliRequest, cliRequest.systemProperties, cliRequest.userProperties);
624             CommandLine.Builder commandLineBuilder = new CommandLine.Builder();
625             for (Option option : cliRequest.commandLine.getOptions()) {
626                 if (!String.valueOf(CLIManager.SET_USER_PROPERTY).equals(option.getOpt())) {
627                     List<String> values = option.getValuesList();
628                     for (ListIterator<String> it = values.listIterator(); it.hasNext(); ) {
629                         it.set(interpolator.interpolate(it.next()));
630                     }
631                 }
632                 commandLineBuilder.addOption(option);
633             }
634             for (String arg : cliRequest.commandLine.getArgList()) {
635                 commandLineBuilder.addArg(interpolator.interpolate(arg));
636             }
637             cliRequest.commandLine = commandLineBuilder.build();
638         } catch (InterpolationException e) {
639             String message = "ERROR: Could not interpolate properties and/or arguments: " + e.getMessage();
640             System.err.println(message);
641             throw new ExitException(1); // user error
642         } catch (IllegalUseOfUndefinedProperty e) {
643             String message = "ERROR: Illegal use of undefined property: " + e.property;
644             System.err.println(message);
645             try {
646                 cliRequest.request.getRootDirectory();
647             } catch (IllegalStateException ex) {
648                 System.err.println();
649                 System.err.println(RootLocator.UNABLE_TO_FIND_ROOT_PROJECT_MESSAGE);
650             }
651             throw new ExitException(1); // user error
652         }
653     }
654 
655     PlexusContainer container(CliRequest cliRequest) throws Exception {
656         if (cliRequest.classWorld == null) {
657             cliRequest.classWorld =
658                     new ClassWorld("plexus.core", Thread.currentThread().getContextClassLoader());
659         }
660 
661         ClassRealm coreRealm = cliRequest.classWorld.getClassRealm("plexus.core");
662         if (coreRealm == null) {
663             coreRealm = cliRequest.classWorld.getRealms().iterator().next();
664         }
665 
666         List<File> extClassPath = parseExtClasspath(cliRequest);
667 
668         CoreExtensionEntry coreEntry = CoreExtensionEntry.discoverFrom(coreRealm);
669         List<CoreExtensionEntry> extensions =
670                 loadCoreExtensions(cliRequest, coreRealm, coreEntry.getExportedArtifacts());
671 
672         ClassRealm containerRealm = setupContainerRealm(cliRequest.classWorld, coreRealm, extClassPath, extensions);
673 
674         ContainerConfiguration cc = new DefaultContainerConfiguration()
675                 .setClassWorld(cliRequest.classWorld)
676                 .setRealm(containerRealm)
677                 .setClassPathScanning(PlexusConstants.SCANNING_INDEX)
678                 .setAutoWiring(true)
679                 .setJSR250Lifecycle(true)
680                 .setName("maven");
681 
682         Set<String> exportedArtifacts = new HashSet<>(coreEntry.getExportedArtifacts());
683         Set<String> exportedPackages = new HashSet<>(coreEntry.getExportedPackages());
684         for (CoreExtensionEntry extension : extensions) {
685             exportedArtifacts.addAll(extension.getExportedArtifacts());
686             exportedPackages.addAll(extension.getExportedPackages());
687         }
688 
689         final CoreExports exports = new CoreExports(containerRealm, exportedArtifacts, exportedPackages);
690 
691         DefaultPlexusContainer container = new DefaultPlexusContainer(cc, new AbstractModule() {
692             @Override
693             protected void configure() {
694                 bind(ILoggerFactory.class).toInstance(slf4jLoggerFactory);
695                 bind(CoreExports.class).toInstance(exports);
696             }
697         });
698 
699         // NOTE: To avoid inconsistencies, we'll use the TCCL exclusively for lookups
700         container.setLookupRealm(null);
701         Thread.currentThread().setContextClassLoader(container.getContainerRealm());
702 
703         container.setLoggerManager(plexusLoggerManager);
704 
705         for (CoreExtensionEntry extension : extensions) {
706             container.discoverComponents(
707                     extension.getClassRealm(),
708                     new SessionScopeModule(container),
709                     new MojoExecutionScopeModule(container));
710         }
711 
712         customizeContainer(container);
713 
714         container.getLoggerManager().setThresholds(cliRequest.request.getLoggingLevel());
715 
716         eventSpyDispatcher = container.lookup(EventSpyDispatcher.class);
717 
718         DefaultEventSpyContext eventSpyContext = new DefaultEventSpyContext();
719         Map<String, Object> data = eventSpyContext.getData();
720         data.put("plexus", container);
721         data.put("workingDirectory", cliRequest.workingDirectory);
722         data.put("systemProperties", cliRequest.systemProperties);
723         data.put("userProperties", cliRequest.userProperties);
724         data.put("versionProperties", CLIReportingUtils.getBuildProperties());
725         eventSpyDispatcher.init(eventSpyContext);
726 
727         // refresh logger in case container got customized by spy
728         slf4jLogger = slf4jLoggerFactory.getLogger(this.getClass().getName());
729 
730         maven = container.lookup(Maven.class);
731 
732         executionRequestPopulator = container.lookup(MavenExecutionRequestPopulator.class);
733 
734         modelProcessor = createModelProcessor(container);
735 
736         configurationProcessors = container.lookupMap(ConfigurationProcessor.class);
737 
738         toolchainsBuilder = container.lookup(ToolchainsBuilder.class);
739 
740         dispatcher = (DefaultSecDispatcher) container.lookup(SecDispatcher.class, "maven");
741 
742         return container;
743     }
744 
745     private List<CoreExtensionEntry> loadCoreExtensions(
746             CliRequest cliRequest, ClassRealm containerRealm, Set<String> providedArtifacts) throws Exception {
747         if (cliRequest.multiModuleProjectDirectory == null) {
748             return Collections.emptyList();
749         }
750 
751         File projectExtensionsFile = new File(cliRequest.multiModuleProjectDirectory, PROJECT_EXTENSIONS_FILENAME);
752         if (!projectExtensionsFile.isFile() && !USER_EXTENSIONS_FILE.isFile() && !GLOBAL_EXTENSIONS_FILE.isFile()) {
753             return Collections.emptyList();
754         }
755 
756         List<CoreExtension> projectExtensions = null;
757         List<CoreExtension> userExtensions = null;
758         List<CoreExtension> globalExtensions = null;
759         if (projectExtensionsFile.isFile()) {
760             projectExtensions = readCoreExtensionsDescriptor(projectExtensionsFile.toPath());
761         }
762         if (USER_EXTENSIONS_FILE.isFile()) {
763             userExtensions = readCoreExtensionsDescriptor(USER_EXTENSIONS_FILE.toPath());
764         }
765         if (GLOBAL_EXTENSIONS_FILE.isFile()) {
766             globalExtensions = readCoreExtensionsDescriptor(GLOBAL_EXTENSIONS_FILE.toPath());
767         }
768 
769         if ((projectExtensions == null || projectExtensions.isEmpty())
770                 && (userExtensions == null || userExtensions.isEmpty())
771                 && (globalExtensions == null || globalExtensions.isEmpty())) {
772             return Collections.emptyList();
773         }
774 
775         Map<Path, List<CoreExtension>> configuredCoreExtensions = new LinkedHashMap<>();
776         if (projectExtensions != null && !projectExtensions.isEmpty()) {
777             configuredCoreExtensions.put(projectExtensionsFile.toPath(), projectExtensions);
778         }
779         if (userExtensions != null && !userExtensions.isEmpty()) {
780             configuredCoreExtensions.put(USER_EXTENSIONS_FILE.toPath(), userExtensions);
781         }
782         if (globalExtensions != null && !globalExtensions.isEmpty()) {
783             configuredCoreExtensions.put(GLOBAL_EXTENSIONS_FILE.toPath(), globalExtensions);
784         }
785         // mediate versions
786         List<CoreExtension> extensions = selectCoreExtensions(configuredCoreExtensions);
787 
788         ContainerConfiguration cc = new DefaultContainerConfiguration() //
789                 .setClassWorld(cliRequest.classWorld) //
790                 .setRealm(containerRealm) //
791                 .setClassPathScanning(PlexusConstants.SCANNING_INDEX) //
792                 .setAutoWiring(true) //
793                 .setJSR250Lifecycle(true) //
794                 .setName("maven");
795 
796         DefaultPlexusContainer container = new DefaultPlexusContainer(cc, new AbstractModule() {
797             @Override
798             protected void configure() {
799                 bind(ILoggerFactory.class).toInstance(slf4jLoggerFactory);
800             }
801         });
802 
803         try {
804             container.setLookupRealm(null);
805 
806             container.setLoggerManager(plexusLoggerManager);
807 
808             container.getLoggerManager().setThresholds(cliRequest.request.getLoggingLevel());
809 
810             Thread.currentThread().setContextClassLoader(container.getContainerRealm());
811 
812             executionRequestPopulator = container.lookup(MavenExecutionRequestPopulator.class);
813 
814             configurationProcessors = container.lookupMap(ConfigurationProcessor.class);
815 
816             configure(cliRequest);
817 
818             MavenExecutionRequest request = DefaultMavenExecutionRequest.copy(cliRequest.request);
819 
820             request = populateRequest(cliRequest, request);
821 
822             request = executionRequestPopulator.populateDefaults(request);
823 
824             BootstrapCoreExtensionManager resolver = container.lookup(BootstrapCoreExtensionManager.class);
825 
826             return Collections.unmodifiableList(resolver.loadCoreExtensions(request, providedArtifacts, extensions));
827 
828         } finally {
829             executionRequestPopulator = null;
830             container.dispose();
831         }
832     }
833 
834     private List<CoreExtension> readCoreExtensionsDescriptor(Path extensionsFile)
835             throws IOException, XmlPullParserException {
836         CoreExtensionsXpp3Reader parser = new CoreExtensionsXpp3Reader();
837         try (BufferedReader is = Files.newBufferedReader(extensionsFile, StandardCharsets.UTF_8)) {
838             return parser.read(is).getExtensions();
839         }
840     }
841 
842     private List<CoreExtension> selectCoreExtensions(Map<Path, List<CoreExtension>> configuredCoreExtensions) {
843         slf4jLogger.debug("Configured core extensions (in precedence order):");
844         for (Map.Entry<Path, List<CoreExtension>> source : configuredCoreExtensions.entrySet()) {
845             slf4jLogger.debug("* Source file: {}", source.getKey());
846             for (CoreExtension extension : source.getValue()) {
847                 slf4jLogger.debug("  - {} -> {}", extension.getId(), source.getKey());
848             }
849         }
850 
851         LinkedHashMap<String, CoreExtension> selectedExtensions = new LinkedHashMap<>();
852         List<String> conflicts = new ArrayList<>();
853         for (Map.Entry<Path, List<CoreExtension>> coreExtensions : configuredCoreExtensions.entrySet()) {
854             for (CoreExtension coreExtension : coreExtensions.getValue()) {
855                 String key = coreExtension.getGroupId() + ":" + coreExtension.getArtifactId();
856                 CoreExtension conflict = selectedExtensions.putIfAbsent(key, coreExtension);
857                 if (conflict != null && !Objects.equals(coreExtension.getVersion(), conflict.getVersion())) {
858                     conflicts.add(String.format(
859                             "Conflicting extension %s: %s vs %s in %s",
860                             key, coreExtension.getVersion(), conflict.getVersion(), coreExtensions.getKey()));
861                 }
862             }
863         }
864         if (!conflicts.isEmpty()) {
865             slf4jLogger.warn("Found {} extension conflict(s):", conflicts.size());
866             for (String conflict : conflicts) {
867                 slf4jLogger.warn("* {}", conflict);
868             }
869             slf4jLogger.warn("");
870             slf4jLogger.warn(
871                     "Order of core extensions precedence is project > user > installation. Selected extensions are:");
872             for (CoreExtension extension : selectedExtensions.values()) {
873                 slf4jLogger.warn("* {}", extension.getId());
874             }
875         }
876 
877         slf4jLogger.debug("Selected core extensions (in loading order):");
878         for (CoreExtension source : selectedExtensions.values()) {
879             slf4jLogger.debug("* {}: ", source.getId());
880         }
881         return new ArrayList<>(selectedExtensions.values());
882     }
883 
884     private ClassRealm setupContainerRealm(
885             ClassWorld classWorld, ClassRealm coreRealm, List<File> extClassPath, List<CoreExtensionEntry> extensions)
886             throws Exception {
887         if (!extClassPath.isEmpty() || !extensions.isEmpty()) {
888             ClassRealm extRealm = classWorld.newRealm("maven.ext", null);
889 
890             extRealm.setParentRealm(coreRealm);
891 
892             slf4jLogger.debug("Populating class realm {}", extRealm.getId());
893 
894             for (File file : extClassPath) {
895                 slf4jLogger.debug("  Included {}", file);
896 
897                 extRealm.addURL(file.toURI().toURL());
898             }
899 
900             for (CoreExtensionEntry entry : reverse(extensions)) {
901                 Set<String> exportedPackages = entry.getExportedPackages();
902                 ClassRealm realm = entry.getClassRealm();
903                 for (String exportedPackage : exportedPackages) {
904                     extRealm.importFrom(realm, exportedPackage);
905                 }
906                 if (exportedPackages.isEmpty()) {
907                     // sisu uses realm imports to establish component visibility
908                     extRealm.importFrom(realm, realm.getId());
909                 }
910             }
911 
912             return extRealm;
913         }
914 
915         return coreRealm;
916     }
917 
918     private static <T> List<T> reverse(List<T> list) {
919         List<T> copy = new ArrayList<>(list);
920         Collections.reverse(copy);
921         return copy;
922     }
923 
924     private List<File> parseExtClasspath(CliRequest cliRequest) {
925         String extClassPath = cliRequest.userProperties.getProperty(EXT_CLASS_PATH);
926         if (extClassPath == null) {
927             extClassPath = cliRequest.systemProperties.getProperty(EXT_CLASS_PATH);
928         }
929 
930         List<File> jars = new ArrayList<>();
931 
932         if (StringUtils.isNotEmpty(extClassPath)) {
933             for (String jar : StringUtils.split(extClassPath, File.pathSeparator)) {
934                 File file = resolveFile(new File(jar), cliRequest.workingDirectory);
935 
936                 slf4jLogger.debug("  Included {}", file);
937 
938                 jars.add(file);
939             }
940         }
941 
942         return jars;
943     }
944 
945     //
946     // This should probably be a separate tool and not be baked into Maven.
947     //
948     private void encryption(CliRequest cliRequest) throws Exception {
949         if (cliRequest.commandLine.hasOption(CLIManager.ENCRYPT_MASTER_PASSWORD)) {
950             String passwd = cliRequest.commandLine.getOptionValue(CLIManager.ENCRYPT_MASTER_PASSWORD);
951 
952             if (passwd == null) {
953                 Console cons = System.console();
954                 char[] password = (cons == null) ? null : cons.readPassword("Master password: ");
955                 if (password != null) {
956                     // Cipher uses Strings
957                     passwd = String.copyValueOf(password);
958 
959                     // Sun/Oracle advises to empty the char array
960                     java.util.Arrays.fill(password, ' ');
961                 }
962             }
963 
964             DefaultPlexusCipher cipher = new DefaultPlexusCipher();
965 
966             System.out.println(cipher.encryptAndDecorate(passwd, DefaultSecDispatcher.SYSTEM_PROPERTY_SEC_LOCATION));
967 
968             throw new ExitException(0);
969         } else if (cliRequest.commandLine.hasOption(CLIManager.ENCRYPT_PASSWORD)) {
970             String passwd = cliRequest.commandLine.getOptionValue(CLIManager.ENCRYPT_PASSWORD);
971 
972             if (passwd == null) {
973                 Console cons = System.console();
974                 char[] password = (cons == null) ? null : cons.readPassword("Password: ");
975                 if (password != null) {
976                     // Cipher uses Strings
977                     passwd = String.copyValueOf(password);
978 
979                     // Sun/Oracle advises to empty the char array
980                     java.util.Arrays.fill(password, ' ');
981                 }
982             }
983 
984             String configurationFile = dispatcher.getConfigurationFile();
985 
986             if (configurationFile.startsWith("~")) {
987                 configurationFile = System.getProperty("user.home") + configurationFile.substring(1);
988             }
989 
990             String file = System.getProperty(DefaultSecDispatcher.SYSTEM_PROPERTY_SEC_LOCATION, configurationFile);
991 
992             String master = null;
993 
994             SettingsSecurity sec = SecUtil.read(file, true);
995             if (sec != null) {
996                 master = sec.getMaster();
997             }
998 
999             if (master == null) {
1000                 throw new IllegalStateException("Master password is not set in the setting security file: " + file);
1001             }
1002 
1003             DefaultPlexusCipher cipher = new DefaultPlexusCipher();
1004             String masterPasswd = cipher.decryptDecorated(master, DefaultSecDispatcher.SYSTEM_PROPERTY_SEC_LOCATION);
1005             System.out.println(cipher.encryptAndDecorate(passwd, masterPasswd));
1006 
1007             throw new ExitException(0);
1008         }
1009     }
1010 
1011     private int execute(CliRequest cliRequest) throws MavenExecutionRequestPopulationException {
1012         MavenExecutionRequest request = executionRequestPopulator.populateDefaults(cliRequest.request);
1013         request.setRepositoryCache(new DefaultRepositoryCache()); // reset caches
1014 
1015         eventSpyDispatcher.onEvent(request);
1016 
1017         MavenExecutionResult result = maven.execute(request);
1018 
1019         eventSpyDispatcher.onEvent(result);
1020 
1021         eventSpyDispatcher.close();
1022 
1023         if (result.hasExceptions()) {
1024             ExceptionHandler handler = new DefaultExceptionHandler();
1025 
1026             Map<String, String> references = new LinkedHashMap<>();
1027 
1028             MavenProject project = null;
1029 
1030             for (Throwable exception : result.getExceptions()) {
1031                 ExceptionSummary summary = handler.handleException(exception);
1032 
1033                 logSummary(summary, references, "", cliRequest.showErrors);
1034 
1035                 if (project == null && exception instanceof LifecycleExecutionException) {
1036                     project = ((LifecycleExecutionException) exception).getProject();
1037                 }
1038             }
1039 
1040             slf4jLogger.error("");
1041 
1042             if (!cliRequest.showErrors) {
1043                 slf4jLogger.error(
1044                         "To see the full stack trace of the errors, re-run Maven with the {} switch.",
1045                         builder().strong("-e"));
1046             }
1047             if (!slf4jLogger.isDebugEnabled()) {
1048                 slf4jLogger.error(
1049                         "Re-run Maven using the {} switch to enable full debug logging.",
1050                         builder().strong("-X"));
1051             }
1052 
1053             if (!references.isEmpty()) {
1054                 slf4jLogger.error("");
1055                 slf4jLogger.error("For more information about the errors and possible solutions"
1056                         + ", please read the following articles:");
1057 
1058                 for (Map.Entry<String, String> entry : references.entrySet()) {
1059                     slf4jLogger.error("{} {}", builder().strong(entry.getValue()), entry.getKey());
1060                 }
1061             }
1062 
1063             if (project != null
1064                     && !project.equals(result.getTopologicallySortedProjects().get(0))) {
1065                 slf4jLogger.error("");
1066                 slf4jLogger.error("After correcting the problems, you can resume the build with the command");
1067                 slf4jLogger.error(builder()
1068                         .a("  ")
1069                         .strong("mvn <args> -rf " + getResumeFrom(result.getTopologicallySortedProjects(), project))
1070                         .toString());
1071             }
1072 
1073             if (MavenExecutionRequest.REACTOR_FAIL_NEVER.equals(cliRequest.request.getReactorFailureBehavior())) {
1074                 slf4jLogger.info("Build failures were ignored.");
1075 
1076                 return 0;
1077             } else {
1078                 return 1;
1079             }
1080         } else {
1081             return 0;
1082         }
1083     }
1084 
1085     /**
1086      * A helper method to determine the value to resume the build with {@code -rf} taking into account the
1087      * edge case where multiple modules in the reactor have the same artifactId.
1088      * <p>
1089      * {@code -rf :artifactId} will pick up the first module which matches, but when multiple modules in the
1090      * reactor have the same artifactId, effective failed module might be later in build reactor.
1091      * This means that developer will either have to type groupId or wait for build execution of all modules
1092      * which were fine, but they are still before one which reported errors.
1093      * <p>Then the returned value is {@code groupId:artifactId} when there is a name clash and
1094      * {@code :artifactId} if there is no conflict.
1095      *
1096      * @param mavenProjects Maven projects which are part of build execution.
1097      * @param failedProject Project which has failed.
1098      * @return Value for -rf flag to resume build exactly from place where it failed ({@code :artifactId} in
1099      *    general and {@code groupId:artifactId} when there is a name clash).
1100      */
1101     private String getResumeFrom(List<MavenProject> mavenProjects, MavenProject failedProject) {
1102         for (MavenProject buildProject : mavenProjects) {
1103             if (failedProject.getArtifactId().equals(buildProject.getArtifactId())
1104                     && !failedProject.equals(buildProject)) {
1105                 return failedProject.getGroupId() + ":" + failedProject.getArtifactId();
1106             }
1107         }
1108         return ":" + failedProject.getArtifactId();
1109     }
1110 
1111     private void logSummary(
1112             ExceptionSummary summary, Map<String, String> references, String indent, boolean showErrors) {
1113         String referenceKey = "";
1114 
1115         if (StringUtils.isNotEmpty(summary.getReference())) {
1116             referenceKey = references.get(summary.getReference());
1117             if (referenceKey == null) {
1118                 referenceKey = "[Help " + (references.size() + 1) + "]";
1119                 references.put(summary.getReference(), referenceKey);
1120             }
1121         }
1122 
1123         String msg = summary.getMessage();
1124 
1125         if (StringUtils.isNotEmpty(referenceKey)) {
1126             if (msg.indexOf('\n') < 0) {
1127                 msg += " -> " + builder().strong(referenceKey);
1128             } else {
1129                 msg += "\n-> " + builder().strong(referenceKey);
1130             }
1131         }
1132 
1133         String[] lines = NEXT_LINE.split(msg);
1134         String currentColor = "";
1135 
1136         for (int i = 0; i < lines.length; i++) {
1137             // add eventual current color inherited from previous line
1138             String line = currentColor + lines[i];
1139 
1140             // look for last ANSI escape sequence to check if nextColor
1141             Matcher matcher = LAST_ANSI_SEQUENCE.matcher(line);
1142             String nextColor = "";
1143             if (matcher.find()) {
1144                 nextColor = matcher.group(1);
1145                 if (ANSI_RESET.equals(nextColor)) {
1146                     // last ANSI escape code is reset: no next color
1147                     nextColor = "";
1148                 }
1149             }
1150 
1151             // effective line, with indent and reset if end is colored
1152             line = indent + line + ("".equals(nextColor) ? "" : ANSI_RESET);
1153 
1154             if ((i == lines.length - 1) && (showErrors || (summary.getException() instanceof InternalErrorException))) {
1155                 slf4jLogger.error(line, summary.getException());
1156             } else {
1157                 slf4jLogger.error(line);
1158             }
1159 
1160             currentColor = nextColor;
1161         }
1162 
1163         indent += "  ";
1164 
1165         for (ExceptionSummary child : summary.getChildren()) {
1166             logSummary(child, references, indent, showErrors);
1167         }
1168     }
1169 
1170     private static final Pattern LAST_ANSI_SEQUENCE = Pattern.compile("(\u001B\\[[;\\d]*[ -/]*[@-~])[^\u001B]*$");
1171 
1172     private static final String ANSI_RESET = "\u001B\u005Bm";
1173 
1174     private void configure(CliRequest cliRequest) throws Exception {
1175         //
1176         // This is not ideal but there are events specifically for configuration from the CLI which I don't
1177         // believe are really valid but there are ITs which assert the right events are published so this
1178         // needs to be supported so the EventSpyDispatcher needs to be put in the CliRequest so that
1179         // it can be accessed by configuration processors.
1180         //
1181         cliRequest.request.setEventSpyDispatcher(eventSpyDispatcher);
1182 
1183         //
1184         // We expect at most 2 implementations to be available. The SettingsXmlConfigurationProcessor implementation
1185         // is always available in the core and likely always will be, but we may have another ConfigurationProcessor
1186         // present supplied by the user. The rule is that we only allow the execution of one ConfigurationProcessor.
1187         // If there is more than one then we execute the one supplied by the user, otherwise we execute the
1188         // the default SettingsXmlConfigurationProcessor.
1189         //
1190         int userSuppliedConfigurationProcessorCount = configurationProcessors.size() - 1;
1191 
1192         if (userSuppliedConfigurationProcessorCount == 0) {
1193             //
1194             // Our settings.xml source is historically how we have configured Maven from the CLI so we are going to
1195             // have to honour its existence forever. So let's run it.
1196             //
1197             configurationProcessors.get(SettingsXmlConfigurationProcessor.HINT).process(cliRequest);
1198         } else if (userSuppliedConfigurationProcessorCount == 1) {
1199             //
1200             // Run the user supplied ConfigurationProcessor
1201             //
1202             for (Entry<String, ConfigurationProcessor> entry : configurationProcessors.entrySet()) {
1203                 String hint = entry.getKey();
1204                 if (!hint.equals(SettingsXmlConfigurationProcessor.HINT)) {
1205                     ConfigurationProcessor configurationProcessor = entry.getValue();
1206                     configurationProcessor.process(cliRequest);
1207                 }
1208             }
1209         } else if (userSuppliedConfigurationProcessorCount > 1) {
1210             //
1211             // There are too many ConfigurationProcessors so we don't know which one to run so report the error.
1212             //
1213             StringBuilder sb = new StringBuilder(String.format(
1214                     "\nThere can only be one user supplied ConfigurationProcessor, there are %s:\n\n",
1215                     userSuppliedConfigurationProcessorCount));
1216             for (Entry<String, ConfigurationProcessor> entry : configurationProcessors.entrySet()) {
1217                 String hint = entry.getKey();
1218                 if (!hint.equals(SettingsXmlConfigurationProcessor.HINT)) {
1219                     ConfigurationProcessor configurationProcessor = entry.getValue();
1220                     sb.append(String.format(
1221                             "%s\n", configurationProcessor.getClass().getName()));
1222                 }
1223             }
1224             sb.append("\n");
1225             throw new Exception(sb.toString());
1226         }
1227     }
1228 
1229     void toolchains(CliRequest cliRequest) throws Exception {
1230         File userToolchainsFile;
1231 
1232         if (cliRequest.commandLine.hasOption(CLIManager.ALTERNATE_USER_TOOLCHAINS)) {
1233             userToolchainsFile = new File(cliRequest.commandLine.getOptionValue(CLIManager.ALTERNATE_USER_TOOLCHAINS));
1234             userToolchainsFile = resolveFile(userToolchainsFile, cliRequest.workingDirectory);
1235 
1236             if (!userToolchainsFile.isFile()) {
1237                 throw new FileNotFoundException(
1238                         "The specified user toolchains file does not exist: " + userToolchainsFile);
1239             }
1240         } else {
1241             userToolchainsFile = DEFAULT_USER_TOOLCHAINS_FILE;
1242         }
1243 
1244         File globalToolchainsFile;
1245 
1246         if (cliRequest.commandLine.hasOption(CLIManager.ALTERNATE_GLOBAL_TOOLCHAINS)) {
1247             globalToolchainsFile =
1248                     new File(cliRequest.commandLine.getOptionValue(CLIManager.ALTERNATE_GLOBAL_TOOLCHAINS));
1249             globalToolchainsFile = resolveFile(globalToolchainsFile, cliRequest.workingDirectory);
1250 
1251             if (!globalToolchainsFile.isFile()) {
1252                 throw new FileNotFoundException(
1253                         "The specified global toolchains file does not exist: " + globalToolchainsFile);
1254             }
1255         } else {
1256             globalToolchainsFile = DEFAULT_GLOBAL_TOOLCHAINS_FILE;
1257         }
1258 
1259         cliRequest.request.setGlobalToolchainsFile(globalToolchainsFile);
1260         cliRequest.request.setUserToolchainsFile(userToolchainsFile);
1261 
1262         DefaultToolchainsBuildingRequest toolchainsRequest = new DefaultToolchainsBuildingRequest();
1263         if (globalToolchainsFile.isFile()) {
1264             toolchainsRequest.setGlobalToolchainsSource(new FileSource(globalToolchainsFile));
1265         }
1266         if (userToolchainsFile.isFile()) {
1267             toolchainsRequest.setUserToolchainsSource(new FileSource(userToolchainsFile));
1268         }
1269 
1270         eventSpyDispatcher.onEvent(toolchainsRequest);
1271 
1272         slf4jLogger.debug(
1273                 "Reading global toolchains from {}",
1274                 getLocation(toolchainsRequest.getGlobalToolchainsSource(), globalToolchainsFile));
1275         slf4jLogger.debug(
1276                 "Reading user toolchains from {}",
1277                 getLocation(toolchainsRequest.getUserToolchainsSource(), userToolchainsFile));
1278 
1279         ToolchainsBuildingResult toolchainsResult = toolchainsBuilder.build(toolchainsRequest);
1280 
1281         eventSpyDispatcher.onEvent(toolchainsResult);
1282 
1283         executionRequestPopulator.populateFromToolchains(cliRequest.request, toolchainsResult.getEffectiveToolchains());
1284 
1285         if (!toolchainsResult.getProblems().isEmpty() && slf4jLogger.isWarnEnabled()) {
1286             slf4jLogger.warn("");
1287             slf4jLogger.warn("Some problems were encountered while building the effective toolchains");
1288 
1289             for (Problem problem : toolchainsResult.getProblems()) {
1290                 slf4jLogger.warn("{} @ {}", problem.getMessage(), problem.getLocation());
1291             }
1292 
1293             slf4jLogger.warn("");
1294         }
1295     }
1296 
1297     private Object getLocation(Source source, File defaultLocation) {
1298         if (source != null) {
1299             return source.getLocation();
1300         }
1301         return defaultLocation;
1302     }
1303 
1304     private MavenExecutionRequest populateRequest(CliRequest cliRequest) {
1305         return populateRequest(cliRequest, cliRequest.request);
1306     }
1307 
1308     @SuppressWarnings("checkstyle:methodlength")
1309     private MavenExecutionRequest populateRequest(CliRequest cliRequest, MavenExecutionRequest request) {
1310         CommandLine commandLine = cliRequest.commandLine;
1311         String workingDirectory = cliRequest.workingDirectory;
1312         boolean quiet = cliRequest.quiet;
1313         boolean showErrors = cliRequest.showErrors;
1314 
1315         String[] deprecatedOptions = {"up", "npu", "cpu", "npr"};
1316         for (String deprecatedOption : deprecatedOptions) {
1317             if (commandLine.hasOption(deprecatedOption)) {
1318                 slf4jLogger.warn(
1319                         "Command line option -{} is deprecated and will be removed in future Maven versions.",
1320                         deprecatedOption);
1321             }
1322         }
1323 
1324         // ----------------------------------------------------------------------
1325         // Now that we have everything that we need we will fire up plexus and
1326         // bring the maven component to life for use.
1327         // ----------------------------------------------------------------------
1328 
1329         if (commandLine.hasOption(CLIManager.BATCH_MODE)) {
1330             request.setInteractiveMode(false);
1331         }
1332 
1333         boolean noSnapshotUpdates = false;
1334         if (commandLine.hasOption(CLIManager.SUPRESS_SNAPSHOT_UPDATES)) {
1335             noSnapshotUpdates = true;
1336         }
1337 
1338         boolean updateSnapshots = false;
1339         if (commandLine.hasOption(CLIManager.UPDATE_SNAPSHOTS)) {
1340             updateSnapshots = true;
1341         }
1342 
1343         String artifactsUpdatePolicy = null;
1344         String metadataUpdatePolicy = null;
1345         if (commandLine.hasOption(CLIManager.UPDATE_ARTIFACTS)) {
1346             artifactsUpdatePolicy = RepositoryPolicy.UPDATE_POLICY_ALWAYS;
1347         }
1348         if (artifactsUpdatePolicy == null && commandLine.hasOption(CLIManager.ARTIFACTS_UPDATE_POLICY)) {
1349             artifactsUpdatePolicy = commandLine.getOptionValue(CLIManager.ARTIFACTS_UPDATE_POLICY);
1350         }
1351         if (commandLine.hasOption(CLIManager.UPDATE_METADATA)) {
1352             metadataUpdatePolicy = RepositoryPolicy.UPDATE_POLICY_ALWAYS;
1353         }
1354         if (metadataUpdatePolicy == null && commandLine.hasOption(CLIManager.METADATA_UPDATE_POLICY)) {
1355             metadataUpdatePolicy = commandLine.getOptionValue(CLIManager.METADATA_UPDATE_POLICY);
1356         }
1357 
1358         // ----------------------------------------------------------------------
1359         //
1360         // ----------------------------------------------------------------------
1361 
1362         List<String> goals = commandLine.getArgList();
1363 
1364         boolean recursive = true;
1365 
1366         // this is the default behavior.
1367         String reactorFailureBehaviour = MavenExecutionRequest.REACTOR_FAIL_FAST;
1368 
1369         if (commandLine.hasOption(CLIManager.NON_RECURSIVE)) {
1370             recursive = false;
1371         }
1372 
1373         if (commandLine.hasOption(CLIManager.FAIL_FAST)) {
1374             reactorFailureBehaviour = MavenExecutionRequest.REACTOR_FAIL_FAST;
1375         } else if (commandLine.hasOption(CLIManager.FAIL_AT_END)) {
1376             reactorFailureBehaviour = MavenExecutionRequest.REACTOR_FAIL_AT_END;
1377         } else if (commandLine.hasOption(CLIManager.FAIL_NEVER)) {
1378             reactorFailureBehaviour = MavenExecutionRequest.REACTOR_FAIL_NEVER;
1379         }
1380 
1381         if (commandLine.hasOption(CLIManager.OFFLINE)) {
1382             request.setOffline(true);
1383         }
1384 
1385         String globalChecksumPolicy = null;
1386 
1387         if (commandLine.hasOption(CLIManager.CHECKSUM_FAILURE_POLICY)) {
1388             globalChecksumPolicy = MavenExecutionRequest.CHECKSUM_POLICY_FAIL;
1389         } else if (commandLine.hasOption(CLIManager.CHECKSUM_WARNING_POLICY)) {
1390             globalChecksumPolicy = MavenExecutionRequest.CHECKSUM_POLICY_WARN;
1391         }
1392 
1393         File baseDirectory = new File(workingDirectory, "").getAbsoluteFile();
1394 
1395         // ----------------------------------------------------------------------
1396         // Profile Activation
1397         // ----------------------------------------------------------------------
1398 
1399         List<String> activeProfiles = new ArrayList<>();
1400 
1401         List<String> inactiveProfiles = new ArrayList<>();
1402 
1403         if (commandLine.hasOption(CLIManager.ACTIVATE_PROFILES)) {
1404             String[] profileOptionValues = commandLine.getOptionValues(CLIManager.ACTIVATE_PROFILES);
1405             if (profileOptionValues != null) {
1406                 for (String profileOptionValue : profileOptionValues) {
1407                     StringTokenizer profileTokens = new StringTokenizer(profileOptionValue, ",");
1408 
1409                     while (profileTokens.hasMoreTokens()) {
1410                         String profileAction = profileTokens.nextToken().trim();
1411 
1412                         if (profileAction.startsWith("-") || profileAction.startsWith("!")) {
1413                             inactiveProfiles.add(profileAction.substring(1));
1414                         } else if (profileAction.startsWith("+")) {
1415                             activeProfiles.add(profileAction.substring(1));
1416                         } else {
1417                             activeProfiles.add(profileAction);
1418                         }
1419                     }
1420                 }
1421             }
1422         }
1423 
1424         TransferListener transferListener;
1425 
1426         if (quiet || cliRequest.commandLine.hasOption(CLIManager.NO_TRANSFER_PROGRESS)) {
1427             transferListener = new QuietMavenTransferListener();
1428         } else if (request.isInteractiveMode() && !cliRequest.commandLine.hasOption(CLIManager.LOG_FILE)) {
1429             //
1430             // If we're logging to a file then we don't want the console transfer listener as it will spew
1431             // download progress all over the place
1432             //
1433             transferListener = getConsoleTransferListener(cliRequest.commandLine.hasOption(CLIManager.DEBUG));
1434         } else {
1435             transferListener = getBatchTransferListener();
1436         }
1437 
1438         ExecutionListener executionListener = new ExecutionEventLogger();
1439         if (eventSpyDispatcher != null) {
1440             executionListener = eventSpyDispatcher.chainListener(executionListener);
1441         }
1442 
1443         String alternatePomFile = null;
1444         if (commandLine.hasOption(CLIManager.ALTERNATE_POM_FILE)) {
1445             alternatePomFile = commandLine.getOptionValue(CLIManager.ALTERNATE_POM_FILE);
1446         }
1447 
1448         request.setBaseDirectory(baseDirectory)
1449                 .setGoals(goals)
1450                 .setSystemProperties(cliRequest.systemProperties)
1451                 .setUserProperties(cliRequest.userProperties)
1452                 .setReactorFailureBehavior(reactorFailureBehaviour) // default: fail fast
1453                 .setRecursive(recursive) // default: true
1454                 .setShowErrors(showErrors) // default: false
1455                 .addActiveProfiles(activeProfiles) // optional
1456                 .addInactiveProfiles(inactiveProfiles) // optional
1457                 .setExecutionListener(executionListener)
1458                 .setTransferListener(transferListener) // default: batch mode which goes along with interactive
1459                 .setUpdateSnapshots(updateSnapshots) // default: false
1460                 .setNoSnapshotUpdates(noSnapshotUpdates) // default: false
1461                 .setArtifactsUpdatePolicy(artifactsUpdatePolicy) // default: null
1462                 .setMetadataUpdatePolicy(metadataUpdatePolicy) // default: null
1463                 .setGlobalChecksumPolicy(globalChecksumPolicy) // default: warn
1464                 .setMultiModuleProjectDirectory(cliRequest.multiModuleProjectDirectory);
1465 
1466         if (alternatePomFile != null) {
1467             File pom = resolveFile(new File(alternatePomFile), workingDirectory);
1468             if (pom.isDirectory()) {
1469                 pom = new File(pom, "pom.xml");
1470             }
1471 
1472             request.setPom(pom);
1473         } else if (modelProcessor != null) {
1474             File pom = modelProcessor.locatePom(baseDirectory);
1475 
1476             if (pom.isFile()) {
1477                 request.setPom(pom);
1478             }
1479         }
1480 
1481         if ((request.getPom() != null) && (request.getPom().getParentFile() != null)) {
1482             request.setBaseDirectory(request.getPom().getParentFile());
1483         }
1484 
1485         if (commandLine.hasOption(CLIManager.RESUME_FROM)) {
1486             request.setResumeFrom(commandLine.getOptionValue(CLIManager.RESUME_FROM));
1487         }
1488 
1489         if (commandLine.hasOption(CLIManager.PROJECT_LIST)) {
1490             String[] projectOptionValues = commandLine.getOptionValues(CLIManager.PROJECT_LIST);
1491 
1492             List<String> inclProjects = new ArrayList<>();
1493             List<String> exclProjects = new ArrayList<>();
1494 
1495             if (projectOptionValues != null) {
1496                 for (String projectOptionValue : projectOptionValues) {
1497                     StringTokenizer projectTokens = new StringTokenizer(projectOptionValue, ",");
1498 
1499                     while (projectTokens.hasMoreTokens()) {
1500                         String projectAction = projectTokens.nextToken().trim();
1501 
1502                         if (projectAction.startsWith("-") || projectAction.startsWith("!")) {
1503                             exclProjects.add(projectAction.substring(1));
1504                         } else if (projectAction.startsWith("+")) {
1505                             inclProjects.add(projectAction.substring(1));
1506                         } else {
1507                             inclProjects.add(projectAction);
1508                         }
1509                     }
1510                 }
1511             }
1512 
1513             request.setSelectedProjects(inclProjects);
1514             request.setExcludedProjects(exclProjects);
1515         }
1516 
1517         if (commandLine.hasOption(CLIManager.ALSO_MAKE) && !commandLine.hasOption(CLIManager.ALSO_MAKE_DEPENDENTS)) {
1518             request.setMakeBehavior(MavenExecutionRequest.REACTOR_MAKE_UPSTREAM);
1519         } else if (!commandLine.hasOption(CLIManager.ALSO_MAKE)
1520                 && commandLine.hasOption(CLIManager.ALSO_MAKE_DEPENDENTS)) {
1521             request.setMakeBehavior(MavenExecutionRequest.REACTOR_MAKE_DOWNSTREAM);
1522         } else if (commandLine.hasOption(CLIManager.ALSO_MAKE)
1523                 && commandLine.hasOption(CLIManager.ALSO_MAKE_DEPENDENTS)) {
1524             request.setMakeBehavior(MavenExecutionRequest.REACTOR_MAKE_BOTH);
1525         }
1526 
1527         String localRepoProperty = request.getUserProperties().getProperty(MavenCli.LOCAL_REPO_PROPERTY);
1528 
1529         // TODO Investigate why this can also be a Java system property and not just a Maven user property like
1530         // other properties
1531         if (localRepoProperty == null) {
1532             localRepoProperty = request.getSystemProperties().getProperty(MavenCli.LOCAL_REPO_PROPERTY);
1533         }
1534 
1535         if (localRepoProperty != null) {
1536             request.setLocalRepositoryPath(localRepoProperty);
1537         }
1538 
1539         request.setCacheNotFound(true);
1540         request.setCacheTransferError(false);
1541         request.setIgnoreTransitiveRepositories(commandLine.hasOption(CLIManager.IGNORE_TRANSITIVE_REPOSITORIES));
1542 
1543         //
1544         // Builder, concurrency and parallelism
1545         //
1546         // We preserve the existing methods for builder selection which is to look for various inputs in the threading
1547         // configuration. We don't have an easy way to allow a pluggable builder to provide its own configuration
1548         // parameters but this is sufficient for now. Ultimately we want components like Builders to provide a way to
1549         // extend the command line to accept its own configuration parameters.
1550         //
1551         final String threadConfiguration =
1552                 commandLine.hasOption(CLIManager.THREADS) ? commandLine.getOptionValue(CLIManager.THREADS) : null;
1553 
1554         if (threadConfiguration != null) {
1555             int degreeOfConcurrency = calculateDegreeOfConcurrency(threadConfiguration);
1556             if (degreeOfConcurrency > 1) {
1557                 request.setBuilderId("multithreaded");
1558                 request.setDegreeOfConcurrency(degreeOfConcurrency);
1559             }
1560         }
1561 
1562         //
1563         // Allow the builder to be overridden by the user if requested. The builders are now pluggable.
1564         //
1565         if (commandLine.hasOption(CLIManager.BUILDER)) {
1566             request.setBuilderId(commandLine.getOptionValue(CLIManager.BUILDER));
1567         }
1568 
1569         return request;
1570     }
1571 
1572     int calculateDegreeOfConcurrency(String originalThreadConfiguration) {
1573         String threadConfiguration = originalThreadConfiguration.trim();
1574         if (threadConfiguration.endsWith("C")) {
1575             threadConfiguration = threadConfiguration.substring(0, threadConfiguration.length() - 1);
1576 
1577             try {
1578                 float coreMultiplier = Float.parseFloat(threadConfiguration);
1579 
1580                 if (coreMultiplier <= 0.0f) {
1581                     throw new IllegalArgumentException("Invalid threads core multiplier value: '" + threadConfiguration
1582                             + "C'. Value must be positive.");
1583                 }
1584 
1585                 int procs = Runtime.getRuntime().availableProcessors();
1586                 int threads = (int) (coreMultiplier * procs);
1587                 return threads == 0 ? 1 : threads;
1588             } catch (NumberFormatException e) {
1589                 throw new IllegalArgumentException(
1590                         "Invalid threads core multiplier value: '" + threadConfiguration
1591                                 + "C'. Supported are int and float values ending with C.",
1592                         e);
1593             }
1594         } else {
1595             try {
1596                 int threads = Integer.parseInt(threadConfiguration);
1597 
1598                 if (threads <= 0) {
1599                     throw new IllegalArgumentException(
1600                             "Invalid threads value: '" + threadConfiguration + "'. Value must be positive.");
1601                 }
1602 
1603                 return threads;
1604             } catch (NumberFormatException e) {
1605                 throw new IllegalArgumentException(
1606                         "Invalid threads value: '" + threadConfiguration + "'. Supported are integer values.");
1607             }
1608         }
1609     }
1610 
1611     // ----------------------------------------------------------------------
1612     // Properties handling
1613     // ----------------------------------------------------------------------
1614 
1615     static void populateProperties(CliRequest cliRequest, Properties systemProperties, Properties userProperties)
1616             throws InterpolationException {
1617 
1618         // ----------------------------------------------------------------------
1619         // Options that are set on the command line become system properties
1620         // and therefore are set in the session properties. System properties
1621         // are most dominant.
1622         // ----------------------------------------------------------------------
1623 
1624         Properties cliProperties = new Properties();
1625         if (cliRequest.commandLine.hasOption(CLIManager.SET_USER_PROPERTY)) {
1626             String[] defStrs = cliRequest.commandLine.getOptionValues(CLIManager.SET_USER_PROPERTY);
1627 
1628             if (defStrs != null) {
1629                 String name;
1630                 String value;
1631                 for (String property : defStrs) {
1632                     int i = property.indexOf('=');
1633                     if (i <= 0) {
1634                         name = property.trim();
1635                         value = "true";
1636                     } else {
1637                         name = property.substring(0, i).trim();
1638                         value = property.substring(i + 1);
1639                     }
1640                     cliProperties.setProperty(name, value);
1641                 }
1642             }
1643         }
1644 
1645         EnvironmentUtils.addEnvVars(systemProperties);
1646         SystemProperties.addSystemProperties(systemProperties);
1647 
1648         // one distinguished env variable: MAVEN_REPO_CENTRAL; if present, we make it into property
1649         if (systemProperties.containsKey(MAVEN_REPO_CENTRAL_ENV)) {
1650             systemProperties.put(
1651                     ModelInterpolator.MAVEN_REPO_CENTRAL_KEY, systemProperties.getProperty(MAVEN_REPO_CENTRAL_ENV));
1652         }
1653 
1654         StringSearchInterpolator interpolator = createInterpolator(cliRequest, cliProperties, systemProperties);
1655         for (Map.Entry<Object, Object> e : cliProperties.entrySet()) {
1656             String name = (String) e.getKey();
1657             String value = interpolator.interpolate((String) e.getValue());
1658             userProperties.setProperty(name, value);
1659         }
1660 
1661         systemProperties.putAll(userProperties);
1662 
1663         // ----------------------------------------------------------------------
1664         // I'm leaving the setting of system properties here as not to break
1665         // the SystemPropertyProfileActivator. This won't harm embedding. jvz.
1666         // ----------------------------------------------------------------------
1667         userProperties.forEach((k, v) -> System.setProperty((String) k, (String) v));
1668 
1669         // ----------------------------------------------------------------------
1670         // Properties containing info about the currently running version of Maven
1671         // These override any corresponding properties set on the command line
1672         // ----------------------------------------------------------------------
1673 
1674         Properties buildProperties = CLIReportingUtils.getBuildProperties();
1675 
1676         String mavenVersion = buildProperties.getProperty(CLIReportingUtils.BUILD_VERSION_PROPERTY);
1677         systemProperties.setProperty("maven.version", mavenVersion);
1678 
1679         String mavenBuildVersion = CLIReportingUtils.createMavenVersionString(buildProperties);
1680         systemProperties.setProperty("maven.build.version", mavenBuildVersion);
1681     }
1682 
1683     protected static StringSearchInterpolator createInterpolator(CliRequest cliRequest, Properties... properties) {
1684         StringSearchInterpolator interpolator = new StringSearchInterpolator();
1685         interpolator.addValueSource(new AbstractValueSource(false) {
1686             @Override
1687             public Object getValue(String expression) {
1688                 if ("session.topDirectory".equals(expression)) {
1689                     Path topDirectory = cliRequest.request.getTopDirectory();
1690                     if (topDirectory != null) {
1691                         return topDirectory.toString();
1692                     } else {
1693                         throw new IllegalUseOfUndefinedProperty(expression);
1694                     }
1695                 } else if ("session.rootDirectory".equals(expression)) {
1696                     try {
1697                         return cliRequest.request.getRootDirectory();
1698                     } catch (IllegalStateException e) {
1699                         throw new IllegalUseOfUndefinedProperty(expression);
1700                     }
1701                 }
1702                 return null;
1703             }
1704         });
1705         interpolator.addValueSource(new AbstractValueSource(false) {
1706             @Override
1707             public Object getValue(String expression) {
1708                 for (Properties props : properties) {
1709                     Object val = props.getProperty(expression);
1710                     if (val != null) {
1711                         return val;
1712                     }
1713                 }
1714                 return null;
1715             }
1716         });
1717         return interpolator;
1718     }
1719 
1720     private static String stripLeadingAndTrailingQuotes(String str) {
1721         final int length = str.length();
1722         if (length > 1
1723                 && str.startsWith("\"")
1724                 && str.endsWith("\"")
1725                 && str.substring(1, length - 1).indexOf('"') == -1) {
1726             str = str.substring(1, length - 1);
1727         }
1728 
1729         return str;
1730     }
1731 
1732     private static Path getCanonicalPath(Path path) {
1733         try {
1734             return path.toRealPath();
1735         } catch (IOException e) {
1736             return getCanonicalPath(path.getParent()).resolve(path.getFileName());
1737         }
1738     }
1739 
1740     static class ExitException extends Exception {
1741         int exitCode;
1742 
1743         ExitException(int exitCode) {
1744             this.exitCode = exitCode;
1745         }
1746     }
1747 
1748     static class IllegalUseOfUndefinedProperty extends IllegalArgumentException {
1749         final String property;
1750 
1751         IllegalUseOfUndefinedProperty(String property) {
1752             this.property = property;
1753         }
1754     }
1755 
1756     //
1757     // Customizations available via the CLI
1758     //
1759 
1760     protected TransferListener getConsoleTransferListener(boolean printResourceNames) {
1761         return new SimplexTransferListener(new ConsoleMavenTransferListener(System.out, printResourceNames));
1762     }
1763 
1764     protected TransferListener getBatchTransferListener() {
1765         return new Slf4jMavenTransferListener();
1766     }
1767 
1768     protected void customizeContainer(PlexusContainer container) {}
1769 
1770     protected ModelProcessor createModelProcessor(PlexusContainer container) throws ComponentLookupException {
1771         return container.lookup(ModelProcessor.class);
1772     }
1773 }