php-censor/src/Builder.php

500 lines
13 KiB
PHP
Raw Permalink Normal View History

2013-05-03 17:02:53 +02:00
<?php
2017-01-06 05:14:45 +01:00
2016-07-19 20:28:11 +02:00
namespace PHPCensor;
2016-07-19 20:28:11 +02:00
use PHPCensor\Helper\BuildInterpolator;
use PHPCensor\Helper\MailerFactory;
use PHPCensor\Logging\BuildLogger;
use PHPCensor\Model\Build;
2018-03-04 08:30:34 +01:00
use PHPCensor\Store\Factory;
use PHPCensor\Store\BuildErrorWriter;
use Psr\Log\LoggerAwareInterface;
use Psr\Log\LoggerInterface;
use Psr\Log\LogLevel;
2016-07-19 20:28:11 +02:00
use PHPCensor\Plugin\Util\Factory as PluginFactory;
2013-05-03 17:02:53 +02:00
2013-05-16 03:16:56 +02:00
/**
2017-03-04 16:39:56 +01:00
* @author Dan Cryer <dan@block8.co.uk>
2013-10-26 17:25:34 +02:00
*/
class Builder implements LoggerAwareInterface
2013-05-03 17:02:53 +02:00
{
/**
2013-10-26 17:25:34 +02:00
* @var string
*/
public $buildPath;
/**
2013-10-26 17:25:34 +02:00
* @var string[]
*/
2016-04-20 17:39:48 +02:00
public $ignore = [];
/**
2013-10-26 17:25:34 +02:00
* @var string
*/
protected $directory;
/**
2013-10-26 17:25:34 +02:00
* @var bool
*/
protected $verbose = true;
/**
* @var \PHPCensor\Model\Build
2013-10-26 17:25:34 +02:00
*/
protected $build;
2013-10-26 17:25:34 +02:00
/**
* @var LoggerInterface
*/
protected $logger;
/**
2013-10-26 17:25:34 +02:00
* @var array
*/
protected $config = [];
/**
* @var string
*/
protected $lastOutput;
2013-05-22 20:17:33 +02:00
/**
* @var BuildInterpolator
2013-05-22 20:17:33 +02:00
*/
protected $interpolator;
/**
* @var \PHPCensor\Store\BuildStore
*/
protected $store;
2013-10-10 02:01:06 +02:00
/**
* @var bool
*/
public $quiet = false;
/**
* @var \PHPCensor\Plugin\Util\Executor
*/
protected $pluginExecutor;
/**
2017-01-13 16:35:41 +01:00
* @var Helper\CommandExecutorInterface
*/
protected $commandExecutor;
/**
* @var Logging\BuildLogger
*/
protected $buildLogger;
/**
* @var BuildErrorWriter
*/
private $buildErrorWriter;
/**
2013-10-26 17:25:34 +02:00
* Set up the builder.
2017-01-06 05:14:45 +01:00
*
* @param \PHPCensor\Model\Build $build
* @param LoggerInterface $logger
2013-10-26 17:25:34 +02:00
*/
public function __construct(Build $build, LoggerInterface $logger = null)
{
$this->build = $build;
2018-03-04 08:30:34 +01:00
$this->store = Factory::getStore('Build');
$this->buildLogger = new BuildLogger($logger, $build);
$pluginFactory = $this->buildPluginFactory($build);
2017-01-06 05:14:45 +01:00
$this->pluginExecutor = new Plugin\Util\Executor($pluginFactory, $this->buildLogger);
$executorClass = 'PHPCensor\Helper\CommandExecutor';
$this->commandExecutor = new $executorClass(
$this->buildLogger,
2016-04-21 19:05:32 +02:00
ROOT_DIR,
$this->quiet,
$this->verbose
);
2017-04-03 15:44:35 +02:00
$this->interpolator = new BuildInterpolator();
2017-12-10 05:51:48 +01:00
$this->buildErrorWriter = new BuildErrorWriter($this->build->getProjectId(), $this->build->getId());
}
/**
2016-07-22 08:14:10 +02:00
* Set the config array, as read from .php-censor.yml
2017-11-05 15:48:36 +01:00
*
* @param array $config
*
* @throws \Exception
2013-10-26 17:25:34 +02:00
*/
public function setConfig(array $config)
{
if (is_null($config) || !is_array($config)) {
throw new \Exception('This project does not contain a .php-censor.yml (.phpci.yml|phpci.yml) file, or it is empty.');
}
2017-03-10 15:10:04 +01:00
$this->logDebug('Config: ' . json_encode($config));
$this->config = $config;
}
/**
2016-07-22 08:14:10 +02:00
* Access a variable from the .php-censor.yml file.
2017-11-05 15:48:36 +01:00
*
* @param string $key
*
* @return mixed
2013-10-26 17:25:34 +02:00
*/
public function getConfig($key = null)
{
$value = null;
if (null === $key) {
$value = $this->config;
} elseif (isset($this->config[$key])) {
$value = $this->config[$key];
2013-10-10 02:01:06 +02:00
}
return $value;
}
/**
* Access a variable from the config.yml
2017-11-05 15:48:36 +01:00
*
* @param string $key
*
* @return mixed
*/
public function getSystemConfig($key)
{
2013-07-30 03:55:29 +02:00
return Config::getInstance()->get($key);
}
/**
2017-11-05 15:48:36 +01:00
* @return string The title of the project being built.
*/
2013-10-10 02:12:30 +02:00
public function getBuildProjectTitle()
{
2013-10-10 02:01:06 +02:00
return $this->build->getProject()->getTitle();
}
/**
2013-10-26 17:25:34 +02:00
* Run the active build.
*/
public function execute()
{
// check current status
if ($this->build->getStatus() != Build::STATUS_PENDING) {
throw new BuilderException('Can`t build - status is not pending', BuilderException::FAIL_START);
}
// set status only if current status pending
if (!$this->build->setStatusSync(Build::STATUS_RUNNING)) {
throw new BuilderException('Can`t build - unable change status to running', BuilderException::FAIL_START);
}
// Update the build in the database, ping any external services.
$this->build->setStartDate(new \DateTime());
$this->store->save($this->build);
$this->build->sendStatusPostback();
$success = true;
$previousBuild = $this->build->getProject()->getPreviousBuild($this->build->getBranch());
$previousState = Build::STATUS_PENDING;
2015-10-13 17:22:39 +02:00
if ($previousBuild) {
$previousState = $previousBuild->getStatus();
2015-10-13 17:22:39 +02:00
}
try {
// Set up the build:
$this->setupBuild();
// Run the core plugin stages:
2017-03-16 15:54:25 +01:00
foreach ([Build::STAGE_SETUP, Build::STAGE_TEST, Build::STAGE_DEPLOY] as $stage) {
$success &= $this->pluginExecutor->executePlugins($this->config, $stage);
if (!$success) {
break;
}
}
// Set the status so this can be used by complete, success and failure
// stages.
if ($success) {
$this->build->setStatus(Build::STATUS_SUCCESS);
} else {
$this->build->setStatus(Build::STATUS_FAILED);
}
} catch (\Exception $ex) {
$success = false;
$this->build->setStatus(Build::STATUS_FAILED);
$this->buildLogger->logFailure('Exception: ' . $ex->getMessage(), $ex);
}
try {
if ($success) {
2017-03-16 15:54:25 +01:00
$this->pluginExecutor->executePlugins($this->config, Build::STAGE_SUCCESS);
2015-10-13 17:22:39 +02:00
if ($previousState == Build::STATUS_FAILED) {
2017-03-16 15:54:25 +01:00
$this->pluginExecutor->executePlugins($this->config, Build::STAGE_FIXED);
2015-10-13 17:22:39 +02:00
}
} else {
2017-03-16 15:54:25 +01:00
$this->pluginExecutor->executePlugins($this->config, Build::STAGE_FAILURE);
2015-10-13 17:22:39 +02:00
if ($previousState == Build::STATUS_SUCCESS || $previousState == Build::STATUS_PENDING) {
2017-03-16 15:54:25 +01:00
$this->pluginExecutor->executePlugins($this->config, Build::STAGE_BROKEN);
2015-10-13 17:22:39 +02:00
}
}
} catch (\Exception $ex) {
$this->buildLogger->logFailure('Exception: ' . $ex->getMessage(), $ex);
}
2017-01-06 05:14:45 +01:00
if (Build::STATUS_FAILED === $this->build->getStatus()) {
$this->buildLogger->logFailure("\nBUILD FAILED");
} else {
$this->buildLogger->logSuccess("\nBUILD SUCCESS");
}
try {
// Complete stage plugins are always run
2017-03-16 15:54:25 +01:00
$this->pluginExecutor->executePlugins($this->config, Build::STAGE_COMPLETE);
} catch (\Exception $ex) {
$this->buildLogger->logFailure('Exception: ' . $ex->getMessage());
}
// Update the build in the database, ping any external services, etc.
$this->build->sendStatusPostback();
$this->build->setFinishDate(new \DateTime());
$removeBuilds = (bool)Config::getInstance()->get('php-censor.build.remove_builds', true);
if ($removeBuilds) {
// Clean up:
$this->buildLogger->log("\nRemoving Build.");
$this->build->removeBuildDirectory();
}
$this->buildErrorWriter->flush();
$this->store->save($this->build);
}
/**
2013-10-26 17:25:34 +02:00
* Used by this class, and plugins, to execute shell commands.
2017-11-05 15:48:36 +01:00
*
* @param array ...$params
*
2017-11-05 15:48:36 +01:00
* @return boolean
2013-10-26 17:25:34 +02:00
*/
public function executeCommand(...$params)
{
return $this->commandExecutor->executeCommand($params);
}
/**
* Returns the output from the last command run.
2017-11-05 15:48:36 +01:00
*
* @return string
*/
public function getLastOutput()
{
return $this->commandExecutor->getLastOutput();
}
/**
* Specify whether exec output should be logged.
2017-11-05 15:48:36 +01:00
*
* @param boolean $enableLog
*/
public function logExecOutput($enableLog = true)
{
$this->commandExecutor->logExecOutput = $enableLog;
}
/**
* Find a binary required by a plugin.
*
* @param array|string $binary
* @param bool $quiet Returns null instead of throwing an exception.
* @param string $priorityPath
*
* @return string|false
*
* @throws \Exception when no binary has been found and $quiet is false.
*/
public function findBinary($binary, $quiet = false, $priorityPath = 'local')
{
return $this->commandExecutor->findBinary($binary, $quiet, $priorityPath);
}
2013-05-22 20:17:33 +02:00
/**
* Replace every occurrence of the interpolation vars in the given string
2013-05-22 20:17:33 +02:00
* Example: "This is build %PHPCI_BUILD%" => "This is build 182"
2017-11-05 15:48:36 +01:00
*
2013-05-22 20:17:33 +02:00
* @param string $input
2017-11-05 15:48:36 +01:00
*
2013-05-22 20:17:33 +02:00
* @return string
*/
public function interpolate($input)
{
return $this->interpolator->interpolate($input);
2013-05-22 20:17:33 +02:00
}
2013-10-26 17:25:34 +02:00
/**
2013-10-26 17:25:34 +02:00
* Set up a working copy of the project for building.
2017-11-05 15:48:36 +01:00
*
* @throws \Exception
*
* @return boolean
2013-10-26 17:25:34 +02:00
*/
protected function setupBuild()
{
$this->buildPath = $this->build->getBuildPath();
2013-10-26 17:25:34 +02:00
$this->interpolator->setupInterpolationVars(
$this->build,
$this->buildPath,
2016-07-21 17:20:34 +02:00
APP_URL
);
2013-10-26 17:25:34 +02:00
$this->commandExecutor->setBuildPath($this->buildPath);
$this->build->handleConfigBeforeClone($this);
// Create a working copy of the project:
if (!$this->build->createWorkingCopy($this, $this->buildPath)) {
throw new \Exception('Could not create a working copy.');
}
2016-07-22 08:14:10 +02:00
// Does the project's .php-censor.yml request verbose mode?
if (!isset($this->config['build_settings']['verbose']) || !$this->config['build_settings']['verbose']) {
$this->verbose = false;
}
// Does the project have any paths it wants plugins to ignore?
if (isset($this->config['build_settings']['ignore'])) {
$this->ignore = $this->config['build_settings']['ignore'];
}
$this->buildLogger->logSuccess(sprintf('Working copy created: %s', $this->buildPath));
return true;
}
2013-10-26 17:25:34 +02:00
/**
* Sets a logger instance on the object
*
* @param LoggerInterface $logger
*/
public function setLogger(LoggerInterface $logger)
{
$this->buildLogger->setLogger($logger);
}
/**
* Write to the build log.
2017-11-05 15:48:36 +01:00
*
* @param string $message
* @param string $level
2017-11-05 15:48:36 +01:00
* @param array $context
*/
2016-04-20 17:39:48 +02:00
public function log($message, $level = LogLevel::INFO, $context = [])
{
$this->buildLogger->log($message, $level, $context);
}
/**
* Add a warning-coloured message to the log.
*
* @param string $message
*/
public function logWarning($message)
{
$this->buildLogger->logWarning($message);
}
/**
* Add a success-coloured message to the log.
2017-01-06 05:14:45 +01:00
*
* @param string $message
*/
public function logSuccess($message)
{
$this->buildLogger->logSuccess($message);
2013-10-26 17:25:34 +02:00
}
/**
* Add a failure-coloured message to the log.
2017-01-06 05:14:45 +01:00
*
2017-11-05 15:48:36 +01:00
* @param string $message
* @param \Exception $exception The exception that caused the error.
2013-10-26 17:25:34 +02:00
*/
public function logFailure($message, \Exception $exception = null)
2013-10-26 17:25:34 +02:00
{
$this->buildLogger->logFailure($message, $exception);
2013-10-26 17:25:34 +02:00
}
/**
* Add a debug-coloured message to the log.
2017-01-06 05:14:45 +01:00
*
* @param string $message
*/
public function logDebug($message)
{
$this->buildLogger->logDebug($message);
}
2017-01-06 05:14:45 +01:00
/**
* Returns a configured instance of the plugin factory.
*
* @param Build $build
2017-11-05 15:48:36 +01:00
*
2017-01-06 05:14:45 +01:00
* @return PluginFactory
*/
private function buildPluginFactory(Build $build)
{
$pluginFactory = new PluginFactory();
$self = $this;
$pluginFactory->registerResource(
function () use ($self) {
return $self;
},
null,
'PHPCensor\Builder'
);
$pluginFactory->registerResource(
function () use ($build) {
return $build;
},
null,
'PHPCensor\Model\Build'
);
$logger = $this->logger;
$pluginFactory->registerResource(
function () use ($logger) {
return $logger;
},
null,
'Psr\Log\LoggerInterface'
);
$pluginFactory->registerResource(
function () use ($self) {
$factory = new MailerFactory($self->getSystemConfig('php-censor'));
return $factory->getSwiftMailerFromConfig();
},
null,
'Swift_Mailer'
);
return $pluginFactory;
}
/**
* @return BuildErrorWriter
*/
public function getBuildErrorWriter()
{
return $this->buildErrorWriter;
}
2015-10-13 17:31:17 +02:00
}