@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
3/**
4 * @task autotarget Automatic Targets
5 */
6abstract class HarbormasterBuildStepImplementation extends Phobject {
7
8 private $settings;
9 private $currentWorkerTaskID;
10
11 public function setCurrentWorkerTaskID($id) {
12 $this->currentWorkerTaskID = $id;
13 return $this;
14 }
15
16 public function getCurrentWorkerTaskID() {
17 return $this->currentWorkerTaskID;
18 }
19
20 public static function getImplementations() {
21 return id(new PhutilClassMapQuery())
22 ->setAncestorClass(self::class)
23 ->execute();
24 }
25
26 public static function getImplementation($class) {
27 $base = idx(self::getImplementations(), $class);
28
29 if ($base) {
30 return (clone $base);
31 }
32
33 return null;
34 }
35
36 public static function requireImplementation($class) {
37 if (!$class) {
38 throw new Exception(pht('No implementation is specified!'));
39 }
40
41 $implementation = self::getImplementation($class);
42 if (!$implementation) {
43 throw new Exception(pht('No such implementation "%s" exists!', $class));
44 }
45
46 return $implementation;
47 }
48
49 /**
50 * The name of the implementation.
51 */
52 abstract public function getName();
53
54 public function getBuildStepGroupKey() {
55 return HarbormasterOtherBuildStepGroup::GROUPKEY;
56 }
57
58 /**
59 * The generic description of the implementation.
60 */
61 public function getGenericDescription() {
62 return '';
63 }
64
65 /**
66 * The description of the implementation, based on the current settings.
67 */
68 public function getDescription() {
69 return $this->getGenericDescription();
70 }
71
72 public function getEditInstructions() {
73 return null;
74 }
75
76 /**
77 * Run the build target against the specified build.
78 */
79 abstract public function execute(
80 HarbormasterBuild $build,
81 HarbormasterBuildTarget $build_target);
82
83 /**
84 * Gets the settings for this build step.
85 */
86 public function getSettings() {
87 return $this->settings;
88 }
89
90 public function getSetting($key, $default = null) {
91 return idx($this->settings, $key, $default);
92 }
93
94 /**
95 * Loads the settings for this build step implementation from a build
96 * step or target.
97 */
98 final public function loadSettings($build_object) {
99 $this->settings = $build_object->getDetails();
100 return $this;
101 }
102
103 /**
104 * Return the name of artifacts produced by this command.
105 *
106 * Future steps will calculate all available artifact mappings
107 * before them and filter on the type.
108 *
109 * @return array The mappings of artifact names to their types.
110 */
111 public function getArtifactInputs() {
112 return array();
113 }
114
115 public function getArtifactOutputs() {
116 return array();
117 }
118
119 public function getDependencies(HarbormasterBuildStep $build_step) {
120 $dependencies = $build_step->getDetail('dependsOn', array());
121
122 $inputs = $build_step->getStepImplementation()->getArtifactInputs();
123 $inputs = ipull($inputs, null, 'key');
124
125 $artifacts = $this->getAvailableArtifacts(
126 $build_step->getBuildPlan(),
127 $build_step,
128 null);
129
130 foreach ($artifacts as $key => $type) {
131 if (!array_key_exists($key, $inputs)) {
132 unset($artifacts[$key]);
133 }
134 }
135
136 $artifact_steps = ipull($artifacts, 'step');
137 $artifact_steps = mpull($artifact_steps, 'getPHID');
138
139 $dependencies = array_merge($dependencies, $artifact_steps);
140
141 return $dependencies;
142 }
143
144 /**
145 * Returns a list of all artifacts made available in the build plan.
146 */
147 public static function getAvailableArtifacts(
148 HarbormasterBuildPlan $build_plan,
149 $current_build_step,
150 $artifact_type) {
151
152 $steps = id(new HarbormasterBuildStepQuery())
153 ->setViewer(PhabricatorUser::getOmnipotentUser())
154 ->withBuildPlanPHIDs(array($build_plan->getPHID()))
155 ->execute();
156
157 $artifacts = array();
158
159 $artifact_arrays = array();
160 foreach ($steps as $step) {
161 if ($current_build_step !== null &&
162 $step->getPHID() === $current_build_step->getPHID()) {
163
164 continue;
165 }
166
167 $implementation = $step->getStepImplementation();
168 $array = $implementation->getArtifactOutputs();
169 $array = ipull($array, 'type', 'key');
170 foreach ($array as $name => $type) {
171 if ($type !== $artifact_type && $artifact_type !== null) {
172 continue;
173 }
174 $artifacts[$name] = array('type' => $type, 'step' => $step);
175 }
176 }
177
178 return $artifacts;
179 }
180
181 /**
182 * Convert a user-provided string with variables in it, like:
183 *
184 * ls ${dirname}
185 *
186 * ...into a string with variables merged into it safely:
187 *
188 * ls 'dir with spaces'
189 *
190 * @param string $function Name of a `vxsprintf` function, like
191 * @{function:vcsprintf}.
192 * @param string $pattern User-provided pattern string containing
193 * `${variables}`.
194 * @param array $variables List of available replacement variables.
195 * @return string String with variables replaced safely into it.
196 */
197 protected function mergeVariables($function, $pattern, array $variables) {
198 $regexp = '@\\$\\{(?P<name>[a-z\\./_-]+)\\}@';
199
200 $matches = null;
201 preg_match_all($regexp, $pattern, $matches);
202
203 $argv = array();
204 foreach ($matches['name'] as $name) {
205 if (!array_key_exists($name, $variables)) {
206 throw new Exception(pht("No such variable '%s'!", $name));
207 }
208 $argv[] = $variables[$name];
209 }
210
211 $pattern = str_replace('%', '%%', $pattern);
212 $pattern = preg_replace($regexp, '%s', $pattern);
213
214 return call_user_func($function, $pattern, $argv);
215 }
216
217 public function getFieldSpecifications() {
218 return array();
219 }
220
221 protected function formatSettingForDescription($key, $default = null) {
222 return $this->formatValueForDescription($this->getSetting($key, $default));
223 }
224
225 protected function formatValueForDescription($value) {
226 if (strlen($value)) {
227 return phutil_tag('strong', array(), $value);
228 } else {
229 return phutil_tag('em', array(), pht('(null)'));
230 }
231 }
232
233 public function supportsWaitForMessage() {
234 return false;
235 }
236
237 public function shouldWaitForMessage(HarbormasterBuildTarget $target) {
238 if (!$this->supportsWaitForMessage()) {
239 return false;
240 }
241
242 $wait = $target->getDetail('builtin.wait-for-message');
243 return ($wait == 'wait');
244 }
245
246 protected function shouldAbort(
247 HarbormasterBuild $build,
248 HarbormasterBuildTarget $target) {
249
250 return $build->getBuildGeneration() !== $target->getBuildGeneration();
251 }
252
253 protected function resolveFutures(
254 HarbormasterBuild $build,
255 HarbormasterBuildTarget $target,
256 array $futures) {
257
258 $did_close = false;
259 $wait_start = PhabricatorTime::getNow();
260
261 $futures = new FutureIterator($futures);
262 foreach ($futures->setUpdateInterval(5) as $key => $future) {
263 if ($future !== null) {
264 continue;
265 }
266
267 $build->reload();
268 if ($this->shouldAbort($build, $target)) {
269 throw new HarbormasterBuildAbortedException();
270 }
271
272 // See PHI916. If we're waiting on a remote system for a while, clean
273 // up database connections to reduce the cost of having a large number
274 // of processes babysitting an `ssh ... ./run-huge-build.sh` process on
275 // a build host.
276 if (!$did_close) {
277 $now = PhabricatorTime::getNow();
278 $elapsed = ($now - $wait_start);
279 $idle_limit = 5;
280
281 if ($elapsed >= $idle_limit) {
282 LiskDAO::closeIdleConnections();
283 $did_close = true;
284 }
285 }
286 }
287
288 }
289
290 protected function logHTTPResponse(
291 HarbormasterBuild $build,
292 HarbormasterBuildTarget $build_target,
293 BaseHTTPFuture $future,
294 $label) {
295
296 list($status, $body, $headers) = $future->resolve();
297
298 $header_lines = array();
299
300 // TODO: We don't currently preserve the entire "HTTP" response header, but
301 // should. Once we do, reproduce it here faithfully.
302 $status_code = $status->getStatusCode();
303 $header_lines[] = "HTTP {$status_code}";
304
305 foreach ($headers as $header) {
306 list($head, $tail) = $header;
307 $header_lines[] = "{$head}: {$tail}";
308 }
309 $header_lines = implode("\n", $header_lines);
310
311 $build_target
312 ->newLog($label, 'http.head')
313 ->append($header_lines);
314
315 $build_target
316 ->newLog($label, 'http.body')
317 ->append($body);
318 }
319
320 protected function logSilencedCall(
321 HarbormasterBuild $build,
322 HarbormasterBuildTarget $build_target,
323 $label) {
324
325 $build_target
326 ->newLog($label, 'silenced')
327 ->append(
328 pht(
329 'Declining to make service call because `phabricator.silent` is '.
330 'enabled in configuration.'));
331 }
332
333 public function willStartBuild(
334 PhabricatorUser $viewer,
335 HarbormasterBuildable $buildable,
336 HarbormasterBuild $build,
337 HarbormasterBuildPlan $plan,
338 HarbormasterBuildStep $step) {
339 return;
340 }
341
342
343/* -( Automatic Targets )-------------------------------------------------- */
344
345
346 public function getBuildStepAutotargetStepKey() {
347 return null;
348 }
349
350 public function getBuildStepAutotargetPlanKey() {
351 throw new PhutilMethodNotImplementedException();
352 }
353
354 public function shouldRequireAutotargeting() {
355 return false;
356 }
357
358}