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