diff --git a/src/applications/harbormaster/step/HarbormasterWaitForPreviousBuildStepImplementation.php b/src/applications/harbormaster/step/HarbormasterWaitForPreviousBuildStepImplementation.php index 7f63eda072..eadf4c94f5 100644 --- a/src/applications/harbormaster/step/HarbormasterWaitForPreviousBuildStepImplementation.php +++ b/src/applications/harbormaster/step/HarbormasterWaitForPreviousBuildStepImplementation.php @@ -1,115 +1,98 @@ getBuildable(); $object = $buildable->getBuildableObject(); if (!($object instanceof PhabricatorRepositoryCommit)) { return; } // Block until all previous builds of the same build plan have // finished. $plan = $build->getBuildPlan(); - - $existing_logs = id(new HarbormasterBuildLogQuery()) - ->setViewer(PhabricatorUser::getOmnipotentUser()) - ->withBuildTargetPHIDs(array($build_target->getPHID())) - ->execute(); - - if ($existing_logs) { - $log = head($existing_logs); - } else { - $log = $build->createLog($build_target, 'waiting', 'blockers'); - } - $blockers = $this->getBlockers($object, $plan, $build); - if ($blockers) { - $log->start(); - $log->append(pht("Blocked by: %s\n", implode(',', $blockers))); - $log->finalize(); - } if ($blockers) { throw new PhabricatorWorkerYieldException(15); } } private function getBlockers( PhabricatorRepositoryCommit $commit, HarbormasterBuildPlan $plan, HarbormasterBuild $source) { $call = new ConduitCall( 'diffusion.commitparentsquery', array( 'commit' => $commit->getCommitIdentifier(), 'repository' => $commit->getRepository()->getPHID(), )); $call->setUser(PhabricatorUser::getOmnipotentUser()); $parents = $call->execute(); $parents = id(new DiffusionCommitQuery()) ->setViewer(PhabricatorUser::getOmnipotentUser()) ->withRepository($commit->getRepository()) ->withIdentifiers($parents) ->execute(); $blockers = array(); $build_objects = array(); foreach ($parents as $parent) { if (!$parent->isImported()) { $blockers[] = pht('Commit %s', $parent->getCommitIdentifier()); } else { $build_objects[] = $parent->getPHID(); } } if ($build_objects) { $buildables = id(new HarbormasterBuildableQuery()) ->setViewer(PhabricatorUser::getOmnipotentUser()) ->withBuildablePHIDs($build_objects) ->withManualBuildables(false) ->execute(); $buildable_phids = mpull($buildables, 'getPHID'); if ($buildable_phids) { $builds = id(new HarbormasterBuildQuery()) ->setViewer(PhabricatorUser::getOmnipotentUser()) ->withBuildablePHIDs($buildable_phids) ->withBuildPlanPHIDs(array($plan->getPHID())) ->execute(); foreach ($builds as $build) { if (!$build->isComplete()) { $blockers[] = pht('Build %d', $build->getID()); } } } } return $blockers; } } diff --git a/src/applications/harbormaster/storage/build/HarbormasterBuild.php b/src/applications/harbormaster/storage/build/HarbormasterBuild.php index 5134d0efa0..4d9278c7bf 100644 --- a/src/applications/harbormaster/storage/build/HarbormasterBuild.php +++ b/src/applications/harbormaster/storage/build/HarbormasterBuild.php @@ -1,542 +1,525 @@ setBuildStatus(self::STATUS_INACTIVE) ->setBuildGeneration(0); } public function delete() { $this->openTransaction(); $this->deleteUnprocessedCommands(); $result = parent::delete(); $this->saveTransaction(); return $result; } protected function getConfiguration() { return array( self::CONFIG_AUX_PHID => true, self::CONFIG_SERIALIZATION => array( 'buildParameters' => self::SERIALIZATION_JSON, ), self::CONFIG_COLUMN_SCHEMA => array( 'buildStatus' => 'text32', 'buildGeneration' => 'uint32', 'planAutoKey' => 'text32?', 'initiatorPHID' => 'phid?', ), self::CONFIG_KEY_SCHEMA => array( 'key_buildable' => array( 'columns' => array('buildablePHID'), ), 'key_plan' => array( 'columns' => array('buildPlanPHID'), ), 'key_status' => array( 'columns' => array('buildStatus'), ), 'key_planautokey' => array( 'columns' => array('buildablePHID', 'planAutoKey'), 'unique' => true, ), ), ) + parent::getConfiguration(); } public function generatePHID() { return PhabricatorPHID::generateNewPHID( HarbormasterBuildPHIDType::TYPECONST); } public function attachBuildable(HarbormasterBuildable $buildable) { $this->buildable = $buildable; return $this; } public function getBuildable() { return $this->assertAttached($this->buildable); } public function getName() { if ($this->getBuildPlan()) { return $this->getBuildPlan()->getName(); } return pht('Build'); } public function attachBuildPlan( HarbormasterBuildPlan $build_plan = null) { $this->buildPlan = $build_plan; return $this; } public function getBuildPlan() { return $this->assertAttached($this->buildPlan); } public function getBuildTargets() { return $this->assertAttached($this->buildTargets); } public function attachBuildTargets(array $targets) { $this->buildTargets = $targets; return $this; } public function isBuilding() { return $this->getBuildStatus() === self::STATUS_PENDING || $this->getBuildStatus() === self::STATUS_BUILDING; } public function isAutobuild() { return ($this->getPlanAutoKey() !== null); } - public function createLog( - HarbormasterBuildTarget $build_target, - $log_source, - $log_type) { - - $log_source = id(new PhutilUTF8StringTruncator()) - ->setMaximumBytes(250) - ->truncateString($log_source); - - $log = HarbormasterBuildLog::initializeNewBuildLog($build_target) - ->setLogSource($log_source) - ->setLogType($log_type) - ->save(); - - return $log; - } - public function retrieveVariablesFromBuild() { $results = array( 'buildable.diff' => null, 'buildable.revision' => null, 'buildable.commit' => null, 'repository.callsign' => null, 'repository.phid' => null, 'repository.vcs' => null, 'repository.uri' => null, 'step.timestamp' => null, 'build.id' => null, 'initiator.phid' => null, ); foreach ($this->getBuildParameters() as $key => $value) { $results['build/'.$key] = $value; } $buildable = $this->getBuildable(); $object = $buildable->getBuildableObject(); $object_variables = $object->getBuildVariables(); $results = $object_variables + $results; $results['step.timestamp'] = time(); $results['build.id'] = $this->getID(); $results['initiator.phid'] = $this->getInitiatorPHID(); return $results; } public static function getAvailableBuildVariables() { $objects = id(new PhutilClassMapQuery()) ->setAncestorClass('HarbormasterBuildableInterface') ->execute(); $variables = array(); $variables[] = array( 'step.timestamp' => pht('The current UNIX timestamp.'), 'build.id' => pht('The ID of the current build.'), 'target.phid' => pht('The PHID of the current build target.'), 'initiator.phid' => pht( 'The PHID of the user or Object that initiated the build, '. 'if applicable.'), ); foreach ($objects as $object) { $variables[] = $object->getAvailableBuildVariables(); } $variables = array_mergev($variables); return $variables; } public function isComplete() { switch ($this->getBuildStatus()) { case self::STATUS_PASSED: case self::STATUS_FAILED: case self::STATUS_ABORTED: case self::STATUS_ERROR: case self::STATUS_PAUSED: return true; } return false; } public function isPaused() { return ($this->getBuildStatus() == self::STATUS_PAUSED); } public function getURI() { $id = $this->getID(); return "/harbormaster/build/{$id}/"; } /* -( Build Commands )----------------------------------------------------- */ private function getUnprocessedCommands() { return $this->assertAttached($this->unprocessedCommands); } public function attachUnprocessedCommands(array $commands) { $this->unprocessedCommands = $commands; return $this; } public function canRestartBuild() { if ($this->isAutobuild()) { return false; } return !$this->isRestarting(); } public function canPauseBuild() { if ($this->isAutobuild()) { return false; } return !$this->isComplete() && !$this->isPaused() && !$this->isPausing(); } public function canAbortBuild() { if ($this->isAutobuild()) { return false; } return !$this->isComplete(); } public function canResumeBuild() { if ($this->isAutobuild()) { return false; } return $this->isPaused() && !$this->isResuming(); } public function isPausing() { $is_pausing = false; foreach ($this->getUnprocessedCommands() as $command_object) { $command = $command_object->getCommand(); switch ($command) { case HarbormasterBuildCommand::COMMAND_PAUSE: $is_pausing = true; break; case HarbormasterBuildCommand::COMMAND_RESUME: case HarbormasterBuildCommand::COMMAND_RESTART: $is_pausing = false; break; case HarbormasterBuildCommand::COMMAND_ABORT: $is_pausing = true; break; } } return $is_pausing; } public function isResuming() { $is_resuming = false; foreach ($this->getUnprocessedCommands() as $command_object) { $command = $command_object->getCommand(); switch ($command) { case HarbormasterBuildCommand::COMMAND_RESTART: case HarbormasterBuildCommand::COMMAND_RESUME: $is_resuming = true; break; case HarbormasterBuildCommand::COMMAND_PAUSE: $is_resuming = false; break; case HarbormasterBuildCommand::COMMAND_ABORT: $is_resuming = false; break; } } return $is_resuming; } public function isRestarting() { $is_restarting = false; foreach ($this->getUnprocessedCommands() as $command_object) { $command = $command_object->getCommand(); switch ($command) { case HarbormasterBuildCommand::COMMAND_RESTART: $is_restarting = true; break; } } return $is_restarting; } public function isAborting() { $is_aborting = false; foreach ($this->getUnprocessedCommands() as $command_object) { $command = $command_object->getCommand(); switch ($command) { case HarbormasterBuildCommand::COMMAND_ABORT: $is_aborting = true; break; } } return $is_aborting; } public function deleteUnprocessedCommands() { foreach ($this->getUnprocessedCommands() as $key => $command_object) { $command_object->delete(); unset($this->unprocessedCommands[$key]); } return $this; } public function canIssueCommand(PhabricatorUser $viewer, $command) { try { $this->assertCanIssueCommand($viewer, $command); return true; } catch (Exception $ex) { return false; } } public function assertCanIssueCommand(PhabricatorUser $viewer, $command) { $need_edit = false; switch ($command) { case HarbormasterBuildCommand::COMMAND_RESTART: break; case HarbormasterBuildCommand::COMMAND_PAUSE: case HarbormasterBuildCommand::COMMAND_RESUME: case HarbormasterBuildCommand::COMMAND_ABORT: $need_edit = true; break; default: throw new Exception( pht( 'Invalid Harbormaster build command "%s".', $command)); } // Issuing these commands requires that you be able to edit the build, to // prevent enemy engineers from sabotaging your builds. See T9614. if ($need_edit) { PhabricatorPolicyFilter::requireCapability( $viewer, $this->getBuildPlan(), PhabricatorPolicyCapability::CAN_EDIT); } } /* -( PhabricatorApplicationTransactionInterface )------------------------- */ public function getApplicationTransactionEditor() { return new HarbormasterBuildTransactionEditor(); } public function getApplicationTransactionObject() { return $this; } public function getApplicationTransactionTemplate() { return new HarbormasterBuildTransaction(); } public function willRenderTimeline( PhabricatorApplicationTransactionView $timeline, AphrontRequest $request) { return $timeline; } /* -( PhabricatorPolicyInterface )----------------------------------------- */ public function getCapabilities() { return array( PhabricatorPolicyCapability::CAN_VIEW, PhabricatorPolicyCapability::CAN_EDIT, ); } public function getPolicy($capability) { return $this->getBuildable()->getPolicy($capability); } public function hasAutomaticCapability($capability, PhabricatorUser $viewer) { return $this->getBuildable()->hasAutomaticCapability( $capability, $viewer); } public function describeAutomaticCapability($capability) { return pht('A build inherits policies from its buildable.'); } } diff --git a/src/applications/harbormaster/storage/build/HarbormasterBuildLog.php b/src/applications/harbormaster/storage/build/HarbormasterBuildLog.php index 85e7ae2411..8b7c5c85bb 100644 --- a/src/applications/harbormaster/storage/build/HarbormasterBuildLog.php +++ b/src/applications/harbormaster/storage/build/HarbormasterBuildLog.php @@ -1,220 +1,221 @@ start) { - $this->finalize($this->start); + if ($this->getLive()) { + $this->closeBuildLog(); } } public static function initializeNewBuildLog( HarbormasterBuildTarget $build_target) { return id(new HarbormasterBuildLog()) ->setBuildTargetPHID($build_target->getPHID()) ->setDuration(null) ->setLive(0); } + public function openBuildLog() { + if ($this->getLive()) { + throw new Exception(pht('This build log is already open!')); + } + + return $this + ->setLive(1) + ->save(); + } + + public function closeBuildLog() { + if (!$this->getLive()) { + throw new Exception(pht('This build log is not open!')); + } + + // TODO: Encode the log contents in a gzipped format. + + $this->reload(); + + $start = $this->getDateCreated(); + $now = PhabricatorTime::getNow(); + + return $this + ->setDuration($now - $start) + ->setLive(0) + ->save(); + } + + protected function getConfiguration() { return array( self::CONFIG_AUX_PHID => true, self::CONFIG_COLUMN_SCHEMA => array( // T6203/NULLABILITY // It seems like these should be non-nullable? All logs should have a // source, etc. 'logSource' => 'text255?', 'logType' => 'text255?', 'duration' => 'uint32?', 'live' => 'bool', ), self::CONFIG_KEY_SCHEMA => array( 'key_buildtarget' => array( 'columns' => array('buildTargetPHID'), ), ), ) + parent::getConfiguration(); } public function generatePHID() { return PhabricatorPHID::generateNewPHID( HarbormasterBuildLogPHIDType::TYPECONST); } public function attachBuildTarget(HarbormasterBuildTarget $build_target) { $this->buildTarget = $build_target; return $this; } public function getBuildTarget() { return $this->assertAttached($this->buildTarget); } public function getName() { return pht('Build Log'); } - public function start() { - if ($this->getLive()) { - throw new Exception( - pht('Live logging has already started for this log.')); - } - - $this->setLive(1); - $this->save(); - - $this->start = PhabricatorTime::getNow(); - - return time(); - } - public function append($content) { if (!$this->getLive()) { - throw new Exception( - pht('Start logging before appending data to the log.')); + throw new PhutilInvalidStateException('openBuildLog'); } - if (strlen($content) === 0) { + + $content = (string)$content; + if (!strlen($content)) { return; } // If the length of the content is greater than the chunk size limit, // then we can never fit the content in a single record. We need to // split our content out and call append on it for as many parts as there // are to the content. if (strlen($content) > self::CHUNK_BYTE_LIMIT) { $current = $content; while (strlen($current) > self::CHUNK_BYTE_LIMIT) { $part = substr($current, 0, self::CHUNK_BYTE_LIMIT); $current = substr($current, self::CHUNK_BYTE_LIMIT); $this->append($part); } $this->append($current); return; } // Retrieve the size of last chunk from the DB for this log. If the // chunk is over 500K, then we need to create a new log entry. $conn = $this->establishConnection('w'); $result = queryfx_all( $conn, 'SELECT id, size, encoding '. 'FROM harbormaster_buildlogchunk '. 'WHERE logID = %d '. 'ORDER BY id DESC '. 'LIMIT 1', $this->getID()); if (count($result) === 0 || $result[0]['size'] + strlen($content) > self::CHUNK_BYTE_LIMIT || $result[0]['encoding'] !== self::ENCODING_TEXT) { // We must insert a new chunk because the data we are appending // won't fit into the existing one, or we don't have any existing // chunk data. queryfx( $conn, 'INSERT INTO harbormaster_buildlogchunk '. '(logID, encoding, size, chunk) '. 'VALUES '. '(%d, %s, %d, %B)', $this->getID(), self::ENCODING_TEXT, strlen($content), $content); } else { // We have a resulting record that we can append our content onto. queryfx( $conn, 'UPDATE harbormaster_buildlogchunk '. 'SET chunk = CONCAT(chunk, %B), size = LENGTH(CONCAT(chunk, %B))'. 'WHERE id = %d', $content, $content, $result[0]['id']); } } - public function finalize($start = 0) { - if (!$this->getLive()) { - // TODO: Clean up this API. - return; - } - - // TODO: Encode the log contents in a gzipped format. - $this->reload(); - if ($start > 0) { - $this->setDuration(time() - $start); - } - $this->setLive(0); - $this->save(); - } - public function getLogText() { // TODO: This won't cope very well if we're pulling like a 700MB // log file out of the DB. We should probably implement some sort // of optional limit parameter so that when we're rendering out only // 25 lines in the UI, we don't wastefully read in the whole log. // We have to read our content out of the database and stitch all of // the log data back together. $conn = $this->establishConnection('r'); $result = queryfx_all( $conn, 'SELECT chunk '. 'FROM harbormaster_buildlogchunk '. 'WHERE logID = %d '. 'ORDER BY id ASC', $this->getID()); $content = ''; foreach ($result as $row) { $content .= $row['chunk']; } return $content; } /* -( PhabricatorPolicyInterface )----------------------------------------- */ public function getCapabilities() { return array( PhabricatorPolicyCapability::CAN_VIEW, ); } public function getPolicy($capability) { return $this->getBuildTarget()->getPolicy($capability); } public function hasAutomaticCapability($capability, PhabricatorUser $viewer) { return $this->getBuildTarget()->hasAutomaticCapability( $capability, $viewer); } public function describeAutomaticCapability($capability) { return pht( "Users must be able to see a build target to view it's build log."); } } diff --git a/src/applications/harbormaster/storage/build/HarbormasterBuildTarget.php b/src/applications/harbormaster/storage/build/HarbormasterBuildTarget.php index 27655189e6..8b47bdfc21 100644 --- a/src/applications/harbormaster/storage/build/HarbormasterBuildTarget.php +++ b/src/applications/harbormaster/storage/build/HarbormasterBuildTarget.php @@ -1,358 +1,357 @@ setName($build_step->getName()) ->setBuildPHID($build->getPHID()) ->setBuildStepPHID($build_step->getPHID()) ->setClassName($build_step->getClassName()) ->setDetails($build_step->getDetails()) ->setTargetStatus(self::STATUS_PENDING) ->setVariables($variables) ->setBuildGeneration($build->getBuildGeneration()); } protected function getConfiguration() { return array( self::CONFIG_AUX_PHID => true, self::CONFIG_SERIALIZATION => array( 'details' => self::SERIALIZATION_JSON, 'variables' => self::SERIALIZATION_JSON, ), self::CONFIG_COLUMN_SCHEMA => array( 'className' => 'text255', 'targetStatus' => 'text64', 'dateStarted' => 'epoch?', 'dateCompleted' => 'epoch?', 'buildGeneration' => 'uint32', // T6203/NULLABILITY // This should not be nullable. 'name' => 'text255?', ), self::CONFIG_KEY_SCHEMA => array( 'key_build' => array( 'columns' => array('buildPHID', 'buildStepPHID'), ), ), ) + parent::getConfiguration(); } public function generatePHID() { return PhabricatorPHID::generateNewPHID( HarbormasterBuildTargetPHIDType::TYPECONST); } public function attachBuild(HarbormasterBuild $build) { $this->build = $build; return $this; } public function getBuild() { return $this->assertAttached($this->build); } public function attachBuildStep(HarbormasterBuildStep $step = null) { $this->buildStep = $step; return $this; } public function getBuildStep() { return $this->assertAttached($this->buildStep); } public function getDetail($key, $default = null) { return idx($this->details, $key, $default); } public function setDetail($key, $value) { $this->details[$key] = $value; return $this; } public function getVariables() { return parent::getVariables() + $this->getBuildTargetVariables(); } public function getVariable($key, $default = null) { return idx($this->variables, $key, $default); } public function setVariable($key, $value) { $this->variables[$key] = $value; return $this; } public function getImplementation() { if ($this->implementation === null) { $obj = HarbormasterBuildStepImplementation::requireImplementation( $this->className); $obj->loadSettings($this); $this->implementation = $obj; } return $this->implementation; } public function isAutotarget() { try { return (bool)$this->getImplementation()->getBuildStepAutotargetPlanKey(); } catch (Exception $e) { return false; } } public function getName() { if (strlen($this->name) && !$this->isAutotarget()) { return $this->name; } try { return $this->getImplementation()->getName(); } catch (Exception $e) { return $this->getClassName(); } } private function getBuildTargetVariables() { return array( 'target.phid' => $this->getPHID(), ); } public function createArtifact( PhabricatorUser $actor, $artifact_key, $artifact_type, array $artifact_data) { $impl = HarbormasterArtifact::getArtifactType($artifact_type); if (!$impl) { throw new Exception( pht( 'There is no implementation available for artifacts of type "%s".', $artifact_type)); } $impl->validateArtifactData($artifact_data); $artifact = HarbormasterBuildArtifact::initializeNewBuildArtifact($this) ->setArtifactKey($artifact_key) ->setArtifactType($artifact_type) ->setArtifactData($artifact_data); $impl = $artifact->getArtifactImplementation(); $impl->willCreateArtifact($actor); return $artifact->save(); } public function loadArtifact($artifact_key) { $indexes = array(); $indexes[] = HarbormasterBuildArtifact::getArtifactIndex( $this, $artifact_key); $artifact = id(new HarbormasterBuildArtifactQuery()) ->setViewer(PhabricatorUser::getOmnipotentUser()) ->withArtifactIndexes($indexes) ->executeOne(); if ($artifact === null) { throw new Exception( pht( 'Artifact "%s" not found!', $artifact_key)); } return $artifact; } public function newLog($log_source, $log_type) { $log_source = id(new PhutilUTF8StringTruncator()) ->setMaximumBytes(250) ->truncateString($log_source); $log = HarbormasterBuildLog::initializeNewBuildLog($this) ->setLogSource($log_source) - ->setLogType($log_type); - - $log->start(); + ->setLogType($log_type) + ->openBuildLog(); return $log; } public function getFieldValue($key) { $field_list = PhabricatorCustomField::getObjectFields( $this->getBuildStep(), PhabricatorCustomField::ROLE_VIEW); $fields = $field_list->getFields(); $full_key = "std:harbormaster:core:{$key}"; $field = idx($fields, $full_key); if (!$field) { throw new Exception( pht( 'Unknown build step field "%s"!', $key)); } $field = clone $field; $field->setValueFromStorage($this->getDetail($key)); return $field->getBuildTargetFieldValue(); } /* -( Status )------------------------------------------------------------- */ public function isComplete() { switch ($this->getTargetStatus()) { case self::STATUS_PASSED: case self::STATUS_FAILED: case self::STATUS_ABORTED: return true; } return false; } public function isFailed() { switch ($this->getTargetStatus()) { case self::STATUS_FAILED: case self::STATUS_ABORTED: return true; } return false; } public function isWaiting() { switch ($this->getTargetStatus()) { case self::STATUS_WAITING: return true; } return false; } public function isUnderway() { switch ($this->getTargetStatus()) { case self::STATUS_PENDING: case self::STATUS_BUILDING: return true; } return false; } /* -( PhabricatorPolicyInterface )----------------------------------------- */ public function getCapabilities() { return array( PhabricatorPolicyCapability::CAN_VIEW, ); } public function getPolicy($capability) { return $this->getBuild()->getPolicy($capability); } public function hasAutomaticCapability($capability, PhabricatorUser $viewer) { return $this->getBuild()->hasAutomaticCapability( $capability, $viewer); } public function describeAutomaticCapability($capability) { return pht('Users must be able to see a build to view its build targets.'); } } diff --git a/src/applications/harbormaster/worker/HarbormasterTargetWorker.php b/src/applications/harbormaster/worker/HarbormasterTargetWorker.php index ac3014dc29..fa3a01272b 100644 --- a/src/applications/harbormaster/worker/HarbormasterTargetWorker.php +++ b/src/applications/harbormaster/worker/HarbormasterTargetWorker.php @@ -1,115 +1,112 @@ loadBuildTarget(); } catch (Exception $ex) { return null; } return $viewer->renderHandle($target->getPHID()); } private function loadBuildTarget() { $data = $this->getTaskData(); $id = idx($data, 'targetID'); $target = id(new HarbormasterBuildTargetQuery()) ->withIDs(array($id)) ->setViewer($this->getViewer()) ->needBuildSteps(true) ->executeOne(); if (!$target) { throw new PhabricatorWorkerPermanentFailureException( pht( 'Bad build target ID "%d".', $id)); } return $target; } protected function doWork() { $target = $this->loadBuildTarget(); $build = $target->getBuild(); $viewer = $this->getViewer(); $target->setDateStarted(time()); try { if ($target->getBuildGeneration() !== $build->getBuildGeneration()) { throw new HarbormasterBuildAbortedException(); } $status_pending = HarbormasterBuildTarget::STATUS_PENDING; if ($target->getTargetStatus() == $status_pending) { $target->setTargetStatus(HarbormasterBuildTarget::STATUS_BUILDING); $target->save(); } $implementation = $target->getImplementation(); $implementation->setCurrentWorkerTaskID($this->getCurrentWorkerTaskID()); $implementation->execute($build, $target); $next_status = HarbormasterBuildTarget::STATUS_PASSED; if ($implementation->shouldWaitForMessage($target)) { $next_status = HarbormasterBuildTarget::STATUS_WAITING; } $target->setTargetStatus($next_status); if ($target->isComplete()) { $target->setDateCompleted(PhabricatorTime::getNow()); } $target->save(); } catch (PhabricatorWorkerYieldException $ex) { // If the target wants to yield, let that escape without further // processing. We'll resume after the task retries. throw $ex; } catch (HarbormasterBuildFailureException $ex) { // A build step wants to fail explicitly. $target->setTargetStatus(HarbormasterBuildTarget::STATUS_FAILED); $target->setDateCompleted(PhabricatorTime::getNow()); $target->save(); } catch (HarbormasterBuildAbortedException $ex) { // A build step is aborting because the build has been restarted. $target->setTargetStatus(HarbormasterBuildTarget::STATUS_ABORTED); $target->setDateCompleted(PhabricatorTime::getNow()); $target->save(); } catch (Exception $ex) { - phlog($ex); - try { - $log = $build->createLog($target, 'core', 'exception'); - $start = $log->start(); - $log->append((string)$ex); - $log->finalize($start); + $log = $target->newLog('core', 'exception') + ->append($ex) + ->closeBuildLog(); } catch (Exception $log_ex) { phlog($log_ex); } $target->setTargetStatus(HarbormasterBuildTarget::STATUS_FAILED); $target->setDateCompleted(time()); $target->save(); } id(new HarbormasterBuildEngine()) ->setViewer($viewer) ->setBuild($build) ->continueBuild(); } }