viewer = $viewer; return $this; } public function getViewer() { return $this->viewer; } public function setObject(HarbormasterBuildableInterface $object) { $this->object = $object; return $this; } public function getObject() { return $this->object; } public function setAutoTargetKeys(array $auto_keys) { $this->autoTargetKeys = $auto_keys; return $this; } public function getAutoTargetKeys() { return $this->autoTargetKeys; } public function buildTargets() { $object = $this->getObject(); $viewer = $this->getViewer(); $step_map = $this->generateBuildStepMap($this->getAutoTargetKeys()); $buildable = HarbormasterBuildable::createOrLoadExisting( $viewer, $object->getHarbormasterBuildablePHID(), $object->getHarbormasterContainerPHID()); $target_map = $this->generateBuildTargetMap($buildable, $step_map); return $target_map; } /** * Get a map of the @{class:HarbormasterBuildStep} objects for a list of * autotarget keys. * * This method creates the steps if they do not yet exist. * * @param list Autotarget keys, like `"core.arc.lint"`. * @return map Map of keys to step objects. */ private function generateBuildStepMap(array $autotargets) { $viewer = $this->getViewer(); $autosteps = $this->getAutosteps($autotargets); $autosteps = mgroup($autosteps, 'getBuildStepAutotargetPlanKey'); $plans = id(new HarbormasterBuildPlanQuery()) ->setViewer($viewer) ->withPlanAutoKeys(array_keys($autosteps)) ->needBuildSteps(true) ->execute(); $plans = mpull($plans, null, 'getPlanAutoKey'); // NOTE: When creating the plan and steps, we save the autokeys as the // names. These won't actually be shown in the UI, but make the data more // consistent for secondary consumers like typeaheads. $step_map = array(); foreach ($autosteps as $plan_key => $steps) { $plan = idx($plans, $plan_key); if (!$plan) { $plan = HarbormasterBuildPlan::initializeNewBuildPlan($viewer) ->setName($plan_key) ->setPlanAutoKey($plan_key); } $current = $plan->getBuildSteps(); $current = mpull($current, null, 'getStepAutoKey'); $new_steps = array(); foreach ($steps as $step_key => $step) { if (isset($current[$step_key])) { $step_map[$step_key] = $current[$step_key]; continue; } $new_step = HarbormasterBuildStep::initializeNewStep($viewer) ->setName($step_key) ->setClassName(get_class($step)) ->setStepAutoKey($step_key); $new_steps[$step_key] = $new_step; } if ($new_steps) { $plan->openTransaction(); if (!$plan->getPHID()) { $plan->save(); } foreach ($new_steps as $step_key => $step) { $step->setBuildPlanPHID($plan->getPHID()); $step->save(); $step->attachBuildPlan($plan); $step_map[$step_key] = $step; } $plan->saveTransaction(); } } return $step_map; } /** * Get all of the @{class:HarbormasterBuildStepImplementation} objects for * a list of autotarget keys. * * @param list Autotarget keys, like `"core.arc.lint"`. * @return map Map of keys to implementations. */ private function getAutosteps(array $autotargets) { $all_steps = HarbormasterBuildStepImplementation::getImplementations(); $all_steps = mpull($all_steps, null, 'getBuildStepAutotargetStepKey'); // Make sure all the targets really exist. foreach ($autotargets as $autotarget) { if (empty($all_steps[$autotarget])) { throw new Exception( pht( 'No build step provides autotarget "%s"!', $autotarget)); } } return array_select_keys($all_steps, $autotargets); } /** * Get a list of @{class:HarbormasterBuildTarget} objects for a list of * autotarget keys. * * If some targets or builds do not exist, they are created. * * @param HarbormasterBuildable A buildable. * @param map Map of keys to steps. * @return map Map of keys to targets. */ private function generateBuildTargetMap( HarbormasterBuildable $buildable, array $step_map) { $viewer = $this->getViewer(); $initiator_phid = null; if (!$viewer->isOmnipotent()) { $initiator_phid = $viewer->getPHID(); } $plan_map = mgroup($step_map, 'getBuildPlanPHID'); $builds = id(new HarbormasterBuildQuery()) ->setViewer($viewer) ->withBuildablePHIDs(array($buildable->getPHID())) ->withBuildPlanPHIDs(array_keys($plan_map)) ->needBuildTargets(true) ->execute(); $autobuilds = array(); foreach ($builds as $build) { $plan_key = $build->getBuildPlan()->getPlanAutoKey(); $autobuilds[$plan_key] = $build; } $new_builds = array(); foreach ($plan_map as $plan_phid => $steps) { $plan = head($steps)->getBuildPlan(); $plan_key = $plan->getPlanAutoKey(); $build = idx($autobuilds, $plan_key); if ($build) { // We already have a build for this set of targets, so we don't need // to do any work. (It's possible the build is an older build that // doesn't have all of the right targets if new autotargets were // recently introduced, but we don't currently try to construct them.) continue; } // NOTE: Normally, `applyPlan()` does not actually generate targets. // We need to apply the plan in-process to perform target generation. // This is fine as long as autotargets are empty containers that don't // do any work, which they always should be. PhabricatorWorker::setRunAllTasksInProcess(true); try { // NOTE: We might race another process here to create the same build // with the same `planAutoKey`. The database will prevent this and // using autotargets only currently makes sense if you just created the // resource and "own" it, so we don't try to handle this, but may need // to be more careful here if use of autotargets expands. $build = $buildable->applyPlan($plan, array(), $initiator_phid); PhabricatorWorker::setRunAllTasksInProcess(false); } catch (Exception $ex) { PhabricatorWorker::setRunAllTasksInProcess(false); throw $ex; } $new_builds[] = $build; } if ($new_builds) { $all_targets = id(new HarbormasterBuildTargetQuery()) ->setViewer($viewer) ->withBuildPHIDs(mpull($new_builds, 'getPHID')) ->execute(); } else { $all_targets = array(); } foreach ($builds as $build) { foreach ($build->getBuildTargets() as $target) { $all_targets[] = $target; } } $target_map = array(); foreach ($all_targets as $target) { $target_key = $target ->getImplementation() ->getBuildStepAutotargetStepKey(); if (!$target_key) { continue; } $target_map[$target_key] = $target; } $target_map = array_select_keys($target_map, array_keys($step_map)); return $target_map; } }