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 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   * Fork Maven using the maven-invoker shared library.
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     // TODO: Configuring an invocation request from a command line could as well be part of the Invoker API
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                 // TODO: setQuiet() currently not supported by InvocationRequest
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         // if null we use the current one
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             // Have to serialize to a file as if Maven is embedded, there may not actually be a settings.xml on disk
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             // NOTE:
537             // logger.setThreadhold( level )
538             // is not supported in plexus-container-default:1.0-alpha-9 as used in Maven 2.x
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 }