View Javadoc

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