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