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