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