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