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