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