View Javadoc

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