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