4 * @task autotarget Automatic Targets
6 abstract class HarbormasterBuildStepImplementation
extends Phobject
{
9 private $currentWorkerTaskID;
11 public function setCurrentWorkerTaskID($id) {
12 $this->currentWorkerTaskID
= $id;
16 public function getCurrentWorkerTaskID() {
17 return $this->currentWorkerTaskID
;
20 public static function getImplementations() {
21 return id(new PhutilClassMapQuery())
22 ->setAncestorClass(__CLASS__
)
26 public static function getImplementation($class) {
27 $base = idx(self
::getImplementations(), $class);
36 public static function requireImplementation($class) {
38 throw new Exception(pht('No implementation is specified!'));
41 $implementation = self
::getImplementation($class);
42 if (!$implementation) {
43 throw new Exception(pht('No such implementation "%s" exists!', $class));
46 return $implementation;
50 * The name of the implementation.
52 abstract public function getName();
54 public function getBuildStepGroupKey() {
55 return HarbormasterOtherBuildStepGroup
::GROUPKEY
;
59 * The generic description of the implementation.
61 public function getGenericDescription() {
66 * The description of the implementation, based on the current settings.
68 public function getDescription() {
69 return $this->getGenericDescription();
72 public function getEditInstructions() {
77 * Run the build target against the specified build.
79 abstract public function execute(
80 HarbormasterBuild
$build,
81 HarbormasterBuildTarget
$build_target);
84 * Gets the settings for this build step.
86 public function getSettings() {
87 return $this->settings
;
90 public function getSetting($key, $default = null) {
91 return idx($this->settings
, $key, $default);
95 * Loads the settings for this build step implementation from a build
98 final public function loadSettings($build_object) {
99 $this->settings
= $build_object->getDetails();
104 * Return the name of artifacts produced by this command.
106 * Future steps will calculate all available artifact mappings
107 * before them and filter on the type.
109 * @return array The mappings of artifact names to their types.
111 public function getArtifactInputs() {
115 public function getArtifactOutputs() {
119 public function getDependencies(HarbormasterBuildStep
$build_step) {
120 $dependencies = $build_step->getDetail('dependsOn', array());
122 $inputs = $build_step->getStepImplementation()->getArtifactInputs();
123 $inputs = ipull($inputs, null, 'key');
125 $artifacts = $this->getAvailableArtifacts(
126 $build_step->getBuildPlan(),
130 foreach ($artifacts as $key => $type) {
131 if (!array_key_exists($key, $inputs)) {
132 unset($artifacts[$key]);
136 $artifact_steps = ipull($artifacts, 'step');
137 $artifact_steps = mpull($artifact_steps, 'getPHID');
139 $dependencies = array_merge($dependencies, $artifact_steps);
141 return $dependencies;
145 * Returns a list of all artifacts made available in the build plan.
147 public static function getAvailableArtifacts(
148 HarbormasterBuildPlan
$build_plan,
152 $steps = id(new HarbormasterBuildStepQuery())
153 ->setViewer(PhabricatorUser
::getOmnipotentUser())
154 ->withBuildPlanPHIDs(array($build_plan->getPHID()))
157 $artifacts = array();
159 $artifact_arrays = array();
160 foreach ($steps as $step) {
161 if ($current_build_step !== null &&
162 $step->getPHID() === $current_build_step->getPHID()) {
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) {
174 $artifacts[$name] = array('type' => $type, 'step' => $step);
182 * Convert a user-provided string with variables in it, like:
186 * ...into a string with variables merged into it safely:
188 * ls 'dir with spaces'
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.
195 protected function mergeVariables($function, $pattern, array $variables) {
196 $regexp = '@\\$\\{(?P<name>[a-z\\./_-]+)\\}@';
199 preg_match_all($regexp, $pattern, $matches);
202 foreach ($matches['name'] as $name) {
203 if (!array_key_exists($name, $variables)) {
204 throw new Exception(pht("No such variable '%s'!", $name));
206 $argv[] = $variables[$name];
209 $pattern = str_replace('%', '%%', $pattern);
210 $pattern = preg_replace($regexp, '%s', $pattern);
212 return call_user_func($function, $pattern, $argv);
215 public function getFieldSpecifications() {
219 protected function formatSettingForDescription($key, $default = null) {
220 return $this->formatValueForDescription($this->getSetting($key, $default));
223 protected function formatValueForDescription($value) {
224 if (strlen($value)) {
225 return phutil_tag('strong', array(), $value);
227 return phutil_tag('em', array(), pht('(null)'));
231 public function supportsWaitForMessage() {
235 public function shouldWaitForMessage(HarbormasterBuildTarget
$target) {
236 if (!$this->supportsWaitForMessage()) {
240 $wait = $target->getDetail('builtin.wait-for-message');
241 return ($wait == 'wait');
244 protected function shouldAbort(
245 HarbormasterBuild
$build,
246 HarbormasterBuildTarget
$target) {
248 return $build->getBuildGeneration() !== $target->getBuildGeneration();
251 protected function resolveFutures(
252 HarbormasterBuild
$build,
253 HarbormasterBuildTarget
$target,
257 $wait_start = PhabricatorTime
::getNow();
259 $futures = new FutureIterator($futures);
260 foreach ($futures->setUpdateInterval(5) as $key => $future) {
261 if ($future !== null) {
266 if ($this->shouldAbort($build, $target)) {
267 throw new HarbormasterBuildAbortedException();
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
275 $now = PhabricatorTime
::getNow();
276 $elapsed = ($now - $wait_start);
279 if ($elapsed >= $idle_limit) {
280 LiskDAO
::closeIdleConnections();
288 protected function logHTTPResponse(
289 HarbormasterBuild
$build,
290 HarbormasterBuildTarget
$build_target,
291 BaseHTTPFuture
$future,
294 list($status, $body, $headers) = $future->resolve();
296 $header_lines = array();
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}";
303 foreach ($headers as $header) {
304 list($head, $tail) = $header;
305 $header_lines[] = "{$head}: {$tail}";
307 $header_lines = implode("\n", $header_lines);
310 ->newLog($label, 'http.head')
311 ->append($header_lines);
314 ->newLog($label, 'http.body')
318 protected function logSilencedCall(
319 HarbormasterBuild
$build,
320 HarbormasterBuildTarget
$build_target,
324 ->newLog($label, 'silenced')
327 'Declining to make service call because `phabricator.silent` is '.
328 'enabled in configuration.'));
331 public function willStartBuild(
332 PhabricatorUser
$viewer,
333 HarbormasterBuildable
$buildable,
334 HarbormasterBuild
$build,
335 HarbormasterBuildPlan
$plan,
336 HarbormasterBuildStep
$step) {
341 /* -( Automatic Targets )-------------------------------------------------- */
344 public function getBuildStepAutotargetStepKey() {
348 public function getBuildStepAutotargetPlanKey() {
349 throw new PhutilMethodNotImplementedException();
352 public function shouldRequireAutotargeting() {