1 | package org.apache.maven.continuum.execution; |
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.util.Collections; |
24 | import java.util.HashMap; |
25 | import java.util.List; |
26 | import java.util.Map; |
27 | import java.util.Properties; |
28 | |
29 | import org.apache.continuum.utils.shell.ExecutionResult; |
30 | import org.apache.continuum.utils.shell.ShellCommandHelper; |
31 | import org.apache.maven.artifact.Artifact; |
32 | import org.apache.maven.continuum.installation.InstallationService; |
33 | import org.apache.maven.continuum.model.project.BuildDefinition; |
34 | import org.apache.maven.continuum.model.project.Project; |
35 | import org.apache.maven.continuum.model.scm.ChangeSet; |
36 | import org.apache.maven.continuum.model.system.Installation; |
37 | import org.apache.maven.continuum.model.system.Profile; |
38 | import org.apache.maven.continuum.project.ContinuumProjectState; |
39 | import org.apache.maven.continuum.utils.WorkingDirectoryService; |
40 | import org.codehaus.plexus.commandline.ExecutableResolver; |
41 | import org.codehaus.plexus.personality.plexus.lifecycle.phase.Initializable; |
42 | import org.codehaus.plexus.personality.plexus.lifecycle.phase.InitializationException; |
43 | import org.codehaus.plexus.util.StringUtils; |
44 | import org.codehaus.plexus.util.cli.CommandLineException; |
45 | import org.slf4j.Logger; |
46 | import org.slf4j.LoggerFactory; |
47 | |
48 | /** |
49 | * @author <a href="mailto:trygvis@inamo.no">Trygve Laugstøl</a> |
50 | * @version $Id: AbstractBuildExecutor.java 764863 2009-04-14 16:28:12Z evenisse $ |
51 | */ |
52 | public abstract class AbstractBuildExecutor |
53 | implements ContinuumBuildExecutor, Initializable |
54 | { |
55 | protected final Logger log = LoggerFactory.getLogger( getClass() ); |
56 | |
57 | private static final String SUDO_EXECUTABLE = "sudo"; |
58 | |
59 | private static final String CHROOT_EXECUTABLE = "chroot"; |
60 | |
61 | // ---------------------------------------------------------------------- |
62 | // |
63 | // ---------------------------------------------------------------------- |
64 | |
65 | /** |
66 | * @plexus.requirement |
67 | */ |
68 | private ShellCommandHelper shellCommandHelper; |
69 | |
70 | /** |
71 | * @plexus.requirement |
72 | */ |
73 | private ExecutableResolver executableResolver; |
74 | |
75 | /** |
76 | * @plexus.requirement |
77 | */ |
78 | private WorkingDirectoryService workingDirectoryService; |
79 | |
80 | /** |
81 | * @plexus.requirement |
82 | */ |
83 | private InstallationService installationService; |
84 | |
85 | /** |
86 | * @plexus.configuration |
87 | */ |
88 | private File chrootJailDirectory; |
89 | |
90 | /** |
91 | * @plexus.configuration |
92 | */ |
93 | private String defaultExecutable; |
94 | |
95 | // ---------------------------------------------------------------------- |
96 | // |
97 | // ---------------------------------------------------------------------- |
98 | |
99 | private final String id; |
100 | |
101 | private boolean resolveExecutable; |
102 | |
103 | // ---------------------------------------------------------------------- |
104 | // |
105 | // ---------------------------------------------------------------------- |
106 | |
107 | protected AbstractBuildExecutor( String id, boolean resolveExecutable ) |
108 | { |
109 | this.id = id; |
110 | |
111 | this.resolveExecutable = resolveExecutable; |
112 | } |
113 | |
114 | public void setShellCommandHelper( ShellCommandHelper shellCommandHelper ) |
115 | { |
116 | this.shellCommandHelper = shellCommandHelper; |
117 | } |
118 | |
119 | public ShellCommandHelper getShellCommandHelper() |
120 | { |
121 | return shellCommandHelper; |
122 | } |
123 | |
124 | public void setWorkingDirectoryService( WorkingDirectoryService workingDirectoryService ) |
125 | { |
126 | this.workingDirectoryService = workingDirectoryService; |
127 | } |
128 | |
129 | public WorkingDirectoryService getWorkingDirectoryService() |
130 | { |
131 | return workingDirectoryService; |
132 | } |
133 | |
134 | public void setDefaultExecutable( String defaultExecutable ) |
135 | { |
136 | this.defaultExecutable = defaultExecutable; |
137 | } |
138 | |
139 | // ---------------------------------------------------------------------- |
140 | // Component Lifecycle |
141 | // ---------------------------------------------------------------------- |
142 | |
143 | public String getDefaultExecutable() |
144 | { |
145 | return defaultExecutable; |
146 | } |
147 | |
148 | public void initialize() |
149 | throws InitializationException |
150 | { |
151 | List path = executableResolver.getDefaultPath(); |
152 | |
153 | if ( resolveExecutable ) |
154 | { |
155 | if ( StringUtils.isEmpty( defaultExecutable ) ) |
156 | { |
157 | log.warn( "The default executable for build executor '" + id + "' is not set. " + |
158 | "This will cause a problem unless the project has a executable configured." ); |
159 | } |
160 | else |
161 | { |
162 | File resolvedExecutable = executableResolver.findExecutable( defaultExecutable, path ); |
163 | |
164 | if ( resolvedExecutable == null ) |
165 | { |
166 | log.warn( |
167 | "Could not find the executable '" + defaultExecutable + "' in the " + "path '" + path + "'." ); |
168 | } |
169 | else |
170 | { |
171 | log.info( "Resolved the executable '" + defaultExecutable + "' to " + "'" + |
172 | resolvedExecutable.getAbsolutePath() + "'." ); |
173 | } |
174 | } |
175 | } |
176 | } |
177 | |
178 | // ---------------------------------------------------------------------- |
179 | // |
180 | // ---------------------------------------------------------------------- |
181 | |
182 | /** |
183 | * Find the actual executable path to be used |
184 | * |
185 | * @param defaultExecutable |
186 | * @return The executable path |
187 | */ |
188 | protected String findExecutable( String executable, String defaultExecutable, boolean resolveExecutable, |
189 | File workingDirectory ) |
190 | { |
191 | // ---------------------------------------------------------------------- |
192 | // If we're not searching the path for the executable, prefix the |
193 | // executable with the working directory to make sure the path is |
194 | // absolute and thus won't be tried resolved by using the PATH |
195 | // ---------------------------------------------------------------------- |
196 | |
197 | String actualExecutable; |
198 | |
199 | if ( !resolveExecutable ) |
200 | { |
201 | actualExecutable = new File( workingDirectory, executable ).getAbsolutePath(); |
202 | } |
203 | else |
204 | { |
205 | List<String> path = executableResolver.getDefaultPath(); |
206 | |
207 | if ( StringUtils.isEmpty( executable ) ) |
208 | { |
209 | executable = defaultExecutable; |
210 | } |
211 | |
212 | File e = executableResolver.findExecutable( executable, path ); |
213 | |
214 | if ( e == null ) |
215 | { |
216 | log.warn( "Could not find the executable '" + executable + "' in this path: " ); |
217 | |
218 | for ( String element : path ) |
219 | { |
220 | log.warn( element ); |
221 | } |
222 | |
223 | actualExecutable = defaultExecutable; |
224 | } |
225 | else |
226 | { |
227 | actualExecutable = e.getAbsolutePath(); |
228 | } |
229 | } |
230 | |
231 | //sometimes executable isn't found in path but it exit (CONTINUUM-365) |
232 | File actualExecutableFile = new File( actualExecutable ); |
233 | |
234 | if ( !actualExecutableFile.exists() ) |
235 | { |
236 | actualExecutable = executable; |
237 | } |
238 | |
239 | return actualExecutable; |
240 | } |
241 | |
242 | protected ContinuumBuildExecutionResult executeShellCommand( Project project, String executable, String arguments, |
243 | File output, Map<String, String> environments ) |
244 | throws ContinuumBuildExecutorException |
245 | { |
246 | |
247 | File workingDirectory = getWorkingDirectory( project ); |
248 | |
249 | String actualExecutable = findExecutable( executable, defaultExecutable, resolveExecutable, workingDirectory ); |
250 | |
251 | // ---------------------------------------------------------------------- |
252 | // Execute the build |
253 | // ---------------------------------------------------------------------- |
254 | |
255 | try |
256 | { |
257 | File chrootJailDirectory = getChrootJailDirectory(); |
258 | if ( chrootJailDirectory != null ) |
259 | { |
260 | StringBuilder sb = new StringBuilder(); |
261 | sb.append( CHROOT_EXECUTABLE ); |
262 | sb.append( " " ); |
263 | sb.append( new File( chrootJailDirectory, project.getGroupId() ) ); |
264 | sb.append( " " ); |
265 | sb.append( " /bin/sh -c 'cd " ); |
266 | sb.append( getRelativePath( chrootJailDirectory, workingDirectory, project.getGroupId() ) ); |
267 | sb.append( " && " ); |
268 | sb.append( actualExecutable ); |
269 | sb.append( " " ); |
270 | sb.append( arguments ); |
271 | sb.append( "'" ); |
272 | |
273 | arguments = sb.toString(); |
274 | actualExecutable = SUDO_EXECUTABLE; |
275 | workingDirectory = chrootJailDirectory; // not really used but must exist |
276 | } |
277 | |
278 | ExecutionResult result = |
279 | getShellCommandHelper().executeShellCommand( workingDirectory, actualExecutable, arguments, output, |
280 | project.getId(), environments ); |
281 | |
282 | log.info( "Exit code: " + result.getExitCode() ); |
283 | |
284 | return new ContinuumBuildExecutionResult( output, result.getExitCode() ); |
285 | } |
286 | catch ( CommandLineException e ) |
287 | { |
288 | if ( e.getCause() instanceof InterruptedException ) |
289 | { |
290 | throw new ContinuumBuildCancelledException( "The build was cancelled", e ); |
291 | } |
292 | else |
293 | { |
294 | throw new ContinuumBuildExecutorException( |
295 | "Error while executing shell command. The most common error is that '" + executable + "' " + |
296 | "is not in your path.", e ); |
297 | } |
298 | } |
299 | catch ( Exception e ) |
300 | { |
301 | throw new ContinuumBuildExecutorException( |
302 | "Error while executing shell command. " + "The most common error is that '" + executable + "' " + |
303 | "is not in your path.", e ); |
304 | } |
305 | } |
306 | |
307 | private String getRelativePath( File chrootDir, File workingDirectory, String groupId ) |
308 | { |
309 | String path = workingDirectory.getPath(); |
310 | String chrootBase = new File( chrootDir, groupId ).getPath(); |
311 | if ( path.startsWith( chrootBase ) ) |
312 | { |
313 | return path.substring( chrootBase.length(), path.length() ); |
314 | } |
315 | else |
316 | { |
317 | throw new IllegalArgumentException( |
318 | "Working directory is not inside the chroot jail " + chrootBase + " , " + path ); |
319 | } |
320 | } |
321 | |
322 | protected abstract Map<String, String> getEnvironments( BuildDefinition buildDefinition ); |
323 | |
324 | protected String getJavaHomeValue( BuildDefinition buildDefinition ) |
325 | { |
326 | Profile profile = buildDefinition.getProfile(); |
327 | if ( profile == null ) |
328 | { |
329 | return null; |
330 | } |
331 | Installation jdk = profile.getJdk(); |
332 | if ( jdk == null ) |
333 | { |
334 | return null; |
335 | } |
336 | return jdk.getVarValue(); |
337 | } |
338 | |
339 | public void backupTestFiles( Project project, int buildId ) |
340 | { |
341 | //Nothing to do, by default |
342 | } |
343 | |
344 | /** |
345 | * By default, we return true because with a change, the project must be rebuilt. |
346 | */ |
347 | public boolean shouldBuild( List<ChangeSet> changes, Project continuumProject, File workingDirectory, |
348 | BuildDefinition buildDefinition ) |
349 | throws ContinuumBuildExecutorException |
350 | { |
351 | return true; |
352 | } |
353 | |
354 | protected Map<String, String> getEnvironmentVariables( BuildDefinition buildDefinition ) |
355 | { |
356 | Profile profile = buildDefinition.getProfile(); |
357 | Map<String, String> envVars = new HashMap<String, String>(); |
358 | if ( profile == null ) |
359 | { |
360 | return envVars; |
361 | } |
362 | List<Installation> environmentVariables = profile.getEnvironmentVariables(); |
363 | if ( environmentVariables.isEmpty() ) |
364 | { |
365 | return envVars; |
366 | } |
367 | for ( Installation installation : environmentVariables ) |
368 | { |
369 | envVars.put( installation.getVarName(), installation.getVarValue() ); |
370 | } |
371 | return envVars; |
372 | } |
373 | |
374 | protected Properties getContinuumSystemProperties( Project project ) |
375 | { |
376 | Properties properties = new Properties(); |
377 | properties.setProperty( "continuum.project.group.name", project.getProjectGroup().getName() ); |
378 | properties.setProperty( "continuum.project.lastBuild.state", String.valueOf( project.getOldState() ) ); |
379 | properties.setProperty( "continuum.project.lastBuild.number", String.valueOf( project.getBuildNumber() ) ); |
380 | properties.setProperty( "continuum.project.nextBuild.number", String.valueOf( project.getBuildNumber() + 1 ) ); |
381 | properties.setProperty( "continuum.project.id", String.valueOf( project.getId() ) ); |
382 | properties.setProperty( "continuum.project.name", project.getName() ); |
383 | properties.setProperty( "continuum.project.version", project.getVersion() ); |
384 | return properties; |
385 | } |
386 | |
387 | protected String getBuildFileForProject( Project project, BuildDefinition buildDefinition ) |
388 | { |
389 | String buildFile = StringUtils.clean( buildDefinition.getBuildFile() ); |
390 | String relPath = StringUtils.clean( project.getRelativePath() ); |
391 | |
392 | if ( StringUtils.isEmpty( relPath ) ) |
393 | { |
394 | return buildFile; |
395 | } |
396 | |
397 | return relPath + File.separator + buildFile; |
398 | } |
399 | |
400 | public boolean isBuilding( Project project ) |
401 | { |
402 | return project.getState() == ContinuumProjectState.BUILDING || |
403 | getShellCommandHelper().isRunning( project.getId() ); |
404 | } |
405 | |
406 | public void killProcess( Project project ) |
407 | { |
408 | getShellCommandHelper().killProcess( project.getId() ); |
409 | } |
410 | |
411 | public List<Artifact> getDeployableArtifacts( Project project, File workingDirectory, |
412 | BuildDefinition buildDefinition ) |
413 | throws ContinuumBuildExecutorException |
414 | { |
415 | // Not supported by this builder |
416 | return Collections.EMPTY_LIST; |
417 | } |
418 | |
419 | public File getWorkingDirectory( Project project ) |
420 | { |
421 | return getWorkingDirectoryService().getWorkingDirectory( project ); |
422 | } |
423 | |
424 | public InstallationService getInstallationService() |
425 | { |
426 | return installationService; |
427 | } |
428 | |
429 | public void setInstallationService( InstallationService installationService ) |
430 | { |
431 | this.installationService = installationService; |
432 | } |
433 | |
434 | public boolean isResolveExecutable() |
435 | { |
436 | return resolveExecutable; |
437 | } |
438 | |
439 | public void setResolveExecutable( boolean resolveExecutable ) |
440 | { |
441 | this.resolveExecutable = resolveExecutable; |
442 | } |
443 | |
444 | public void setExecutableResolver( ExecutableResolver executableResolver ) |
445 | { |
446 | this.executableResolver = executableResolver; |
447 | } |
448 | |
449 | public ExecutableResolver getExecutableResolver() |
450 | { |
451 | return executableResolver; |
452 | } |
453 | |
454 | public void setChrootJailDirectory( File chrootJailDirectory ) |
455 | { |
456 | this.chrootJailDirectory = chrootJailDirectory; |
457 | } |
458 | |
459 | public File getChrootJailDirectory() |
460 | { |
461 | return chrootJailDirectory; |
462 | } |
463 | } |