@recaptime-dev's working patches + fork for Phorge, a community fork of Phabricator. (Upstream dev and stable branches are at upstream/main and upstream/stable respectively.)
hq.recaptime.dev/wiki/Phorge
phorge
phabricator
1<?php
2
3final class HarbormasterBuildTarget
4 extends HarbormasterDAO
5 implements
6 PhabricatorPolicyInterface,
7 PhabricatorDestructibleInterface,
8 PhabricatorConduitResultInterface {
9
10 protected $name;
11 protected $buildPHID;
12 protected $buildStepPHID;
13 protected $className;
14 protected $details;
15 protected $variables;
16 protected $targetStatus;
17 protected $dateStarted;
18 protected $dateCompleted;
19 protected $buildGeneration;
20
21 const STATUS_PENDING = 'target/pending';
22 const STATUS_BUILDING = 'target/building';
23 const STATUS_WAITING = 'target/waiting';
24 const STATUS_PASSED = 'target/passed';
25 const STATUS_FAILED = 'target/failed';
26 const STATUS_ABORTED = 'target/aborted';
27
28 private $build = self::ATTACHABLE;
29 private $buildStep = self::ATTACHABLE;
30 private $implementation;
31
32 public static function getBuildTargetStatusName($status) {
33 switch ($status) {
34 case self::STATUS_PENDING:
35 return pht('Pending');
36 case self::STATUS_BUILDING:
37 return pht('Building');
38 case self::STATUS_WAITING:
39 return pht('Waiting for Message');
40 case self::STATUS_PASSED:
41 return pht('Passed');
42 case self::STATUS_FAILED:
43 return pht('Failed');
44 case self::STATUS_ABORTED:
45 return pht('Aborted');
46 default:
47 return pht('Unknown');
48 }
49 }
50
51 public static function getBuildTargetStatusIcon($status) {
52 switch ($status) {
53 case self::STATUS_PENDING:
54 return PHUIStatusItemView::ICON_OPEN;
55 case self::STATUS_BUILDING:
56 case self::STATUS_WAITING:
57 return PHUIStatusItemView::ICON_RIGHT;
58 case self::STATUS_PASSED:
59 return PHUIStatusItemView::ICON_ACCEPT;
60 case self::STATUS_FAILED:
61 return PHUIStatusItemView::ICON_REJECT;
62 case self::STATUS_ABORTED:
63 return PHUIStatusItemView::ICON_MINUS;
64 default:
65 return PHUIStatusItemView::ICON_QUESTION;
66 }
67 }
68
69 public static function getBuildTargetStatusColor($status) {
70 switch ($status) {
71 case self::STATUS_PENDING:
72 case self::STATUS_BUILDING:
73 case self::STATUS_WAITING:
74 return 'blue';
75 case self::STATUS_PASSED:
76 return 'green';
77 case self::STATUS_FAILED:
78 case self::STATUS_ABORTED:
79 return 'red';
80 default:
81 return 'bluegrey';
82 }
83 }
84
85 public static function initializeNewBuildTarget(
86 HarbormasterBuild $build,
87 HarbormasterBuildStep $build_step,
88 array $variables) {
89 return id(new HarbormasterBuildTarget())
90 ->setName($build_step->getName())
91 ->setBuildPHID($build->getPHID())
92 ->setBuildStepPHID($build_step->getPHID())
93 ->setClassName($build_step->getClassName())
94 ->setDetails($build_step->getDetails())
95 ->setTargetStatus(self::STATUS_PENDING)
96 ->setVariables($variables)
97 ->setBuildGeneration($build->getBuildGeneration());
98 }
99
100 protected function getConfiguration() {
101 return array(
102 self::CONFIG_AUX_PHID => true,
103 self::CONFIG_SERIALIZATION => array(
104 'details' => self::SERIALIZATION_JSON,
105 'variables' => self::SERIALIZATION_JSON,
106 ),
107 self::CONFIG_COLUMN_SCHEMA => array(
108 'className' => 'text255',
109 'targetStatus' => 'text64',
110 'dateStarted' => 'epoch?',
111 'dateCompleted' => 'epoch?',
112 'buildGeneration' => 'uint32',
113
114 // T6203/NULLABILITY
115 // This should not be nullable.
116 'name' => 'text255?',
117 ),
118 self::CONFIG_KEY_SCHEMA => array(
119 'key_build' => array(
120 'columns' => array('buildPHID', 'buildStepPHID'),
121 ),
122 'key_started' => array(
123 'columns' => array('dateStarted'),
124 ),
125 'key_completed' => array(
126 'columns' => array('dateCompleted'),
127 ),
128 'key_created' => array(
129 'columns' => array('dateCreated'),
130 ),
131 ),
132 ) + parent::getConfiguration();
133 }
134
135 public function generatePHID() {
136 return PhabricatorPHID::generateNewPHID(
137 HarbormasterBuildTargetPHIDType::TYPECONST);
138 }
139
140 public function attachBuild(HarbormasterBuild $build) {
141 $this->build = $build;
142 return $this;
143 }
144
145 public function getBuild() {
146 return $this->assertAttached($this->build);
147 }
148
149 public function attachBuildStep(?HarbormasterBuildStep $step = null) {
150 $this->buildStep = $step;
151 return $this;
152 }
153
154 public function getBuildStep() {
155 return $this->assertAttached($this->buildStep);
156 }
157
158 public function getDetail($key, $default = null) {
159 return idx($this->details, $key, $default);
160 }
161
162 public function setDetail($key, $value) {
163 $this->details[$key] = $value;
164 return $this;
165 }
166
167 public function getVariables() {
168 return parent::getVariables() + $this->getBuildTargetVariables();
169 }
170
171 public function getVariable($key, $default = null) {
172 return idx($this->variables, $key, $default);
173 }
174
175 public function setVariable($key, $value) {
176 $this->variables[$key] = $value;
177 return $this;
178 }
179
180 public function getImplementation() {
181 if ($this->implementation === null) {
182 $obj = HarbormasterBuildStepImplementation::requireImplementation(
183 $this->className);
184 $obj->loadSettings($this);
185 $this->implementation = $obj;
186 }
187
188 return $this->implementation;
189 }
190
191 public function isAutotarget() {
192 try {
193 return (bool)$this->getImplementation()->getBuildStepAutotargetPlanKey();
194 } catch (Exception $e) {
195 return false;
196 }
197 }
198
199 public function getName() {
200 if (strlen($this->name) && !$this->isAutotarget()) {
201 return $this->name;
202 }
203
204 try {
205 return $this->getImplementation()->getName();
206 } catch (Exception $e) {
207 return $this->getClassName();
208 }
209 }
210
211 private function getBuildTargetVariables() {
212 return array(
213 'target.phid' => $this->getPHID(),
214 );
215 }
216
217 public function createArtifact(
218 PhabricatorUser $actor,
219 $artifact_key,
220 $artifact_type,
221 array $artifact_data) {
222
223 $impl = HarbormasterArtifact::getArtifactType($artifact_type);
224 if (!$impl) {
225 throw new Exception(
226 pht(
227 'There is no implementation available for artifacts of type "%s".',
228 $artifact_type));
229 }
230
231 $impl->validateArtifactData($artifact_data);
232
233 $artifact = HarbormasterBuildArtifact::initializeNewBuildArtifact($this)
234 ->setArtifactKey($artifact_key)
235 ->setArtifactType($artifact_type)
236 ->setArtifactData($artifact_data);
237
238 $impl = $artifact->getArtifactImplementation();
239 $impl->willCreateArtifact($actor);
240
241 return $artifact->save();
242 }
243
244 public function loadArtifact($artifact_key) {
245 $indexes = array();
246
247 $indexes[] = HarbormasterBuildArtifact::getArtifactIndex(
248 $this,
249 $artifact_key);
250
251 $artifact = id(new HarbormasterBuildArtifactQuery())
252 ->setViewer(PhabricatorUser::getOmnipotentUser())
253 ->withArtifactIndexes($indexes)
254 ->executeOne();
255 if ($artifact === null) {
256 throw new Exception(
257 pht(
258 'Artifact "%s" not found!',
259 $artifact_key));
260 }
261
262 return $artifact;
263 }
264
265 public function newLog($log_source, $log_type) {
266 $log_source = id(new PhutilUTF8StringTruncator())
267 ->setMaximumBytes(250)
268 ->truncateString($log_source);
269
270 $log = HarbormasterBuildLog::initializeNewBuildLog($this)
271 ->setLogSource($log_source)
272 ->setLogType($log_type)
273 ->openBuildLog();
274
275 return $log;
276 }
277
278 public function getFieldValue($key) {
279 $field_list = PhabricatorCustomField::getObjectFields(
280 $this->getBuildStep(),
281 PhabricatorCustomField::ROLE_VIEW);
282
283 $fields = $field_list->getFields();
284 $full_key = "std:harbormaster:core:{$key}";
285
286 $field = idx($fields, $full_key);
287 if (!$field) {
288 throw new Exception(
289 pht(
290 'Unknown build step field "%s"!',
291 $key));
292 }
293
294 $field = clone $field;
295 $field->setValueFromStorage($this->getDetail($key));
296 return $field->getBuildTargetFieldValue();
297 }
298
299
300
301/* -( Status )------------------------------------------------------------- */
302
303
304 public function isComplete() {
305 switch ($this->getTargetStatus()) {
306 case self::STATUS_PASSED:
307 case self::STATUS_FAILED:
308 case self::STATUS_ABORTED:
309 return true;
310 }
311
312 return false;
313 }
314
315
316 public function isFailed() {
317 switch ($this->getTargetStatus()) {
318 case self::STATUS_FAILED:
319 case self::STATUS_ABORTED:
320 return true;
321 }
322
323 return false;
324 }
325
326
327 public function isWaiting() {
328 switch ($this->getTargetStatus()) {
329 case self::STATUS_WAITING:
330 return true;
331 }
332
333 return false;
334 }
335
336 public function isUnderway() {
337 switch ($this->getTargetStatus()) {
338 case self::STATUS_PENDING:
339 case self::STATUS_BUILDING:
340 return true;
341 }
342
343 return false;
344 }
345
346
347/* -( PhabricatorPolicyInterface )----------------------------------------- */
348
349
350 public function getCapabilities() {
351 return array(
352 PhabricatorPolicyCapability::CAN_VIEW,
353 );
354 }
355
356 public function getPolicy($capability) {
357 return $this->getBuild()->getPolicy($capability);
358 }
359
360 public function hasAutomaticCapability($capability, PhabricatorUser $viewer) {
361 return $this->getBuild()->hasAutomaticCapability(
362 $capability,
363 $viewer);
364 }
365
366 public function describeAutomaticCapability($capability) {
367 return pht('Users must be able to see a build to view its build targets.');
368 }
369
370
371/* -( PhabricatorDestructibleInterface )----------------------------------- */
372
373
374 public function destroyObjectPermanently(
375 PhabricatorDestructionEngine $engine) {
376 $viewer = $engine->getViewer();
377
378 $this->openTransaction();
379
380 $lint_message = new HarbormasterBuildLintMessage();
381 $conn = $lint_message->establishConnection('w');
382 queryfx(
383 $conn,
384 'DELETE FROM %T WHERE buildTargetPHID = %s',
385 $lint_message->getTableName(),
386 $this->getPHID());
387
388 $unit_message = new HarbormasterBuildUnitMessage();
389 $conn = $unit_message->establishConnection('w');
390 queryfx(
391 $conn,
392 'DELETE FROM %T WHERE buildTargetPHID = %s',
393 $unit_message->getTableName(),
394 $this->getPHID());
395
396 $logs = id(new HarbormasterBuildLogQuery())
397 ->setViewer($viewer)
398 ->withBuildTargetPHIDs(array($this->getPHID()))
399 ->execute();
400 foreach ($logs as $log) {
401 $engine->destroyObject($log);
402 }
403
404 $artifacts = id(new HarbormasterBuildArtifactQuery())
405 ->setViewer($viewer)
406 ->withBuildTargetPHIDs(array($this->getPHID()))
407 ->execute();
408 foreach ($artifacts as $artifact) {
409 $engine->destroyObject($artifact);
410 }
411
412 $messages = id(new HarbormasterBuildMessageQuery())
413 ->setViewer($viewer)
414 ->withReceiverPHIDs(array($this->getPHID()))
415 ->execute();
416 foreach ($messages as $message) {
417 $engine->destroyObject($message);
418 }
419
420 $this->delete();
421 $this->saveTransaction();
422 }
423
424
425/* -( PhabricatorConduitResultInterface )---------------------------------- */
426
427
428 public function getFieldSpecificationsForConduit() {
429 return array(
430 id(new PhabricatorConduitSearchFieldSpecification())
431 ->setKey('name')
432 ->setType('string')
433 ->setDescription(pht('The name of the build target.')),
434 id(new PhabricatorConduitSearchFieldSpecification())
435 ->setKey('buildPHID')
436 ->setType('phid')
437 ->setDescription(pht('The build the target is associated with.')),
438 id(new PhabricatorConduitSearchFieldSpecification())
439 ->setKey('buildStepPHID')
440 ->setType('phid')
441 ->setDescription(pht('The build step the target runs.')),
442 id(new PhabricatorConduitSearchFieldSpecification())
443 ->setKey('status')
444 ->setType('map<string, wild>')
445 ->setDescription(pht('Status for the build target.')),
446 id(new PhabricatorConduitSearchFieldSpecification())
447 ->setKey('epochStarted')
448 ->setType('epoch?')
449 ->setDescription(
450 pht(
451 'Epoch timestamp for target start, if the target '.
452 'has started.')),
453 id(new PhabricatorConduitSearchFieldSpecification())
454 ->setKey('epochCompleted')
455 ->setType('epoch?')
456 ->setDescription(
457 pht(
458 'Epoch timestamp for target completion, if the target '.
459 'has completed.')),
460 id(new PhabricatorConduitSearchFieldSpecification())
461 ->setKey('buildGeneration')
462 ->setType('int')
463 ->setDescription(
464 pht(
465 'Build generation this target belongs to. When builds '.
466 'restart, a new generation with new targets is created.')),
467 );
468 }
469
470 public function getFieldValuesForConduit() {
471 $status = $this->getTargetStatus();
472
473 $epoch_started = $this->getDateStarted();
474 if ($epoch_started) {
475 $epoch_started = (int)$epoch_started;
476 } else {
477 $epoch_started = null;
478 }
479
480 $epoch_completed = $this->getDateCompleted();
481 if ($epoch_completed) {
482 $epoch_completed = (int)$epoch_completed;
483 } else {
484 $epoch_completed = null;
485 }
486
487 return array(
488 'name' => $this->getName(),
489 'buildPHID' => $this->getBuildPHID(),
490 'buildStepPHID' => $this->getBuildStepPHID(),
491 'status' => array(
492 'value' => $status,
493 'name' => self::getBuildTargetStatusName($status),
494 ),
495 'epochStarted' => $epoch_started,
496 'epochCompleted' => $epoch_completed,
497 'buildGeneration' => (int)$this->getBuildGeneration(),
498 );
499 }
500
501 public function getConduitSearchAttachments() {
502 return array();
503 }
504
505
506}