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