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