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