Skip to content

Commit

Permalink
Replace the goals goal with a help plugin which supports both v1 …
Browse files Browse the repository at this point in the history
…and v2 (#7598)

### Problem

In #6880, there will be two types of Goals:
1. v1 `pants.goal.goal.Goal`s, registered on a singleton
2. v2 `pants.engine.goal.Goal`s, registered as a `ScopeInfo` category via `@console_rules`.

But `./pants goals` supports only the first form (via access to the singleton), and would need to dig deep into `self.context` in order to get access to the scope info (...like `./pants options` does: but that's a story for another day!).

### Solution

Convert `./pants goals` into a plugin to the arg splitter, similar to `./pants help`. While the arg-splitter method is not incredibly scalable, it seems like it should be sufficiently scalable to support most of our "meta-goals". 

### Result

v1 and v2 Goals are supported, we have one fewer v1 Task, and #6880 is unblocked.
  • Loading branch information
Stu Hood authored Apr 20, 2019
1 parent 9f3f01a commit d31c194
Show file tree
Hide file tree
Showing 12 changed files with 76 additions and 260 deletions.
2 changes: 1 addition & 1 deletion src/docs/docs.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ of the task class.

**Option help** When registering a `Task` option, pass a `help` parameter to describe that option.

!inc[start-at=register_options&end-at=help=](../python/pants/core_tasks/list_goals.py)
!inc[start-at=register_options&end-at=help=](../python/pants/backend/project_info/tasks/export.py)

### Target types and other `BUILD` file symbols

Expand Down
88 changes: 0 additions & 88 deletions src/python/pants/core_tasks/list_goals.py

This file was deleted.

2 changes: 0 additions & 2 deletions src/python/pants/core_tasks/register.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
from pants.core_tasks.deferred_sources_mapper import DeferredSourcesMapper
from pants.core_tasks.explain_options_task import ExplainOptionsTask
from pants.core_tasks.generate_pants_ini import GeneratePantsIni
from pants.core_tasks.list_goals import ListGoals
from pants.core_tasks.login import Login
from pants.core_tasks.noop import NoopCompile, NoopTest
from pants.core_tasks.pantsd_kill import PantsDaemonKill
Expand Down Expand Up @@ -73,7 +72,6 @@ def register_goals():
task(name='login', action=Login).install()

# Getting help.
task(name='goals', action=ListGoals).install()
task(name='options', action=ExplainOptionsTask).install()
task(name='targets', action=TargetsHelp).install()

Expand Down
9 changes: 6 additions & 3 deletions src/python/pants/goal/goal.py
Original file line number Diff line number Diff line change
Expand Up @@ -157,11 +157,14 @@ def description(self):
namesake_task = self._task_type_by_name.get(self.name)
if namesake_task and namesake_task.__doc__:
# First line of docstring.
# TODO: This is repetitive of Optionable.get_description(). We should probably just
# make Goal an Optionable, for uniformity.
return namesake_task.__doc__.partition('\n')[0].strip()
return namesake_task.__doc__
return ''

@property
def description_first_line(self):
# TODO: This is repetitive of Optionable.get_description(), which is used by v2 Goals.
return self.description.partition('\n')[0].strip()

def register_options(self, options):
if self._options_registrar_cls:
self._options_registrar_cls.register_options_on_scope(options)
Expand Down
9 changes: 9 additions & 0 deletions src/python/pants/help/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,17 @@ python_library(
'src/python/pants/base:build_environment',
'src/python/pants/base:exceptions',
'src/python/pants/build_graph',
'src/python/pants/goal',
'src/python/pants/option',
'src/python/pants/subsystem',
'src/python/pants/util:memo',
]
)

python_tests(
name='integration',
dependencies=[
'tests/python/pants_test:int-test',
],
tags = {'integration'},
)
25 changes: 23 additions & 2 deletions src/python/pants/help/help_printer.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,11 @@
from builtins import object

from pants.base.build_environment import pants_release, pants_version
from pants.goal.goal import Goal
from pants.help.help_formatter import HelpFormatter
from pants.help.scope_info_iterator import ScopeInfoIterator
from pants.option.arg_splitter import (GLOBAL_SCOPE, NoGoalHelp, OptionsHelp, UnknownGoalHelp,
VersionHelp)
from pants.option.arg_splitter import (GLOBAL_SCOPE, GoalsHelp, NoGoalHelp, OptionsHelp,
UnknownGoalHelp, VersionHelp)
from pants.option.scope import ScopeInfo


Expand All @@ -37,6 +38,8 @@ def print_hint():
print(pants_version())
elif isinstance(self._help_request, OptionsHelp):
self._print_options_help()
elif isinstance(self._help_request, GoalsHelp):
self._print_goals_help()
elif isinstance(self._help_request, UnknownGoalHelp):
print('Unknown goals: {}'.format(', '.join(self._help_request.unknown_goals)))
print_hint()
Expand All @@ -47,6 +50,24 @@ def print_hint():
return 1
return 0

def _print_goals_help(self):
print('\nUse `pants help $goal` to get help for a particular goal.\n')
goal_descriptions = {}
for scope_info in self._options.known_scope_to_info.values():
if scope_info.category not in (ScopeInfo.GOAL,):
continue
components = scope_info.scope.split('.', 1)
if len(components) == 1 and scope_info.description:
goal_descriptions[scope_info.scope] = scope_info.description
goal_descriptions.update({goal.name: goal.description_first_line
for goal in Goal.all()
if goal.description})

max_width = max(len(name) for name in goal_descriptions.keys())
for name, description in sorted(goal_descriptions.items()):
print(' {}: {}'.format(name.rjust(max_width), description))
print()

def _print_options_help(self):
"""Print a help screen.
Expand Down
26 changes: 26 additions & 0 deletions src/python/pants/help/test_list_goals_integration.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# coding=utf-8
# Copyright 2019 Pants project contributors (see CONTRIBUTORS.md).
# Licensed under the Apache License, Version 2.0 (see LICENSE).

from __future__ import absolute_import, division, print_function, unicode_literals

from pants_test.pants_run_integration_test import PantsRunIntegrationTest


class TestListGoalsIntegration(PantsRunIntegrationTest):
def test_goals(self):
command = ['goals']
pants_run = self.run_pants(command=command)
self.assert_success(pants_run)
self.assertIn('to get help for a particular goal', pants_run.stdout_data)
# Spot check a few core goals.
self.assertIn('list:', pants_run.stdout_data)
self.assertIn('test:', pants_run.stdout_data)
self.assertIn(' fmt:', pants_run.stdout_data)

def test_ignored_args(self):
# Test that arguments (some of which used to be relevant) are ignored.
command = ['goals', '--all', '--graphviz', '--llama']
pants_run = self.run_pants(command=command)
self.assert_success(pants_run)
self.assertIn('to get help for a particular goal', pants_run.stdout_data)
11 changes: 9 additions & 2 deletions src/python/pants/option/arg_splitter.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,10 @@ def __init__(self, advanced=False, all_scopes=False):
self.all_scopes = all_scopes


class GoalsHelp(HelpRequest):
"""The user requested help for installed Goals."""


class VersionHelp(HelpRequest):
"""The user asked for the version of this instance of pants."""

Expand Down Expand Up @@ -86,15 +90,16 @@ class ArgSplitter(object):
_HELP_ADVANCED_ARGS = ('--help-advanced', 'help-advanced')
_HELP_ALL_SCOPES_ARGS = ('--help-all', 'help-all')
_HELP_VERSION_ARGS = ('-v', '-V', '--version')
_HELP_ARGS = _HELP_BASIC_ARGS + _HELP_ADVANCED_ARGS + _HELP_ALL_SCOPES_ARGS + _HELP_VERSION_ARGS
_HELP_GOALS_ARGS = ('goals',)
_HELP_ARGS = _HELP_BASIC_ARGS + _HELP_ADVANCED_ARGS + _HELP_ALL_SCOPES_ARGS + _HELP_VERSION_ARGS + _HELP_GOALS_ARGS

def __init__(self, known_scope_infos):
self._known_scope_infos = known_scope_infos
# TODO: Get rid of our reliance on known scopes here. We don't really need it now
# that we heuristically identify target specs based on it containing /, : or being
# a top-level directory.
self._known_scopes = ({si.scope for si in known_scope_infos} |
{'help', 'help-advanced', 'help-all'})
{'goals', 'help', 'help-advanced', 'help-all'})
self._unknown_scopes = []
self._unconsumed_args = [] # In reverse order, for efficient popping off the end.
self._help_request = None # Will be set if we encounter any help flags.
Expand All @@ -119,6 +124,8 @@ def _check_for_help_request(self, arg):
return False
if arg in self._HELP_VERSION_ARGS:
self._help_request = VersionHelp()
elif arg in self._HELP_GOALS_ARGS:
self._help_request = GoalsHelp()
else:
# First ensure that we have a basic OptionsHelp.
if not self._help_request:
Expand Down
12 changes: 0 additions & 12 deletions tests/python/pants_test/core_tasks/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -23,18 +23,6 @@ python_tests(
tags = {'integration'},
)

python_tests(
name = 'list_goals',
sources = ['test_list_goals.py'],
dependencies = [
'src/python/pants/core_tasks',
'src/python/pants/goal',
'src/python/pants/goal:task_registrar',
'src/python/pants/task',
'tests/python/pants_test:task_test_base',
],
)

python_tests(
name = 'generate_pants_ini',
sources = ['test_generate_pants_ini.py'],
Expand Down
Loading

0 comments on commit d31c194

Please sign in to comment.