001package org.apache.maven.cli;
002
003/*
004 * Licensed to the Apache Software Foundation (ASF) under one
005 * or more contributor license agreements.  See the NOTICE file
006 * distributed with this work for additional information
007 * regarding copyright ownership.  The ASF licenses this file
008 * to you under the Apache License, Version 2.0 (the
009 * "License"); you may not use this file except in compliance
010 * with the License.  You may obtain a copy of the License at
011 *
012 *  http://www.apache.org/licenses/LICENSE-2.0
013 *
014 * Unless required by applicable law or agreed to in writing,
015 * software distributed under the License is distributed on an
016 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
017 * KIND, either express or implied.  See the License for the
018 * specific language governing permissions and limitations
019 * under the License.
020 */
021
022import java.io.Console;
023import java.io.File;
024import java.io.FileNotFoundException;
025import java.io.FileOutputStream;
026import java.io.PrintStream;
027import java.util.ArrayList;
028import java.util.LinkedHashMap;
029import java.util.List;
030import java.util.Map;
031import java.util.Properties;
032import java.util.StringTokenizer;
033
034import org.apache.commons.cli.CommandLine;
035import org.apache.commons.cli.ParseException;
036import org.apache.commons.cli.UnrecognizedOptionException;
037import org.apache.maven.BuildAbort;
038import org.apache.maven.InternalErrorException;
039import org.apache.maven.Maven;
040import org.apache.maven.cli.event.DefaultEventSpyContext;
041import org.apache.maven.cli.event.ExecutionEventLogger;
042import org.apache.maven.cli.logging.Slf4jConfiguration;
043import org.apache.maven.cli.logging.Slf4jConfigurationFactory;
044import org.apache.maven.cli.logging.Slf4jLoggerManager;
045import org.apache.maven.cli.logging.Slf4jStdoutLogger;
046import org.apache.maven.cli.transfer.ConsoleMavenTransferListener;
047import org.apache.maven.cli.transfer.QuietMavenTransferListener;
048import org.apache.maven.cli.transfer.Slf4jMavenTransferListener;
049import org.apache.maven.eventspy.internal.EventSpyDispatcher;
050import org.apache.maven.exception.DefaultExceptionHandler;
051import org.apache.maven.exception.ExceptionHandler;
052import org.apache.maven.exception.ExceptionSummary;
053import org.apache.maven.execution.DefaultMavenExecutionRequest;
054import org.apache.maven.execution.ExecutionListener;
055import org.apache.maven.execution.MavenExecutionRequest;
056import org.apache.maven.execution.MavenExecutionRequestPopulator;
057import org.apache.maven.execution.MavenExecutionResult;
058import org.apache.maven.lifecycle.LifecycleExecutionException;
059import org.apache.maven.model.building.ModelProcessor;
060import org.apache.maven.project.MavenProject;
061import org.apache.maven.properties.internal.EnvironmentUtils;
062import org.apache.maven.properties.internal.SystemProperties;
063import org.apache.maven.settings.building.DefaultSettingsBuildingRequest;
064import org.apache.maven.settings.building.SettingsBuilder;
065import org.apache.maven.settings.building.SettingsBuildingRequest;
066import org.apache.maven.settings.building.SettingsBuildingResult;
067import org.apache.maven.settings.building.SettingsProblem;
068import org.apache.maven.settings.building.SettingsSource;
069import org.codehaus.plexus.ContainerConfiguration;
070import org.codehaus.plexus.DefaultContainerConfiguration;
071import org.codehaus.plexus.DefaultPlexusContainer;
072import org.codehaus.plexus.PlexusConstants;
073import org.codehaus.plexus.PlexusContainer;
074import org.codehaus.plexus.classworlds.ClassWorld;
075import org.codehaus.plexus.classworlds.realm.ClassRealm;
076import org.codehaus.plexus.component.repository.exception.ComponentLookupException;
077import org.codehaus.plexus.logging.LoggerManager;
078import org.codehaus.plexus.util.StringUtils;
079import org.eclipse.aether.transfer.TransferListener;
080import org.slf4j.ILoggerFactory;
081import org.slf4j.Logger;
082import org.slf4j.LoggerFactory;
083import org.sonatype.plexus.components.cipher.DefaultPlexusCipher;
084import org.sonatype.plexus.components.sec.dispatcher.DefaultSecDispatcher;
085import org.sonatype.plexus.components.sec.dispatcher.SecDispatcher;
086import org.sonatype.plexus.components.sec.dispatcher.SecUtil;
087import org.sonatype.plexus.components.sec.dispatcher.model.SettingsSecurity;
088
089import com.google.inject.AbstractModule;
090
091// TODO: push all common bits back to plexus cli and prepare for transition to Guice. We don't need 50 ways to make CLIs
092
093/**
094 * @author Jason van Zyl
095 * @noinspection UseOfSystemOutOrSystemErr,ACCESS_STATIC_VIA_INSTANCE
096 */
097public class MavenCli
098{
099    public static final String LOCAL_REPO_PROPERTY = "maven.repo.local";
100
101    public static final String THREADS_DEPRECATED = "maven.threads.experimental";
102
103    public static final String userHome = System.getProperty( "user.home" );
104
105    public static final File userMavenConfigurationHome = new File( userHome, ".m2" );
106
107    public static final File DEFAULT_USER_SETTINGS_FILE = new File( userMavenConfigurationHome, "settings.xml" );
108
109    public static final File DEFAULT_GLOBAL_SETTINGS_FILE =
110        new File( System.getProperty( "maven.home", System.getProperty( "user.dir", "" ) ), "conf/settings.xml" );
111
112    public static final File DEFAULT_USER_TOOLCHAINS_FILE = new File( userMavenConfigurationHome, "toolchains.xml" );
113
114    private static final String EXT_CLASS_PATH = "maven.ext.class.path";
115
116    private ClassWorld classWorld;
117
118    private LoggerManager plexusLoggerManager;
119
120    private ILoggerFactory slf4jLoggerFactory;
121    
122    private Logger slf4jLogger;
123    
124    private EventSpyDispatcher eventSpyDispatcher;
125
126    private ModelProcessor modelProcessor;
127
128    private Maven maven;
129
130    private MavenExecutionRequestPopulator executionRequestPopulator;
131
132    private SettingsBuilder settingsBuilder;
133
134    private DefaultSecDispatcher dispatcher;
135
136    public MavenCli()
137    {
138        this( null );
139    }
140
141    // This supports painless invocation by the Verifier during embedded execution of the core ITs
142    public MavenCli( ClassWorld classWorld )
143    {
144        this.classWorld = classWorld;
145    }
146
147    public static void main( String[] args )
148    {
149        int result = main( args, null );
150
151        System.exit( result );
152    }
153
154    /** @noinspection ConfusingMainMethod */
155    public static int main( String[] args, ClassWorld classWorld )
156    {
157        MavenCli cli = new MavenCli();
158        return cli.doMain( new CliRequest( args, classWorld ) );
159    }
160
161    // TODO: need to externalize CliRequest
162    public static int doMain( String[] args, ClassWorld classWorld )
163    {
164        MavenCli cli = new MavenCli();
165        return cli.doMain( new CliRequest( args, classWorld ) );
166    }
167
168    // This supports painless invocation by the Verifier during embedded execution of the core ITs
169    public int doMain( String[] args, String workingDirectory, PrintStream stdout, PrintStream stderr )
170    {
171        PrintStream oldout = System.out;
172        PrintStream olderr = System.err;
173
174        try
175        {
176            if ( stdout != null )
177            {
178                System.setOut( stdout );
179            }
180            if ( stderr != null )
181            {
182                System.setErr( stderr );
183            }
184
185            CliRequest cliRequest = new CliRequest( args, classWorld );
186            cliRequest.workingDirectory = workingDirectory;
187
188            return doMain( cliRequest );
189        }
190        finally
191        {
192            System.setOut( oldout );
193            System.setErr( olderr );
194        }
195    }
196
197    // TODO: need to externalize CliRequest
198    public int doMain( CliRequest cliRequest )
199    {
200        PlexusContainer localContainer = null;
201        try
202        {
203            initialize( cliRequest );
204            cli( cliRequest );
205            logging( cliRequest );
206            version( cliRequest );
207            properties( cliRequest );
208            localContainer = container( cliRequest );
209            commands( cliRequest );
210            settings( cliRequest );
211            populateRequest( cliRequest );
212            encryption( cliRequest );
213            repository( cliRequest );
214            return execute( cliRequest );
215        }
216        catch ( ExitException e )
217        {
218            return e.exitCode;
219        }
220        catch ( UnrecognizedOptionException e )
221        {
222            // pure user error, suppress stack trace
223            return 1;
224        }
225        catch ( BuildAbort e )
226        {
227            CLIReportingUtils.showError( slf4jLogger, "ABORTED", e, cliRequest.showErrors );
228
229            return 2;
230        }
231        catch ( Exception e )
232        {
233            CLIReportingUtils.showError( slf4jLogger, "Error executing Maven.", e, cliRequest.showErrors );
234
235            return 1;
236        }
237        finally
238        {
239            if ( localContainer != null )
240            {
241                localContainer.dispose();
242            }
243        }
244    }
245
246    private void initialize( CliRequest cliRequest )
247    {
248        if ( cliRequest.workingDirectory == null )
249        {
250            cliRequest.workingDirectory = System.getProperty( "user.dir" );
251        }
252
253        //
254        // Make sure the Maven home directory is an absolute path to save us from confusion with say drive-relative
255        // Windows paths.
256        //
257        String mavenHome = System.getProperty( "maven.home" );
258
259        if ( mavenHome != null )
260        {
261            System.setProperty( "maven.home", new File( mavenHome ).getAbsolutePath() );
262        }
263    }
264
265    private void cli( CliRequest cliRequest )
266        throws Exception
267    {
268        //
269        // Parsing errors can happen during the processing of the arguments and we prefer not having to check if the logger is null
270        // and construct this so we can use an SLF4J logger everywhere.
271        //
272        slf4jLogger = new Slf4jStdoutLogger();
273
274        CLIManager cliManager = new CLIManager();
275
276        try
277        {
278            cliRequest.commandLine = cliManager.parse( cliRequest.args );
279        }
280        catch ( ParseException e )
281        {
282            System.err.println( "Unable to parse command line options: " + e.getMessage() );
283            cliManager.displayHelp( System.out );
284            throw e;
285        }
286
287        if ( cliRequest.commandLine.hasOption( CLIManager.HELP ) )
288        {
289            cliManager.displayHelp( System.out );
290            throw new ExitException( 0 );
291        }
292
293        if ( cliRequest.commandLine.hasOption( CLIManager.VERSION ) )
294        {
295            System.out.println( CLIReportingUtils.showVersion() );
296            throw new ExitException( 0 );
297        }
298    }    
299
300    /**
301     * configure logging
302     */
303    private void logging( CliRequest cliRequest )
304    {
305        cliRequest.debug = cliRequest.commandLine.hasOption( CLIManager.DEBUG );
306        cliRequest.quiet = !cliRequest.debug && cliRequest.commandLine.hasOption( CLIManager.QUIET );
307        cliRequest.showErrors = cliRequest.debug || cliRequest.commandLine.hasOption( CLIManager.ERRORS );
308
309        slf4jLoggerFactory = LoggerFactory.getILoggerFactory();
310        Slf4jConfiguration slf4jConfiguration = Slf4jConfigurationFactory.getConfiguration( slf4jLoggerFactory );
311
312        if ( cliRequest.debug )
313        {
314            cliRequest.request.setLoggingLevel( MavenExecutionRequest.LOGGING_LEVEL_DEBUG );
315            slf4jConfiguration.setRootLoggerLevel( Slf4jConfiguration.Level.DEBUG );
316        }
317        else if ( cliRequest.quiet )
318        {
319            cliRequest.request.setLoggingLevel( MavenExecutionRequest.LOGGING_LEVEL_ERROR );
320            slf4jConfiguration.setRootLoggerLevel( Slf4jConfiguration.Level.ERROR );
321        }
322        // else fall back to default log level specified in conf
323        // see http://jira.codehaus.org/browse/MNG-2570
324
325        if ( cliRequest.commandLine.hasOption( CLIManager.LOG_FILE ) )
326        {
327            File logFile = new File( cliRequest.commandLine.getOptionValue( CLIManager.LOG_FILE ) );
328            logFile = resolveFile( logFile, cliRequest.workingDirectory );
329
330            // redirect stdout and stderr to file
331            try
332            {
333                PrintStream ps = new PrintStream( new FileOutputStream( logFile ) );
334                System.setOut( ps );
335                System.setErr( ps );
336            }
337            catch ( FileNotFoundException e )
338            {
339                //
340                // Ignore
341                //
342            }
343        }
344
345        slf4jConfiguration.activate();
346
347        plexusLoggerManager = new Slf4jLoggerManager();
348        slf4jLogger = slf4jLoggerFactory.getLogger( this.getClass().getName() );
349    }
350
351    private void version( CliRequest cliRequest )
352    {
353        if ( cliRequest.debug || cliRequest.commandLine.hasOption( CLIManager.SHOW_VERSION ) )
354        {
355            System.out.println( CLIReportingUtils.showVersion() );
356        }
357    }
358
359    private void commands( CliRequest cliRequest )
360    {
361        if ( cliRequest.showErrors )
362        {
363            slf4jLogger.info( "Error stacktraces are turned on." );
364        }
365
366        if ( MavenExecutionRequest.CHECKSUM_POLICY_WARN.equals( cliRequest.request.getGlobalChecksumPolicy() ) )
367        {
368            slf4jLogger.info( "Disabling strict checksum verification on all artifact downloads." );
369        }
370        else if ( MavenExecutionRequest.CHECKSUM_POLICY_FAIL.equals( cliRequest.request.getGlobalChecksumPolicy() ) )
371        {
372            slf4jLogger.info( "Enabling strict checksum verification on all artifact downloads." );
373        }
374    }
375
376    private void properties( CliRequest cliRequest )
377    {
378        populateProperties( cliRequest.commandLine, cliRequest.systemProperties, cliRequest.userProperties );
379    }
380
381    private PlexusContainer container( CliRequest cliRequest )
382        throws Exception
383    {
384        if ( cliRequest.classWorld == null )
385        {
386            cliRequest.classWorld = new ClassWorld( "plexus.core", Thread.currentThread().getContextClassLoader() );
387        }
388
389        DefaultPlexusContainer container;
390
391        ContainerConfiguration cc = new DefaultContainerConfiguration()
392            .setClassWorld( cliRequest.classWorld )
393            .setRealm( setupContainerRealm( cliRequest ) )
394            .setClassPathScanning( PlexusConstants.SCANNING_INDEX )
395            .setAutoWiring( true )
396            .setName( "maven" );
397
398        container = new DefaultPlexusContainer( cc, new AbstractModule()
399        {
400            protected void configure()
401            {
402                bind( ILoggerFactory.class ).toInstance( slf4jLoggerFactory );
403            }
404        } );
405
406        // NOTE: To avoid inconsistencies, we'll use the TCCL exclusively for lookups
407        container.setLookupRealm( null );
408
409        container.setLoggerManager( plexusLoggerManager );
410
411        customizeContainer( container );
412
413        container.getLoggerManager().setThresholds( cliRequest.request.getLoggingLevel() );
414
415        Thread.currentThread().setContextClassLoader( container.getContainerRealm() );
416
417        eventSpyDispatcher = container.lookup( EventSpyDispatcher.class );
418
419        DefaultEventSpyContext eventSpyContext = new DefaultEventSpyContext();
420        Map<String, Object> data = eventSpyContext.getData();
421        data.put( "plexus", container );
422        data.put( "workingDirectory", cliRequest.workingDirectory );
423        data.put( "systemProperties", cliRequest.systemProperties );
424        data.put( "userProperties", cliRequest.userProperties );
425        data.put( "versionProperties", CLIReportingUtils.getBuildProperties() );
426        eventSpyDispatcher.init( eventSpyContext );
427
428        // refresh logger in case container got customized by spy
429        slf4jLogger = slf4jLoggerFactory.getLogger( this.getClass().getName() );
430
431        maven = container.lookup( Maven.class );
432
433        executionRequestPopulator = container.lookup( MavenExecutionRequestPopulator.class );
434
435        modelProcessor = createModelProcessor( container );
436
437        settingsBuilder = container.lookup( SettingsBuilder.class );
438
439        dispatcher = (DefaultSecDispatcher) container.lookup( SecDispatcher.class, "maven" );
440
441        return container;
442    }
443
444    private ClassRealm setupContainerRealm( CliRequest cliRequest )
445        throws Exception
446    {
447        ClassRealm containerRealm = null;
448
449        String extClassPath = cliRequest.userProperties.getProperty( EXT_CLASS_PATH );
450        if ( extClassPath == null )
451        {
452            extClassPath = cliRequest.systemProperties.getProperty( EXT_CLASS_PATH );
453        }
454
455        if ( StringUtils.isNotEmpty( extClassPath ) )
456        {
457            String[] jars = StringUtils.split( extClassPath, File.pathSeparator );
458
459            if ( jars.length > 0 )
460            {
461                ClassRealm coreRealm = cliRequest.classWorld.getClassRealm( "plexus.core" );
462                if ( coreRealm == null )
463                {
464                    coreRealm = cliRequest.classWorld.getRealms().iterator().next();
465                }
466
467                ClassRealm extRealm = cliRequest.classWorld.newRealm( "maven.ext", null );
468
469                slf4jLogger.debug( "Populating class realm " + extRealm.getId() );
470
471                for ( String jar : jars )
472                {
473                    File file = resolveFile( new File( jar ), cliRequest.workingDirectory );
474
475                    slf4jLogger.debug( "  Included " + file );
476
477                    extRealm.addURL( file.toURI().toURL() );
478                }
479
480                extRealm.setParentRealm( coreRealm );
481
482                containerRealm = extRealm;
483            }
484        }
485
486        return containerRealm;
487    }
488
489    //
490    // This should probably be a separate tool and not be baked into Maven.
491    //
492    private void encryption( CliRequest cliRequest )
493        throws Exception
494    {
495        if ( cliRequest.commandLine.hasOption( CLIManager.ENCRYPT_MASTER_PASSWORD ) )
496        {
497            String passwd = cliRequest.commandLine.getOptionValue( CLIManager.ENCRYPT_MASTER_PASSWORD );
498            
499            if ( passwd == null )
500            {
501                Console cons;
502                char[] password;
503                if ( ( cons = System.console() ) != null
504                    && ( password = cons.readPassword( "Master password: " ) ) != null )
505                {
506                    // Cipher uses Strings
507                    passwd = String.copyValueOf( password );
508                    
509                    // Sun/Oracle advises to empty the char array
510                    java.util.Arrays.fill( password, ' ' );
511                }
512            }
513
514            DefaultPlexusCipher cipher = new DefaultPlexusCipher();
515
516            System.out.println( cipher.encryptAndDecorate( passwd, DefaultSecDispatcher.SYSTEM_PROPERTY_SEC_LOCATION ) );
517
518            throw new ExitException( 0 );
519        }
520        else if ( cliRequest.commandLine.hasOption( CLIManager.ENCRYPT_PASSWORD ) )
521        {
522            String passwd = cliRequest.commandLine.getOptionValue( CLIManager.ENCRYPT_PASSWORD );
523            
524            if ( passwd == null )
525            {
526                Console cons;
527                char[] password;
528                if ( ( cons = System.console() ) != null
529                    && ( password = cons.readPassword( "Password: " ) ) != null )
530                {
531                    // Cipher uses Strings
532                    passwd = String.copyValueOf( password );
533
534                    // Sun/Oracle advises to empty the char array
535                    java.util.Arrays.fill( password, ' ' );
536                }
537            }
538
539            String configurationFile = dispatcher.getConfigurationFile();
540
541            if ( configurationFile.startsWith( "~" ) )
542            {
543                configurationFile = System.getProperty( "user.home" ) + configurationFile.substring( 1 );
544            }
545
546            String file = System.getProperty( DefaultSecDispatcher.SYSTEM_PROPERTY_SEC_LOCATION, configurationFile );
547
548            String master = null;
549
550            SettingsSecurity sec = SecUtil.read( file, true );
551            if ( sec != null )
552            {
553                master = sec.getMaster();
554            }
555
556            if ( master == null )
557            {
558                throw new IllegalStateException( "Master password is not set in the setting security file: " + file );
559            }
560
561            DefaultPlexusCipher cipher = new DefaultPlexusCipher();
562            String masterPasswd = cipher.decryptDecorated( master, DefaultSecDispatcher.SYSTEM_PROPERTY_SEC_LOCATION );
563            System.out.println( cipher.encryptAndDecorate( passwd, masterPasswd ) );
564
565            throw new ExitException( 0 );
566        }
567    }
568
569    private void repository( CliRequest cliRequest )
570        throws Exception
571    {
572        if ( cliRequest.commandLine.hasOption( CLIManager.LEGACY_LOCAL_REPOSITORY ) || Boolean.getBoolean( "maven.legacyLocalRepo" ) )
573        {
574           cliRequest.request.setUseLegacyLocalRepository( true );
575        }
576    }
577
578    private int execute( CliRequest cliRequest )
579    {
580        eventSpyDispatcher.onEvent( cliRequest.request );
581
582        MavenExecutionResult result = maven.execute( cliRequest.request );
583
584        eventSpyDispatcher.onEvent( result );
585
586        eventSpyDispatcher.close();
587
588        if ( result.hasExceptions() )
589        {
590            ExceptionHandler handler = new DefaultExceptionHandler();
591
592            Map<String, String> references = new LinkedHashMap<String, String>();
593
594            MavenProject project = null;
595
596            for ( Throwable exception : result.getExceptions() )
597            {
598                ExceptionSummary summary = handler.handleException( exception );
599
600                logSummary( summary, references, "", cliRequest.showErrors );
601
602                if ( project == null && exception instanceof LifecycleExecutionException )
603                {
604                    project = ( (LifecycleExecutionException) exception ).getProject();
605                }
606            }
607
608            slf4jLogger.error( "" );
609
610            if ( !cliRequest.showErrors )
611            {
612                slf4jLogger.error( "To see the full stack trace of the errors, re-run Maven with the -e switch." );
613            }
614            if ( !slf4jLogger.isDebugEnabled() )
615            {
616                slf4jLogger.error( "Re-run Maven using the -X switch to enable full debug logging." );
617            }
618
619            if ( !references.isEmpty() )
620            {
621                slf4jLogger.error( "" );
622                slf4jLogger.error( "For more information about the errors and possible solutions"
623                              + ", please read the following articles:" );
624
625                for ( Map.Entry<String, String> entry : references.entrySet() )
626                {
627                    slf4jLogger.error( entry.getValue() + " " + entry.getKey() );
628                }
629            }
630
631            if ( project != null && !project.equals( result.getTopologicallySortedProjects().get( 0 ) ) )
632            {
633                slf4jLogger.error( "" );
634                slf4jLogger.error( "After correcting the problems, you can resume the build with the command" );
635                slf4jLogger.error( "  mvn <goals> -rf :" + project.getArtifactId() );
636            }
637
638            if ( MavenExecutionRequest.REACTOR_FAIL_NEVER.equals( cliRequest.request.getReactorFailureBehavior() ) )
639            {
640                slf4jLogger.info( "Build failures were ignored." );
641
642                return 0;
643            }
644            else
645            {
646                return 1;
647            }
648        }
649        else
650        {
651            return 0;
652        }
653    }
654
655    private void logSummary( ExceptionSummary summary, Map<String, String> references, String indent,
656                             boolean showErrors )
657    {
658        String referenceKey = "";
659
660        if ( StringUtils.isNotEmpty( summary.getReference() ) )
661        {
662            referenceKey = references.get( summary.getReference() );
663            if ( referenceKey == null )
664            {
665                referenceKey = "[Help " + ( references.size() + 1 ) + "]";
666                references.put( summary.getReference(), referenceKey );
667            }
668        }
669
670        String msg = summary.getMessage();
671
672        if ( StringUtils.isNotEmpty( referenceKey ) )
673        {
674            if ( msg.indexOf( '\n' ) < 0 )
675            {
676                msg += " -> " + referenceKey;
677            }
678            else
679            {
680                msg += "\n-> " + referenceKey;
681            }
682        }
683
684        String[] lines = msg.split( "(\r\n)|(\r)|(\n)" );
685
686        for ( int i = 0; i < lines.length; i++ )
687        {
688            String line = indent + lines[i].trim();
689
690            if ( i == lines.length - 1 && ( showErrors || ( summary.getException() instanceof InternalErrorException ) ) )
691            {
692                slf4jLogger.error( line, summary.getException() );
693            }
694            else
695            {
696                slf4jLogger.error( line );
697            }
698        }
699
700        indent += "  ";
701
702        for ( ExceptionSummary child : summary.getChildren() )
703        {
704            logSummary( child, references, indent, showErrors );
705        }
706    }
707
708    private void settings( CliRequest cliRequest )
709        throws Exception
710    {
711        File userSettingsFile;
712
713        if ( cliRequest.commandLine.hasOption( CLIManager.ALTERNATE_USER_SETTINGS ) )
714        {
715            userSettingsFile = new File( cliRequest.commandLine.getOptionValue( CLIManager.ALTERNATE_USER_SETTINGS ) );
716            userSettingsFile = resolveFile( userSettingsFile, cliRequest.workingDirectory );
717
718            if ( !userSettingsFile.isFile() )
719            {
720                throw new FileNotFoundException( "The specified user settings file does not exist: "
721                    + userSettingsFile );
722            }
723        }
724        else
725        {
726            userSettingsFile = DEFAULT_USER_SETTINGS_FILE;
727        }
728
729        File globalSettingsFile;
730
731        if ( cliRequest.commandLine.hasOption( CLIManager.ALTERNATE_GLOBAL_SETTINGS ) )
732        {
733            globalSettingsFile =
734                new File( cliRequest.commandLine.getOptionValue( CLIManager.ALTERNATE_GLOBAL_SETTINGS ) );
735            globalSettingsFile = resolveFile( globalSettingsFile, cliRequest.workingDirectory );
736
737            if ( !globalSettingsFile.isFile() )
738            {
739                throw new FileNotFoundException( "The specified global settings file does not exist: "
740                    + globalSettingsFile );
741            }
742        }
743        else
744        {
745            globalSettingsFile = DEFAULT_GLOBAL_SETTINGS_FILE;
746        }
747
748        cliRequest.request.setGlobalSettingsFile( globalSettingsFile );
749        cliRequest.request.setUserSettingsFile( userSettingsFile );
750
751        SettingsBuildingRequest settingsRequest = new DefaultSettingsBuildingRequest();
752        settingsRequest.setGlobalSettingsFile( globalSettingsFile );
753        settingsRequest.setUserSettingsFile( userSettingsFile );
754        settingsRequest.setSystemProperties( cliRequest.systemProperties );
755        settingsRequest.setUserProperties( cliRequest.userProperties );
756
757        eventSpyDispatcher.onEvent( settingsRequest );
758
759        slf4jLogger.debug( "Reading global settings from "
760            + getSettingsLocation( settingsRequest.getGlobalSettingsSource(), settingsRequest.getGlobalSettingsFile() ) );
761        slf4jLogger.debug( "Reading user settings from "
762            + getSettingsLocation( settingsRequest.getUserSettingsSource(), settingsRequest.getUserSettingsFile() ) );
763
764        SettingsBuildingResult settingsResult = settingsBuilder.build( settingsRequest );
765
766        eventSpyDispatcher.onEvent( settingsResult );
767
768        executionRequestPopulator.populateFromSettings( cliRequest.request, settingsResult.getEffectiveSettings() );
769
770        if ( !settingsResult.getProblems().isEmpty() && slf4jLogger.isWarnEnabled() )
771        {
772            slf4jLogger.warn( "" );
773            slf4jLogger.warn( "Some problems were encountered while building the effective settings" );
774
775            for ( SettingsProblem problem : settingsResult.getProblems() )
776            {
777                slf4jLogger.warn( problem.getMessage() + " @ " + problem.getLocation() );
778            }
779
780            slf4jLogger.warn( "" );
781        }
782    }
783
784    private Object getSettingsLocation( SettingsSource source, File file )
785    {
786        if ( source != null )
787        {
788            return source.getLocation();
789        }
790        return file;
791    }
792
793    private MavenExecutionRequest populateRequest( CliRequest cliRequest )
794    {
795        MavenExecutionRequest request = cliRequest.request;
796        CommandLine commandLine = cliRequest.commandLine;
797        String workingDirectory = cliRequest.workingDirectory;
798        boolean quiet = cliRequest.quiet;
799        boolean showErrors = cliRequest.showErrors;
800
801        String[] deprecatedOptions = { "up", "npu", "cpu", "npr" };
802        for ( String deprecatedOption : deprecatedOptions )
803        {
804            if ( commandLine.hasOption( deprecatedOption ) )
805            {
806                slf4jLogger.warn( "Command line option -" + deprecatedOption
807                    + " is deprecated and will be removed in future Maven versions." );
808            }
809        }
810
811        // ----------------------------------------------------------------------
812        // Now that we have everything that we need we will fire up plexus and
813        // bring the maven component to life for use.
814        // ----------------------------------------------------------------------
815
816        if ( commandLine.hasOption( CLIManager.BATCH_MODE ) )
817        {
818            request.setInteractiveMode( false );
819        }
820
821        boolean noSnapshotUpdates = false;
822        if ( commandLine.hasOption( CLIManager.SUPRESS_SNAPSHOT_UPDATES ) )
823        {
824            noSnapshotUpdates = true;
825        }
826
827        // ----------------------------------------------------------------------
828        //
829        // ----------------------------------------------------------------------
830
831        @SuppressWarnings( "unchecked" )
832        List<String> goals = commandLine.getArgList();
833
834        boolean recursive = true;
835
836        // this is the default behavior.
837        String reactorFailureBehaviour = MavenExecutionRequest.REACTOR_FAIL_FAST;
838
839        if ( commandLine.hasOption( CLIManager.NON_RECURSIVE ) )
840        {
841            recursive = false;
842        }
843
844        if ( commandLine.hasOption( CLIManager.FAIL_FAST ) )
845        {
846            reactorFailureBehaviour = MavenExecutionRequest.REACTOR_FAIL_FAST;
847        }
848        else if ( commandLine.hasOption( CLIManager.FAIL_AT_END ) )
849        {
850            reactorFailureBehaviour = MavenExecutionRequest.REACTOR_FAIL_AT_END;
851        }
852        else if ( commandLine.hasOption( CLIManager.FAIL_NEVER ) )
853        {
854            reactorFailureBehaviour = MavenExecutionRequest.REACTOR_FAIL_NEVER;
855        }
856
857        if ( commandLine.hasOption( CLIManager.OFFLINE ) )
858        {
859            request.setOffline( true );
860        }
861
862        boolean updateSnapshots = false;
863
864        if ( commandLine.hasOption( CLIManager.UPDATE_SNAPSHOTS ) )
865        {
866            updateSnapshots = true;
867        }
868
869        String globalChecksumPolicy = null;
870
871        if ( commandLine.hasOption( CLIManager.CHECKSUM_FAILURE_POLICY ) )
872        {
873            globalChecksumPolicy = MavenExecutionRequest.CHECKSUM_POLICY_FAIL;
874        }
875        else if ( commandLine.hasOption( CLIManager.CHECKSUM_WARNING_POLICY ) )
876        {
877            globalChecksumPolicy = MavenExecutionRequest.CHECKSUM_POLICY_WARN;
878        }
879
880        File baseDirectory = new File( workingDirectory, "" ).getAbsoluteFile();
881
882        // ----------------------------------------------------------------------
883        // Profile Activation
884        // ----------------------------------------------------------------------
885
886        List<String> activeProfiles = new ArrayList<String>();
887
888        List<String> inactiveProfiles = new ArrayList<String>();
889
890        if ( commandLine.hasOption( CLIManager.ACTIVATE_PROFILES ) )
891        {
892            String[] profileOptionValues = commandLine.getOptionValues( CLIManager.ACTIVATE_PROFILES );
893            if ( profileOptionValues != null )
894            {
895                for ( String profileOptionValue : profileOptionValues )
896                {
897                    StringTokenizer profileTokens = new StringTokenizer( profileOptionValue, "," );
898
899                    while ( profileTokens.hasMoreTokens() )
900                    {
901                        String profileAction = profileTokens.nextToken().trim();
902
903                        if ( profileAction.startsWith( "-" ) || profileAction.startsWith( "!" ) )
904                        {
905                            inactiveProfiles.add( profileAction.substring( 1 ) );
906                        }
907                        else if ( profileAction.startsWith( "+" ) )
908                        {
909                            activeProfiles.add( profileAction.substring( 1 ) );
910                        }
911                        else
912                        {
913                            activeProfiles.add( profileAction );
914                        }
915                    }
916                }
917            }
918        }
919
920        TransferListener transferListener;
921
922        if ( quiet )
923        {
924            transferListener = new QuietMavenTransferListener();
925        }        
926        else if ( request.isInteractiveMode() && !cliRequest.commandLine.hasOption( CLIManager.LOG_FILE ) )
927        {
928            //
929            // If we're logging to a file then we don't want the console transfer listener as it will spew
930            // download progress all over the place
931            //
932            transferListener = getConsoleTransferListener();
933        }
934        else
935        {
936            transferListener = getBatchTransferListener();
937        }
938
939        ExecutionListener executionListener = new ExecutionEventLogger();
940        executionListener = eventSpyDispatcher.chainListener( executionListener );
941
942        String alternatePomFile = null;
943        if ( commandLine.hasOption( CLIManager.ALTERNATE_POM_FILE ) )
944        {
945            alternatePomFile = commandLine.getOptionValue( CLIManager.ALTERNATE_POM_FILE );
946        }
947
948        File userToolchainsFile;
949        if ( commandLine.hasOption( CLIManager.ALTERNATE_USER_TOOLCHAINS ) )
950        {
951            userToolchainsFile = new File( commandLine.getOptionValue( CLIManager.ALTERNATE_USER_TOOLCHAINS ) );
952            userToolchainsFile = resolveFile( userToolchainsFile, workingDirectory );
953        }
954        else
955        {
956            userToolchainsFile = MavenCli.DEFAULT_USER_TOOLCHAINS_FILE;
957        }
958
959        request.setBaseDirectory( baseDirectory ).setGoals( goals )
960            .setSystemProperties( cliRequest.systemProperties )
961            .setUserProperties( cliRequest.userProperties )
962            .setReactorFailureBehavior( reactorFailureBehaviour ) // default: fail fast
963            .setRecursive( recursive ) // default: true
964            .setShowErrors( showErrors ) // default: false
965            .addActiveProfiles( activeProfiles ) // optional
966            .addInactiveProfiles( inactiveProfiles ) // optional
967            .setExecutionListener( executionListener )
968            .setTransferListener( transferListener ) // default: batch mode which goes along with interactive
969            .setUpdateSnapshots( updateSnapshots ) // default: false
970            .setNoSnapshotUpdates( noSnapshotUpdates ) // default: false
971            .setGlobalChecksumPolicy( globalChecksumPolicy ) // default: warn
972            .setUserToolchainsFile( userToolchainsFile );
973
974        if ( alternatePomFile != null )
975        {
976            File pom = resolveFile( new File( alternatePomFile ), workingDirectory );
977            if ( pom.isDirectory() )
978            {
979                pom = new File( pom, "pom.xml" );
980            }
981
982            request.setPom( pom );
983        }
984        else
985        {
986            File pom = modelProcessor.locatePom( baseDirectory );
987
988            if ( pom.isFile() )
989            {
990                request.setPom( pom );
991            }
992        }
993
994        if ( ( request.getPom() != null ) && ( request.getPom().getParentFile() != null ) )
995        {
996            request.setBaseDirectory( request.getPom().getParentFile() );
997        }
998
999        if ( commandLine.hasOption( CLIManager.RESUME_FROM ) )
1000        {
1001            request.setResumeFrom( commandLine.getOptionValue( CLIManager.RESUME_FROM ) );
1002        }
1003
1004        if ( commandLine.hasOption( CLIManager.PROJECT_LIST ) )
1005        {
1006            String[] projectOptionValues = commandLine.getOptionValues( CLIManager.PROJECT_LIST );
1007            
1008            List<String> inclProjects = new ArrayList<String>();
1009            List<String> exclProjects = new ArrayList<String>();
1010            
1011            if ( projectOptionValues != null )
1012            {
1013                for ( String projectOptionValue : projectOptionValues )
1014                {
1015                    StringTokenizer projectTokens = new StringTokenizer( projectOptionValue, "," );
1016
1017                    while ( projectTokens.hasMoreTokens() )
1018                    {
1019                        String projectAction = projectTokens.nextToken().trim();
1020
1021                        if ( projectAction.startsWith( "-" ) || projectAction.startsWith( "!" ) )
1022                        {
1023                            exclProjects.add( projectAction.substring( 1 ) );
1024                        }
1025                        else if ( projectAction.startsWith( "+" ) )
1026                        {
1027                            inclProjects.add( projectAction.substring( 1 ) );
1028                        }
1029                        else
1030                        {
1031                            inclProjects.add( projectAction );
1032                        }
1033                    }
1034                }
1035            }
1036            
1037            request.setSelectedProjects( inclProjects );
1038            request.setExcludedProjects( exclProjects );
1039        }
1040
1041        if ( commandLine.hasOption( CLIManager.ALSO_MAKE )
1042                        && !commandLine.hasOption( CLIManager.ALSO_MAKE_DEPENDENTS ) )
1043        {
1044            request.setMakeBehavior( MavenExecutionRequest.REACTOR_MAKE_UPSTREAM );
1045        }
1046        else if ( !commandLine.hasOption( CLIManager.ALSO_MAKE )
1047                        && commandLine.hasOption( CLIManager.ALSO_MAKE_DEPENDENTS ) )
1048        {
1049            request.setMakeBehavior( MavenExecutionRequest.REACTOR_MAKE_DOWNSTREAM );
1050        }
1051        else if ( commandLine.hasOption( CLIManager.ALSO_MAKE )
1052                        && commandLine.hasOption( CLIManager.ALSO_MAKE_DEPENDENTS ) )
1053        {
1054            request.setMakeBehavior( MavenExecutionRequest.REACTOR_MAKE_BOTH );
1055        }
1056
1057        String localRepoProperty = request.getUserProperties().getProperty( MavenCli.LOCAL_REPO_PROPERTY );
1058
1059        if ( localRepoProperty == null )
1060        {
1061            localRepoProperty = request.getSystemProperties().getProperty( MavenCli.LOCAL_REPO_PROPERTY );
1062        }
1063
1064        if ( localRepoProperty != null )
1065        {
1066            request.setLocalRepositoryPath( localRepoProperty );
1067        }
1068        
1069
1070        request.setCacheNotFound( true );
1071        request.setCacheTransferError( false );
1072
1073        // 
1074        // Builder, concurrency and parallelism
1075        //
1076        // We preserve the existing methods for builder selection which is to look for various inputs in the threading
1077        // configuration. We don't have an easy way to allow a pluggable builder to provide its own configuration parameters
1078        // but this is sufficient for now. Ultimately we want components like Builders to provide a way to extend the command
1079        // line to accept its own configuration parameters. 
1080        //
1081        final String threadConfiguration = commandLine.hasOption( CLIManager.THREADS )
1082            ? commandLine.getOptionValue( CLIManager.THREADS )
1083            : request.getSystemProperties().getProperty(
1084                MavenCli.THREADS_DEPRECATED ); // TODO: Remove this setting. Note that the int-tests use it
1085        
1086        if ( threadConfiguration != null )
1087        {
1088            //
1089            // Default to the standard multithreaded builder
1090            //
1091            request.setBuilderId( "multithreaded" );
1092
1093            if ( threadConfiguration.contains( "C" ) )
1094            {
1095                request.setDegreeOfConcurrency( calculateDegreeOfConcurrencyWithCoreMultiplier( threadConfiguration ) );
1096            }
1097            else
1098            {
1099                request.setDegreeOfConcurrency( Integer.valueOf( threadConfiguration ) );
1100            }
1101        }
1102              
1103        //
1104        // Allow the builder to be overriden by the user if requested. The builders are now pluggable.
1105        //
1106        if ( commandLine.hasOption( CLIManager.BUILDER ) )
1107        {          
1108            request.setBuilderId( commandLine.getOptionValue( CLIManager.BUILDER ) );
1109        }        
1110        
1111        return request;
1112    }
1113
1114    int calculateDegreeOfConcurrencyWithCoreMultiplier( String threadConfiguration )
1115    {
1116        return (int) ( Float.valueOf( threadConfiguration.replace( "C", "" ) ) * Runtime.getRuntime().availableProcessors() );
1117    }
1118    
1119    static File resolveFile( File file, String workingDirectory )
1120    {
1121        if ( file == null )
1122        {
1123            return null;
1124        }
1125        else if ( file.isAbsolute() )
1126        {
1127            return file;
1128        }
1129        else if ( file.getPath().startsWith( File.separator ) )
1130        {
1131            // drive-relative Windows path
1132            return file.getAbsoluteFile();
1133        }
1134        else
1135        {
1136            return new File( workingDirectory, file.getPath() ).getAbsoluteFile();
1137        }
1138    }
1139
1140    // ----------------------------------------------------------------------
1141    // System properties handling
1142    // ----------------------------------------------------------------------
1143
1144    static void populateProperties( CommandLine commandLine, Properties systemProperties, Properties userProperties )
1145    {
1146        EnvironmentUtils.addEnvVars( systemProperties );
1147
1148        // ----------------------------------------------------------------------
1149        // Options that are set on the command line become system properties
1150        // and therefore are set in the session properties. System properties
1151        // are most dominant.
1152        // ----------------------------------------------------------------------
1153
1154        if ( commandLine.hasOption( CLIManager.SET_SYSTEM_PROPERTY ) )
1155        {
1156            String[] defStrs = commandLine.getOptionValues( CLIManager.SET_SYSTEM_PROPERTY );
1157
1158            if ( defStrs != null )
1159            {
1160                for ( String defStr : defStrs )
1161                {
1162                    setCliProperty( defStr, userProperties );
1163                }
1164            }
1165        }
1166
1167        SystemProperties.addSystemProperties( systemProperties );
1168
1169        // ----------------------------------------------------------------------
1170        // Properties containing info about the currently running version of Maven
1171        // These override any corresponding properties set on the command line
1172        // ----------------------------------------------------------------------
1173
1174        Properties buildProperties = CLIReportingUtils.getBuildProperties();
1175
1176        String mavenVersion = buildProperties.getProperty( CLIReportingUtils.BUILD_VERSION_PROPERTY );
1177        systemProperties.setProperty( "maven.version", mavenVersion );
1178
1179        String mavenBuildVersion = CLIReportingUtils.createMavenVersionString( buildProperties );
1180        systemProperties.setProperty( "maven.build.version", mavenBuildVersion );
1181    }
1182
1183    private static void setCliProperty( String property, Properties properties )
1184    {
1185        String name;
1186
1187        String value;
1188
1189        int i = property.indexOf( "=" );
1190
1191        if ( i <= 0 )
1192        {
1193            name = property.trim();
1194
1195            value = "true";
1196        }
1197        else
1198        {
1199            name = property.substring( 0, i ).trim();
1200
1201            value = property.substring( i + 1 );
1202        }
1203
1204        properties.setProperty( name, value );
1205
1206        // ----------------------------------------------------------------------
1207        // I'm leaving the setting of system properties here as not to break
1208        // the SystemPropertyProfileActivator. This won't harm embedding. jvz.
1209        // ----------------------------------------------------------------------
1210
1211        System.setProperty( name, value );
1212    }
1213
1214    static class CliRequest
1215    {
1216        String[] args;
1217        CommandLine commandLine;
1218        ClassWorld classWorld;
1219        String workingDirectory;
1220        boolean debug;
1221        boolean quiet;
1222        boolean showErrors = true;
1223        Properties userProperties = new Properties();
1224        Properties systemProperties = new Properties();
1225        MavenExecutionRequest request;
1226
1227        CliRequest( String[] args, ClassWorld classWorld )
1228        {
1229            this.args = args;
1230            this.classWorld = classWorld;
1231            this.request = new DefaultMavenExecutionRequest();
1232        }
1233    }
1234
1235    static class ExitException
1236        extends Exception
1237    {
1238
1239        public int exitCode;
1240
1241        public ExitException( int exitCode )
1242        {
1243            this.exitCode = exitCode;
1244        }
1245
1246    }
1247    
1248    //
1249    // Customizations available via the CLI
1250    //
1251    
1252    protected TransferListener getConsoleTransferListener() 
1253    {
1254        return new ConsoleMavenTransferListener( System.out );
1255    }
1256    
1257    protected TransferListener getBatchTransferListener()
1258    {
1259        return new Slf4jMavenTransferListener();
1260    }
1261    
1262    protected void customizeContainer( PlexusContainer container )
1263    {
1264    }
1265
1266    protected ModelProcessor createModelProcessor( PlexusContainer container )
1267        throws ComponentLookupException
1268    {
1269        return container.lookup( ModelProcessor.class );
1270    }        
1271}