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

pantsbuild / pants / 20147226056

11 Dec 2025 08:58PM UTC coverage: 78.827% (-1.5%) from 80.293%
20147226056

push

github

web-flow
Forwarded the `style` and `complete-platform` args from pants.toml to PEX (#22910)

## Context

After Apple switched to the `arm64` architecture, some package
publishers stopped releasing `x86_64` variants of their packages for
`darwin`. As a result, generating a universal lockfile now fails because
no single package version is compatible with both `x86_64` and `arm64`
on `darwin`.

The solution is to use the `--style` and `--complete-platform` flags
with PEX. For example:
```
pex3 lock create \
    --style strict \
    --complete-platform 3rdparty/platforms/manylinux_2_28_aarch64.json \
    --complete-platform 3rdparty/platforms/macosx_26_0_arm64.json \
    -r 3rdparty/python/requirements_pyarrow.txt \
    -o python-pyarrow.lock
```

See the Slack discussion here:
https://pantsbuild.slack.com/archives/C046T6T9U/p1760098582461759

## Reproduction

* `BUILD`
```
python_requirement(
    name="awswrangler",
    requirements=["awswrangler==3.12.1"],
    resolve="awswrangler",
)
```
* Run `pants generate-lockfiles --resolve=awswrangler` on macOS with an
`arm64` CPU
```
pip: ERROR: Cannot install awswrangler==3.12.1 because these package versions have conflicting dependencies.
pip: ERROR: ResolutionImpossible: for help visit https://pip.pypa.io/en/latest/topics/dependency-resolution/#dealing-with-dependency-conflicts
pip:  
pip:  The conflict is caused by:
pip:      awswrangler 3.12.1 depends on pyarrow<18.0.0 and >=8.0.0; sys_platform == "darwin" and platform_machine == "x86_64"
pip:      awswrangler 3.12.1 depends on pyarrow<21.0.0 and >=18.0.0; sys_platform != "darwin" or platform_machine != "x86_64"
pip:  
pip:  Additionally, some packages in these conflicts have no matching distributions available for your environment:
pip:      pyarrow
pip:  
pip:  To fix this you could try to:
pip:  1. loosen the range of package versions you've specified
pip:  2. remove package versions to allow pip to attempt to solve the dependency conflict
```

## Implementation
... (continued)

77 of 100 new or added lines in 6 files covered. (77.0%)

868 existing lines in 42 files now uncovered.

74471 of 94474 relevant lines covered (78.83%)

3.18 hits per line

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

93.18
/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
11✔
4

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

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

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

23

24
class GoalSubsystem(Subsystem):
11✔
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
11✔
43
    def activated(cls, union_membership: UnionMembership) -> bool:
11✔
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
        """
UNCOV
48
        return True
×
49

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

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

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

64

65
@dataclass(frozen=True)
11✔
66
class Goal:
11✔
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):
11✔
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
11✔
92

93
        f""" Indicates that the goal chooses the environments to use to execute rules within the goal.
11✔
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
11✔
99

100
    exit_code: int
11✔
101
    subsystem_cls: ClassVar[type[GoalSubsystem]]
11✔
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
11✔
106

107
    f"""Indicates that a Goal has been migrated to compute EnvironmentNames to build targets in.
11✔
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]
11✔
115

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

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

125

126
class Outputting:
11✔
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(
11✔
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
11✔
141
    @contextmanager
11✔
142
    def output(self, console: Console) -> Iterator[Callable[[str], None]]:
11✔
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:
1✔
148
            yield lambda msg: output_sink.write(msg)
1✔
149

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

166

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

174
    @final
11✔
175
    @contextmanager
11✔
176
    def line_oriented(self, console: Console) -> Iterator[Callable[[str], None]]:
11✔
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")
2✔
182
        with self.output_sink(console) as output_sink:
2✔
183
            yield lambda msg: print(msg, file=output_sink, end=sep)
2✔
184

185

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

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

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

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

204
    def cacheable(self) -> bool:
11✔
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