/**
  * 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<string, object> Map of keys to steps.
  * @return map<string, object> Map of keys to targets.
  */
 private function generateBuildTargetMap(HarbormasterBuildable $buildable, array $step_map)
 {
     $viewer = $this->getViewer();
     $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());
             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;
 }