@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
fork

Configure Feed

Select the types of activity you want to include in your feed.

at recaptime-dev/main 358 lines 9.2 kB view raw
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}