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