1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2024-12-18 19:40:55 +01:00

Add "Autoplans" to Harbormaster

Summary:
Ref T8095. Two general problems:

  - I want Harbormaster to own all lint and unit test results.
  - I don't want users to have to configure anything for `arc` to keep working automatically.

These are in conflict because generic lint/unit test ownership in Harbormaster requires that build targets exist which we can attach build results to. However, we can't currently create build targets on demand: Harbormaster assumes it is responsible for creating targets, then running code or making third-party service calls to actually run the builds.

I considered two broad approaches to let `arc` push results into Harbormaster without requiring administrators to configure some kind of "arc results" build plan:

  # Add magic target PHIDs like `PHID-MAGIC-this-is-really-arc-unit`.
  # Add new code to build real targets with real PHIDs.

(1) is probably a bit less work to get off the ground, but I think it's worse overall and very likely to create more problems in the long run. I particularly worry that it will lead to a small amount of special casing in a very large number of places, which seems more fragile.

(2) is more work upfront but I think does a better job of putting all the special casing in one place that we can, e.g., more reasonably unit test, and letting the rest of the code rarely/never care about this case since it's just dealing with normal plans/steps/targets as far as it can tell.

This diff introduces "autoplans", which are source templates for plans/steps. This let us "push" these targets into Harbormaster. Hypthetically, any process "like" arc can use autoplans to upload test/lint/etc results. In practice, probably only `arc` will ever use this, but I think it's still quite a bit cleaner than the alternative despite all the generality.

Workflow is basically:

  - `arc` creates a diff.
  - `arc` calls `harbormaster.queryautotargets`, passing the diff PHID and saying "I have some lint and unit results I want to stick on this thing".
  - Harbormaster builds the plan, steps, and targets (if any of them don't already exist), and hands back the target PHIDs so `arc` has a completely standard-looking place to put results.
  - `arc` uploads the test results to the right targets, as though Harbormaster had asked it to run unit/lint in the first place.

(This doesn't actually do any of that yet, just sets things up.)

I'll maybe doc turn that ^^^^^^ into a doc for posterity since I think it's hard to guess what an "autotarget" is, but I'm going to grab some lunch first.

Test Plan:
  - Added unit tests to make sure we can build these things properly.
  - Used `harbormaster.queryautotargets` to build autotargets for a bunch of diffs.
  - Verified targets come up in "waiting for message" state.
  - Verified plans and steps are not editable.

Reviewers: btrahan

Reviewed By: btrahan

Subscribers: hach-que, epriestley

Maniphest Tasks: T8095

Differential Revision: https://secure.phabricator.com/D13345
This commit is contained in:
epriestley 2015-06-21 09:04:21 -07:00
parent 081300c7f8
commit 76194a0dc1
23 changed files with 733 additions and 15 deletions

View file

@ -0,0 +1,2 @@
ALTER TABLE {$NAMESPACE}_harbormaster.harbormaster_buildplan
ADD planAutoKey VARCHAR(32) COLLATE {$COLLATE_TEXT};

View file

@ -0,0 +1,2 @@
ALTER TABLE {$NAMESPACE}_harbormaster.harbormaster_buildstep
ADD stepAutoKey VARCHAR(32) COLLATE {$COLLATE_TEXT};

View file

@ -0,0 +1,2 @@
ALTER TABLE {$NAMESPACE}_harbormaster.harbormaster_build
ADD planAutoKey VARCHAR(32) COLLATE {$COLLATE_TEXT};

View file

@ -357,6 +357,7 @@ phutil_register_library_map(array(
'DifferentialDiffTableOfContentsView' => 'applications/differential/view/DifferentialDiffTableOfContentsView.php',
'DifferentialDiffTestCase' => 'applications/differential/storage/__tests__/DifferentialDiffTestCase.php',
'DifferentialDiffTransaction' => 'applications/differential/storage/DifferentialDiffTransaction.php',
'DifferentialDiffTransactionQuery' => 'applications/differential/query/DifferentialDiffTransactionQuery.php',
'DifferentialDiffViewController' => 'applications/differential/controller/DifferentialDiffViewController.php',
'DifferentialDoorkeeperRevisionFeedStoryPublisher' => 'applications/differential/doorkeeper/DifferentialDoorkeeperRevisionFeedStoryPublisher.php',
'DifferentialDraft' => 'applications/differential/storage/DifferentialDraft.php',
@ -823,11 +824,16 @@ phutil_register_library_map(array(
'FundInitiativeTransactionQuery' => 'applications/fund/query/FundInitiativeTransactionQuery.php',
'FundInitiativeViewController' => 'applications/fund/controller/FundInitiativeViewController.php',
'FundSchemaSpec' => 'applications/fund/storage/FundSchemaSpec.php',
'HarbormasterArcLintBuildStepImplementation' => 'applications/harbormaster/step/HarbormasterArcLintBuildStepImplementation.php',
'HarbormasterArcUnitBuildStepImplementation' => 'applications/harbormaster/step/HarbormasterArcUnitBuildStepImplementation.php',
'HarbormasterAutotargetsTestCase' => 'applications/harbormaster/__tests__/HarbormasterAutotargetsTestCase.php',
'HarbormasterBuild' => 'applications/harbormaster/storage/build/HarbormasterBuild.php',
'HarbormasterBuildAbortedException' => 'applications/harbormaster/exception/HarbormasterBuildAbortedException.php',
'HarbormasterBuildActionController' => 'applications/harbormaster/controller/HarbormasterBuildActionController.php',
'HarbormasterBuildArcanistAutoplan' => 'applications/harbormaster/autoplan/HarbormasterBuildArcanistAutoplan.php',
'HarbormasterBuildArtifact' => 'applications/harbormaster/storage/build/HarbormasterBuildArtifact.php',
'HarbormasterBuildArtifactQuery' => 'applications/harbormaster/query/HarbormasterBuildArtifactQuery.php',
'HarbormasterBuildAutoplan' => 'applications/harbormaster/autoplan/HarbormasterBuildAutoplan.php',
'HarbormasterBuildCommand' => 'applications/harbormaster/storage/HarbormasterBuildCommand.php',
'HarbormasterBuildDependencyDatasource' => 'applications/harbormaster/typeahead/HarbormasterBuildDependencyDatasource.php',
'HarbormasterBuildEngine' => 'applications/harbormaster/engine/HarbormasterBuildEngine.php',
@ -897,6 +903,7 @@ phutil_register_library_map(array(
'HarbormasterPlanRunController' => 'applications/harbormaster/controller/HarbormasterPlanRunController.php',
'HarbormasterPlanViewController' => 'applications/harbormaster/controller/HarbormasterPlanViewController.php',
'HarbormasterPublishFragmentBuildStepImplementation' => 'applications/harbormaster/step/HarbormasterPublishFragmentBuildStepImplementation.php',
'HarbormasterQueryAutotargetsConduitAPIMethod' => 'applications/harbormaster/conduit/HarbormasterQueryAutotargetsConduitAPIMethod.php',
'HarbormasterQueryBuildablesConduitAPIMethod' => 'applications/harbormaster/conduit/HarbormasterQueryBuildablesConduitAPIMethod.php',
'HarbormasterQueryBuildsConduitAPIMethod' => 'applications/harbormaster/conduit/HarbormasterQueryBuildsConduitAPIMethod.php',
'HarbormasterRemarkupRule' => 'applications/harbormaster/remarkup/HarbormasterRemarkupRule.php',
@ -907,6 +914,7 @@ phutil_register_library_map(array(
'HarbormasterStepAddController' => 'applications/harbormaster/controller/HarbormasterStepAddController.php',
'HarbormasterStepDeleteController' => 'applications/harbormaster/controller/HarbormasterStepDeleteController.php',
'HarbormasterStepEditController' => 'applications/harbormaster/controller/HarbormasterStepEditController.php',
'HarbormasterTargetEngine' => 'applications/harbormaster/engine/HarbormasterTargetEngine.php',
'HarbormasterTargetWorker' => 'applications/harbormaster/worker/HarbormasterTargetWorker.php',
'HarbormasterThrowExceptionBuildStep' => 'applications/harbormaster/step/HarbormasterThrowExceptionBuildStep.php',
'HarbormasterUIEventListener' => 'applications/harbormaster/event/HarbormasterUIEventListener.php',
@ -3704,6 +3712,7 @@ phutil_register_library_map(array(
'DifferentialDiffTableOfContentsView' => 'AphrontView',
'DifferentialDiffTestCase' => 'PhutilTestCase',
'DifferentialDiffTransaction' => 'PhabricatorApplicationTransaction',
'DifferentialDiffTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
'DifferentialDiffViewController' => 'DifferentialController',
'DifferentialDoorkeeperRevisionFeedStoryPublisher' => 'DoorkeeperFeedStoryPublisher',
'DifferentialDraft' => 'DifferentialDAO',
@ -4235,6 +4244,9 @@ phutil_register_library_map(array(
'FundInitiativeTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
'FundInitiativeViewController' => 'FundController',
'FundSchemaSpec' => 'PhabricatorConfigSchemaSpec',
'HarbormasterArcLintBuildStepImplementation' => 'HarbormasterBuildStepImplementation',
'HarbormasterArcUnitBuildStepImplementation' => 'HarbormasterBuildStepImplementation',
'HarbormasterAutotargetsTestCase' => 'PhabricatorTestCase',
'HarbormasterBuild' => array(
'HarbormasterDAO',
'PhabricatorApplicationTransactionInterface',
@ -4242,11 +4254,13 @@ phutil_register_library_map(array(
),
'HarbormasterBuildAbortedException' => 'Exception',
'HarbormasterBuildActionController' => 'HarbormasterController',
'HarbormasterBuildArcanistAutoplan' => 'HarbormasterBuildAutoplan',
'HarbormasterBuildArtifact' => array(
'HarbormasterDAO',
'PhabricatorPolicyInterface',
),
'HarbormasterBuildArtifactQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'HarbormasterBuildAutoplan' => 'Phobject',
'HarbormasterBuildCommand' => 'HarbormasterDAO',
'HarbormasterBuildDependencyDatasource' => 'PhabricatorTypeaheadDatasource',
'HarbormasterBuildEngine' => 'Phobject',
@ -4342,6 +4356,7 @@ phutil_register_library_map(array(
'HarbormasterPlanRunController' => 'HarbormasterController',
'HarbormasterPlanViewController' => 'HarbormasterPlanController',
'HarbormasterPublishFragmentBuildStepImplementation' => 'HarbormasterBuildStepImplementation',
'HarbormasterQueryAutotargetsConduitAPIMethod' => 'HarbormasterConduitAPIMethod',
'HarbormasterQueryBuildablesConduitAPIMethod' => 'HarbormasterConduitAPIMethod',
'HarbormasterQueryBuildsConduitAPIMethod' => 'HarbormasterConduitAPIMethod',
'HarbormasterRemarkupRule' => 'PhabricatorObjectRemarkupRule',
@ -4352,6 +4367,7 @@ phutil_register_library_map(array(
'HarbormasterStepAddController' => 'HarbormasterController',
'HarbormasterStepDeleteController' => 'HarbormasterController',
'HarbormasterStepEditController' => 'HarbormasterController',
'HarbormasterTargetEngine' => 'Phobject',
'HarbormasterTargetWorker' => 'HarbormasterWorker',
'HarbormasterThrowExceptionBuildStep' => 'HarbormasterBuildStepImplementation',
'HarbormasterUIEventListener' => 'PhabricatorEventListener',

View file

@ -0,0 +1,10 @@
<?php
final class DifferentialDiffTransactionQuery
extends PhabricatorApplicationTransactionQuery {
public function getTemplateApplicationTransaction() {
return new DifferentialDiffTransaction();
}
}

View file

@ -381,6 +381,7 @@ final class DifferentialDiff
$results = array();
$results['buildable.diff'] = $this->getID();
if ($this->revisionID) {
$revision = $this->getRevision();
$results['buildable.revision'] = $revision->getID();
$repo = $revision->getRepository();
@ -390,6 +391,7 @@ final class DifferentialDiff
$results['repository.vcs'] = $repo->getVersionControlSystem();
$results['repository.uri'] = $repo->getPublicCloneURI();
}
}
return $results;
}

View file

@ -0,0 +1,61 @@
<?php
final class HarbormasterAutotargetsTestCase extends PhabricatorTestCase {
protected function getPhabricatorTestCaseConfiguration() {
return array(
self::PHABRICATOR_TESTCONFIG_BUILD_STORAGE_FIXTURES => true,
);
}
public function testGenerateHarbormasterAutotargets() {
$viewer = $this->generateNewTestUser();
$raw_diff = <<<EODIFF
diff --git a/fruit b/fruit
new file mode 100644
index 0000000..1c0f49d
--- /dev/null
+++ b/fruit
@@ -0,0 +1,2 @@
+apal
+banan
EODIFF;
$parser = new ArcanistDiffParser();
$changes = $parser->parseDiff($raw_diff);
$diff = DifferentialDiff::newFromRawChanges($viewer, $changes)
->setLintStatus(DifferentialLintStatus::LINT_AUTO_SKIP)
->setUnitStatus(DifferentialUnitStatus::UNIT_AUTO_SKIP)
->attachRevision(null)
->save();
$params = array(
'objectPHID' => $diff->getPHID(),
'targetKeys' => array(
HarbormasterArcLintBuildStepImplementation::STEPKEY,
HarbormasterArcUnitBuildStepImplementation::STEPKEY,
),
);
// Creation of autotargets should work from an empty state.
$result = id(new ConduitCall('harbormaster.queryautotargets', $params))
->setUser($viewer)
->execute();
$targets = idx($result, 'targetMap');
foreach ($params['targetKeys'] as $target_key) {
$this->assertTrue((bool)$result['targetMap'][$target_key]);
}
// Querying the same autotargets again should produce the same results,
// not make new ones.
$retry = id(new ConduitCall('harbormaster.queryautotargets', $params))
->setUser($viewer)
->execute();
$this->assertEqual($result, $retry);
}
}

View file

@ -0,0 +1,16 @@
<?php
final class HarbormasterBuildArcanistAutoplan
extends HarbormasterBuildAutoplan {
const PLANKEY = 'arcanist';
public function getAutoplanPlanKey() {
return self::PLANKEY;
}
public function getAutoplanName() {
return pht('Arcanist Client Results');
}
}

View file

@ -0,0 +1,44 @@
<?php
abstract class HarbormasterBuildAutoplan extends Phobject {
abstract public function getAutoplanPlanKey();
abstract public function getAutoplanName();
public static function getAutoplan($key) {
return idx(self::getAllAutoplans(), $key);
}
public static function getAllAutoplans() {
static $plans;
if ($plans === null) {
$objects = id(new PhutilSymbolLoader())
->setAncestorClass(__CLASS__)
->loadObjects();
$map = array();
foreach ($objects as $object) {
$key = $object->getAutoplanPlanKey();
if (!empty($map[$key])) {
$other = $map[$key];
throw new Exception(
pht(
'Two build autoplans (of classes "%s" and "%s") define the same '.
'key ("%s"). Each autoplan must have a unique key.',
get_class($other),
get_class($object),
$key));
}
$map[$key] = $object;
}
ksort($map);
$plans = $map;
}
return $plans;
}
}

View file

@ -0,0 +1,76 @@
<?php
final class HarbormasterQueryAutotargetsConduitAPIMethod
extends HarbormasterConduitAPIMethod {
public function getAPIMethodName() {
return 'harbormaster.queryautotargets';
}
public function getMethodDescription() {
return pht('Load or create build autotargets.');
}
protected function defineParamTypes() {
return array(
'objectPHID' => 'phid',
'targetKeys' => 'list<string>',
);
}
protected function defineReturnType() {
return 'map<string, phid>';
}
protected function execute(ConduitAPIRequest $request) {
$viewer = $request->getUser();
$phid = $request->getValue('objectPHID');
// NOTE: We use withNames() to let monograms like "D123" work, which makes
// this a little easier to test. Real PHIDs will still work as expected.
$object = id(new PhabricatorObjectQuery())
->setViewer($viewer)
->withNames(array($phid))
->executeOne();
if (!$object) {
throw new Exception(
pht(
'No such object "%s" exists.',
$phid));
}
if (!($object instanceof HarbormasterBuildableInterface)) {
throw new Exception(
pht(
'Object "%s" does not implement interface "%s". Autotargets may '.
'only be queried for buildable objects.',
$phid,
'HarbormasterBuildableInterface'));
}
$autotargets = $request->getValue('targetKeys', array());
if ($autotargets) {
$targets = id(new HarbormasterTargetEngine())
->setViewer($viewer)
->setObject($object)
->setAutoTargetKeys($autotargets)
->buildTargets();
} else {
$targets = array();
}
// Reorder the results according to the request order so we can make test
// assertions that subsequent calls return the same results.
$map = mpull($targets, 'getPHID');
$map = array_select_keys($map, $autotargets);
return array(
'targetMap' => $map,
);
}
}

View file

@ -24,10 +24,17 @@ final class HarbormasterStepAddController extends HarbormasterController {
$plan_id = $plan->getID();
$cancel_uri = $this->getApplicationURI("plan/{$plan_id}/");
$all = HarbormasterBuildStepImplementation::getImplementations();
foreach ($all as $key => $impl) {
if ($impl->shouldRequireAutotargeting()) {
unset($all[$key]);
}
}
$errors = array();
if ($request->isFormPost()) {
$class = $request->getStr('class');
if (!HarbormasterBuildStepImplementation::getImplementation($class)) {
if (empty($all[$class])) {
$errors[] = pht('Choose the type of build step you want to add.');
}
if (!$errors) {
@ -39,7 +46,6 @@ final class HarbormasterStepAddController extends HarbormasterController {
$control = id(new AphrontFormRadioButtonControl())
->setName('class');
$all = HarbormasterBuildStepImplementation::getImplementations();
foreach ($all as $class => $implementation) {
$control->addButton(
$class,

View file

@ -47,6 +47,11 @@ final class HarbormasterStepEditController extends HarbormasterController {
return new Aphront404Response();
}
if ($impl->shouldRequireAutotargeting()) {
// No manual creation of autotarget steps.
return new Aphront404Response();
}
$step = HarbormasterBuildStep::initializeNewStep($viewer)
->setBuildPlanPHID($plan->getPHID())
->setClassName($class);

View file

@ -0,0 +1,251 @@
<?php
final class HarbormasterTargetEngine extends Phobject {
private $viewer;
private $object;
private $autoTargetKeys;
public function setViewer($viewer) {
$this->viewer = $viewer;
return $this;
}
public function getViewer() {
return $this->viewer;
}
public function setObject(HarbormasterBuildableInterface $object) {
$this->object = $object;
return $this;
}
public function getObject() {
return $this->object;
}
public function setAutoTargetKeys(array $auto_keys) {
$this->autoTargetKeys = $auto_keys;
return $this;
}
public function getAutoTargetKeys() {
return $this->autoTargetKeys;
}
public function buildTargets() {
$object = $this->getObject();
$viewer = $this->getViewer();
$step_map = $this->generateBuildStepMap($this->getAutoTargetKeys());
$buildable = HarbormasterBuildable::createOrLoadExisting(
$viewer,
$object->getHarbormasterBuildablePHID(),
$object->getHarbormasterContainerPHID());
$target_map = $this->generateBuildTargetMap($buildable, $step_map);
return $target_map;
}
/**
* Get a map of the @{class:HarbormasterBuildStep} objects for a list of
* autotarget keys.
*
* This method creates the steps if they do not yet exist.
*
* @param list<string> Autotarget keys, like `"core.arc.lint"`.
* @return map<string, object> Map of keys to step objects.
*/
private function generateBuildStepMap(array $autotargets) {
$viewer = $this->getViewer();
$autosteps = $this->getAutosteps($autotargets);
$autosteps = mgroup($autosteps, 'getBuildStepAutotargetPlanKey');
$plans = id(new HarbormasterBuildPlanQuery())
->setViewer($viewer)
->withPlanAutoKeys(array_keys($autosteps))
->needBuildSteps(true)
->execute();
$plans = mpull($plans, null, 'getPlanAutoKey');
// NOTE: When creating the plan and steps, we save the autokeys as the
// names. These won't actually be shown in the UI, but make the data more
// consistent for secondary consumers like typeaheads.
$step_map = array();
foreach ($autosteps as $plan_key => $steps) {
$plan = idx($plans, $plan_key);
if (!$plan) {
$plan = HarbormasterBuildPlan::initializeNewBuildPlan($viewer)
->setName($plan_key)
->setPlanAutoKey($plan_key);
}
$current = $plan->getBuildSteps();
$current = mpull($current, null, 'getStepAutoKey');
$new_steps = array();
foreach ($steps as $step_key => $step) {
if (isset($current[$step_key])) {
$step_map[$step_key] = $current[$step_key];
continue;
}
$new_step = HarbormasterBuildStep::initializeNewStep($viewer)
->setName($step_key)
->setClassName(get_class($step))
->setStepAutoKey($step_key);
$new_steps[$step_key] = $new_step;
}
if ($new_steps) {
$plan->openTransaction();
if (!$plan->getPHID()) {
$plan->save();
}
foreach ($new_steps as $step_key => $step) {
$step->setBuildPlanPHID($plan->getPHID());
$step->save();
$step->attachBuildPlan($plan);
$step_map[$step_key] = $step;
}
$plan->saveTransaction();
}
}
return $step_map;
}
/**
* Get all of the @{class:HarbormasterBuildStepImplementation} objects for
* a list of autotarget keys.
*
* @param list<string> Autotarget keys, like `"core.arc.lint"`.
* @return map<string, object> Map of keys to implementations.
*/
private function getAutosteps(array $autotargets) {
$all_steps = HarbormasterBuildStepImplementation::getImplementations();
$all_steps = mpull($all_steps, null, 'getBuildStepAutotargetStepKey');
// Make sure all the targets really exist.
foreach ($autotargets as $autotarget) {
if (empty($all_steps[$autotarget])) {
throw new Exception(
pht(
'No build step provides autotarget "%s"!',
$autotarget));
}
}
return array_select_keys($all_steps, $autotargets);
}
/**
* Get a list of @{class:HarbormasterBuildTarget} objects for a list of
* autotarget keys.
*
* If some targets or builds do not exist, they are created.
*
* @param HarbormasterBuildable A buildable.
* @param map<string, object> Map of keys to steps.
* @return map<string, object> Map of keys to targets.
*/
private function generateBuildTargetMap(
HarbormasterBuildable $buildable,
array $step_map) {
$viewer = $this->getViewer();
$plan_map = mgroup($step_map, 'getBuildPlanPHID');
$builds = id(new HarbormasterBuildQuery())
->setViewer($viewer)
->withBuildablePHIDs(array($buildable->getPHID()))
->withBuildPlanPHIDs(array_keys($plan_map))
->needBuildTargets(true)
->execute();
$autobuilds = array();
foreach ($builds as $build) {
$plan_key = $build->getBuildPlan()->getPlanAutoKey();
$autobuilds[$plan_key] = $build;
}
$new_builds = array();
foreach ($plan_map as $plan_phid => $steps) {
$plan = head($steps)->getBuildPlan();
$plan_key = $plan->getPlanAutoKey();
$build = idx($autobuilds, $plan_key);
if ($build) {
// We already have a build for this set of targets, so we don't need
// to do any work. (It's possible the build is an older build that
// doesn't have all of the right targets if new autotargets were
// recently introduced, but we don't currently try to construct them.)
continue;
}
// NOTE: Normally, `applyPlan()` does not actually generate targets.
// We need to apply the plan in-process to perform target generation.
// This is fine as long as autotargets are empty containers that don't
// do any work, which they always should be.
PhabricatorWorker::setRunAllTasksInProcess(true);
try {
// NOTE: We might race another process here to create the same build
// with the same `planAutoKey`. The database will prevent this and
// using autotargets only currently makes sense if you just created the
// resource and "own" it, so we don't try to handle this, but may need
// to be more careful here if use of autotargets expands.
$build = $buildable->applyPlan($plan);
PhabricatorWorker::setRunAllTasksInProcess(false);
} catch (Exception $ex) {
PhabricatorWorker::setRunAllTasksInProcess(false);
throw $ex;
}
$new_builds[] = $build;
}
if ($new_builds) {
$all_targets = id(new HarbormasterBuildTargetQuery())
->setViewer($viewer)
->withBuildPHIDs(mpull($new_builds, 'getPHID'))
->execute();
} else {
$all_targets = array();
}
foreach ($builds as $build) {
foreach ($build->getBuildTargets() as $target) {
$all_targets[] = $target;
}
}
$target_map = array();
foreach ($all_targets as $target) {
$target_key = $target
->getImplementation()
->getBuildStepAutotargetStepKey();
if (!$target_key) {
continue;
}
$target_map[$target_key] = $target;
}
$target_map = array_select_keys($target_map, array_keys($step_map));
return $target_map;
}
}

View file

@ -7,6 +7,8 @@ final class HarbormasterBuildPlanQuery
private $phids;
private $statuses;
private $datasourceQuery;
private $planAutoKeys;
private $needBuildSteps;
public function withIDs(array $ids) {
$this->ids = $ids;
@ -28,6 +30,16 @@ final class HarbormasterBuildPlanQuery
return $this;
}
public function withPlanAutoKeys(array $keys) {
$this->planAutoKeys = $keys;
return $this;
}
public function needBuildSteps($need) {
$this->needBuildSteps = $need;
return $this;
}
public function newResultObject() {
return new HarbormasterBuildPlan();
}
@ -36,6 +48,26 @@ final class HarbormasterBuildPlanQuery
return $this->loadStandardPage($this->newResultObject());
}
protected function didFilterPage(array $page) {
if ($this->needBuildSteps) {
$plan_phids = mpull($page, 'getPHID');
$steps = id(new HarbormasterBuildStepQuery())
->setParentQuery($this)
->setViewer($this->getViewer())
->withBuildPlanPHIDs($plan_phids)
->execute();
$steps = mgroup($steps, 'getBuildPlanPHID');
foreach ($page as $plan) {
$plan_steps = idx($steps, $plan->getPHID(), array());
$plan->attachBuildSteps($plan_steps);
}
}
return $page;
}
protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) {
$where = parent::buildWhereClauseParts($conn);
@ -67,6 +99,13 @@ final class HarbormasterBuildPlanQuery
$this->datasourceQuery);
}
if ($this->planAutoKeys !== null) {
$where[] = qsprintf(
$conn,
'planAutoKey IN (%Ls)',
$this->planAutoKeys);
}
return $where;
}

View file

@ -88,6 +88,10 @@ final class HarbormasterBuildPlanSearchEngine
$item->setDisabled(true);
}
if ($plan->isAutoplan()) {
$item->addIcon('fa-lock grey', pht('Autoplan'));
}
$item->setHref($this->getApplicationURI("plan/{$id}/"));
$list->addItem($item);

View file

@ -0,0 +1,38 @@
<?php
final class HarbormasterArcLintBuildStepImplementation
extends HarbormasterBuildStepImplementation {
const STEPKEY = 'arcanist.lint';
public function getBuildStepAutotargetPlanKey() {
return HarbormasterBuildArcanistAutoplan::PLANKEY;
}
public function getBuildStepAutotargetStepKey() {
return self::STEPKEY;
}
public function shouldRequireAutotargeting() {
return true;
}
public function getName() {
return pht('Arcanist Lint Results');
}
public function getGenericDescription() {
return pht('Automatic `arc lint` step.');
}
public function execute(
HarbormasterBuild $build,
HarbormasterBuildTarget $build_target) {
return;
}
public function shouldWaitForMessage(HarbormasterBuildTarget $target) {
return true;
}
}

View file

@ -0,0 +1,38 @@
<?php
final class HarbormasterArcUnitBuildStepImplementation
extends HarbormasterBuildStepImplementation {
const STEPKEY = 'arcanist.unit';
public function getBuildStepAutotargetPlanKey() {
return HarbormasterBuildArcanistAutoplan::PLANKEY;
}
public function getBuildStepAutotargetStepKey() {
return self::STEPKEY;
}
public function shouldRequireAutotargeting() {
return true;
}
public function getName() {
return pht('Arcanist Unit Results');
}
public function getGenericDescription() {
return pht('Automatic `arc unit` step.');
}
public function execute(
HarbormasterBuild $build,
HarbormasterBuildTarget $build_target) {
return;
}
public function shouldWaitForMessage(HarbormasterBuildTarget $target) {
return true;
}
}

View file

@ -1,5 +1,8 @@
<?php
/**
* @task autotarget Automatic Targets
*/
abstract class HarbormasterBuildStepImplementation extends Phobject {
private $settings;
@ -249,4 +252,20 @@ abstract class HarbormasterBuildStepImplementation extends Phobject {
}
}
/* -( Automatic Targets )-------------------------------------------------- */
public function getBuildStepAutotargetStepKey() {
return null;
}
public function getBuildStepAutotargetPlanKey() {
throw new PhutilMethodNotImplementedException();
}
public function shouldRequireAutotargeting() {
return false;
}
}

View file

@ -141,8 +141,14 @@ final class HarbormasterBuildable extends HarbormasterDAO
$build = HarbormasterBuild::initializeNewBuild($viewer)
->setBuildablePHID($this->getPHID())
->setBuildPlanPHID($plan->getPHID())
->setBuildStatus(HarbormasterBuild::STATUS_PENDING)
->save();
->setBuildStatus(HarbormasterBuild::STATUS_PENDING);
$auto_key = $plan->getPlanAutoKey();
if ($auto_key) {
$build->setPlanAutoKey($auto_key);
}
$build->save();
PhabricatorWorker::scheduleTask(
'HarbormasterBuildWorker',

View file

@ -9,6 +9,7 @@ final class HarbormasterBuild extends HarbormasterDAO
protected $buildPlanPHID;
protected $buildStatus;
protected $buildGeneration;
protected $planAutoKey;
private $buildable = self::ATTACHABLE;
private $buildPlan = self::ATTACHABLE;
@ -148,6 +149,7 @@ final class HarbormasterBuild extends HarbormasterDAO
self::CONFIG_COLUMN_SCHEMA => array(
'buildStatus' => 'text32',
'buildGeneration' => 'uint32',
'planAutoKey' => 'text32?',
),
self::CONFIG_KEY_SCHEMA => array(
'key_buildable' => array(
@ -159,6 +161,10 @@ final class HarbormasterBuild extends HarbormasterDAO
'key_status' => array(
'columns' => array('buildStatus'),
),
'key_planautokey' => array(
'columns' => array('buildablePHID', 'planAutoKey'),
'unique' => true,
),
),
) + parent::getConfiguration();
}

View file

@ -175,8 +175,16 @@ final class HarbormasterBuildTarget extends HarbormasterDAO
return $this->implementation;
}
public function isAutotarget() {
try {
return (bool)$this->getImplementation()->getBuildStepAutotargetPlanKey();
} catch (Exception $e) {
return false;
}
}
public function getName() {
if (strlen($this->name)) {
if (strlen($this->name) && !$this->isAutotarget()) {
return $this->name;
}

View file

@ -1,5 +1,8 @@
<?php
/**
* @task autoplan Autoplans
*/
final class HarbormasterBuildPlan extends HarbormasterDAO
implements
PhabricatorApplicationTransactionInterface,
@ -8,6 +11,7 @@ final class HarbormasterBuildPlan extends HarbormasterDAO
protected $name;
protected $planStatus;
protected $planAutoKey;
const STATUS_ACTIVE = 'active';
const STATUS_DISABLED = 'disabled';
@ -16,7 +20,9 @@ final class HarbormasterBuildPlan extends HarbormasterDAO
public static function initializeNewBuildPlan(PhabricatorUser $actor) {
return id(new HarbormasterBuildPlan())
->setPlanStatus(self::STATUS_ACTIVE);
->setName('')
->setPlanStatus(self::STATUS_ACTIVE)
->attachBuildSteps(array());
}
protected function getConfiguration() {
@ -25,6 +31,7 @@ final class HarbormasterBuildPlan extends HarbormasterDAO
self::CONFIG_COLUMN_SCHEMA => array(
'name' => 'sort128',
'planStatus' => 'text32',
'planAutoKey' => 'text32?',
),
self::CONFIG_KEY_SCHEMA => array(
'key_status' => array(
@ -33,6 +40,10 @@ final class HarbormasterBuildPlan extends HarbormasterDAO
'key_name' => array(
'columns' => array('name'),
),
'key_planautokey' => array(
'columns' => array('planAutoKey'),
'unique' => true,
),
),
) + parent::getConfiguration();
}
@ -57,6 +68,33 @@ final class HarbormasterBuildPlan extends HarbormasterDAO
}
/* -( Autoplans )---------------------------------------------------------- */
public function isAutoplan() {
return ($this->getPlanAutoKey() !== null);
}
public function getAutoplan() {
if (!$this->isAutoplan()) {
return null;
}
return HarbormasterBuildAutoplan::getAutoplan($this->getPlanAutoKey());
}
public function getName() {
$autoplan = $this->getAutoplan();
if ($autoplan) {
return $autoplan->getAutoplanName();
}
return parent::getName();
}
/* -( PhabricatorSubscribableInterface )----------------------------------- */
@ -113,6 +151,11 @@ final class HarbormasterBuildPlan extends HarbormasterDAO
case PhabricatorPolicyCapability::CAN_EDIT:
// NOTE: In practice, this policy is always limited by the "Mangage
// Build Plans" policy.
if ($this->isAutoplan()) {
return PhabricatorPolicies::POLICY_NOONE;
}
return PhabricatorPolicies::getMostOpenPolicy();
}
}
@ -122,7 +165,19 @@ final class HarbormasterBuildPlan extends HarbormasterDAO
}
public function describeAutomaticCapability($capability) {
return null;
$messages = array();
switch ($capability) {
case PhabricatorPolicyCapability::CAN_EDIT:
if ($this->isAutoplan()) {
$messages[] = pht(
'This is an autoplan (a builtin plan provided by an application) '.
'so it can not be edited.');
}
break;
}
return $messages;
}
}

View file

@ -12,13 +12,16 @@ final class HarbormasterBuildStep extends HarbormasterDAO
protected $className;
protected $details = array();
protected $sequence = 0;
protected $stepAutoKey;
private $buildPlan = self::ATTACHABLE;
private $customFields = self::ATTACHABLE;
private $implementation;
public static function initializeNewStep(PhabricatorUser $actor) {
return id(new HarbormasterBuildStep());
return id(new HarbormasterBuildStep())
->setName('')
->setDescription('');
}
protected function getConfiguration() {
@ -37,11 +40,16 @@ final class HarbormasterBuildStep extends HarbormasterDAO
// which predated editable names. These should be backfilled with
// default names, then the code for handling `null` shoudl be removed.
'name' => 'text255?',
'stepAutoKey' => 'text32?',
),
self::CONFIG_KEY_SCHEMA => array(
'key_plan' => array(
'columns' => array('buildPlanPHID'),
),
'key_stepautokey' => array(
'columns' => array('buildPlanPHID', 'stepAutoKey'),
'unique' => true,
),
),
) + parent::getConfiguration();
}
@ -88,6 +96,10 @@ final class HarbormasterBuildStep extends HarbormasterDAO
return $this->implementation;
}
public function isAutostep() {
return ($this->getStepAutoKey() !== null);
}
/* -( PhabricatorApplicationTransactionInterface )------------------------- */