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