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