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 }