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 }