1 package org.apache.maven.shared.release.exec;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 import java.io.File;
23 import java.io.FileWriter;
24 import java.io.IOException;
25 import java.util.Arrays;
26 import java.util.List;
27 import java.util.Properties;
28
29 import org.apache.commons.cli.CommandLine;
30 import org.apache.commons.cli.OptionBuilder;
31 import org.apache.commons.cli.Options;
32 import org.apache.commons.cli.PosixParser;
33 import org.apache.maven.settings.io.xpp3.SettingsXpp3Writer;
34 import org.apache.maven.shared.invoker.DefaultInvocationRequest;
35 import org.apache.maven.shared.invoker.DefaultInvoker;
36 import org.apache.maven.shared.invoker.InvocationOutputHandler;
37 import org.apache.maven.shared.invoker.InvocationRequest;
38 import org.apache.maven.shared.invoker.InvocationResult;
39 import org.apache.maven.shared.invoker.Invoker;
40 import org.apache.maven.shared.invoker.InvokerLogger;
41 import org.apache.maven.shared.invoker.MavenInvocationException;
42 import org.apache.maven.shared.release.ReleaseResult;
43 import org.apache.maven.shared.release.env.ReleaseEnvironment;
44 import org.codehaus.plexus.logging.Logger;
45 import org.codehaus.plexus.util.IOUtil;
46 import org.codehaus.plexus.util.cli.CommandLineUtils;
47
48
49
50
51
52
53 @SuppressWarnings( "static-access" )
54 public class InvokerMavenExecutor
55 extends AbstractMavenExecutor
56 {
57
58 private static final Options OPTIONS = new Options();
59
60 private static final char SET_SYSTEM_PROPERTY = 'D';
61
62 private static final char OFFLINE = 'o';
63
64 private static final char REACTOR = 'r';
65
66 private static final char QUIET = 'q';
67
68 private static final char DEBUG = 'X';
69
70 private static final char ERRORS = 'e';
71
72 private static final char NON_RECURSIVE = 'N';
73
74 private static final char UPDATE_SNAPSHOTS = 'U';
75
76 private static final char ACTIVATE_PROFILES = 'P';
77
78 private static final String FORCE_PLUGIN_UPDATES = "cpu";
79
80 private static final String FORCE_PLUGIN_UPDATES2 = "up";
81
82 private static final String SUPPRESS_PLUGIN_UPDATES = "npu";
83
84 private static final String SUPPRESS_PLUGIN_REGISTRY = "npr";
85
86 private static final char CHECKSUM_FAILURE_POLICY = 'C';
87
88 private static final char CHECKSUM_WARNING_POLICY = 'c';
89
90 private static final char ALTERNATE_USER_SETTINGS = 's';
91
92 private static final String ALTERNATE_GLOBAL_SETTINGS = "gs";
93
94 private static final String FAIL_FAST = "ff";
95
96 private static final String FAIL_AT_END = "fae";
97
98 private static final String FAIL_NEVER = "fn";
99
100 private static final String ALTERNATE_POM_FILE = "f";
101
102 private static final String THREADS = "T";
103
104 private static final String BATCH_MODE = "B";
105
106 public static final char ALTERNATE_USER_TOOLCHAINS = 't';
107
108 static
109 {
110 OPTIONS.addOption(
111 OptionBuilder.withLongOpt( "define" ).hasArg().withDescription( "Define a system property" ).create(
112 SET_SYSTEM_PROPERTY ) );
113
114 OPTIONS.addOption( OptionBuilder.withLongOpt( "offline" ).withDescription( "Work offline" ).create( OFFLINE ) );
115
116 OPTIONS.addOption(
117 OptionBuilder.withLongOpt( "quiet" ).withDescription( "Quiet output - only show errors" ).create( QUIET ) );
118
119 OPTIONS.addOption(
120 OptionBuilder.withLongOpt( "debug" ).withDescription( "Produce execution debug output" ).create( DEBUG ) );
121
122 OPTIONS.addOption(
123 OptionBuilder.withLongOpt( "errors" ).withDescription( "Produce execution error messages" ).create(
124 ERRORS ) );
125
126 OPTIONS.addOption( OptionBuilder.withLongOpt( "reactor" ).withDescription(
127 "Execute goals for project found in the reactor" ).create( REACTOR ) );
128
129 OPTIONS.addOption(
130 OptionBuilder.withLongOpt( "non-recursive" ).withDescription( "Do not recurse into sub-projects" ).create(
131 NON_RECURSIVE ) );
132
133 OPTIONS.addOption( OptionBuilder.withLongOpt( "update-snapshots" ).withDescription(
134 "Forces a check for updated releases and snapshots on remote repositories" ).create( UPDATE_SNAPSHOTS ) );
135
136 OPTIONS.addOption( OptionBuilder.withLongOpt( "activate-profiles" ).withDescription(
137 "Comma-delimited list of profiles to activate" ).hasArg().create( ACTIVATE_PROFILES ) );
138
139 OPTIONS.addOption( OptionBuilder.withLongOpt( "check-plugin-updates" ).withDescription(
140 "Force upToDate check for any relevant registered plugins" ).create( FORCE_PLUGIN_UPDATES ) );
141
142 OPTIONS.addOption( OptionBuilder.withLongOpt( "update-plugins" ).withDescription(
143 "Synonym for " + FORCE_PLUGIN_UPDATES ).create( FORCE_PLUGIN_UPDATES2 ) );
144
145 OPTIONS.addOption( OptionBuilder.withLongOpt( "no-plugin-updates" ).withDescription(
146 "Suppress upToDate check for any relevant registered plugins" ).create( SUPPRESS_PLUGIN_UPDATES ) );
147
148 OPTIONS.addOption( OptionBuilder.withLongOpt( "no-plugin-registry" ).withDescription(
149 "Don't use ~/.m2/plugin-registry.xml for plugin versions" ).create( SUPPRESS_PLUGIN_REGISTRY ) );
150
151 OPTIONS.addOption( OptionBuilder.withLongOpt( "strict-checksums" ).withDescription(
152 "Fail the build if checksums don't match" ).create( CHECKSUM_FAILURE_POLICY ) );
153
154 OPTIONS.addOption(
155 OptionBuilder.withLongOpt( "lax-checksums" ).withDescription( "Warn if checksums don't match" ).create(
156 CHECKSUM_WARNING_POLICY ) );
157
158 OPTIONS.addOption( OptionBuilder.withLongOpt( "settings" ).withDescription(
159 "Alternate path for the user settings file" ).hasArg().create( ALTERNATE_USER_SETTINGS ) );
160
161 OPTIONS.addOption( OptionBuilder.withLongOpt( "global-settings" ).withDescription(
162 " Alternate path for the global settings file" ).hasArg().create( ALTERNATE_GLOBAL_SETTINGS ) );
163
164 OPTIONS.addOption( OptionBuilder.withLongOpt( "fail-fast" ).withDescription(
165 "Stop at first failure in reactorized builds" ).create( FAIL_FAST ) );
166
167 OPTIONS.addOption( OptionBuilder.withLongOpt( "fail-at-end" ).withDescription(
168 "Only fail the build afterwards; allow all non-impacted builds to continue" ).create( FAIL_AT_END ) );
169
170 OPTIONS.addOption( OptionBuilder.withLongOpt( "fail-never" ).withDescription(
171 "NEVER fail the build, regardless of project result" ).create( FAIL_NEVER ) );
172
173 OPTIONS.addOption( OptionBuilder.withLongOpt( "file" ).withDescription(
174 "Force the use of an alternate POM file." ).hasArg().create( ALTERNATE_POM_FILE ) );
175
176 OPTIONS.addOption( OptionBuilder.withLongOpt( "threads" ).withDescription(
177 "Thread count, for instance 2.0C where C is core multiplied" ).hasArg().create( THREADS ) );
178
179 OPTIONS.addOption( OptionBuilder.withLongOpt( "batch-mode" ).withDescription(
180 "Run in non-interactive (batch) mode" ).create( BATCH_MODE ) );
181
182 OPTIONS.addOption( OptionBuilder.withLongOpt( "toolchains" ).withDescription(
183 "Alternate path for the user toolchains file" ).hasArg().create( ALTERNATE_USER_TOOLCHAINS ) );
184 }
185
186
187 protected void setupRequest( InvocationRequest req,
188 InvokerLogger bridge,
189 String additionalArguments )
190 throws MavenExecutorException
191 {
192 try
193 {
194 String[] args = CommandLineUtils.translateCommandline( additionalArguments );
195 CommandLine cli = new PosixParser().parse( OPTIONS, args );
196
197 if ( cli.hasOption( SET_SYSTEM_PROPERTY ) )
198 {
199 String[] properties = cli.getOptionValues( SET_SYSTEM_PROPERTY );
200 Properties props = new Properties();
201 for ( int i = 0; i < properties.length; i++ )
202 {
203 String property = properties[i];
204 String name, value;
205 int sep = property.indexOf( "=" );
206 if ( sep <= 0 )
207 {
208 name = property.trim();
209 value = "true";
210 }
211 else
212 {
213 name = property.substring( 0, sep ).trim();
214 value = property.substring( sep + 1 ).trim();
215 }
216 props.setProperty( name, value );
217 }
218
219 req.setProperties( props );
220 }
221
222 if ( cli.hasOption( OFFLINE ) )
223 {
224 req.setOffline( true );
225 }
226
227 if ( cli.hasOption( QUIET ) )
228 {
229
230 req.setDebug( false );
231 }
232 else if ( cli.hasOption( DEBUG ) )
233 {
234 req.setDebug( true );
235 }
236 else if ( cli.hasOption( ERRORS ) )
237 {
238 req.setShowErrors( true );
239 }
240
241 if ( cli.hasOption( REACTOR ) )
242 {
243 req.setRecursive( true );
244 }
245 else if ( cli.hasOption( NON_RECURSIVE ) )
246 {
247 req.setRecursive( false );
248 }
249
250 if ( cli.hasOption( UPDATE_SNAPSHOTS ) )
251 {
252 req.setUpdateSnapshots( true );
253 }
254
255 if ( cli.hasOption( ACTIVATE_PROFILES ) )
256 {
257 String[] profiles = cli.getOptionValues( ACTIVATE_PROFILES );
258
259 if ( profiles != null )
260 {
261 req.setProfiles( Arrays.asList( profiles ) );
262 }
263 }
264
265 if ( cli.hasOption( FORCE_PLUGIN_UPDATES ) || cli.hasOption( FORCE_PLUGIN_UPDATES2 ) )
266 {
267 getLogger().warn( "Forcing plugin updates is not supported currently." );
268 }
269 else if ( cli.hasOption( SUPPRESS_PLUGIN_UPDATES ) )
270 {
271 req.setNonPluginUpdates( true );
272 }
273
274 if ( cli.hasOption( SUPPRESS_PLUGIN_REGISTRY ) )
275 {
276 getLogger().warn( "Explicit suppression of the plugin registry is not supported currently." );
277 }
278
279 if ( cli.hasOption( CHECKSUM_FAILURE_POLICY ) )
280 {
281 req.setGlobalChecksumPolicy( InvocationRequest.CHECKSUM_POLICY_FAIL );
282 }
283 else if ( cli.hasOption( CHECKSUM_WARNING_POLICY ) )
284 {
285 req.setGlobalChecksumPolicy( InvocationRequest.CHECKSUM_POLICY_WARN );
286 }
287
288 if ( cli.hasOption( ALTERNATE_USER_SETTINGS ) )
289 {
290 req.setUserSettingsFile( new File( cli.getOptionValue( ALTERNATE_USER_SETTINGS ) ) );
291 }
292
293 if ( cli.hasOption( ALTERNATE_GLOBAL_SETTINGS ) )
294 {
295 req.setGlobalSettingsFile( new File( cli.getOptionValue( ALTERNATE_GLOBAL_SETTINGS ) ) );
296 }
297
298 if ( cli.hasOption( FAIL_AT_END ) )
299 {
300 req.setFailureBehavior( InvocationRequest.REACTOR_FAIL_AT_END );
301 }
302 else if ( cli.hasOption( FAIL_FAST ) )
303 {
304 req.setFailureBehavior( InvocationRequest.REACTOR_FAIL_FAST );
305 }
306 if ( cli.hasOption( FAIL_NEVER ) )
307 {
308 req.setFailureBehavior( InvocationRequest.REACTOR_FAIL_NEVER );
309 }
310 if ( cli.hasOption( ALTERNATE_POM_FILE ) )
311 {
312 if ( req.getPomFileName() != null )
313 {
314 getLogger().info( "pomFileName is already set, ignoring the -f argument" );
315 }
316 else
317 {
318 req.setPomFileName( cli.getOptionValue( ALTERNATE_POM_FILE ) );
319 }
320 }
321
322 if ( cli.hasOption( THREADS ) )
323 {
324 req.setThreads( cli.getOptionValue( THREADS ) );
325 }
326
327 if ( cli.hasOption( BATCH_MODE ) )
328 {
329 req.setInteractive( false );
330 }
331
332 if ( cli.hasOption( ALTERNATE_USER_TOOLCHAINS ) )
333 {
334 req.setToolchainsFile( new File( cli.getOptionValue( ALTERNATE_USER_TOOLCHAINS ) ) );
335 }
336
337 }
338 catch ( Exception e )
339 {
340 throw new MavenExecutorException( "Failed to re-parse additional arguments for Maven invocation.", e );
341 }
342 }
343
344 @Override
345 public void executeGoals( File workingDirectory, List<String> goals, ReleaseEnvironment releaseEnvironment,
346 boolean interactive, String additionalArguments, String pomFileName,
347 ReleaseResult result )
348 throws MavenExecutorException
349 {
350 InvocationOutputHandler handler = getOutputHandler();
351 InvokerLogger bridge = getInvokerLogger();
352
353 File mavenPath = null;
354
355 if ( releaseEnvironment.getMavenHome() != null )
356 {
357 mavenPath = releaseEnvironment.getMavenHome();
358 }
359 else
360 {
361 String mavenHome = System.getProperty( "maven.home" );
362 if ( mavenHome == null )
363 {
364 mavenHome = System.getenv( "MAVEN_HOME" );
365 }
366 if ( mavenHome == null )
367 {
368 mavenHome = System.getenv( "M2_HOME" );
369 }
370 mavenPath = mavenHome == null ? null : new File( mavenHome );
371 }
372 Invoker invoker =
373 new DefaultInvoker().setMavenHome( mavenPath ).setLogger( bridge )
374 .setOutputHandler( handler ).setErrorHandler( handler );
375
376 InvocationRequest req =
377 new DefaultInvocationRequest().setDebug( getLogger().isDebugEnabled() )
378 .setBaseDirectory( workingDirectory ).setInteractive( interactive );
379
380 if ( pomFileName != null )
381 {
382 req.setPomFileName( pomFileName );
383 }
384
385 File settingsFile = null;
386 if ( releaseEnvironment.getSettings() != null )
387 {
388
389 try
390 {
391 settingsFile = File.createTempFile( "release-settings", ".xml" );
392 SettingsXpp3Writer writer = getSettingsWriter();
393 FileWriter fileWriter = null;
394 try
395 {
396 fileWriter = new FileWriter( settingsFile );
397 writer.write( fileWriter, encryptSettings( releaseEnvironment.getSettings() ) );
398 }
399 finally
400 {
401 IOUtil.close( fileWriter );
402 }
403 req.setUserSettingsFile( settingsFile );
404 }
405 catch ( IOException e )
406 {
407 throw new MavenExecutorException( "Could not create temporary file for release settings.xml", e );
408 }
409 }
410 try
411 {
412 File localRepoDir = releaseEnvironment.getLocalRepositoryDirectory();
413 if ( localRepoDir != null )
414 {
415 req.setLocalRepositoryDirectory( localRepoDir );
416 }
417
418 setupRequest( req, bridge, additionalArguments );
419
420 req.setGoals( goals );
421
422 try
423 {
424 InvocationResult invocationResult = invoker.execute( req );
425
426 if ( invocationResult.getExecutionException() != null )
427 {
428 throw new MavenExecutorException( "Error executing Maven.",
429 invocationResult.getExecutionException() );
430 }
431 if ( invocationResult.getExitCode() != 0 )
432 {
433 throw new MavenExecutorException(
434 "Maven execution failed, exit code: \'" + invocationResult.getExitCode() + "\'",
435 invocationResult.getExitCode(), "", "" );
436 }
437 }
438 catch ( MavenInvocationException e )
439 {
440 throw new MavenExecutorException( "Failed to invoke Maven build.", e );
441 }
442 }
443 finally
444 {
445 if ( settingsFile != null && settingsFile.exists() && !settingsFile.delete() )
446 {
447 settingsFile.deleteOnExit();
448 }
449 }
450 }
451
452 protected InvokerLogger getInvokerLogger()
453 {
454 return new LoggerBridge( getLogger() );
455 }
456
457 protected InvocationOutputHandler getOutputHandler()
458 {
459 return new Handler( getLogger() );
460 }
461
462 private static final class Handler
463 implements InvocationOutputHandler
464 {
465 private Logger logger;
466
467 Handler( Logger logger )
468 {
469 this.logger = logger;
470 }
471
472 public void consumeLine( String line )
473 {
474 logger.info( line );
475 }
476 }
477
478 private static final class LoggerBridge
479 implements InvokerLogger
480 {
481
482 private Logger logger;
483
484 LoggerBridge( Logger logger )
485 {
486 this.logger = logger;
487 }
488
489 public void debug( String message, Throwable error )
490 {
491 logger.debug( message, error );
492 }
493
494 public void debug( String message )
495 {
496 logger.debug( message );
497 }
498
499 public void error( String message, Throwable error )
500 {
501 logger.error( message, error );
502 }
503
504 public void error( String message )
505 {
506 logger.error( message );
507 }
508
509 public void fatalError( String message, Throwable error )
510 {
511 logger.fatalError( message, error );
512 }
513
514 public void fatalError( String message )
515 {
516 logger.fatalError( message );
517 }
518
519 public Logger getChildLogger( String message )
520 {
521 return logger.getChildLogger( message );
522 }
523
524 public String getName()
525 {
526 return logger.getName();
527 }
528
529 public int getThreshold()
530 {
531 return logger.getThreshold();
532 }
533
534 public void info( String message, Throwable error )
535 {
536 logger.info( message, error );
537 }
538
539 public void info( String message )
540 {
541 logger.info( message );
542 }
543
544 public boolean isDebugEnabled()
545 {
546 return logger.isDebugEnabled();
547 }
548
549 public boolean isErrorEnabled()
550 {
551 return logger.isErrorEnabled();
552 }
553
554 public boolean isFatalErrorEnabled()
555 {
556 return logger.isFatalErrorEnabled();
557 }
558
559 public boolean isInfoEnabled()
560 {
561 return logger.isInfoEnabled();
562 }
563
564 public boolean isWarnEnabled()
565 {
566 return logger.isWarnEnabled();
567 }
568
569 public void setThreshold( int level )
570 {
571
572
573
574 }
575
576 public void warn( String message, Throwable error )
577 {
578 logger.warn( message, error );
579 }
580
581 public void warn( String message )
582 {
583 logger.warn( message );
584 }
585
586 }
587
588 }