Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/phabricator
Path: blob/master/src/applications/harbormaster/step/HarbormasterBuildStepImplementation.php
12256 views
1
<?php
2
3
/**
4
* @task autotarget Automatic Targets
5
*/
6
abstract 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(__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 Name of a `vxsprintf` function, like @{function:vcsprintf}.
191
* @param string User-provided pattern string containing `${variables}`.
192
* @param dict List of available replacement variables.
193
* @return string String with variables replaced safely into it.
194
*/
195
protected function mergeVariables($function, $pattern, array $variables) {
196
$regexp = '@\\$\\{(?P<name>[a-z\\./_-]+)\\}@';
197
198
$matches = null;
199
preg_match_all($regexp, $pattern, $matches);
200
201
$argv = array();
202
foreach ($matches['name'] as $name) {
203
if (!array_key_exists($name, $variables)) {
204
throw new Exception(pht("No such variable '%s'!", $name));
205
}
206
$argv[] = $variables[$name];
207
}
208
209
$pattern = str_replace('%', '%%', $pattern);
210
$pattern = preg_replace($regexp, '%s', $pattern);
211
212
return call_user_func($function, $pattern, $argv);
213
}
214
215
public function getFieldSpecifications() {
216
return array();
217
}
218
219
protected function formatSettingForDescription($key, $default = null) {
220
return $this->formatValueForDescription($this->getSetting($key, $default));
221
}
222
223
protected function formatValueForDescription($value) {
224
if (strlen($value)) {
225
return phutil_tag('strong', array(), $value);
226
} else {
227
return phutil_tag('em', array(), pht('(null)'));
228
}
229
}
230
231
public function supportsWaitForMessage() {
232
return false;
233
}
234
235
public function shouldWaitForMessage(HarbormasterBuildTarget $target) {
236
if (!$this->supportsWaitForMessage()) {
237
return false;
238
}
239
240
$wait = $target->getDetail('builtin.wait-for-message');
241
return ($wait == 'wait');
242
}
243
244
protected function shouldAbort(
245
HarbormasterBuild $build,
246
HarbormasterBuildTarget $target) {
247
248
return $build->getBuildGeneration() !== $target->getBuildGeneration();
249
}
250
251
protected function resolveFutures(
252
HarbormasterBuild $build,
253
HarbormasterBuildTarget $target,
254
array $futures) {
255
256
$did_close = false;
257
$wait_start = PhabricatorTime::getNow();
258
259
$futures = new FutureIterator($futures);
260
foreach ($futures->setUpdateInterval(5) as $key => $future) {
261
if ($future !== null) {
262
continue;
263
}
264
265
$build->reload();
266
if ($this->shouldAbort($build, $target)) {
267
throw new HarbormasterBuildAbortedException();
268
}
269
270
// See PHI916. If we're waiting on a remote system for a while, clean
271
// up database connections to reduce the cost of having a large number
272
// of processes babysitting an `ssh ... ./run-huge-build.sh` process on
273
// a build host.
274
if (!$did_close) {
275
$now = PhabricatorTime::getNow();
276
$elapsed = ($now - $wait_start);
277
$idle_limit = 5;
278
279
if ($elapsed >= $idle_limit) {
280
LiskDAO::closeIdleConnections();
281
$did_close = true;
282
}
283
}
284
}
285
286
}
287
288
protected function logHTTPResponse(
289
HarbormasterBuild $build,
290
HarbormasterBuildTarget $build_target,
291
BaseHTTPFuture $future,
292
$label) {
293
294
list($status, $body, $headers) = $future->resolve();
295
296
$header_lines = array();
297
298
// TODO: We don't currently preserve the entire "HTTP" response header, but
299
// should. Once we do, reproduce it here faithfully.
300
$status_code = $status->getStatusCode();
301
$header_lines[] = "HTTP {$status_code}";
302
303
foreach ($headers as $header) {
304
list($head, $tail) = $header;
305
$header_lines[] = "{$head}: {$tail}";
306
}
307
$header_lines = implode("\n", $header_lines);
308
309
$build_target
310
->newLog($label, 'http.head')
311
->append($header_lines);
312
313
$build_target
314
->newLog($label, 'http.body')
315
->append($body);
316
}
317
318
protected function logSilencedCall(
319
HarbormasterBuild $build,
320
HarbormasterBuildTarget $build_target,
321
$label) {
322
323
$build_target
324
->newLog($label, 'silenced')
325
->append(
326
pht(
327
'Declining to make service call because `phabricator.silent` is '.
328
'enabled in configuration.'));
329
}
330
331
public function willStartBuild(
332
PhabricatorUser $viewer,
333
HarbormasterBuildable $buildable,
334
HarbormasterBuild $build,
335
HarbormasterBuildPlan $plan,
336
HarbormasterBuildStep $step) {
337
return;
338
}
339
340
341
/* -( Automatic Targets )-------------------------------------------------- */
342
343
344
public function getBuildStepAutotargetStepKey() {
345
return null;
346
}
347
348
public function getBuildStepAutotargetPlanKey() {
349
throw new PhutilMethodNotImplementedException();
350
}
351
352
public function shouldRequireAutotargeting() {
353
return false;
354
}
355
356
}
357
358