From f562de55c73135a7bdacb59974dd196c5c7abeeb Mon Sep 17 00:00:00 2001
From: Dan Cryer
Date: Mon, 1 Dec 2014 17:04:03 +0000
Subject: [PATCH] Allow PHPCI to mark builds as failed if the builder crashed,
after a user-defined timeout. Limit to one build per project in RunCommand.
Fixes #623
---
PHPCI/Builder.php | 10 +---
PHPCI/Command/DaemoniseCommand.php | 2 +-
PHPCI/Command/RunCommand.php | 62 +++++++++++++++++++++++--
PHPCI/Controller/SettingsController.php | 53 +++++++++++++++++++++
PHPCI/View/Settings/index.phtml | 7 +++
5 files changed, 120 insertions(+), 14 deletions(-)
diff --git a/PHPCI/Builder.php b/PHPCI/Builder.php
index 6246cc2f..69e23ff1 100644
--- a/PHPCI/Builder.php
+++ b/PHPCI/Builder.php
@@ -36,11 +36,6 @@ class Builder implements LoggerAwareInterface
*/
public $ignore = array();
- /**
- * @var string
- */
- protected $ciDir;
-
/**
* @var string
*/
@@ -287,10 +282,7 @@ class Builder implements LoggerAwareInterface
*/
protected function setupBuild()
{
- $buildId = 'project' . $this->build->getProject()->getId()
- . '-build' . $this->build->getId();
- $this->ciDir = dirname(dirname(__FILE__) . '/../') . '/';
- $this->buildPath = $this->ciDir . 'build/' . $buildId . '/';
+ $this->buildPath = PHPCI_DIR . 'PHPCI/build/' . $this->build->getId() . '/';
$this->build->currentBuildPath = $this->buildPath;
$this->interpolator->setupInterpolationVars(
diff --git a/PHPCI/Command/DaemoniseCommand.php b/PHPCI/Command/DaemoniseCommand.php
index 2850ff5f..d2f1f236 100644
--- a/PHPCI/Command/DaemoniseCommand.php
+++ b/PHPCI/Command/DaemoniseCommand.php
@@ -75,7 +75,7 @@ class DaemoniseCommand extends Command
$this->run = true;
$this->sleep = 0;
$runner = new RunCommand($this->logger);
- $runner->setBaxBuilds(1);
+ $runner->setMaxBuilds(1);
$emptyInput = new ArgvInput(array());
diff --git a/PHPCI/Command/RunCommand.php b/PHPCI/Command/RunCommand.php
index 6f7ea0d1..8149d362 100644
--- a/PHPCI/Command/RunCommand.php
+++ b/PHPCI/Command/RunCommand.php
@@ -9,6 +9,7 @@
namespace PHPCI\Command;
+use b8\Config;
use Monolog\Logger;
use PHPCI\Logging\BuildDBLogHandler;
use PHPCI\Logging\LoggedBuildContextTidier;
@@ -73,14 +74,15 @@ class RunCommand extends Command
// For verbose mode we want to output all informational and above
// messages to the symphony output interface.
- if ($input->hasOption('verbose')) {
+ if ($input->getOption('verbose')) {
$this->logger->pushHandler(
new OutputLogHandler($this->output, Logger::INFO)
);
}
- $this->logger->pushProcessor(new LoggedBuildContextTidier());
+ $running = $this->validateRunningBuilds();
+ $this->logger->pushProcessor(new LoggedBuildContextTidier());
$this->logger->addInfo("Finding builds to process");
$store = Factory::getStore('Build');
$result = $store->getByStatus(0, $this->maxBuilds);
@@ -89,10 +91,17 @@ class RunCommand extends Command
$builds = 0;
foreach ($result['items'] as $build) {
- $builds++;
$build = BuildFactory::getBuild($build);
+ // Skip build (for now) if there's already a build running in that project:
+ if (in_array($build->getProjectId(), $running)) {
+ $this->logger->addInfo('Skipping Build #'.$build->getId() . ' - Project build already in progress.');
+ continue;
+ }
+
+ $builds++;
+
try {
// Logging relevant to this build should be stored
// against the build itself.
@@ -118,8 +127,53 @@ class RunCommand extends Command
return $builds;
}
- public function setBaxBuilds($numBuilds)
+ public function setMaxBuilds($numBuilds)
{
$this->maxBuilds = (int)$numBuilds;
}
+
+ protected function validateRunningBuilds()
+ {
+ /** @var \PHPCI\Store\BuildStore $store */
+ $store = Factory::getStore('Build');
+ $running = $store->getByStatus(1);
+ $rtn = array();
+
+ $timeout = Config::getInstance()->get('phpci.build.failed_after', 1800);
+
+ foreach ($running['items'] as $build) {
+ /** @var \PHPCI\Model\Build $build */
+ $build = BuildFactory::getBuild($build);
+
+ $now = time();
+ $start = $build->getStarted()->getTimestamp();
+
+ if (($now - $start) > $timeout) {
+ $this->logger->addInfo('Build #'.$build->getId().' marked as failed due to timeout.');
+ $build->setStatus(Build::STATUS_FAILED);
+ $store->save($build);
+ $this->removeBuildDirectory($build);
+ continue;
+ }
+
+ $rtn[$build->getProjectId()] = true;
+ }
+
+ return $rtn;
+ }
+
+ protected function removeBuildDirectory($build)
+ {
+ $buildPath = PHPCI_DIR . 'PHPCI/build/' . $build->getId() . '/';
+
+ if (is_dir($buildPath)) {
+ $cmd = 'rm -Rf "%s"';
+
+ if (IS_WIN) {
+ $cmd = 'rmdir /S /Q "%s"';
+ }
+
+ shell_exec($cmd);
+ }
+ }
}
diff --git a/PHPCI/Controller/SettingsController.php b/PHPCI/Controller/SettingsController.php
index 3589cd5e..bc5558ba 100644
--- a/PHPCI/Controller/SettingsController.php
+++ b/PHPCI/Controller/SettingsController.php
@@ -45,8 +45,14 @@ class SettingsController extends Controller
$emailSettings = $this->settings['phpci']['email_settings'];
}
+ $buildSettings = array();
+ if (isset($this->settings['phpci']['build'])) {
+ $buildSettings = $this->settings['phpci']['build'];
+ }
+
$this->view->github = $this->getGithubForm();
$this->view->emailSettings = $this->getEmailForm($emailSettings);
+ $this->view->buildSettings = $this->getBuildForm($buildSettings);
$this->view->isWriteable = $this->canWriteConfig();
if (!empty($this->settings['phpci']['github']['token'])) {
@@ -87,6 +93,21 @@ class SettingsController extends Controller
die;
}
+ public function build()
+ {
+ $this->settings['phpci']['build'] = $this->getParams();
+
+ $error = $this->storeSettings();
+
+ if ($error) {
+ header('Location: ' . PHPCI_URL . 'settings?saved=2');
+ } else {
+ header('Location: ' . PHPCI_URL . 'settings?saved=1');
+ }
+
+ die;
+ }
+
/**
* Github redirects users back to this URL when t
*/
@@ -249,4 +270,36 @@ class SettingsController extends Controller
{
return is_writeable(APPLICATION_PATH . 'PHPCI/config.yml');
}
+
+ protected function getBuildForm($values = array())
+ {
+ $form = new Form();
+ $form->setMethod('POST');
+ $form->setAction(PHPCI_URL . 'settings/build');
+
+ $field = new Form\Element\Select('failed_after');
+ $field->setRequired(false);
+ $field->setLabel('Consider a build failed after');
+ $field->setClass('form-control');
+ $field->setContainerClass('form-group');
+ $field->setOptions([
+ 300 => '5 Minutes',
+ 900 => '15 Minutes',
+ 1800 => '30 Minutes',
+ 3600 => '1 Hour',
+ 10800 => '3 Hours',
+ ]);
+ $field->setValue(1800);
+ $form->addField($field);
+
+
+ $field = new Form\Element\Submit();
+ $field->setValue('Save »');
+ $field->setClass('btn btn-success pull-right');
+ $form->addField($field);
+
+ $form->setValues($values);
+
+ return $form;
+ }
}
diff --git a/PHPCI/View/Settings/index.phtml b/PHPCI/View/Settings/index.phtml
index 9724053a..8637ad2b 100644
--- a/PHPCI/View/Settings/index.phtml
+++ b/PHPCI/View/Settings/index.phtml
@@ -28,6 +28,13 @@
+
+