blob: 3b488150f26d1439feddf4ba1aaaa1d1e9bd418d [file] [log] [blame]
package org.apache.maven.continuum.buildcontroller;
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import org.apache.continuum.dao.BuildDefinitionDao;
import org.apache.continuum.dao.BuildResultDao;
import org.apache.continuum.dao.ProjectDao;
import org.apache.continuum.dao.ProjectGroupDao;
import org.apache.continuum.dao.ProjectScmRootDao;
import org.apache.continuum.model.project.ProjectScmRoot;
import org.apache.continuum.utils.ContinuumUtils;
import org.apache.continuum.utils.build.BuildTrigger;
import org.apache.maven.continuum.core.action.AbstractContinuumAction;
import org.apache.maven.continuum.core.action.ExecuteBuilderContinuumAction;
import org.apache.maven.continuum.execution.ContinuumBuildExecutor;
import org.apache.maven.continuum.execution.ContinuumBuildExecutorConstants;
import org.apache.maven.continuum.execution.manager.BuildExecutorManager;
import org.apache.maven.continuum.model.project.BuildDefinition;
import org.apache.maven.continuum.model.project.BuildResult;
import org.apache.maven.continuum.model.project.Project;
import org.apache.maven.continuum.model.project.ProjectDependency;
import org.apache.maven.continuum.model.project.ProjectGroup;
import org.apache.maven.continuum.model.scm.ChangeFile;
import org.apache.maven.continuum.model.scm.ChangeSet;
import org.apache.maven.continuum.model.scm.ScmResult;
import org.apache.maven.continuum.notification.ContinuumNotificationDispatcher;
import org.apache.maven.continuum.project.ContinuumProjectState;
import org.apache.maven.continuum.store.ContinuumObjectNotFoundException;
import org.apache.maven.continuum.store.ContinuumStoreException;
import org.apache.maven.continuum.utils.WorkingDirectoryService;
import org.apache.maven.scm.ScmException;
import org.apache.maven.scm.repository.ScmRepositoryException;
import org.codehaus.plexus.action.ActionManager;
import org.codehaus.plexus.action.ActionNotFoundException;
import org.codehaus.plexus.taskqueue.execution.TaskExecutionException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
/**
* @author <a href="mailto:trygvis@inamo.no">Trygve Laugst&oslash;l</a>
* @version $Id$
* @plexus.component role="org.apache.maven.continuum.buildcontroller.BuildController" role-hint="default"
*/
public class DefaultBuildController
implements BuildController
{
private static final Logger log = LoggerFactory.getLogger( DefaultBuildController.class );
/**
* @plexus.requirement
*/
private BuildDefinitionDao buildDefinitionDao;
/**
* @plexus.requirement
*/
private BuildResultDao buildResultDao;
/**
* @plexus.requirement
*/
private ProjectDao projectDao;
/**
* @plexus.requirement
*/
private ProjectGroupDao projectGroupDao;
/**
* @plexus.requirement
*/
private ProjectScmRootDao projectScmRootDao;
/**
* @plexus.requirement
*/
private ContinuumNotificationDispatcher notifierDispatcher;
/**
* @plexus.requirement
*/
private ActionManager actionManager;
/**
* @plexus.requirement
*/
private WorkingDirectoryService workingDirectoryService;
/**
* @plexus.requirement
*/
private BuildExecutorManager buildExecutorManager;
// ----------------------------------------------------------------------
// BuildController Implementation
// ----------------------------------------------------------------------
/**
* @param projectId
* @param buildDefinitionId
* @param buildTrigger
* @param scmResult
* @throws TaskExecutionException
*/
public void build( int projectId, int buildDefinitionId, BuildTrigger buildTrigger, ScmResult scmResult )
throws TaskExecutionException
{
log.info( "Initializing build" );
BuildContext context = initializeBuildContext( projectId, buildDefinitionId, buildTrigger, scmResult );
// ignore this if AlwaysBuild ?
if ( !checkScmResult( context ) )
{
log.info( "Error updating from SCM, not building" );
return;
}
log.info( "Starting build of " + context.getProject().getName() );
startBuild( context );
try
{
checkProjectDependencies( context );
if ( !shouldBuild( context ) )
{
return;
}
Map<String, Object> actionContext = context.getActionContext();
try
{
performAction( "update-project-from-working-directory", context );
}
catch ( TaskExecutionException e )
{
updateBuildResult( context, ContinuumUtils.throwableToString( e ) );
//just log the error but don't stop the build from progressing in order not to suppress any build result messages there
log.error( "Error executing action update-project-from-working-directory '", e );
}
performAction( "execute-builder", context );
performAction( "deploy-artifact", context );
context.setCancelled( ExecuteBuilderContinuumAction.isCancelled( actionContext ) );
String s = AbstractContinuumAction.getBuildId( actionContext, null );
if ( s != null && !context.isCancelled() )
{
try
{
context.setBuildResult( buildResultDao.getBuildResult( Integer.valueOf( s ) ) );
}
catch ( NumberFormatException e )
{
throw new TaskExecutionException( "Internal error: build id not an integer", e );
}
catch ( ContinuumObjectNotFoundException e )
{
throw new TaskExecutionException( "Internal error: Cannot find build result", e );
}
catch ( ContinuumStoreException e )
{
throw new TaskExecutionException( "Error loading build result", e );
}
}
}
finally
{
endBuild( context );
}
}
/**
* Checks if the build should be marked as ERROR and notifies the end of the build.
*
* @param context
* @throws TaskExecutionException
*/
private void endBuild( BuildContext context )
throws TaskExecutionException
{
Project project = context.getProject();
try
{
if ( project.getState() != ContinuumProjectState.NEW &&
project.getState() != ContinuumProjectState.CHECKEDOUT &&
project.getState() != ContinuumProjectState.OK && project.getState() != ContinuumProjectState.FAILED &&
project.getState() != ContinuumProjectState.ERROR && !context.isCancelled() )
{
try
{
String s = AbstractContinuumAction.getBuildId( context.getActionContext(), null );
if ( s != null )
{
BuildResult buildResult = buildResultDao.getBuildResult( Integer.valueOf( s ) );
project.setState( buildResult.getState() );
projectDao.updateProject( project );
}
}
catch ( ContinuumStoreException e )
{
throw new TaskExecutionException( "Error storing the project", e );
}
}
}
finally
{
if ( !context.isCancelled() )
{
notifierDispatcher.buildComplete( project, context.getBuildDefinition(), context.getBuildResult() );
}
}
}
private void updateBuildResult( BuildContext context, String error )
throws TaskExecutionException
{
BuildResult build = context.getBuildResult();
if ( build == null )
{
build = makeAndStoreBuildResult( context, error );
}
else
{
updateBuildResult( build, context );
build.setError( error );
try
{
buildResultDao.updateBuildResult( build );
build = buildResultDao.getBuildResult( build.getId() );
context.setBuildResult( build );
}
catch ( ContinuumStoreException e )
{
throw new TaskExecutionException( "Error updating build result", e );
}
}
context.getProject().setState( build.getState() );
try
{
projectDao.updateProject( context.getProject() );
}
catch ( ContinuumStoreException e )
{
throw new TaskExecutionException( "Error updating project", e );
}
}
private void updateBuildResult( BuildResult build, BuildContext context )
{
if ( build.getScmResult() == null && context.getScmResult() != null )
{
build.setScmResult( context.getScmResult() );
}
if ( build.getModifiedDependencies() == null && context.getModifiedDependencies() != null )
{
build.setModifiedDependencies( context.getModifiedDependencies() );
}
}
private void startBuild( BuildContext context )
throws TaskExecutionException
{
Project project = context.getProject();
project.setOldState( project.getState() );
project.setState( ContinuumProjectState.BUILDING );
try
{
projectDao.updateProject( project );
}
catch ( ContinuumStoreException e )
{
throw new TaskExecutionException( "Error persisting project", e );
}
notifierDispatcher.buildStarted( project, context.getBuildDefinition() );
}
/**
* Initializes a BuildContext for the build.
*
* @param projectId
* @param buildDefinitionId
* @param buildTrigger
* @param scmResult
* @return
* @throws TaskExecutionException
*/
@SuppressWarnings( "unchecked" )
protected BuildContext initializeBuildContext( int projectId, int buildDefinitionId, BuildTrigger buildTrigger,
ScmResult scmResult )
throws TaskExecutionException
{
BuildContext context = new BuildContext();
context.setStartTime( System.currentTimeMillis() );
Map actionContext = context.getActionContext();
try
{
Project project = projectDao.getProject( projectId );
context.setProject( project );
BuildDefinition buildDefinition = buildDefinitionDao.getBuildDefinition( buildDefinitionId );
BuildTrigger newBuildTrigger = buildTrigger;
if ( newBuildTrigger.getTrigger() == ContinuumProjectState.TRIGGER_SCHEDULED )
{
newBuildTrigger.setTriggeredBy( buildDefinition.getSchedule().getName() );
}
context.setBuildTrigger( newBuildTrigger );
context.setBuildDefinition( buildDefinition );
BuildResult oldBuildResult = buildResultDao.getLatestBuildResultForBuildDefinition( projectId,
buildDefinitionId );
context.setOldBuildResult( oldBuildResult );
context.setScmResult( scmResult );
// CONTINUUM-2193
ProjectGroup projectGroup = project.getProjectGroup();
List<ProjectScmRoot> scmRoots = projectScmRootDao.getProjectScmRootByProjectGroup( projectGroup.getId() );
String projectScmUrl = project.getScmUrl();
String projectScmRootAddress = "";
for ( ProjectScmRoot projectScmRoot : scmRoots )
{
projectScmRootAddress = projectScmRoot.getScmRootAddress();
if ( projectScmUrl.startsWith( projectScmRoot.getScmRootAddress() ) )
{
AbstractContinuumAction.setProjectScmRootUrl( actionContext, projectScmRoot.getScmRootAddress() );
break;
}
}
if ( project.isCheckedOutInSingleDirectory() )
{
List<Project> projectsInGroup = projectGroupDao.getProjectGroupWithProjects(
projectGroup.getId() ).getProjects();
List<Project> projectsWithCommonScmRoot = new ArrayList<Project>();
for ( Project projectInGroup : projectsInGroup )
{
if ( projectInGroup.getScmUrl().startsWith( projectScmRootAddress ) )
{
projectsWithCommonScmRoot.add( projectInGroup );
}
}
AbstractContinuumAction.setListOfProjectsInGroupWithCommonScmRoot( actionContext,
projectsWithCommonScmRoot );
}
// CONTINUUM-1871 olamy if continuum is killed during building oldBuildResult will have a endTime 0
// this means all changes since the project has been loaded in continuum will be in memory
// now we will load all BuildResult with an Id bigger or equals than the oldBuildResult one
//if ( oldBuildResult != null )
//{
// context.setOldScmResult(
// getOldScmResults( projectId, oldBuildResult.getBuildNumber(), oldBuildResult.getEndTime() ) );
//}
}
catch ( ContinuumStoreException e )
{
throw new TaskExecutionException( "Error initializing the build context", e );
}
// Map<String, Object> actionContext = context.getActionContext();
AbstractContinuumAction.setProjectId( actionContext, projectId );
AbstractContinuumAction.setProject( actionContext, context.getProject() );
AbstractContinuumAction.setBuildDefinitionId( actionContext, buildDefinitionId );
AbstractContinuumAction.setBuildDefinition( actionContext, context.getBuildDefinition() );
AbstractContinuumAction.setBuildTrigger( actionContext, buildTrigger );
AbstractContinuumAction.setScmResult( actionContext, context.getScmResult() );
if ( context.getOldBuildResult() != null )
{
AbstractContinuumAction.setOldBuildId( actionContext, context.getOldBuildResult().getId() );
}
return context;
}
private void performAction( String actionName, BuildContext context )
throws TaskExecutionException
{
String error;
TaskExecutionException exception;
try
{
log.info( "Performing action " + actionName );
actionManager.lookup( actionName ).execute( context.getActionContext() );
return;
}
catch ( ActionNotFoundException e )
{
error = ContinuumUtils.throwableToString( e );
exception = new TaskExecutionException( "Error looking up action '" + actionName + "'", e );
}
catch ( ScmRepositoryException e )
{
error = getValidationMessages( e ) + "\n" + ContinuumUtils.throwableToString( e );
exception = new TaskExecutionException( "SCM error while executing '" + actionName + "'", e );
}
catch ( ScmException e )
{
error = ContinuumUtils.throwableToString( e );
exception = new TaskExecutionException( "SCM error while executing '" + actionName + "'", e );
}
catch ( Exception e )
{
exception = new TaskExecutionException( "Error executing action '" + actionName + "'", e );
error = ContinuumUtils.throwableToString( exception );
}
// TODO: clean this up. We catch the original exception from the action, and then update the buildresult
// for it - we need to because of the specialized error message for SCM.
// If updating the buildresult fails, log the previous error and throw the new one.
// If updating the buildresult succeeds, throw the original exception. The build result should NOT
// be updated again - a TaskExecutionException is final, no further action should be taken upon it.
try
{
updateBuildResult( context, error );
}
catch ( TaskExecutionException e )
{
log.error( "Error updating build result after receiving the following exception: ", exception );
throw e;
}
throw exception;
}
protected boolean shouldBuild( BuildContext context )
throws TaskExecutionException
{
BuildDefinition buildDefinition = context.getBuildDefinition();
if ( buildDefinition.isAlwaysBuild() )
{
log.info( "AlwaysBuild configured, building" );
return true;
}
if ( context.getOldBuildResult() == null )
{
log.info( "The project has never been built with the current build definition, building" );
return true;
}
Project project = context.getProject();
//CONTINUUM-1428
if ( project.getOldState() == ContinuumProjectState.ERROR ||
context.getOldBuildResult().getState() == ContinuumProjectState.ERROR )
{
log.info( "Latest state was 'ERROR', building" );
return true;
}
if ( context.getBuildTrigger().getTrigger() == ContinuumProjectState.TRIGGER_FORCED )
{
log.info( "The project build is forced, building" );
return true;
}
boolean shouldBuild = false;
boolean allChangesUnknown = true;
if ( project.getOldState() != ContinuumProjectState.NEW &&
project.getOldState() != ContinuumProjectState.CHECKEDOUT &&
context.getBuildTrigger().getTrigger() != ContinuumProjectState.TRIGGER_FORCED &&
project.getState() != ContinuumProjectState.NEW && project.getState() != ContinuumProjectState.CHECKEDOUT )
{
// Check SCM changes
if ( context.getScmResult() != null )
{
allChangesUnknown = checkAllChangesUnknown( context.getScmResult().getChanges() );
}
if ( allChangesUnknown )
{
if ( context.getScmResult() != null && !context.getScmResult().getChanges().isEmpty() )
{
log.info(
"The project was not built because all changes are unknown (maybe local modifications or ignored files not defined in your SCM tool." );
}
else
{
log.info(
"The project was not built because no changes were detected in sources since the last build." );
}
}
// Check dependencies changes
if ( context.getModifiedDependencies() != null && !context.getModifiedDependencies().isEmpty() )
{
log.info( "Found dependencies changes, building" );
shouldBuild = true;
}
}
// Check changes
if ( !shouldBuild && ( ( !allChangesUnknown && context.getScmResult() != null &&
!context.getScmResult().getChanges().isEmpty() ) || project.getExecutorId().equals(
ContinuumBuildExecutorConstants.MAVEN_TWO_BUILD_EXECUTOR ) ) )
{
try
{
ContinuumBuildExecutor executor = buildExecutorManager.getBuildExecutor( project.getExecutorId() );
Map<String, Object> actionContext = context.getActionContext();
List<Project> projectsWithCommonScmRoot =
AbstractContinuumAction.getListOfProjectsInGroupWithCommonScmRoot( actionContext );
String projectScmRootUrl = AbstractContinuumAction.getProjectScmRootUrl( actionContext,
project.getScmUrl() );
if ( executor == null )
{
log.warn( "No continuum build executor found for project " + project.getId() +
" with executor '" + project.getExecutorId() + "'" );
}
else if ( context.getScmResult() != null )
{
shouldBuild = executor.shouldBuild( context.getScmResult().getChanges(), project,
workingDirectoryService.getWorkingDirectory( project,
projectScmRootUrl,
projectsWithCommonScmRoot ),
context.getBuildDefinition() );
}
}
catch ( Exception e )
{
updateBuildResult( context, ContinuumUtils.throwableToString( e ) );
throw new TaskExecutionException( "Can't determine if the project should build or not", e );
}
}
if ( shouldBuild )
{
log.info( "Changes found in the current project, building" );
}
else
{
project.setState( project.getOldState() );
project.setOldState( 0 );
try
{
projectDao.updateProject( project );
}
catch ( ContinuumStoreException e )
{
throw new TaskExecutionException( "Error storing project", e );
}
log.info( "No changes in the current project, not building" );
}
return shouldBuild;
}
private boolean checkAllChangesUnknown( List<ChangeSet> changes )
{
for ( ChangeSet changeSet : changes )
{
List<ChangeFile> changeFiles = changeSet.getFiles();
for ( ChangeFile changeFile : changeFiles )
{
if ( !"unknown".equalsIgnoreCase( changeFile.getStatus() ) )
{
return false;
}
}
}
return true;
}
private String getValidationMessages( ScmRepositoryException ex )
{
List<String> messages = ex.getValidationMessages();
StringBuffer message = new StringBuffer();
if ( messages != null && !messages.isEmpty() )
{
for ( Iterator<String> i = messages.iterator(); i.hasNext(); )
{
message.append( i.next() );
if ( i.hasNext() )
{
message.append( System.getProperty( "line.separator" ) );
}
}
}
return message.toString();
}
protected void checkProjectDependencies( BuildContext context )
{
if ( context.getOldBuildResult() == null )
{
return;
}
try
{
Project project = projectDao.getProjectWithDependencies( context.getProject().getId() );
List<ProjectDependency> dependencies = project.getDependencies();
if ( dependencies == null )
{
dependencies = new ArrayList<ProjectDependency>();
}
if ( project.getParent() != null )
{
dependencies.add( project.getParent() );
}
if ( dependencies.isEmpty() )
{
return;
}
List<ProjectDependency> modifiedDependencies = new ArrayList<ProjectDependency>();
for ( ProjectDependency dep : dependencies )
{
Project dependencyProject = projectDao.getProject( dep.getGroupId(), dep.getArtifactId(),
dep.getVersion() );
if ( dependencyProject != null )
{
long nbBuild = buildResultDao.getNbBuildResultsInSuccessForProject( dependencyProject.getId(),
context.getOldBuildResult().getEndTime() );
if ( nbBuild > 0 )
{
log.debug( "Dependency changed: " + dep.getGroupId() + ":" + dep.getArtifactId() + ":" +
dep.getVersion() );
modifiedDependencies.add( dep );
}
else
{
log.debug( "Dependency not changed: " + dep.getGroupId() + ":" + dep.getArtifactId() + ":" +
dep.getVersion() );
}
}
else
{
log.debug( "Skip non Continuum project: " + dep.getGroupId() + ":" + dep.getArtifactId() + ":" +
dep.getVersion() );
}
}
context.setModifiedDependencies( modifiedDependencies );
AbstractContinuumAction.setUpdatedDependencies( context.getActionContext(), modifiedDependencies );
}
catch ( ContinuumStoreException e )
{
log.warn( "Can't get the project dependencies", e );
}
}
// ----------------------------------------------------------------------
//
// ----------------------------------------------------------------------
private BuildResult makeAndStoreBuildResult( BuildContext context, String error )
throws TaskExecutionException
{
// Project project, ScmResult scmResult, long startTime, int trigger )
// project, scmResult, startTime, trigger );
BuildResult build = new BuildResult();
build.setState( ContinuumProjectState.ERROR );
build.setTrigger( context.getBuildTrigger().getTrigger() );
build.setUsername( context.getBuildTrigger().getTriggeredBy() );
build.setStartTime( context.getStartTime() );
build.setEndTime( System.currentTimeMillis() );
updateBuildResult( build, context );
build.setScmResult( context.getScmResult() );
build.setBuildDefinition( context.getBuildDefinition() );
if ( error != null )
{
build.setError( error );
}
try
{
buildResultDao.addBuildResult( context.getProject(), build );
build = buildResultDao.getBuildResult( build.getId() );
context.setBuildResult( build );
return build;
}
catch ( ContinuumStoreException e )
{
throw new TaskExecutionException( "Error storing build result", e );
}
}
/**
* Check to see if there was a error while checking out/updating the project
*
* @param context The build context
* @return true if scm result is ok
* @throws TaskExecutionException
*/
private boolean checkScmResult( BuildContext context )
throws TaskExecutionException
{
Project project = context.getProject();
int projectGroupId = project.getProjectGroup().getId();
List<ProjectScmRoot> scmRoots = projectScmRootDao.getProjectScmRootByProjectGroup( projectGroupId );
for ( ProjectScmRoot projectScmRoot : scmRoots )
{
if ( project.getScmUrl().startsWith( projectScmRoot.getScmRootAddress() ) )
{
if ( projectScmRoot.getState() == ContinuumProjectState.UPDATED )
{
return true;
}
break;
}
}
return false;
}
}