View Javadoc

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&oslash;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 }