Page MenuHomePhabricator

D13345.id32288.diff
No OneTemporary

D13345.id32288.diff

diff --git a/resources/sql/autopatches/20150618.harbor.1.planauto.sql b/resources/sql/autopatches/20150618.harbor.1.planauto.sql
new file mode 100644
--- /dev/null
+++ b/resources/sql/autopatches/20150618.harbor.1.planauto.sql
@@ -0,0 +1,2 @@
+ALTER TABLE {$NAMESPACE}_harbormaster.harbormaster_buildplan
+ ADD planAutoKey VARCHAR(32) COLLATE {$COLLATE_TEXT};
diff --git a/resources/sql/autopatches/20150618.harbor.2.stepauto.sql b/resources/sql/autopatches/20150618.harbor.2.stepauto.sql
new file mode 100644
--- /dev/null
+++ b/resources/sql/autopatches/20150618.harbor.2.stepauto.sql
@@ -0,0 +1,2 @@
+ALTER TABLE {$NAMESPACE}_harbormaster.harbormaster_buildstep
+ ADD stepAutoKey VARCHAR(32) COLLATE {$COLLATE_TEXT};
diff --git a/resources/sql/autopatches/20150618.harbor.3.buildauto.sql b/resources/sql/autopatches/20150618.harbor.3.buildauto.sql
new file mode 100644
--- /dev/null
+++ b/resources/sql/autopatches/20150618.harbor.3.buildauto.sql
@@ -0,0 +1,2 @@
+ALTER TABLE {$NAMESPACE}_harbormaster.harbormaster_build
+ ADD planAutoKey VARCHAR(32) COLLATE {$COLLATE_TEXT};
diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php
--- a/src/__phutil_library_map__.php
+++ b/src/__phutil_library_map__.php
@@ -357,6 +357,7 @@
'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',
@@ -822,11 +823,16 @@
'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',
@@ -896,6 +902,7 @@
'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',
@@ -906,6 +913,7 @@
'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 @@
'DifferentialDiffTableOfContentsView' => 'AphrontView',
'DifferentialDiffTestCase' => 'PhutilTestCase',
'DifferentialDiffTransaction' => 'PhabricatorApplicationTransaction',
+ 'DifferentialDiffTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
'DifferentialDiffViewController' => 'DifferentialController',
'DifferentialDoorkeeperRevisionFeedStoryPublisher' => 'DoorkeeperFeedStoryPublisher',
'DifferentialDraft' => 'DifferentialDAO',
@@ -4234,6 +4243,9 @@
'FundInitiativeTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
'FundInitiativeViewController' => 'FundController',
'FundSchemaSpec' => 'PhabricatorConfigSchemaSpec',
+ 'HarbormasterArcLintBuildStepImplementation' => 'HarbormasterBuildStepImplementation',
+ 'HarbormasterArcUnitBuildStepImplementation' => 'HarbormasterBuildStepImplementation',
+ 'HarbormasterAutotargetsTestCase' => 'PhabricatorTestCase',
'HarbormasterBuild' => array(
'HarbormasterDAO',
'PhabricatorApplicationTransactionInterface',
@@ -4241,11 +4253,13 @@
),
'HarbormasterBuildAbortedException' => 'Exception',
'HarbormasterBuildActionController' => 'HarbormasterController',
+ 'HarbormasterBuildArcanistAutoplan' => 'HarbormasterBuildAutoplan',
'HarbormasterBuildArtifact' => array(
'HarbormasterDAO',
'PhabricatorPolicyInterface',
),
'HarbormasterBuildArtifactQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
+ 'HarbormasterBuildAutoplan' => 'Phobject',
'HarbormasterBuildCommand' => 'HarbormasterDAO',
'HarbormasterBuildDependencyDatasource' => 'PhabricatorTypeaheadDatasource',
'HarbormasterBuildEngine' => 'Phobject',
@@ -4341,6 +4355,7 @@
'HarbormasterPlanRunController' => 'HarbormasterController',
'HarbormasterPlanViewController' => 'HarbormasterPlanController',
'HarbormasterPublishFragmentBuildStepImplementation' => 'HarbormasterBuildStepImplementation',
+ 'HarbormasterQueryAutotargetsConduitAPIMethod' => 'HarbormasterConduitAPIMethod',
'HarbormasterQueryBuildablesConduitAPIMethod' => 'HarbormasterConduitAPIMethod',
'HarbormasterQueryBuildsConduitAPIMethod' => 'HarbormasterConduitAPIMethod',
'HarbormasterRemarkupRule' => 'PhabricatorObjectRemarkupRule',
@@ -4351,6 +4366,7 @@
'HarbormasterStepAddController' => 'HarbormasterController',
'HarbormasterStepDeleteController' => 'HarbormasterController',
'HarbormasterStepEditController' => 'HarbormasterController',
+ 'HarbormasterTargetEngine' => 'Phobject',
'HarbormasterTargetWorker' => 'HarbormasterWorker',
'HarbormasterThrowExceptionBuildStep' => 'HarbormasterBuildStepImplementation',
'HarbormasterUIEventListener' => 'PhabricatorEventListener',
diff --git a/src/applications/differential/query/DifferentialDiffTransactionQuery.php b/src/applications/differential/query/DifferentialDiffTransactionQuery.php
new file mode 100644
--- /dev/null
+++ b/src/applications/differential/query/DifferentialDiffTransactionQuery.php
@@ -0,0 +1,10 @@
+<?php
+
+final class DifferentialDiffTransactionQuery
+ extends PhabricatorApplicationTransactionQuery {
+
+ public function getTemplateApplicationTransaction() {
+ return new DifferentialDiffTransaction();
+ }
+
+}
diff --git a/src/applications/differential/storage/DifferentialDiff.php b/src/applications/differential/storage/DifferentialDiff.php
--- a/src/applications/differential/storage/DifferentialDiff.php
+++ b/src/applications/differential/storage/DifferentialDiff.php
@@ -381,14 +381,16 @@
$results = array();
$results['buildable.diff'] = $this->getID();
- $revision = $this->getRevision();
- $results['buildable.revision'] = $revision->getID();
- $repo = $revision->getRepository();
-
- if ($repo) {
- $results['repository.callsign'] = $repo->getCallsign();
- $results['repository.vcs'] = $repo->getVersionControlSystem();
- $results['repository.uri'] = $repo->getPublicCloneURI();
+ if ($this->revisionID) {
+ $revision = $this->getRevision();
+ $results['buildable.revision'] = $revision->getID();
+ $repo = $revision->getRepository();
+
+ if ($repo) {
+ $results['repository.callsign'] = $repo->getCallsign();
+ $results['repository.vcs'] = $repo->getVersionControlSystem();
+ $results['repository.uri'] = $repo->getPublicCloneURI();
+ }
}
return $results;
diff --git a/src/applications/harbormaster/__tests__/HarbormasterAutotargetsTestCase.php b/src/applications/harbormaster/__tests__/HarbormasterAutotargetsTestCase.php
new file mode 100644
--- /dev/null
+++ b/src/applications/harbormaster/__tests__/HarbormasterAutotargetsTestCase.php
@@ -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);
+ }
+
+}
diff --git a/src/applications/harbormaster/autoplan/HarbormasterBuildArcanistAutoplan.php b/src/applications/harbormaster/autoplan/HarbormasterBuildArcanistAutoplan.php
new file mode 100644
--- /dev/null
+++ b/src/applications/harbormaster/autoplan/HarbormasterBuildArcanistAutoplan.php
@@ -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');
+ }
+
+}
diff --git a/src/applications/harbormaster/autoplan/HarbormasterBuildAutoplan.php b/src/applications/harbormaster/autoplan/HarbormasterBuildAutoplan.php
new file mode 100644
--- /dev/null
+++ b/src/applications/harbormaster/autoplan/HarbormasterBuildAutoplan.php
@@ -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;
+ }
+
+}
diff --git a/src/applications/harbormaster/conduit/HarbormasterQueryAutotargetsConduitAPIMethod.php b/src/applications/harbormaster/conduit/HarbormasterQueryAutotargetsConduitAPIMethod.php
new file mode 100644
--- /dev/null
+++ b/src/applications/harbormaster/conduit/HarbormasterQueryAutotargetsConduitAPIMethod.php
@@ -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,
+ );
+ }
+
+}
diff --git a/src/applications/harbormaster/controller/HarbormasterStepAddController.php b/src/applications/harbormaster/controller/HarbormasterStepAddController.php
--- a/src/applications/harbormaster/controller/HarbormasterStepAddController.php
+++ b/src/applications/harbormaster/controller/HarbormasterStepAddController.php
@@ -24,10 +24,17 @@
$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 @@
$control = id(new AphrontFormRadioButtonControl())
->setName('class');
- $all = HarbormasterBuildStepImplementation::getImplementations();
foreach ($all as $class => $implementation) {
$control->addButton(
$class,
diff --git a/src/applications/harbormaster/controller/HarbormasterStepEditController.php b/src/applications/harbormaster/controller/HarbormasterStepEditController.php
--- a/src/applications/harbormaster/controller/HarbormasterStepEditController.php
+++ b/src/applications/harbormaster/controller/HarbormasterStepEditController.php
@@ -47,6 +47,11 @@
return new Aphront404Response();
}
+ if ($impl->shouldRequireAutotargeting()) {
+ // No manual creation of autotarget steps.
+ return new Aphront404Response();
+ }
+
$step = HarbormasterBuildStep::initializeNewStep($viewer)
->setBuildPlanPHID($plan->getPHID())
->setClassName($class);
diff --git a/src/applications/harbormaster/engine/HarbormasterTargetEngine.php b/src/applications/harbormaster/engine/HarbormasterTargetEngine.php
new file mode 100644
--- /dev/null
+++ b/src/applications/harbormaster/engine/HarbormasterTargetEngine.php
@@ -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;
+ }
+
+
+}
diff --git a/src/applications/harbormaster/query/HarbormasterBuildPlanQuery.php b/src/applications/harbormaster/query/HarbormasterBuildPlanQuery.php
--- a/src/applications/harbormaster/query/HarbormasterBuildPlanQuery.php
+++ b/src/applications/harbormaster/query/HarbormasterBuildPlanQuery.php
@@ -7,6 +7,8 @@
private $phids;
private $statuses;
private $datasourceQuery;
+ private $planAutoKeys;
+ private $needBuildSteps;
public function withIDs(array $ids) {
$this->ids = $ids;
@@ -28,6 +30,16 @@
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 @@
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 @@
$this->datasourceQuery);
}
+ if ($this->planAutoKeys !== null) {
+ $where[] = qsprintf(
+ $conn,
+ 'planAutoKey IN (%Ls)',
+ $this->planAutoKeys);
+ }
+
return $where;
}
diff --git a/src/applications/harbormaster/query/HarbormasterBuildPlanSearchEngine.php b/src/applications/harbormaster/query/HarbormasterBuildPlanSearchEngine.php
--- a/src/applications/harbormaster/query/HarbormasterBuildPlanSearchEngine.php
+++ b/src/applications/harbormaster/query/HarbormasterBuildPlanSearchEngine.php
@@ -88,6 +88,10 @@
$item->setDisabled(true);
}
+ if ($plan->isAutoplan()) {
+ $item->addIcon('fa-lock grey', pht('Autoplan'));
+ }
+
$item->setHref($this->getApplicationURI("plan/{$id}/"));
$list->addItem($item);
diff --git a/src/applications/harbormaster/step/HarbormasterArcLintBuildStepImplementation.php b/src/applications/harbormaster/step/HarbormasterArcLintBuildStepImplementation.php
new file mode 100644
--- /dev/null
+++ b/src/applications/harbormaster/step/HarbormasterArcLintBuildStepImplementation.php
@@ -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;
+ }
+
+}
diff --git a/src/applications/harbormaster/step/HarbormasterArcUnitBuildStepImplementation.php b/src/applications/harbormaster/step/HarbormasterArcUnitBuildStepImplementation.php
new file mode 100644
--- /dev/null
+++ b/src/applications/harbormaster/step/HarbormasterArcUnitBuildStepImplementation.php
@@ -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;
+ }
+
+}
diff --git a/src/applications/harbormaster/step/HarbormasterBuildStepImplementation.php b/src/applications/harbormaster/step/HarbormasterBuildStepImplementation.php
--- a/src/applications/harbormaster/step/HarbormasterBuildStepImplementation.php
+++ b/src/applications/harbormaster/step/HarbormasterBuildStepImplementation.php
@@ -1,5 +1,8 @@
<?php
+/**
+ * @task autotarget Automatic Targets
+ */
abstract class HarbormasterBuildStepImplementation extends Phobject {
private $settings;
@@ -249,4 +252,20 @@
}
}
+
+/* -( Automatic Targets )-------------------------------------------------- */
+
+
+ public function getBuildStepAutotargetStepKey() {
+ return null;
+ }
+
+ public function getBuildStepAutotargetPlanKey() {
+ throw new PhutilMethodNotImplementedException();
+ }
+
+ public function shouldRequireAutotargeting() {
+ return false;
+ }
+
}
diff --git a/src/applications/harbormaster/storage/HarbormasterBuildable.php b/src/applications/harbormaster/storage/HarbormasterBuildable.php
--- a/src/applications/harbormaster/storage/HarbormasterBuildable.php
+++ b/src/applications/harbormaster/storage/HarbormasterBuildable.php
@@ -141,8 +141,14 @@
$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',
diff --git a/src/applications/harbormaster/storage/build/HarbormasterBuild.php b/src/applications/harbormaster/storage/build/HarbormasterBuild.php
--- a/src/applications/harbormaster/storage/build/HarbormasterBuild.php
+++ b/src/applications/harbormaster/storage/build/HarbormasterBuild.php
@@ -9,6 +9,7 @@
protected $buildPlanPHID;
protected $buildStatus;
protected $buildGeneration;
+ protected $planAutoKey;
private $buildable = self::ATTACHABLE;
private $buildPlan = self::ATTACHABLE;
@@ -148,6 +149,7 @@
self::CONFIG_COLUMN_SCHEMA => array(
'buildStatus' => 'text32',
'buildGeneration' => 'uint32',
+ 'planAutoKey' => 'text32?',
),
self::CONFIG_KEY_SCHEMA => array(
'key_buildable' => array(
@@ -159,6 +161,10 @@
'key_status' => array(
'columns' => array('buildStatus'),
),
+ 'key_planautokey' => array(
+ 'columns' => array('buildablePHID', 'planAutoKey'),
+ 'unique' => true,
+ ),
),
) + parent::getConfiguration();
}
diff --git a/src/applications/harbormaster/storage/build/HarbormasterBuildTarget.php b/src/applications/harbormaster/storage/build/HarbormasterBuildTarget.php
--- a/src/applications/harbormaster/storage/build/HarbormasterBuildTarget.php
+++ b/src/applications/harbormaster/storage/build/HarbormasterBuildTarget.php
@@ -175,8 +175,16 @@
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;
}
diff --git a/src/applications/harbormaster/storage/configuration/HarbormasterBuildPlan.php b/src/applications/harbormaster/storage/configuration/HarbormasterBuildPlan.php
--- a/src/applications/harbormaster/storage/configuration/HarbormasterBuildPlan.php
+++ b/src/applications/harbormaster/storage/configuration/HarbormasterBuildPlan.php
@@ -1,5 +1,8 @@
<?php
+/**
+ * @task autoplan Autoplans
+ */
final class HarbormasterBuildPlan extends HarbormasterDAO
implements
PhabricatorApplicationTransactionInterface,
@@ -8,6 +11,9 @@
protected $name;
protected $planStatus;
+ protected $planAutoKey;
+
+ const PLANKEY_CORE = 'core';
const STATUS_ACTIVE = 'active';
const STATUS_DISABLED = 'disabled';
@@ -16,7 +22,9 @@
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 +33,7 @@
self::CONFIG_COLUMN_SCHEMA => array(
'name' => 'sort128',
'planStatus' => 'text32',
+ 'planAutoKey' => 'text32?',
),
self::CONFIG_KEY_SCHEMA => array(
'key_status' => array(
@@ -33,6 +42,10 @@
'key_name' => array(
'columns' => array('name'),
),
+ 'key_planautokey' => array(
+ 'columns' => array('planAutoKey'),
+ 'unique' => true,
+ ),
),
) + parent::getConfiguration();
}
@@ -57,6 +70,33 @@
}
+/* -( 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 +153,11 @@
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 +167,19 @@
}
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;
}
}
diff --git a/src/applications/harbormaster/storage/configuration/HarbormasterBuildStep.php b/src/applications/harbormaster/storage/configuration/HarbormasterBuildStep.php
--- a/src/applications/harbormaster/storage/configuration/HarbormasterBuildStep.php
+++ b/src/applications/harbormaster/storage/configuration/HarbormasterBuildStep.php
@@ -12,13 +12,16 @@
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 @@
// 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 @@
return $this->implementation;
}
+ public function isAutostep() {
+ return ($this->getStepAutoKey() !== null);
+ }
+
/* -( PhabricatorApplicationTransactionInterface )------------------------- */

File Metadata

Mime Type
text/plain
Expires
Tue, May 21, 10:46 PM (1 w, 5 d ago)
Storage Engine
amazon-s3
Storage Format
Encrypted (AES-256-CBC)
Storage Handle
phabricator/secure/6i/w7/w7znc52xactvi2bp
Default Alt Text
D13345.id32288.diff (38 KB)

Event Timeline