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 org.apache.commons.cli.CommandLine;
23 import org.apache.commons.cli.CommandLineParser;
24 import org.apache.commons.cli.GnuParser;
25 import org.apache.commons.cli.HelpFormatter;
26 import org.apache.commons.cli.OptionBuilder;
27 import org.apache.commons.cli.Options;
28 import org.apache.commons.cli.ParseException;
29 import org.apache.maven.Maven;
30 import org.apache.maven.SettingsConfigurationException;
31 import org.apache.maven.artifact.manager.WagonManager;
32 import org.apache.maven.artifact.repository.ArtifactRepository;
33 import org.apache.maven.artifact.repository.ArtifactRepositoryFactory;
34 import org.apache.maven.artifact.repository.ArtifactRepositoryPolicy;
35 import org.apache.maven.artifact.repository.DefaultArtifactRepository;
36 import org.apache.maven.artifact.repository.layout.ArtifactRepositoryLayout;
37 import org.apache.maven.execution.DefaultMavenExecutionRequest;
38 import org.apache.maven.execution.MavenExecutionRequest;
39 import org.apache.maven.execution.ReactorManager;
40 import org.apache.maven.monitor.event.DefaultEventDispatcher;
41 import org.apache.maven.monitor.event.DefaultEventMonitor;
42 import org.apache.maven.monitor.event.EventDispatcher;
43 import org.apache.maven.plugin.Mojo;
44 import org.apache.maven.profiles.DefaultProfileManager;
45 import org.apache.maven.profiles.ProfileManager;
46 import org.apache.maven.reactor.MavenExecutionException;
47 import org.apache.maven.settings.MavenSettingsBuilder;
48 import org.apache.maven.settings.RuntimeInfo;
49 import org.apache.maven.settings.Settings;
50 import org.codehaus.classworlds.ClassWorld;
51 import org.codehaus.plexus.PlexusContainerException;
52 import org.codehaus.plexus.component.repository.exception.ComponentLifecycleException;
53 import org.codehaus.plexus.component.repository.exception.ComponentLookupException;
54 import org.codehaus.plexus.embed.Embedder;
55 import org.codehaus.plexus.logging.Logger;
56 import org.codehaus.plexus.logging.LoggerManager;
57 import org.codehaus.plexus.util.Os;
58 import org.codehaus.plexus.util.cli.CommandLineUtils;
59 import org.codehaus.plexus.util.xml.pull.XmlPullParserException;
60
61 import java.io.File;
62 import java.io.IOException;
63 import java.io.InputStream;
64 import java.util.ArrayList;
65 import java.util.Iterator;
66 import java.util.List;
67 import java.util.Properties;
68 import java.util.StringTokenizer;
69 import java.util.Map.Entry;
70
71 /**
72 * @author <a href="mailto:jason@maven.org">Jason van Zyl</a>
73 * @version $Id: MavenCli.java 642024 2008-03-27 23:30:59Z jdcasey $
74 * @noinspection UseOfSystemOutOrSystemErr,ACCESS_STATIC_VIA_INSTANCE
75 */
76 public class MavenCli
77 {
78 /** @deprecated use {@link Os#OS_NAME} */
79 public static final String OS_NAME = Os.OS_NAME;
80
81 /** @deprecated use {@link Os#OS_ARCH} */
82 public static final String OS_ARCH = Os.OS_ARCH;
83
84 /** @deprecated use {@link Os#OS_VERSION} */
85 public static final String OS_VERSION = Os.OS_VERSION;
86
87 private static Embedder embedder;
88
89 /**
90 * @noinspection ConfusingMainMethod
91 */
92 public static int main( String[] args, ClassWorld classWorld )
93 {
94 // ----------------------------------------------------------------------
95 // Setup the command line parser
96 // ----------------------------------------------------------------------
97
98 CLIManager cliManager = new CLIManager();
99
100 CommandLine commandLine;
101 try
102 {
103 commandLine = cliManager.parse( args );
104 }
105 catch ( ParseException e )
106 {
107 System.err.println( "Unable to parse command line options: " + e.getMessage() );
108 cliManager.displayHelp();
109 return 1;
110 }
111
112 // TODO: maybe classworlds could handle this requirement...
113 if ( "1.4".compareTo( System.getProperty( "java.specification.version" ) ) > 0 )
114 {
115 System.err.println( "Sorry, but JDK 1.4 or above is required to execute Maven. You appear to be using "
116 + "Java:" );
117 System.err.println( "java version \"" + System.getProperty( "java.version", "<unknown java version>" )
118 + "\"" );
119 System.err.println( System.getProperty( "java.runtime.name", "<unknown runtime name>" ) + " (build "
120 + System.getProperty( "java.runtime.version", "<unknown runtime version>" ) + ")" );
121 System.err.println( System.getProperty( "java.vm.name", "<unknown vm name>" ) + " (build "
122 + System.getProperty( "java.vm.version", "<unknown vm version>" ) + ", "
123 + System.getProperty( "java.vm.info", "<unknown vm info>" ) + ")" );
124
125 return 1;
126 }
127
128 boolean debug = commandLine.hasOption( CLIManager.DEBUG );
129
130 boolean showErrors = debug || commandLine.hasOption( CLIManager.ERRORS );
131
132 if ( showErrors )
133 {
134 System.out.println( "+ Error stacktraces are turned on." );
135 }
136
137 // ----------------------------------------------------------------------
138 // Process particular command line options
139 // ----------------------------------------------------------------------
140
141 if ( commandLine.hasOption( CLIManager.HELP ) )
142 {
143 cliManager.displayHelp();
144 return 0;
145 }
146
147 if ( commandLine.hasOption( CLIManager.VERSION ) )
148 {
149 showVersion();
150
151 return 0;
152 }
153 else if ( debug )
154 {
155 showVersion();
156 }
157
158 EventDispatcher eventDispatcher = new DefaultEventDispatcher();
159
160 // ----------------------------------------------------------------------
161 // Now that we have everything that we need we will fire up plexus and
162 // bring the maven component to life for use.
163 // ----------------------------------------------------------------------
164
165 embedder = new Embedder();
166
167 try
168 {
169 embedder.start( classWorld );
170 }
171 catch ( PlexusContainerException e )
172 {
173 showFatalError( "Unable to start the embedded plexus container", e, showErrors );
174
175 return 1;
176 }
177
178 // ----------------------------------------------------------------------
179 // The execution properties need to be created before the settings
180 // are constructed.
181 // ----------------------------------------------------------------------
182
183 Properties executionProperties = new Properties();
184 Properties userProperties = new Properties();
185 populateProperties( commandLine, executionProperties, userProperties );
186
187 Settings settings;
188
189 try
190 {
191 settings = buildSettings( commandLine );
192 }
193 catch ( SettingsConfigurationException e )
194 {
195 showError( "Error reading settings.xml: " + e.getMessage(), e, showErrors );
196
197 return 1;
198 }
199 catch ( ComponentLookupException e )
200 {
201 showFatalError( "Unable to read settings.xml", e, showErrors );
202
203 return 1;
204 }
205
206 Maven maven = null;
207
208 MavenExecutionRequest request = null;
209
210 LoggerManager loggerManager = null;
211
212 try
213 {
214 // logger must be created first
215 loggerManager = (LoggerManager) embedder.lookup( LoggerManager.ROLE );
216
217 if ( debug )
218 {
219 loggerManager.setThreshold( Logger.LEVEL_DEBUG );
220 }
221 else if ( commandLine.hasOption( CLIManager.QUIET ) )
222 {
223 // TODO: we need to do some more work here. Some plugins use sys out or log errors at info level.
224 // Ideally, we could use Warn across the board
225 loggerManager.setThreshold( Logger.LEVEL_ERROR );
226 // TODO:Additionally, we can't change the mojo level because the component key includes the version and it isn't known ahead of time. This seems worth changing.
227 }
228
229 ProfileManager profileManager = new DefaultProfileManager( embedder.getContainer(), executionProperties );
230
231 if ( commandLine.hasOption( CLIManager.ACTIVATE_PROFILES ) )
232 {
233 String profilesLine = commandLine.getOptionValue( CLIManager.ACTIVATE_PROFILES );
234
235 StringTokenizer profileTokens = new StringTokenizer( profilesLine, "," );
236
237 while ( profileTokens.hasMoreTokens() )
238 {
239 String profileAction = profileTokens.nextToken().trim();
240
241 if ( profileAction.startsWith( "-" ) )
242 {
243 profileManager.explicitlyDeactivate( profileAction.substring( 1 ) );
244 }
245 else if ( profileAction.startsWith( "+" ) )
246 {
247 profileManager.explicitlyActivate( profileAction.substring( 1 ) );
248 }
249 else
250 {
251 // TODO: deprecate this eventually!
252 profileManager.explicitlyActivate( profileAction );
253 }
254 }
255 }
256
257 request = createRequest( commandLine, settings, eventDispatcher, loggerManager, profileManager,
258 executionProperties, userProperties, showErrors );
259
260 setProjectFileOptions( commandLine, request );
261
262 maven = createMavenInstance( settings.isInteractiveMode() );
263 }
264 catch ( ComponentLookupException e )
265 {
266 showFatalError( "Unable to configure the Maven application", e, showErrors );
267
268 return 1;
269 }
270 finally
271 {
272 if ( loggerManager != null )
273 {
274 try
275 {
276 embedder.release( loggerManager );
277 }
278 catch ( ComponentLifecycleException e )
279 {
280 showFatalError( "Error releasing logging manager", e, showErrors );
281 }
282 }
283 }
284
285 try
286 {
287 maven.execute( request );
288 }
289 catch ( MavenExecutionException e )
290 {
291 return 1;
292 }
293
294 return 0;
295 }
296
297 private static Settings buildSettings( CommandLine commandLine )
298 throws ComponentLookupException, SettingsConfigurationException
299 {
300 String userSettingsPath = null;
301
302 if ( commandLine.hasOption( CLIManager.ALTERNATE_USER_SETTINGS ) )
303 {
304 userSettingsPath = commandLine.getOptionValue( CLIManager.ALTERNATE_USER_SETTINGS );
305 }
306
307 Settings settings = null;
308
309 MavenSettingsBuilder settingsBuilder = (MavenSettingsBuilder) embedder.lookup( MavenSettingsBuilder.ROLE );
310
311 try
312 {
313 if ( userSettingsPath != null )
314 {
315 File userSettingsFile = new File( userSettingsPath );
316
317 if ( userSettingsFile.exists() && !userSettingsFile.isDirectory() )
318 {
319 settings = settingsBuilder.buildSettings( userSettingsFile );
320 }
321 else
322 {
323 System.out.println( "WARNING: Alternate user settings file: " + userSettingsPath +
324 " is invalid. Using default path." );
325 }
326 }
327
328 if ( settings == null )
329 {
330 settings = settingsBuilder.buildSettings();
331 }
332 }
333 catch ( IOException e )
334 {
335 throw new SettingsConfigurationException( "Error reading settings file", e );
336 }
337 catch ( XmlPullParserException e )
338 {
339 throw new SettingsConfigurationException( e.getMessage(), e.getDetail(), e.getLineNumber(),
340 e.getColumnNumber() );
341 }
342
343 // why aren't these part of the runtime info? jvz.
344
345 if ( commandLine.hasOption( CLIManager.BATCH_MODE ) )
346 {
347 settings.setInteractiveMode( false );
348 }
349
350 if ( commandLine.hasOption( CLIManager.SUPPRESS_PLUGIN_REGISTRY ) )
351 {
352 settings.setUsePluginRegistry( false );
353 }
354
355 // Create settings runtime info
356
357 settings.setRuntimeInfo( createRuntimeInfo( commandLine, settings ) );
358
359 return settings;
360 }
361
362 private static RuntimeInfo createRuntimeInfo( CommandLine commandLine, Settings settings )
363 {
364 RuntimeInfo runtimeInfo = new RuntimeInfo( settings );
365
366 if ( commandLine.hasOption( CLIManager.FORCE_PLUGIN_UPDATES ) ||
367 commandLine.hasOption( CLIManager.FORCE_PLUGIN_UPDATES2 ) )
368 {
369 runtimeInfo.setPluginUpdateOverride( Boolean.TRUE );
370 }
371 else if ( commandLine.hasOption( CLIManager.SUPPRESS_PLUGIN_UPDATES ) )
372 {
373 runtimeInfo.setPluginUpdateOverride( Boolean.FALSE );
374 }
375
376 return runtimeInfo;
377 }
378
379
380 private static void showFatalError( String message, Exception e, boolean show )
381 {
382 System.err.println( "FATAL ERROR: " + message );
383 if ( show )
384 {
385 System.err.println( "Error stacktrace:" );
386
387 e.printStackTrace();
388 }
389 else
390 {
391 System.err.println( "For more information, run with the -e flag" );
392 }
393 }
394
395 private static void showError( String message, Exception e, boolean show )
396 {
397 System.err.println( message );
398 if ( show )
399 {
400 System.err.println( "Error stacktrace:" );
401
402 e.printStackTrace();
403 }
404 }
405
406 private static MavenExecutionRequest createRequest( CommandLine commandLine, Settings settings,
407 EventDispatcher eventDispatcher, LoggerManager loggerManager,
408 ProfileManager profileManager, Properties executionProperties,
409 Properties userProperties, boolean showErrors )
410 throws ComponentLookupException
411 {
412 MavenExecutionRequest request;
413
414 ArtifactRepository localRepository = createLocalRepository( embedder, settings, commandLine );
415
416 File userDir = new File( System.getProperty( "user.dir" ) );
417
418 request = new DefaultMavenExecutionRequest( localRepository, settings, eventDispatcher,
419 commandLine.getArgList(), userDir.getPath(), profileManager,
420 executionProperties, userProperties, showErrors );
421
422 // TODO [BP]: do we set one per mojo? where to do it?
423 Logger logger = loggerManager.getLoggerForComponent( Mojo.ROLE );
424
425 if ( logger != null )
426 {
427 request.addEventMonitor( new DefaultEventMonitor( logger ) );
428 }
429
430 if ( commandLine.hasOption( CLIManager.NON_RECURSIVE ) )
431 {
432 request.setRecursive( false );
433 }
434
435 if ( commandLine.hasOption( CLIManager.FAIL_FAST ) )
436 {
437 request.setFailureBehavior( ReactorManager.FAIL_FAST );
438 }
439 else if ( commandLine.hasOption( CLIManager.FAIL_AT_END ) )
440 {
441 request.setFailureBehavior( ReactorManager.FAIL_AT_END );
442 }
443 else if ( commandLine.hasOption( CLIManager.FAIL_NEVER ) )
444 {
445 request.setFailureBehavior( ReactorManager.FAIL_NEVER );
446 }
447
448 return request;
449 }
450
451 private static void setProjectFileOptions( CommandLine commandLine, MavenExecutionRequest request )
452 {
453 if ( commandLine.hasOption( CLIManager.REACTOR ) )
454 {
455 request.setReactorActive( true );
456 }
457 else if ( commandLine.hasOption( CLIManager.ALTERNATE_POM_FILE ) )
458 {
459 request.setPomFile( commandLine.getOptionValue( CLIManager.ALTERNATE_POM_FILE ) );
460 }
461 }
462
463 private static Maven createMavenInstance( boolean interactive )
464 throws ComponentLookupException
465 {
466 // TODO [BP]: doing this here as it is CLI specific, though it doesn't feel like the right place (likewise logger).
467 WagonManager wagonManager = (WagonManager) embedder.lookup( WagonManager.ROLE );
468 if ( interactive )
469 {
470 wagonManager.setDownloadMonitor( new ConsoleDownloadMonitor() );
471 }
472 else
473 {
474 wagonManager.setDownloadMonitor( new BatchModeDownloadMonitor() );
475 }
476
477 wagonManager.setInteractive( interactive );
478
479 return (Maven) embedder.lookup( Maven.ROLE );
480 }
481
482 private static ArtifactRepository createLocalRepository( Embedder embedder, Settings settings,
483 CommandLine commandLine )
484 throws ComponentLookupException
485 {
486 // TODO: release
487 // TODO: something in plexus to show all active hooks?
488 ArtifactRepositoryLayout repositoryLayout =
489 (ArtifactRepositoryLayout) embedder.lookup( ArtifactRepositoryLayout.ROLE, "default" );
490
491 ArtifactRepositoryFactory artifactRepositoryFactory =
492 (ArtifactRepositoryFactory) embedder.lookup( ArtifactRepositoryFactory.ROLE );
493
494 String url = settings.getLocalRepository();
495
496 if ( !url.startsWith( "file:" ) )
497 {
498 url = "file://" + url;
499 }
500
501 ArtifactRepository localRepository = new DefaultArtifactRepository( "local", url, repositoryLayout );
502
503 boolean snapshotPolicySet = false;
504
505 if ( commandLine.hasOption( CLIManager.OFFLINE ) )
506 {
507 settings.setOffline( true );
508
509 snapshotPolicySet = true;
510 }
511
512 if ( !snapshotPolicySet && commandLine.hasOption( CLIManager.UPDATE_SNAPSHOTS ) )
513 {
514 artifactRepositoryFactory.setGlobalUpdatePolicy( ArtifactRepositoryPolicy.UPDATE_POLICY_ALWAYS );
515 }
516
517 if ( commandLine.hasOption( CLIManager.CHECKSUM_FAILURE_POLICY ) )
518 {
519 System.out.println( "+ Enabling strict checksum verification on all artifact downloads." );
520
521 artifactRepositoryFactory.setGlobalChecksumPolicy( ArtifactRepositoryPolicy.CHECKSUM_POLICY_FAIL );
522 }
523 else if ( commandLine.hasOption( CLIManager.CHECKSUM_WARNING_POLICY ) )
524 {
525 System.out.println( "+ Disabling strict checksum verification on all artifact downloads." );
526
527 artifactRepositoryFactory.setGlobalChecksumPolicy( ArtifactRepositoryPolicy.CHECKSUM_POLICY_WARN );
528 }
529
530 return localRepository;
531 }
532
533 private static void showVersion()
534 {
535 InputStream resourceAsStream;
536 try
537 {
538 Properties properties = new Properties();
539 resourceAsStream = MavenCli.class.getClassLoader().getResourceAsStream(
540 "META-INF/maven/org.apache.maven/maven-core/pom.properties" );
541
542 if ( resourceAsStream != null )
543 {
544 properties.load( resourceAsStream );
545
546 if( properties.getProperty( "builtOn" ) != null )
547 {
548 System.out.println( "Maven version: " + properties.getProperty( "version", "unknown" )
549 + " built on " + properties.getProperty( "builtOn" ) );
550 }
551 else
552 {
553 System.out.println( "Maven version: " + properties.getProperty( "version", "unknown" ) );
554 }
555 }
556 else
557 {
558 System.out.println( "Maven version: unknown" );
559 }
560
561 System.out.println( "Java version: " + System.getProperty( "java.version", "<unknown java version>" ) );
562
563 System.out.println( "OS name: \"" + Os.OS_NAME + "\" version: \"" + Os.OS_VERSION +
564 "\" arch: \"" + Os.OS_ARCH + "\" Family: \"" + Os.OS_FAMILY + "\"" );
565
566 }
567 catch ( IOException e )
568 {
569 System.err.println( "Unable determine version from JAR file: " + e.getMessage() );
570 }
571 }
572
573 // ----------------------------------------------------------------------
574 // System properties handling
575 // ----------------------------------------------------------------------
576
577 static void populateProperties( CommandLine commandLine, Properties executionProperties, Properties userProperties )
578 {
579 // add the env vars to the property set, with the "env." prefix
580 // XXX support for env vars should probably be removed from the ModelInterpolator
581 try
582 {
583 Properties envVars = CommandLineUtils.getSystemEnvVars();
584 Iterator i = envVars.entrySet().iterator();
585 while ( i.hasNext() )
586 {
587 Entry e = (Entry) i.next();
588 executionProperties.setProperty( "env." + e.getKey().toString(), e.getValue().toString() );
589 }
590 }
591 catch ( IOException e )
592 {
593 System.err.println( "Error getting environment vars for profile activation: " + e );
594 }
595
596 // ----------------------------------------------------------------------
597 // Options that are set on the command line become system properties
598 // and therefore are set in the session properties. System properties
599 // are most dominant.
600 // ----------------------------------------------------------------------
601
602 if ( commandLine.hasOption( CLIManager.SET_SYSTEM_PROPERTY ) )
603 {
604 String[] defStrs = commandLine.getOptionValues( CLIManager.SET_SYSTEM_PROPERTY );
605
606 if ( defStrs != null )
607 {
608 for ( int i = 0; i < defStrs.length; ++i )
609 {
610 setCliProperty( defStrs[i], userProperties );
611 }
612 }
613
614 executionProperties.putAll( userProperties );
615 }
616
617 executionProperties.putAll( System.getProperties() );
618 }
619
620 private static void setCliProperty( String property, Properties requestProperties )
621 {
622 String name;
623
624 String value;
625
626 int i = property.indexOf( "=" );
627
628 if ( i <= 0 )
629 {
630 name = property.trim();
631
632 value = "true";
633 }
634 else
635 {
636 name = property.substring( 0, i ).trim();
637
638 value = property.substring( i + 1 ).trim();
639 }
640
641 requestProperties.setProperty( name, value );
642
643 // ----------------------------------------------------------------------
644 // I'm leaving the setting of system properties here as not to break
645 // the SystemPropertyProfileActivator. This won't harm embedding. jvz.
646 // ----------------------------------------------------------------------
647
648 System.setProperty( name, value );
649 }
650
651 // ----------------------------------------------------------------------
652 // Command line manager
653 // ----------------------------------------------------------------------
654
655 static class CLIManager
656 {
657 public static final char ALTERNATE_POM_FILE = 'f';
658
659 public static final char BATCH_MODE = 'B';
660
661 public static final char SET_SYSTEM_PROPERTY = 'D';
662
663 public static final char OFFLINE = 'o';
664
665 public static final char REACTOR = 'r';
666
667 public static final char QUIET = 'q';
668
669 public static final char DEBUG = 'X';
670
671 public static final char ERRORS = 'e';
672
673 public static final char HELP = 'h';
674
675 public static final char VERSION = 'v';
676
677 private Options options;
678
679 public static final char NON_RECURSIVE = 'N';
680
681 public static final char UPDATE_SNAPSHOTS = 'U';
682
683 public static final char ACTIVATE_PROFILES = 'P';
684
685 public static final String FORCE_PLUGIN_UPDATES = "cpu";
686
687 public static final String FORCE_PLUGIN_UPDATES2 = "up";
688
689 public static final String SUPPRESS_PLUGIN_UPDATES = "npu";
690
691 public static final String SUPPRESS_PLUGIN_REGISTRY = "npr";
692
693 public static final char CHECKSUM_FAILURE_POLICY = 'C';
694
695 public static final char CHECKSUM_WARNING_POLICY = 'c';
696
697 private static final char ALTERNATE_USER_SETTINGS = 's';
698
699 private static final String FAIL_FAST = "ff";
700
701 private static final String FAIL_AT_END = "fae";
702
703 private static final String FAIL_NEVER = "fn";
704
705 public CLIManager()
706 {
707 options = new Options();
708
709 options.addOption( OptionBuilder.withLongOpt( "file" ).hasArg().withDescription(
710 "Force the use of an alternate POM file." ).create( ALTERNATE_POM_FILE ) );
711
712 options.addOption(
713 OptionBuilder.withLongOpt( "define" ).hasArg().withDescription( "Define a system property" ).create(
714 SET_SYSTEM_PROPERTY ) );
715 options.addOption(
716 OptionBuilder.withLongOpt( "offline" ).withDescription( "Work offline" ).create( OFFLINE ) );
717 options.addOption(
718 OptionBuilder.withLongOpt( "help" ).withDescription( "Display help information" ).create( HELP ) );
719 options.addOption(
720 OptionBuilder.withLongOpt( "version" ).withDescription( "Display version information" ).create(
721 VERSION ) );
722 options.addOption(
723 OptionBuilder.withLongOpt( "quiet" ).withDescription( "Quiet output - only show errors" ).create(
724 QUIET ) );
725 options.addOption(
726 OptionBuilder.withLongOpt( "debug" ).withDescription( "Produce execution debug output" ).create(
727 DEBUG ) );
728 options.addOption(
729 OptionBuilder.withLongOpt( "errors" ).withDescription( "Produce execution error messages" ).create(
730 ERRORS ) );
731 options.addOption( OptionBuilder.withLongOpt( "reactor" ).withDescription(
732 "Execute goals for project found in the reactor" ).create( REACTOR ) );
733 options.addOption( OptionBuilder.withLongOpt( "non-recursive" ).withDescription(
734 "Do not recurse into sub-projects" ).create( NON_RECURSIVE ) );
735 options.addOption( OptionBuilder.withLongOpt( "update-snapshots" ).withDescription(
736 "Forces a check for updated releases and snapshots on remote repositories" ).create( UPDATE_SNAPSHOTS ) );
737 options.addOption( OptionBuilder.withLongOpt( "activate-profiles" ).withDescription(
738 "Comma-delimited list of profiles to activate" ).hasArg().create( ACTIVATE_PROFILES ) );
739
740 options.addOption( OptionBuilder.withLongOpt( "batch-mode" ).withDescription(
741 "Run in non-interactive (batch) mode" ).create( BATCH_MODE ) );
742
743 options.addOption( OptionBuilder.withLongOpt( "check-plugin-updates" ).withDescription(
744 "Force upToDate check for any relevant registered plugins" ).create( FORCE_PLUGIN_UPDATES ) );
745 options.addOption( OptionBuilder.withLongOpt( "update-plugins" ).withDescription(
746 "Synonym for " + FORCE_PLUGIN_UPDATES ).create( FORCE_PLUGIN_UPDATES2 ) );
747 options.addOption( OptionBuilder.withLongOpt( "no-plugin-updates" ).withDescription(
748 "Suppress upToDate check for any relevant registered plugins" ).create( SUPPRESS_PLUGIN_UPDATES ) );
749
750 options.addOption( OptionBuilder.withLongOpt( "no-plugin-registry" ).withDescription(
751 "Don't use ~/.m2/plugin-registry.xml for plugin versions" ).create( SUPPRESS_PLUGIN_REGISTRY ) );
752
753 options.addOption( OptionBuilder.withLongOpt( "strict-checksums" ).withDescription(
754 "Fail the build if checksums don't match" ).create( CHECKSUM_FAILURE_POLICY ) );
755 options.addOption(
756 OptionBuilder.withLongOpt( "lax-checksums" ).withDescription( "Warn if checksums don't match" ).create(
757 CHECKSUM_WARNING_POLICY ) );
758
759 options.addOption( OptionBuilder.withLongOpt( "settings" )
760 .withDescription( "Alternate path for the user settings file" ).hasArg()
761 .create( ALTERNATE_USER_SETTINGS ) );
762
763 options.addOption( OptionBuilder.withLongOpt( "fail-fast" ).withDescription(
764 "Stop at first failure in reactorized builds" ).create( FAIL_FAST ) );
765
766 options.addOption( OptionBuilder.withLongOpt( "fail-at-end" ).withDescription(
767 "Only fail the build afterwards; allow all non-impacted builds to continue" ).create( FAIL_AT_END ) );
768
769 options.addOption( OptionBuilder.withLongOpt( "fail-never" ).withDescription(
770 "NEVER fail the build, regardless of project result" ).create( FAIL_NEVER ) );
771 }
772
773 public CommandLine parse( String[] args )
774 throws ParseException
775 {
776 // We need to eat any quotes surrounding arguments...
777 String[] cleanArgs = cleanArgs( args );
778
779 CommandLineParser parser = new GnuParser();
780 return parser.parse( options, cleanArgs );
781 }
782
783 private String[] cleanArgs( String[] args )
784 {
785 List cleaned = new ArrayList();
786
787 StringBuffer currentArg = null;
788
789 for ( int i = 0; i < args.length; i++ )
790 {
791 String arg = args[i];
792
793 // System.out.println( "Processing raw arg: " + arg );
794
795 boolean addedToBuffer = false;
796
797 if ( arg.startsWith( "\"" ) )
798 {
799 // if we're in the process of building up another arg, push it and start over.
800 // this is for the case: "-Dfoo=bar "-Dfoo2=bar two" (note the first unterminated quote)
801 if ( currentArg != null )
802 {
803 // System.out.println( "Flushing last arg buffer: \'" + currentArg + "\' to cleaned list." );
804 cleaned.add( currentArg.toString() );
805 }
806
807 // start building an argument here.
808 currentArg = new StringBuffer( arg.substring( 1 ) );
809 addedToBuffer = true;
810 }
811
812 // this has to be a separate "if" statement, to capture the case of: "-Dfoo=bar"
813 if ( arg.endsWith( "\"" ) )
814 {
815 String cleanArgPart = arg.substring( 0, arg.length() - 1 );
816
817 // if we're building an argument, keep doing so.
818 if ( currentArg != null )
819 {
820 // if this is the case of "-Dfoo=bar", then we need to adjust the buffer.
821 if ( addedToBuffer )
822 {
823 // System.out.println( "Adjusting argument already appended to the arg buffer." );
824 currentArg.setLength( currentArg.length() - 1 );
825 }
826 // otherwise, we trim the trailing " and append to the buffer.
827 else
828 {
829 // System.out.println( "Appending arg part: \'" + cleanArgPart + "\' with preceding space to arg buffer." );
830 // TODO: introducing a space here...not sure what else to do but collapse whitespace
831 currentArg.append( ' ' ).append( cleanArgPart );
832 }
833
834 // System.out.println( "Flushing completed arg buffer: \'" + currentArg + "\' to cleaned list." );
835
836 // we're done with this argument, so add it.
837 cleaned.add( currentArg.toString() );
838 }
839 else
840 {
841 // System.out.println( "appending cleaned arg: \'" + cleanArgPart + "\' directly to cleaned list." );
842 // this is a simple argument...just add it.
843 cleaned.add( cleanArgPart );
844 }
845
846 // System.out.println( "Clearing arg buffer." );
847 // the currentArg MUST be finished when this completes.
848 currentArg = null;
849 continue;
850 }
851
852 // if we haven't added this arg to the buffer, and we ARE building an argument
853 // buffer, then append it with a preceding space...again, not sure what else to
854 // do other than collapse whitespace.
855 // NOTE: The case of a trailing quote is handled by nullifying the arg buffer.
856 if ( !addedToBuffer )
857 {
858 // append to the argument we're building, collapsing whitespace to a single space.
859 if ( currentArg != null )
860 {
861 // System.out.println( "Append unquoted arg part: \'" + arg + "\' to arg buffer." );
862 currentArg.append( ' ' ).append( arg );
863 }
864 // this is a loner, just add it directly.
865 else
866 {
867 // System.out.println( "Append unquoted arg part: \'" + arg + "\' directly to cleaned list." );
868 cleaned.add( arg );
869 }
870 }
871 }
872
873 // clean up.
874 if ( currentArg != null )
875 {
876 // System.out.println( "Adding unterminated arg buffer: \'" + currentArg + "\' to cleaned list." );
877 cleaned.add( currentArg.toString() );
878 }
879
880 int cleanedSz = cleaned.size();
881 String[] cleanArgs = null;
882
883 if ( cleanedSz == 0 )
884 {
885 // if we didn't have any arguments to clean, simply pass the original array through
886 cleanArgs = args;
887 }
888 else
889 {
890 // System.out.println( "Cleaned argument list:\n" + cleaned );
891 cleanArgs = (String[]) cleaned.toArray( new String[cleanedSz] );
892 }
893
894 return cleanArgs;
895 }
896
897 public void displayHelp()
898 {
899 System.out.println();
900
901 HelpFormatter formatter = new HelpFormatter();
902 formatter.printHelp( "mvn [options] [<goal(s)>] [<phase(s)>]", "\nOptions:", options, "\n" );
903 }
904 }
905 }