3 final class HarbormasterTargetEngine
extends Phobject
{
7 private $autoTargetKeys;
9 public function setViewer(PhabricatorUser
$viewer) {
10 $this->viewer
= $viewer;
14 public function getViewer() {
18 public function setObject(HarbormasterBuildableInterface
$object) {
19 $this->object = $object;
23 public function getObject() {
27 public function setAutoTargetKeys(array $auto_keys) {
28 $this->autoTargetKeys
= $auto_keys;
32 public function getAutoTargetKeys() {
33 return $this->autoTargetKeys
;
36 public function buildTargets() {
37 $object = $this->getObject();
38 $viewer = $this->getViewer();
40 $step_map = $this->generateBuildStepMap($this->getAutoTargetKeys());
42 $buildable = HarbormasterBuildable
::createOrLoadExisting(
44 $object->getHarbormasterBuildablePHID(),
45 $object->getHarbormasterContainerPHID());
47 $target_map = $this->generateBuildTargetMap($buildable, $step_map);
54 * Get a map of the @{class:HarbormasterBuildStep} objects for a list of
57 * This method creates the steps if they do not yet exist.
59 * @param list<string> Autotarget keys, like `"core.arc.lint"`.
60 * @return map<string, object> Map of keys to step objects.
62 private function generateBuildStepMap(array $autotargets) {
63 $viewer = $this->getViewer();
65 $autosteps = $this->getAutosteps($autotargets);
66 $autosteps = mgroup($autosteps, 'getBuildStepAutotargetPlanKey');
68 $plans = id(new HarbormasterBuildPlanQuery())
70 ->withPlanAutoKeys(array_keys($autosteps))
71 ->needBuildSteps(true)
73 $plans = mpull($plans, null, 'getPlanAutoKey');
75 // NOTE: When creating the plan and steps, we save the autokeys as the
76 // names. These won't actually be shown in the UI, but make the data more
77 // consistent for secondary consumers like typeaheads.
80 foreach ($autosteps as $plan_key => $steps) {
81 $plan = idx($plans, $plan_key);
83 $plan = HarbormasterBuildPlan
::initializeNewBuildPlan($viewer)
85 ->setPlanAutoKey($plan_key);
88 $current = $plan->getBuildSteps();
89 $current = mpull($current, null, 'getStepAutoKey');
92 foreach ($steps as $step_key => $step) {
93 if (isset($current[$step_key])) {
94 $step_map[$step_key] = $current[$step_key];
98 $new_step = HarbormasterBuildStep
::initializeNewStep($viewer)
100 ->setClassName(get_class($step))
101 ->setStepAutoKey($step_key);
103 $new_steps[$step_key] = $new_step;
107 $plan->openTransaction();
108 if (!$plan->getPHID()) {
111 foreach ($new_steps as $step_key => $step) {
112 $step->setBuildPlanPHID($plan->getPHID());
115 $step->attachBuildPlan($plan);
116 $step_map[$step_key] = $step;
118 $plan->saveTransaction();
127 * Get all of the @{class:HarbormasterBuildStepImplementation} objects for
128 * a list of autotarget keys.
130 * @param list<string> Autotarget keys, like `"core.arc.lint"`.
131 * @return map<string, object> Map of keys to implementations.
133 private function getAutosteps(array $autotargets) {
134 $all_steps = HarbormasterBuildStepImplementation
::getImplementations();
135 $all_steps = mpull($all_steps, null, 'getBuildStepAutotargetStepKey');
137 // Make sure all the targets really exist.
138 foreach ($autotargets as $autotarget) {
139 if (empty($all_steps[$autotarget])) {
142 'No build step provides autotarget "%s"!',
147 return array_select_keys($all_steps, $autotargets);
152 * Get a list of @{class:HarbormasterBuildTarget} objects for a list of
155 * If some targets or builds do not exist, they are created.
157 * @param HarbormasterBuildable A buildable.
158 * @param map<string, object> Map of keys to steps.
159 * @return map<string, object> Map of keys to targets.
161 private function generateBuildTargetMap(
162 HarbormasterBuildable
$buildable,
165 $viewer = $this->getViewer();
166 $initiator_phid = null;
167 if (!$viewer->isOmnipotent()) {
168 $initiator_phid = $viewer->getPHID();
170 $plan_map = mgroup($step_map, 'getBuildPlanPHID');
172 $builds = id(new HarbormasterBuildQuery())
174 ->withBuildablePHIDs(array($buildable->getPHID()))
175 ->withBuildPlanPHIDs(array_keys($plan_map))
176 ->needBuildTargets(true)
179 $autobuilds = array();
180 foreach ($builds as $build) {
181 $plan_key = $build->getBuildPlan()->getPlanAutoKey();
182 $autobuilds[$plan_key] = $build;
185 $new_builds = array();
186 foreach ($plan_map as $plan_phid => $steps) {
187 $plan = head($steps)->getBuildPlan();
188 $plan_key = $plan->getPlanAutoKey();
190 $build = idx($autobuilds, $plan_key);
192 // We already have a build for this set of targets, so we don't need
193 // to do any work. (It's possible the build is an older build that
194 // doesn't have all of the right targets if new autotargets were
195 // recently introduced, but we don't currently try to construct them.)
199 // NOTE: Normally, `applyPlan()` does not actually generate targets.
200 // We need to apply the plan in-process to perform target generation.
201 // This is fine as long as autotargets are empty containers that don't
202 // do any work, which they always should be.
204 PhabricatorWorker
::setRunAllTasksInProcess(true);
207 // NOTE: We might race another process here to create the same build
208 // with the same `planAutoKey`. The database will prevent this and
209 // using autotargets only currently makes sense if you just created the
210 // resource and "own" it, so we don't try to handle this, but may need
211 // to be more careful here if use of autotargets expands.
213 $build = $buildable->applyPlan($plan, array(), $initiator_phid);
214 PhabricatorWorker
::setRunAllTasksInProcess(false);
215 } catch (Exception
$ex) {
216 PhabricatorWorker
::setRunAllTasksInProcess(false);
220 $new_builds[] = $build;
224 $all_targets = id(new HarbormasterBuildTargetQuery())
226 ->withBuildPHIDs(mpull($new_builds, 'getPHID'))
229 $all_targets = array();
232 foreach ($builds as $build) {
233 foreach ($build->getBuildTargets() as $target) {
234 $all_targets[] = $target;
238 $target_map = array();
239 foreach ($all_targets as $target) {
240 $target_key = $target
241 ->getImplementation()
242 ->getBuildStepAutotargetStepKey();
246 $target_map[$target_key] = $target;
249 $target_map = array_select_keys($target_map, array_keys($step_map));