• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

pantsbuild / pants / 18252174847

05 Oct 2025 01:36AM UTC coverage: 43.382% (-36.9%) from 80.261%
18252174847

push

github

web-flow
run tests on mac arm (#22717)

Just doing the minimal to pull forward the x86_64 pattern.

ref #20993

25776 of 59416 relevant lines covered (43.38%)

1.3 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

79.55
/src/python/pants/engine/goal.py
1
# Copyright 2019 Pants project contributors (see CONTRIBUTORS.md).
2
# Licensed under the Apache License, Version 2.0 (see LICENSE).
3
from __future__ import annotations
3✔
4

5
from abc import abstractmethod
3✔
6
from collections.abc import Callable, Iterator
3✔
7
from contextlib import contextmanager
3✔
8
from dataclasses import dataclass, field
3✔
9
from enum import Enum
3✔
10
from typing import TYPE_CHECKING, ClassVar, cast, final
3✔
11

12
from pants.engine.engine_aware import EngineAwareReturnType
3✔
13
from pants.engine.unions import UnionMembership
3✔
14
from pants.option.option_types import StrOption
3✔
15
from pants.option.scope import ScopeInfo
3✔
16
from pants.option.subsystem import Subsystem
3✔
17
from pants.util.docutil import doc_url
3✔
18
from pants.util.meta import classproperty
3✔
19

20
if TYPE_CHECKING:
21
    from pants.engine.console import Console
22

23

24
class GoalSubsystem(Subsystem):
3✔
25
    """The Subsystem used by `Goal`s to register the external API, meaning the goal name, the help
26
    message, and any options.
27

28
    This class should be subclassed and given a `GoalSubsystem.name` that it will be referred to by
29
    when invoked from the command line. The `Goal.name` also acts as the options_scope for the Goal.
30

31
    Rules that need to consume the GoalSubsystem's options may directly request the type:
32

33
    ```
34
    @rule
35
    async def list(console: Console, list_subsystem: ListSubsystem) -> List:
36
      transitive = list_subsystem.transitive
37
      documented = list_subsystem.documented
38
      ...
39
    ```
40
    """
41

42
    @classmethod
3✔
43
    def activated(cls, union_membership: UnionMembership) -> bool:
3✔
44
        """Return `False` if this goal should not show up in `./pants help`.
45

46
        Usually this is determined by checking `MyType in union_membership`.
47
        """
48
        return True
×
49

50
    @classmethod
3✔
51
    def create_scope_info(cls, **scope_info_kwargs) -> ScopeInfo:
3✔
52
        return super().create_scope_info(is_goal=True, **scope_info_kwargs)
3✔
53

54
    @classproperty
3✔
55
    @abstractmethod
3✔
56
    def name(cls):
3✔
57
        """The name used to select the corresponding Goal on the commandline and the options_scope
58
        for its options."""
59

60
    @classproperty
3✔
61
    def options_scope(cls) -> str:
3✔
62
        return cast(str, cls.name)
3✔
63

64

65
@dataclass(frozen=True)
3✔
66
class Goal:
3✔
67
    """The named product of a `@goal_rule`.
68

69
    This class should be subclassed and linked to a corresponding `GoalSubsystem`:
70

71
    ```
72
    class ListSubsystem(GoalSubsystem):
73
      '''List targets.'''
74
      name = "list"
75

76
    class List(Goal):
77
      subsystem_cls = ListSubsystem
78
    ```
79

80
    Since `@goal_rules` always run in order to produce side effects (generally: console output),
81
    they are not cacheable, and the `Goal` product of a `@goal_rule` contains only an exit_code
82
    value to indicate whether the rule exited cleanly.
83
    """
84

85
    class EnvironmentBehavior(Enum):
3✔
86
        """Indicates that the goal will always operate on the local environment target.
87

88
        This is largely the same behavior as Pants has had pre-2.15.
89
        """
90

91
        LOCAL_ONLY = 2
3✔
92

93
        f""" Indicates that the goal chooses the environments to use to execute rules within the goal.
3✔
94

95
        This requires migration work to be done by the goal author. See
96
        {doc_url("docs/writing-plugins/common-plugin-tasks/plugin-upgrade-guide")}.
97
        """
98
        USES_ENVIRONMENTS = 3
3✔
99

100
    exit_code: int
3✔
101
    subsystem_cls: ClassVar[type[GoalSubsystem]]
3✔
102

103
    # A marker that allows initialization code to check for Goal subclasses without
104
    # having to import Goal and use issubclass(), which can cause a dependency cycle.
105
    __goal__ = True
3✔
106

107
    f"""Indicates that a Goal has been migrated to compute EnvironmentNames to build targets in.
3✔
108

109
    All goals in `pantsbuild/pants` should be migrated before the 2.15.x branch is cut, but end
110
    user goals have until `2.17.0.dev4` to migrate.
111

112
    See {doc_url("docs/writing-plugins/common-plugin-tasks/plugin-upgrade-guide")}.
113
    """
114
    environment_behavior: ClassVar[EnvironmentBehavior]
3✔
115

116
    @classmethod
3✔
117
    def _selects_environments(cls) -> bool:
3✔
118
        return cls.environment_behavior == Goal.EnvironmentBehavior.USES_ENVIRONMENTS
3✔
119

120
    @final
3✔
121
    @classproperty
3✔
122
    def name(cls) -> str:
3✔
123
        return cast(str, cls.subsystem_cls.name)
3✔
124

125

126
class Outputting:
3✔
127
    """A mixin for Goal that adds options to support output-related context managers.
128

129
    Allows output to go to a file or to stdout.
130

131
    Useful for goals whose purpose is to emit output to the end user (as distinct from incidental logging to stderr).
132
    """
133

134
    output_file = StrOption(
3✔
135
        default=None,
136
        metavar="<path>",
137
        help="Output the goal's stdout to this file. If unspecified, outputs to stdout.",
138
    )
139

140
    @final
3✔
141
    @contextmanager
3✔
142
    def output(self, console: Console) -> Iterator[Callable[[str], None]]:
3✔
143
        """Given a Console, yields a function for writing data to stdout, or a file.
144

145
        The passed options instance will generally be the `Goal.Options` of an `Outputting` `Goal`.
146
        """
147
        with self.output_sink(console) as output_sink:
×
148
            yield lambda msg: output_sink.write(msg)
×
149

150
    @final
3✔
151
    @contextmanager
3✔
152
    def output_sink(self, console: Console) -> Iterator:
3✔
153
        stdout_file = None
×
154
        if self.output_file:
×
155
            stdout_file = open(self.output_file, "w")
×
156
            output_sink = stdout_file
×
157
        else:
158
            output_sink = console.stdout  # type: ignore[assignment]
×
159
        try:
×
160
            yield output_sink
×
161
        finally:
162
            output_sink.flush()
×
163
            if stdout_file:
×
164
                stdout_file.close()
×
165

166

167
class LineOriented(Outputting):
3✔
168
    sep = StrOption(
3✔
169
        default="\\n",
170
        metavar="<separator>",
171
        help="String to use to separate lines in line-oriented output.",
172
    )
173

174
    @final
3✔
175
    @contextmanager
3✔
176
    def line_oriented(self, console: Console) -> Iterator[Callable[[str], None]]:
3✔
177
        """Given a Console, yields a function for printing lines to stdout or a file.
178

179
        The passed options instance will generally be the `Goal.Options` of an `Outputting` `Goal`.
180
        """
181
        sep = self.sep.encode().decode("unicode_escape")
×
182
        with self.output_sink(console) as output_sink:
×
183
            yield lambda msg: print(msg, file=output_sink, end=sep)
×
184

185

186
@dataclass(frozen=True)
3✔
187
class CurrentExecutingGoals(EngineAwareReturnType):
3✔
188
    executing: dict[str, type[Goal]] = field(default_factory=dict)
3✔
189

190
    def __hash__(self) -> int:
3✔
191
        return hash(tuple(self.executing.keys()))
3✔
192

193
    def is_running(self, goal: str) -> bool:
3✔
194
        return goal in self.executing
×
195

196
    @contextmanager
3✔
197
    def _execute(self, goal: type[Goal]) -> Iterator[None]:
3✔
198
        self.executing[goal.name] = goal
3✔
199
        try:
3✔
200
            yield
3✔
201
        finally:
202
            self.executing.pop(goal.name, None)
3✔
203

204
    def cacheable(self) -> bool:
3✔
205
        return False
×
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc