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