Skip to content

Commit

Permalink
[internal] Introduce new BuiltinGoal subsystem type. (#13991)
Browse files Browse the repository at this point in the history
Refactoring how the help system is integrated with the arg spllitter, to open up for adding more builtin goals without bloating the arg splitter by introducing a new `BuiltinGoal` subsystem type.
  • Loading branch information
kaos authored Jan 11, 2022
1 parent 60cf171 commit 0238387
Show file tree
Hide file tree
Showing 10 changed files with 375 additions and 160 deletions.
48 changes: 22 additions & 26 deletions src/python/pants/bin/local_pants_runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,16 +22,13 @@
WorkunitsCallback,
WorkunitsCallbackFactories,
)
from pants.engine.target import RegisteredTargetTypes
from pants.engine.unions import UnionMembership
from pants.goal.builtin_goal import BuiltinGoal
from pants.goal.run_tracker import RunTracker
from pants.help.help_info_extracter import HelpInfoExtracter
from pants.help.help_printer import HelpPrinter
from pants.init.engine_initializer import EngineInitializer, GraphScheduler, GraphSession
from pants.init.logging import stdio_destination_use_color
from pants.init.options_initializer import OptionsInitializer
from pants.init.specs_calculator import calculate_specs
from pants.option.arg_splitter import HelpRequest
from pants.option.global_options import DynamicRemoteOptions
from pants.option.options import Options
from pants.option.options_bootstrapper import OptionsBootstrapper
Expand Down Expand Up @@ -126,8 +123,10 @@ def create(
# Option values are usually computed lazily on demand, but command line options are
# eagerly computed for validation.
with options_initializer.handle_unknown_flags(options_bootstrapper, env, raise_=True):
for scope in options.scope_to_flags.keys():
options.for_scope(scope)
for scope, values in options.scope_to_flags.items():
if values:
# Only compute values if there were any command line options presented.
options.for_scope(scope)

# Verify configs.
global_bootstrap_options = options_bootstrapper.bootstrap_options.for_global_scope()
Expand Down Expand Up @@ -198,24 +197,6 @@ def _perform_run_body(self, goals: tuple[str, ...], poll: bool) -> ExitCode:
def _finish_run(self, code: ExitCode) -> None:
"""Cleans up the run tracker."""

def _print_help(self, request: HelpRequest) -> ExitCode:
global_options = self.options.for_global_scope()

all_help_info = HelpInfoExtracter.get_all_help_info(
self.options,
self.union_membership,
self.graph_session.goal_consumed_subsystem_scopes,
RegisteredTargetTypes.create(self.build_config.target_types),
self.build_config,
)
help_printer = HelpPrinter(
bin_name=global_options.pants_bin_name,
help_request=request,
all_help_info=all_help_info,
color=global_options.colors,
)
return help_printer.print_help()

def _get_workunits_callbacks(self) -> tuple[WorkunitsCallback, ...]:
# Load WorkunitsCallbacks by requesting WorkunitsCallbackFactories, and then constructing
# a per-run instance of each WorkunitsCallback.
Expand All @@ -224,10 +205,25 @@ def _get_workunits_callbacks(self) -> tuple[WorkunitsCallback, ...]:
)
return tuple(filter(bool, (wcf.callback_factory() for wcf in workunits_callback_factories)))

def _run_builtin_goal(self, builtin_goal: str) -> ExitCode:
scope_info = self.options.known_scope_to_info[builtin_goal]
assert scope_info.subsystem_cls
scoped_options = self.options.for_scope(builtin_goal)
goal = scope_info.subsystem_cls(scoped_options)
assert isinstance(goal, BuiltinGoal)
return goal.run(
build_config=self.build_config,
graph_session=self.graph_session,
options=self.options,
specs=self.specs,
union_membership=self.union_membership,
)

def _run_inner(self) -> ExitCode:
if self.options.builtin_goal:
return self._run_builtin_goal(self.options.builtin_goal)

goals = tuple(self.options.goals)
if self.options.help_request:
return self._print_help(self.options.help_request)
if not goals:
return PANTS_SUCCEEDED_EXIT_CODE

Expand Down
42 changes: 42 additions & 0 deletions src/python/pants/goal/builtin_goal.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# Copyright 2021 Pants project contributors (see CONTRIBUTORS.md).
# Licensed under the Apache License, Version 2.0 (see LICENSE).

from __future__ import annotations

from abc import ABC, abstractmethod
from typing import ClassVar

from pants.base.exiter import ExitCode
from pants.base.specs import Specs
from pants.build_graph.build_configuration import BuildConfiguration
from pants.engine.goal import GoalSubsystem
from pants.engine.unions import UnionMembership
from pants.init.engine_initializer import GraphSession
from pants.option.options import Options
from pants.option.scope import ScopeInfo


class BuiltinGoal(ABC, GoalSubsystem):
"""Builtin goals have precedence over regular goal rules.
If a builtin goal is invoked, any remaining arguments are passed unaltered to the builtin goal.
"""

# Used by `pants.option.arg_splitter.ArgSplitter()` to optionally allow aliasing builtin goals.
aliases: ClassVar[tuple[str, ...]] = ()

@classmethod
def create_scope_info(cls, **scope_info_kwargs) -> ScopeInfo:
return super().create_scope_info(is_builtin=True, **scope_info_kwargs)

@abstractmethod
def run(
self,
*,
build_config: BuildConfiguration,
graph_session: GraphSession,
options: Options,
specs: Specs,
union_membership: UnionMembership,
) -> ExitCode:
pass
23 changes: 23 additions & 0 deletions src/python/pants/goal/builtins.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Copyright 2021 Pants project contributors (see CONTRIBUTORS.md).
# Licensed under the Apache License, Version 2.0 (see LICENSE).

from __future__ import annotations

from pants.build_graph.build_configuration import BuildConfiguration
from pants.goal import help
from pants.goal.builtin_goal import BuiltinGoal


def register_builtin_goals(build_configuration: BuildConfiguration.Builder) -> None:
build_configuration.register_subsystems("pants.goal", builtin_goals())


def builtin_goals() -> tuple[type[BuiltinGoal], ...]:
return (
help.AllHelpBuiltinGoal,
help.NoGoalHelpBuiltinGoal,
help.ThingHelpBuiltinGoal,
help.ThingHelpAdvancedBuiltinGoal,
help.UnknownGoalHelpBuiltinGoal,
help.VersionHelpBuiltinGoal,
)
117 changes: 117 additions & 0 deletions src/python/pants/goal/help.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
# Copyright 2021 Pants project contributors (see CONTRIBUTORS.md).
# Licensed under the Apache License, Version 2.0 (see LICENSE).

from __future__ import annotations

from abc import abstractmethod

from pants.base.exiter import ExitCode
from pants.base.specs import Specs
from pants.build_graph.build_configuration import BuildConfiguration
from pants.engine.target import RegisteredTargetTypes
from pants.engine.unions import UnionMembership
from pants.goal.builtin_goal import BuiltinGoal
from pants.help.help_info_extracter import HelpInfoExtracter
from pants.help.help_printer import HelpPrinter
from pants.init.engine_initializer import GraphSession
from pants.option.arg_splitter import (
NO_GOAL_NAME,
UNKNOWN_GOAL_NAME,
AllHelp,
HelpRequest,
NoGoalHelp,
ThingHelp,
UnknownGoalHelp,
VersionHelp,
)
from pants.option.options import Options


class HelpBuiltinGoalBase(BuiltinGoal):
def run(
self,
build_config: BuildConfiguration,
graph_session: GraphSession,
options: Options,
specs: Specs,
union_membership: UnionMembership,
) -> ExitCode:
all_help_info = HelpInfoExtracter.get_all_help_info(
options,
union_membership,
graph_session.goal_consumed_subsystem_scopes,
RegisteredTargetTypes.create(build_config.target_types),
build_config,
)
global_options = options.for_global_scope()
help_printer = HelpPrinter(
bin_name=global_options.pants_bin_name,
help_request=self.create_help_request(options),
all_help_info=all_help_info,
color=global_options.colors,
)
return help_printer.print_help()

@abstractmethod
def create_help_request(self, options: Options) -> HelpRequest:
raise NotImplementedError


class AllHelpBuiltinGoal(HelpBuiltinGoalBase):
name = "help-all"
help = "Print a JSON object containing all help info."

def create_help_request(self, options: Options) -> HelpRequest:
return AllHelp()


class NoGoalHelpBuiltinGoal(HelpBuiltinGoalBase):
name = NO_GOAL_NAME
help = "(internal goal not presented on the CLI)"

def create_help_request(self, options: Options) -> HelpRequest:
return NoGoalHelp()


class ThingHelpBuiltinGoal(HelpBuiltinGoalBase):
name = "help"
help = "Display usage message."
aliases = (
"-h",
"--help",
)

def create_help_request(self, options: Options) -> HelpRequest:
return ThingHelp(
advanced=False,
things=tuple(options.goals) + tuple(options.unknown_goals),
)


class ThingHelpAdvancedBuiltinGoal(HelpBuiltinGoalBase):
name = "help-advanced"
help = "Help for advanced options."
aliases = ("--help-advanced",)

def create_help_request(self, options: Options) -> HelpRequest:
return ThingHelp(
advanced=True,
things=tuple(options.goals) + tuple(options.unknown_goals),
)


class UnknownGoalHelpBuiltinGoal(HelpBuiltinGoalBase):
name = UNKNOWN_GOAL_NAME
help = "(internal goal not presented on the CLI)"

def create_help_request(self, options: Options) -> HelpRequest:
return UnknownGoalHelp(tuple(options.unknown_goals))


class VersionHelpBuiltinGoal(HelpBuiltinGoalBase):
name = "version"
help = "Display Pants version."
aliases = ("-v", "-V", "--version")

def create_help_request(self, options: Options) -> HelpRequest:
return VersionHelp()
3 changes: 3 additions & 0 deletions src/python/pants/help/help_info_extracter.py
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,9 @@ def get_all_help_info(
scope_to_help_info = {}
name_to_goal_info = {}
for scope_info in sorted(options.known_scope_to_info.values(), key=lambda x: x.scope):
if scope_info.scope.startswith("_"):
# Exclude "private" subsystems.
continue
options.for_scope(scope_info.scope) # Force parsing.
subsystem_cls = scope_info.subsystem_cls
if not scope_info.description:
Expand Down
2 changes: 2 additions & 0 deletions src/python/pants/init/extension_loader.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

from pants.base.exceptions import BackendConfigurationError
from pants.build_graph.build_configuration import BuildConfiguration
from pants.goal.builtins import register_builtin_goals
from pants.util.ordered_set import FrozenOrderedSet


Expand Down Expand Up @@ -40,6 +41,7 @@ def load_backends_and_plugins(
bc_builder = bc_builder or BuildConfiguration.Builder()
load_build_configuration_from_source(bc_builder, backends)
load_plugins(bc_builder, plugins, working_set)
register_builtin_goals(bc_builder)
return bc_builder.create()


Expand Down
Loading

0 comments on commit 0238387

Please sign in to comment.