Path: blob/master/src/applications/harbormaster/engine/HarbormasterTargetEngine.php
12256 views
<?php12final class HarbormasterTargetEngine extends Phobject {34private $viewer;5private $object;6private $autoTargetKeys;78public function setViewer(PhabricatorUser $viewer) {9$this->viewer = $viewer;10return $this;11}1213public function getViewer() {14return $this->viewer;15}1617public function setObject(HarbormasterBuildableInterface $object) {18$this->object = $object;19return $this;20}2122public function getObject() {23return $this->object;24}2526public function setAutoTargetKeys(array $auto_keys) {27$this->autoTargetKeys = $auto_keys;28return $this;29}3031public function getAutoTargetKeys() {32return $this->autoTargetKeys;33}3435public function buildTargets() {36$object = $this->getObject();37$viewer = $this->getViewer();3839$step_map = $this->generateBuildStepMap($this->getAutoTargetKeys());4041$buildable = HarbormasterBuildable::createOrLoadExisting(42$viewer,43$object->getHarbormasterBuildablePHID(),44$object->getHarbormasterContainerPHID());4546$target_map = $this->generateBuildTargetMap($buildable, $step_map);4748return $target_map;49}505152/**53* Get a map of the @{class:HarbormasterBuildStep} objects for a list of54* autotarget keys.55*56* This method creates the steps if they do not yet exist.57*58* @param list<string> Autotarget keys, like `"core.arc.lint"`.59* @return map<string, object> Map of keys to step objects.60*/61private function generateBuildStepMap(array $autotargets) {62$viewer = $this->getViewer();6364$autosteps = $this->getAutosteps($autotargets);65$autosteps = mgroup($autosteps, 'getBuildStepAutotargetPlanKey');6667$plans = id(new HarbormasterBuildPlanQuery())68->setViewer($viewer)69->withPlanAutoKeys(array_keys($autosteps))70->needBuildSteps(true)71->execute();72$plans = mpull($plans, null, 'getPlanAutoKey');7374// NOTE: When creating the plan and steps, we save the autokeys as the75// names. These won't actually be shown in the UI, but make the data more76// consistent for secondary consumers like typeaheads.7778$step_map = array();79foreach ($autosteps as $plan_key => $steps) {80$plan = idx($plans, $plan_key);81if (!$plan) {82$plan = HarbormasterBuildPlan::initializeNewBuildPlan($viewer)83->setName($plan_key)84->setPlanAutoKey($plan_key);85}8687$current = $plan->getBuildSteps();88$current = mpull($current, null, 'getStepAutoKey');89$new_steps = array();9091foreach ($steps as $step_key => $step) {92if (isset($current[$step_key])) {93$step_map[$step_key] = $current[$step_key];94continue;95}9697$new_step = HarbormasterBuildStep::initializeNewStep($viewer)98->setName($step_key)99->setClassName(get_class($step))100->setStepAutoKey($step_key);101102$new_steps[$step_key] = $new_step;103}104105if ($new_steps) {106$plan->openTransaction();107if (!$plan->getPHID()) {108$plan->save();109}110foreach ($new_steps as $step_key => $step) {111$step->setBuildPlanPHID($plan->getPHID());112$step->save();113114$step->attachBuildPlan($plan);115$step_map[$step_key] = $step;116}117$plan->saveTransaction();118}119}120121return $step_map;122}123124125/**126* Get all of the @{class:HarbormasterBuildStepImplementation} objects for127* a list of autotarget keys.128*129* @param list<string> Autotarget keys, like `"core.arc.lint"`.130* @return map<string, object> Map of keys to implementations.131*/132private function getAutosteps(array $autotargets) {133$all_steps = HarbormasterBuildStepImplementation::getImplementations();134$all_steps = mpull($all_steps, null, 'getBuildStepAutotargetStepKey');135136// Make sure all the targets really exist.137foreach ($autotargets as $autotarget) {138if (empty($all_steps[$autotarget])) {139throw new Exception(140pht(141'No build step provides autotarget "%s"!',142$autotarget));143}144}145146return array_select_keys($all_steps, $autotargets);147}148149150/**151* Get a list of @{class:HarbormasterBuildTarget} objects for a list of152* autotarget keys.153*154* If some targets or builds do not exist, they are created.155*156* @param HarbormasterBuildable A buildable.157* @param map<string, object> Map of keys to steps.158* @return map<string, object> Map of keys to targets.159*/160private function generateBuildTargetMap(161HarbormasterBuildable $buildable,162array $step_map) {163164$viewer = $this->getViewer();165$initiator_phid = null;166if (!$viewer->isOmnipotent()) {167$initiator_phid = $viewer->getPHID();168}169$plan_map = mgroup($step_map, 'getBuildPlanPHID');170171$builds = id(new HarbormasterBuildQuery())172->setViewer($viewer)173->withBuildablePHIDs(array($buildable->getPHID()))174->withBuildPlanPHIDs(array_keys($plan_map))175->needBuildTargets(true)176->execute();177178$autobuilds = array();179foreach ($builds as $build) {180$plan_key = $build->getBuildPlan()->getPlanAutoKey();181$autobuilds[$plan_key] = $build;182}183184$new_builds = array();185foreach ($plan_map as $plan_phid => $steps) {186$plan = head($steps)->getBuildPlan();187$plan_key = $plan->getPlanAutoKey();188189$build = idx($autobuilds, $plan_key);190if ($build) {191// We already have a build for this set of targets, so we don't need192// to do any work. (It's possible the build is an older build that193// doesn't have all of the right targets if new autotargets were194// recently introduced, but we don't currently try to construct them.)195continue;196}197198// NOTE: Normally, `applyPlan()` does not actually generate targets.199// We need to apply the plan in-process to perform target generation.200// This is fine as long as autotargets are empty containers that don't201// do any work, which they always should be.202203PhabricatorWorker::setRunAllTasksInProcess(true);204try {205206// NOTE: We might race another process here to create the same build207// with the same `planAutoKey`. The database will prevent this and208// using autotargets only currently makes sense if you just created the209// resource and "own" it, so we don't try to handle this, but may need210// to be more careful here if use of autotargets expands.211212$build = $buildable->applyPlan($plan, array(), $initiator_phid);213PhabricatorWorker::setRunAllTasksInProcess(false);214} catch (Exception $ex) {215PhabricatorWorker::setRunAllTasksInProcess(false);216throw $ex;217}218219$new_builds[] = $build;220}221222if ($new_builds) {223$all_targets = id(new HarbormasterBuildTargetQuery())224->setViewer($viewer)225->withBuildPHIDs(mpull($new_builds, 'getPHID'))226->execute();227} else {228$all_targets = array();229}230231foreach ($builds as $build) {232foreach ($build->getBuildTargets() as $target) {233$all_targets[] = $target;234}235}236237$target_map = array();238foreach ($all_targets as $target) {239$target_key = $target240->getImplementation()241->getBuildStepAutotargetStepKey();242if (!$target_key) {243continue;244}245$target_map[$target_key] = $target;246}247248$target_map = array_select_keys($target_map, array_keys($step_map));249250return $target_map;251}252253254}255256257