mirror of
https://we.phorge.it/source/arcanist.git
synced 2024-11-26 00:32:41 +01:00
Implement "Warn When Landing" behavior for Build Plans in Arcanist
Summary: Ref T13258. This makes "arc land" respect the new "Warn When Landing" behavior. This will only work if you have very up-to-date APIs. Just fall back to the older code if the new API calls fail. Test Plan: Ran `arc land` on a revision with builds in various states and with the different "Warn When Landing" behaviors. Saw appropriate warnings. Reviewers: amckinley Reviewed By: amckinley Maniphest Tasks: T13258 Differential Revision: https://secure.phabricator.com/D20236
This commit is contained in:
parent
96fde137a1
commit
f6b8480adc
4 changed files with 226 additions and 1 deletions
|
@ -42,6 +42,7 @@ phutil_register_library_map(array(
|
||||||
'ArcanistBraceFormattingXHPASTLinterRuleTestCase' => 'lint/linter/xhpast/rules/__tests__/ArcanistBraceFormattingXHPASTLinterRuleTestCase.php',
|
'ArcanistBraceFormattingXHPASTLinterRuleTestCase' => 'lint/linter/xhpast/rules/__tests__/ArcanistBraceFormattingXHPASTLinterRuleTestCase.php',
|
||||||
'ArcanistBranchWorkflow' => 'workflow/ArcanistBranchWorkflow.php',
|
'ArcanistBranchWorkflow' => 'workflow/ArcanistBranchWorkflow.php',
|
||||||
'ArcanistBrowseWorkflow' => 'workflow/ArcanistBrowseWorkflow.php',
|
'ArcanistBrowseWorkflow' => 'workflow/ArcanistBrowseWorkflow.php',
|
||||||
|
'ArcanistBuildPlanRef' => 'ref/ArcanistBuildPlanRef.php',
|
||||||
'ArcanistBuildRef' => 'ref/ArcanistBuildRef.php',
|
'ArcanistBuildRef' => 'ref/ArcanistBuildRef.php',
|
||||||
'ArcanistBundle' => 'parser/ArcanistBundle.php',
|
'ArcanistBundle' => 'parser/ArcanistBundle.php',
|
||||||
'ArcanistBundleTestCase' => 'parser/__tests__/ArcanistBundleTestCase.php',
|
'ArcanistBundleTestCase' => 'parser/__tests__/ArcanistBundleTestCase.php',
|
||||||
|
@ -464,6 +465,7 @@ phutil_register_library_map(array(
|
||||||
'ArcanistBraceFormattingXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase',
|
'ArcanistBraceFormattingXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase',
|
||||||
'ArcanistBranchWorkflow' => 'ArcanistFeatureWorkflow',
|
'ArcanistBranchWorkflow' => 'ArcanistFeatureWorkflow',
|
||||||
'ArcanistBrowseWorkflow' => 'ArcanistWorkflow',
|
'ArcanistBrowseWorkflow' => 'ArcanistWorkflow',
|
||||||
|
'ArcanistBuildPlanRef' => 'Phobject',
|
||||||
'ArcanistBuildRef' => 'Phobject',
|
'ArcanistBuildRef' => 'Phobject',
|
||||||
'ArcanistBundle' => 'Phobject',
|
'ArcanistBundle' => 'Phobject',
|
||||||
'ArcanistBundleTestCase' => 'PhutilTestCase',
|
'ArcanistBundleTestCase' => 'PhutilTestCase',
|
||||||
|
|
25
src/ref/ArcanistBuildPlanRef.php
Normal file
25
src/ref/ArcanistBuildPlanRef.php
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
final class ArcanistBuildPlanRef
|
||||||
|
extends Phobject {
|
||||||
|
|
||||||
|
private $parameters;
|
||||||
|
|
||||||
|
public static function newFromConduit(array $data) {
|
||||||
|
$ref = new self();
|
||||||
|
$ref->parameters = $data;
|
||||||
|
return $ref;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getPHID() {
|
||||||
|
return $this->parameters['phid'];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getBehavior($behavior_key, $default = null) {
|
||||||
|
return idxv(
|
||||||
|
$this->parameters,
|
||||||
|
array('fields', 'behaviors', $behavior_key, 'value'),
|
||||||
|
$default);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -69,6 +69,10 @@ final class ArcanistBuildRef
|
||||||
return $this->parameters['id'];
|
return $this->parameters['id'];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getPHID() {
|
||||||
|
return $this->parameters['phid'];
|
||||||
|
}
|
||||||
|
|
||||||
public function getName() {
|
public function getName() {
|
||||||
if (isset($this->parameters['fields']['name'])) {
|
if (isset($this->parameters['fields']['name'])) {
|
||||||
return $this->parameters['fields']['name'];
|
return $this->parameters['fields']['name'];
|
||||||
|
@ -96,11 +100,32 @@ final class ArcanistBuildRef
|
||||||
return pht('Build %d', $this->getID());
|
return pht('Build %d', $this->getID());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getBuildPlanPHID() {
|
||||||
|
return idxv($this->parameters, array('fields', 'buildPlanPHID'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function isComplete() {
|
||||||
|
switch ($this->getStatus()) {
|
||||||
|
case 'passed':
|
||||||
|
case 'failed':
|
||||||
|
case 'aborted':
|
||||||
|
case 'error':
|
||||||
|
case 'deadlocked':
|
||||||
|
return true;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function isPassed() {
|
||||||
|
return ($this->getStatus() === 'passed');
|
||||||
|
}
|
||||||
|
|
||||||
public function getStatusSortVector() {
|
public function getStatusSortVector() {
|
||||||
$status = $this->getStatus();
|
$status = $this->getStatus();
|
||||||
|
|
||||||
// For now, just sort passed builds first.
|
// For now, just sort passed builds first.
|
||||||
if ($this->getStatus() == 'passed') {
|
if ($this->isPassed()) {
|
||||||
$status_class = 1;
|
$status_class = 1;
|
||||||
} else {
|
} else {
|
||||||
$status_class = 2;
|
$status_class = 2;
|
||||||
|
|
|
@ -1369,6 +1369,19 @@ EOTEXT
|
||||||
* before landing if it does.
|
* before landing if it does.
|
||||||
*/
|
*/
|
||||||
private function checkForBuildables($diff_phid) {
|
private function checkForBuildables($diff_phid) {
|
||||||
|
// Try to use the more modern check which respects the "Warn on Land"
|
||||||
|
// behavioral flag on build plans if we can. This newer check won't work
|
||||||
|
// unless the server is running code from March 2019 or newer since the
|
||||||
|
// API methods we need won't exist yet. We'll fall back to the older check
|
||||||
|
// if this one doesn't work out.
|
||||||
|
try {
|
||||||
|
$this->checkForBuildablesWithPlanBehaviors($diff_phid);
|
||||||
|
} catch (ArcanistUserAbortException $abort_ex) {
|
||||||
|
throw $abort_ex;
|
||||||
|
} catch (Exception $ex) {
|
||||||
|
// Continue with the older approach, below.
|
||||||
|
}
|
||||||
|
|
||||||
// NOTE: Since Harbormaster is still beta and this stuff all got added
|
// NOTE: Since Harbormaster is still beta and this stuff all got added
|
||||||
// recently, just bail if we can't find a buildable. This is just an
|
// recently, just bail if we can't find a buildable. This is just an
|
||||||
// advisory check intended to prevent human error.
|
// advisory check intended to prevent human error.
|
||||||
|
@ -1449,6 +1462,166 @@ EOTEXT
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function checkForBuildablesWithPlanBehaviors($diff_phid) {
|
||||||
|
// TODO: These queries should page through all results instead of fetching
|
||||||
|
// only the first page, but we don't have good primitives to support that
|
||||||
|
// in "master" yet.
|
||||||
|
|
||||||
|
$this->writeInfo(
|
||||||
|
pht('BUILDS'),
|
||||||
|
pht('Checking build status...'));
|
||||||
|
|
||||||
|
$raw_buildables = $this->getConduit()->callMethodSynchronous(
|
||||||
|
'harbormaster.buildable.search',
|
||||||
|
array(
|
||||||
|
'constraints' => array(
|
||||||
|
'objectPHIDs' => array(
|
||||||
|
$diff_phid,
|
||||||
|
),
|
||||||
|
'manual' => false,
|
||||||
|
),
|
||||||
|
));
|
||||||
|
|
||||||
|
if (!$raw_buildables['data']) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$buildables = $raw_buildables['data'];
|
||||||
|
$buildable_phids = ipull($buildables, 'phid');
|
||||||
|
|
||||||
|
$raw_builds = $this->getConduit()->callMethodSynchronous(
|
||||||
|
'harbormaster.build.search',
|
||||||
|
array(
|
||||||
|
'constraints' => array(
|
||||||
|
'buildables' => $buildable_phids,
|
||||||
|
),
|
||||||
|
));
|
||||||
|
|
||||||
|
if (!$raw_builds['data']) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$builds = array();
|
||||||
|
foreach ($raw_builds['data'] as $raw_build) {
|
||||||
|
$build_ref = ArcanistBuildRef::newFromConduit($raw_build);
|
||||||
|
$build_phid = $build_ref->getPHID();
|
||||||
|
$builds[$build_phid] = $build_ref;
|
||||||
|
}
|
||||||
|
|
||||||
|
$plan_phids = mpull($builds, 'getBuildPlanPHID');
|
||||||
|
$plan_phids = array_values($plan_phids);
|
||||||
|
|
||||||
|
$raw_plans = $this->getConduit()->callMethodSynchronous(
|
||||||
|
'harbormaster.buildplan.search',
|
||||||
|
array(
|
||||||
|
'constraints' => array(
|
||||||
|
'phids' => $plan_phids,
|
||||||
|
),
|
||||||
|
));
|
||||||
|
|
||||||
|
$plans = array();
|
||||||
|
foreach ($raw_plans['data'] as $raw_plan) {
|
||||||
|
$plan_ref = ArcanistBuildPlanRef::newFromConduit($raw_plan);
|
||||||
|
$plan_phid = $plan_ref->getPHID();
|
||||||
|
$plans[$plan_phid] = $plan_ref;
|
||||||
|
}
|
||||||
|
|
||||||
|
$ongoing_builds = array();
|
||||||
|
$failed_builds = array();
|
||||||
|
|
||||||
|
$builds = msort($builds, 'getStatusSortVector');
|
||||||
|
foreach ($builds as $build_ref) {
|
||||||
|
$plan = idx($plans, $build_ref->getBuildPlanPHID());
|
||||||
|
if (!$plan) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$plan_behavior = $plan->getBehavior('arc-land', 'always');
|
||||||
|
$if_building = ($plan_behavior == 'building');
|
||||||
|
$if_complete = ($plan_behavior == 'complete');
|
||||||
|
$if_never = ($plan_behavior == 'never');
|
||||||
|
|
||||||
|
// If the build plan "Never" warns when landing, skip it.
|
||||||
|
if ($if_never) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the build plan warns when landing "If Complete" but the build is
|
||||||
|
// not complete, skip it.
|
||||||
|
if ($if_complete && !$build_ref->isComplete()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the build plan warns when landing "If Building" but the build is
|
||||||
|
// complete, skip it.
|
||||||
|
if ($if_building && $build_ref->isComplete()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ignore passing builds.
|
||||||
|
if ($build_ref->isPassed()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$build_ref->isComplete()) {
|
||||||
|
$ongoing_builds[] = $build_ref;
|
||||||
|
} else {
|
||||||
|
$failed_builds[] = $build_ref;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$ongoing_builds && !$failed_builds) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($failed_builds) {
|
||||||
|
$this->writeWarn(
|
||||||
|
pht('BUILD FAILURES'),
|
||||||
|
pht(
|
||||||
|
'Harbormaster failed to build the active diff for this revision:'));
|
||||||
|
$prompt = pht('Land revision anyway, despite build failures?');
|
||||||
|
} else if ($ongoing_builds) {
|
||||||
|
$this->writeWarn(
|
||||||
|
pht('ONGOING BUILDS'),
|
||||||
|
pht(
|
||||||
|
'Harbormaster is still building the active diff for this revision:'));
|
||||||
|
$prompt = pht('Land revision anyway, despite ongoing build?');
|
||||||
|
}
|
||||||
|
|
||||||
|
$show_builds = array_merge($failed_builds, $ongoing_builds);
|
||||||
|
echo "\n";
|
||||||
|
foreach ($show_builds as $build_ref) {
|
||||||
|
$ansi_color = $build_ref->getStatusANSIColor();
|
||||||
|
$status_name = $build_ref->getStatusName();
|
||||||
|
$object_name = $build_ref->getObjectName();
|
||||||
|
$build_name = $build_ref->getName();
|
||||||
|
|
||||||
|
echo tsprintf(
|
||||||
|
" **<bg:".$ansi_color."> %s </bg>** %s: %s\n",
|
||||||
|
$status_name,
|
||||||
|
$object_name,
|
||||||
|
$build_name);
|
||||||
|
}
|
||||||
|
|
||||||
|
echo tsprintf(
|
||||||
|
"\n%s\n\n",
|
||||||
|
pht('You can review build details here:'));
|
||||||
|
|
||||||
|
foreach ($buildables as $buildable) {
|
||||||
|
$buildable_uri = id(new PhutilURI($this->getConduitURI()))
|
||||||
|
->setPath(sprintf('/B%d', $buildable['id']));
|
||||||
|
|
||||||
|
echo tsprintf(
|
||||||
|
" **%s**: __%s__\n",
|
||||||
|
pht('Buildable %d', $buildable['id']),
|
||||||
|
$buildable_uri);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!phutil_console_confirm($prompt)) {
|
||||||
|
throw new ArcanistUserAbortException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public function buildEngineMessage(ArcanistLandEngine $engine) {
|
public function buildEngineMessage(ArcanistLandEngine $engine) {
|
||||||
// TODO: This is oh-so-gross.
|
// TODO: This is oh-so-gross.
|
||||||
$this->findRevision();
|
$this->findRevision();
|
||||||
|
|
Loading…
Reference in a new issue