/* * HeadsUp Agile * Copyright 2009-2012 Heads Up Development Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package org.headsupdev.agile.app.ci; import org.headsupdev.agile.app.ci.builders.BuildHandler; import org.headsupdev.support.java.FileUtil; import org.headsupdev.agile.api.logging.Logger; import org.headsupdev.agile.api.*; import org.headsupdev.agile.app.ci.event.BuildFailedEvent; import org.headsupdev.agile.app.ci.event.BuildSucceededEvent; import org.headsupdev.agile.storage.HibernateStorage; import org.headsupdev.agile.storage.ci.Build; import java.io.*; import java.util.*; /** * The main CI thread that runs the builds * * @author Andrew Williams * @version $Id$ * @since 1.0 */ public class CIBuilder implements ProjectListener { private static final List<CIQueuedBuild> pendingBuilds = new LinkedList<CIQueuedBuild>(); private static boolean building = false; private CIApplication application; private boolean buildNow; private Logger log = Manager.getLogger( getClass().getName() ); public void buildProject( Project project ) { buildProject( project, true ); } public void buildProject( Project project, boolean notify ) { PropertyTree config = Manager.getStorageInstance().getGlobalConfiguration(). getApplicationConfigurationForProject( CIApplication.ID, project ).getSubTree( "schedule.default" ); buildProject( project, "default", config, notify ); } public void buildProject( Project project, String id, PropertyTree config, boolean notify ) { if ( !CIApplication.getHandlerFactory().supportsBuilding( project ) ) { return; } synchronized ( pendingBuilds ) { CIQueuedBuild building = new CIQueuedBuild( project, id, config, notify ); Iterator<CIQueuedBuild> queue = pendingBuilds.iterator(); while ( queue.hasNext() ) { CIQueuedBuild build = queue.next(); if ( build != null && build.getProject() != null && build.getProject().equals( project ) && build.getConfigName().equals( building.getConfigName() ) ) { queue.remove(); } } pendingBuilds.add( 0, building ); } buildNow = true; buildQueuedProjects(); } public void queueProject( Project project ) { queueProject( project, true ); } public void queueProject( Project project, boolean notify ) { PropertyTree config = Manager.getStorageInstance().getGlobalConfiguration(). getApplicationConfigurationForProject( CIApplication.ID, project ).getSubTree( "schedule.default" ); queueProject( project, "default", config, notify ); } public void queueProject( Project project, String id, PropertyTree config, boolean notify ) { if ( !CIApplication.getHandlerFactory().supportsBuilding( project ) ) { return; } if ( isBuildConfigDisabled( config ) ) { return; } synchronized ( pendingBuilds ) { if ( !isProjectQueued( project ) ) { queueBuild( new CIQueuedBuild( project, id, config, notify ) ); buildQueuedProjects(); } } } private void queueBuild( CIQueuedBuild build ) { synchronized ( pendingBuilds ) { pendingBuilds.add( build ); } } public void dequeueProject( Project project ) { if ( !CIApplication.getHandlerFactory().supportsBuilding( project )) { return; } synchronized( pendingBuilds ) { Iterator<CIQueuedBuild> queue = pendingBuilds.iterator(); while ( queue.hasNext() ) { CIQueuedBuild build = queue.next(); if ( build != null && build.getProject() != null && build.getProject().equals( project ) ) { queue.remove(); } } } } public void queueAllProjects() { Enumeration<Project> projects = new Vector<Project>( Manager.getStorageInstance().getProjects() ).elements(); while ( projects.hasMoreElements() ) { queueProject( projects.nextElement() ); } } public static boolean isProjectQueued( Project project ) { synchronized( pendingBuilds ) { for ( CIQueuedBuild build : pendingBuilds ) { if ( build != null && build.getProject() != null && build.getProject().equals( project ) ) { return true; } } } return false; } public void unsetAllDeferred() { synchronized( pendingBuilds ) { for ( CIQueuedBuild build : pendingBuilds ) { build.setDeferred( false ); } } } protected void buildQueuedProjects() { if ( building ) { buildNow = false; return; } building = true; new Thread( "CIBuilderWorker" ) { public void run() { if ( !buildNow ) { try { Thread.sleep( 15000 ); } catch ( InterruptedException e ) { // ignore, we were just pausing to avoid dupes... } } buildNow = false; try { while ( pendingBuilds.size() > 0 ) { CIQueuedBuild build; synchronized( pendingBuilds ) { try { build = pendingBuilds.remove( 0 ); } catch ( NoSuchElementException e ) { // changed whilst building - just ignore and try later break; } } buildQueuedProject( build ); } } catch ( Exception e ) { e.printStackTrace(); } finally { building = false; } } }.start(); } private void buildQueuedProject( CIQueuedBuild queued ) { Project project = queued.getProject(); BuildHandler buildHandler = CIApplication.getHandlerFactory().getBuildHandler( project ); if ( !queued.isDeferred() && !buildHandler.isReadyToBuild( project, this ) ) { queued.setDeferred( true ); queueBuild( queued ); return; } unsetAllDeferred(); PropertyTree config = queued.getConfig(); if ( queued.getConfigName() == null ) { log.info( "Preparing build for project " + project.getAlias() ); } else { log.info( "Preparing build \"" + queued.getConfigName() + "\" for project " + project.getAlias() ); } File base = null; File projectDir = CIApplication.getProjectDir( project ); projectDir.mkdirs(); Task buildTask = new BuildTask( project ); try { HibernateStorage storage = (HibernateStorage) Manager.getStorageInstance(); storage.getHibernateSession(); Manager.getInstance().addTask( buildTask ); try { base = FileUtil.createTempDir( "build-", "", projectDir ); storage.copyWorkingDirectory( project, base ); } catch ( Exception e ) { log.error( "Unable to prepare project " + project + " for build", e ); try { FileUtil.delete( base ); } catch ( IOException e2 ) { log.error( "Error removing failed build", e ); } Manager.getInstance().removeTask( buildTask ); return; } log.info( "Building project " + project.getAlias() + " in " + base.getPath() ); Build build = new Build( project, project.getRevision() ); build.setConfigName( queued.getConfigName() ); build.setStatus( Build.BUILD_RUNNING ); long buildId = application.addBuild( build ); File output = new File( projectDir, buildId + ".txt" ); buildHandler.runBuild( project, config, application.getConfiguration(), base, output, build ); Event event; if ( build.getStatus() != Build.BUILD_SUCCEEDED ) { event = new BuildFailedEvent( build ); } else { event = new BuildSucceededEvent( build ); } application.addEvent( event, queued.getNotify() ); storage.closeSession(); try { FileUtil.delete( base ); } catch ( IOException e ) { log.error( "Error cleaning up finished build", e ); } } finally { Manager.getInstance().removeTask( buildTask ); } } public void projectAdded( Project project ) { } public void projectModified( Project project ) { } public void projectFileModified( Project project, String path, File file ) { queueProject( project ); } public void projectRemoved(Project project) { } public void setApplication( CIApplication application ) { this.application = application; // clear out un-finished builds caused by forced shutdowns... List<Build> runningBuilds = application.getRunningBuilds(); if ( runningBuilds != null && runningBuilds.size() > 0 ) { for ( Build running : runningBuilds ) { running.setStatus( Build.BUILD_CANCELLED ); running.setEndTime( running.getStartTime() ); application.saveBuild( running ); } } } public static boolean isBuildConfigDisabled( PropertyTree config ) { if ( config == null ) { return (Boolean) CIApplication.CONFIGURATION_BUILD_DISABLED.getDefault(); } return config.getProperty( CIApplication.CONFIGURATION_BUILD_DISABLED.getKey(), String.valueOf( CIApplication.CONFIGURATION_BUILD_DISABLED.getDefault() ) ).equals( "true" ); } }