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