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