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