View Javadoc

1   package org.apache.maven.shared.test.plugin;
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.List;
26  import java.util.Properties;
27  
28  import org.apache.maven.shared.invoker.DefaultInvocationRequest;
29  import org.apache.maven.shared.invoker.DefaultInvoker;
30  import org.apache.maven.shared.invoker.InvocationOutputHandler;
31  import org.apache.maven.shared.invoker.InvocationRequest;
32  import org.apache.maven.shared.invoker.InvocationResult;
33  import org.apache.maven.shared.invoker.Invoker;
34  import org.apache.maven.shared.invoker.MavenInvocationException;
35  import org.codehaus.plexus.component.annotations.Component;
36  import org.codehaus.plexus.personality.plexus.lifecycle.phase.Disposable;
37  import org.codehaus.plexus.personality.plexus.lifecycle.phase.Initializable;
38  import org.codehaus.plexus.personality.plexus.lifecycle.phase.InitializationException;
39  import org.codehaus.plexus.util.IOUtil;
40  import org.codehaus.plexus.util.cli.CommandLineUtils;
41  
42  /**
43   * Test-tool used to execute Maven builds in order to test plugin functionality.
44   *
45   * @author jdcasey
46   * @version $Id$
47   */
48  @Component( role = BuildTool.class )
49  public class BuildTool
50      implements Initializable, Disposable
51  {
52      /** Plexus role */
53      public static final String ROLE = BuildTool.class.getName();
54  
55      private Invoker mavenInvoker;
56  
57      /**
58       * Build a standard InvocationRequest using the specified test-build POM, command-line properties,
59       * goals, and output logfile. Then, execute Maven using this standard request. Return the result
60       * of the invocation.
61       *
62       * @param pom The test-build POM
63       * @param properties command-line properties to fine-tune the test build, or test parameter
64       *   extraction from CLI properties
65       * @param goals The list of goals and/or lifecycle phases to execute during this build
66       * @param buildLogFile The logfile used to capture build output
67       * @return The result of the Maven invocation, including exit value and any execution exceptions
68       *   resulting from the Maven invocation.
69       * @throws TestToolsException if any
70       */
71      public InvocationResult executeMaven( File pom, Properties properties, List<String> goals, File buildLogFile )
72          throws TestToolsException
73      {
74          InvocationRequest request = createBasicInvocationRequest( pom, properties, goals, buildLogFile );
75  
76          return executeMaven( request );
77      }
78  
79      /**
80       * Execute a test build using a customized InvocationRequest. Normally, this request would be
81       * created using the <code>createBasicInvocationRequest</code> method in this class.
82       *
83       * @param request The customized InvocationRequest containing the configuration used to execute
84       *   the current test build
85       * @return The result of the Maven invocation, containing exit value, along with any execution
86       *   exceptions resulting from the [attempted] Maven invocation.
87       * @throws TestToolsException if any
88       */
89      public InvocationResult executeMaven( InvocationRequest request )
90          throws TestToolsException
91      {
92          try
93          {
94              return mavenInvoker.execute( request );
95          }
96          catch ( MavenInvocationException e )
97          {
98              throw new TestToolsException( "Error executing maven.", e );
99          }
100         finally
101         {
102             closeHandlers( request );
103         }
104     }
105 
106     /**
107      * Detect the location of the local Maven installation, and start up the MavenInvoker using that
108      * path. Detection uses the system property <code>maven.home</code>, and falls back to the shell
109      * environment variable <code>M2_HOME</code>.
110      *
111      * @throws IOException in case the shell environment variables cannot be read
112      */
113     private void startInvoker()
114         throws IOException
115     {
116         if ( mavenInvoker == null )
117         {
118             mavenInvoker = new DefaultInvoker();
119 
120             if ( System.getProperty( "maven.home" ) == null )
121             {
122                 Properties envars = CommandLineUtils.getSystemEnvVars();
123 
124                 String mavenHome = envars.getProperty( "M2_HOME" );
125 
126                 if ( mavenHome != null )
127                 {
128                     mavenInvoker.setMavenHome( new File( mavenHome ) );
129                 }
130             }
131         }
132     }
133 
134     /**
135      * If we're logging output to a log file using standard output handlers, make sure these are
136      * closed.
137      *
138      * @param request
139      */
140     private void closeHandlers( InvocationRequest request )
141     {
142         InvocationOutputHandler outHandler = request.getOutputHandler( null );
143 
144         if ( outHandler != null && ( outHandler instanceof LoggerHandler ) )
145         {
146             ( (LoggerHandler) outHandler ).close();
147         }
148 
149         InvocationOutputHandler errHandler = request.getErrorHandler( null );
150 
151         if ( errHandler != null && ( outHandler == null || errHandler != outHandler )
152             && ( errHandler instanceof LoggerHandler ) )
153         {
154             ( (LoggerHandler) errHandler ).close();
155         }
156     }
157 
158     /**
159      * Construct a standardized InvocationRequest given the test-build POM, a set of CLI properties,
160      * a list of goals to execute, and the location of a log file to which build output should be
161      * directed. The resulting InvocationRequest can then be customized by the test class before
162      * being used to execute a test build. Both standard-out and standard-error will be directed
163      * to the specified log file.
164      *
165      * @param pom The POM for the test build
166      * @param properties The command-line properties for use in this test build
167      * @param goals The goals and/or lifecycle phases to execute during the test build
168      * @param buildLogFile Location to which build output should be logged
169      * @return The standardized InvocationRequest for the test build, ready for any necessary
170      *   customizations.
171      */
172     public InvocationRequest createBasicInvocationRequest( File pom, Properties properties, List<String> goals,
173                                                            File buildLogFile )
174     {
175         InvocationRequest request = new DefaultInvocationRequest();
176 
177         request.setPomFile( pom );
178 
179         request.setGoals( goals );
180 
181         request.setProperties( properties );
182 
183         LoggerHandler handler = new LoggerHandler( buildLogFile );
184 
185         request.setOutputHandler( handler );
186         request.setErrorHandler( handler );
187 
188         return request;
189     }
190 
191     private static final class LoggerHandler
192         implements InvocationOutputHandler
193     {
194         private static final String LS = System.getProperty( "line.separator" );
195 
196         private final File output;
197 
198         private FileWriter writer;
199 
200         LoggerHandler( File logFile )
201         {
202             output = logFile;
203         }
204 
205         /** {@inheritDoc} */
206         public void consumeLine( String line )
207         {
208             if ( writer == null )
209             {
210                 try
211                 {
212                     output.getParentFile().mkdirs();
213                     writer = new FileWriter( output );
214                 }
215                 catch ( IOException e )
216                 {
217                     throw new IllegalStateException( "Failed to open build log: " + output + "\n\nError: "
218                         + e.getMessage() );
219                 }
220             }
221 
222             try
223             {
224                 writer.write( line + LS );
225                 writer.flush();
226             }
227             catch ( IOException e )
228             {
229                 throw new IllegalStateException( "Failed to write to build log: " + output + " output:\n\n\'" + line
230                     + "\'\n\nError: " + e.getMessage() );
231             }
232         }
233 
234         void close()
235         {
236             IOUtil.close( writer );
237         }
238     }
239 
240     /**
241      * Initialize this tool once it's been instantiated and composed, in order to start up the
242      * MavenInvoker instance.
243      *
244      * @throws InitializationException if any
245      */
246     public void initialize()
247         throws InitializationException
248     {
249         try
250         {
251             startInvoker();
252         }
253         catch ( IOException e )
254         {
255             throw new InitializationException( "Error detecting maven home.", e );
256         }
257     }
258 
259     /**
260      * Not currently used; when this API switches to use the Maven Embedder, it will be used to
261      * shutdown the embedder and its associated container, to free up JVM memory.
262      */
263     public void dispose()
264     {
265         // TODO: When we switch to the embedder, use this to deallocate the MavenEmbedder, along
266         // with the PlexusContainer and ClassRealm that it wraps.
267     }
268 }