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