• 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

27.78
/src/python/pants/backend/go/conftest.py
1
# Copyright 2022 Pants project contributors (see CONTRIBUTORS.md).
2
# Licensed under the Apache License, Version 2.0 (see LICENSE).
3

4
import os
11✔
5
import subprocess
11✔
6
from functools import lru_cache
11✔
7

8
import pytest
11✔
9

10

11
@lru_cache(None)
11✔
12
def _go_present() -> bool:
11✔
13
    try:
11✔
14
        subprocess.run(
11✔
15
            ["go", "version"], check=False, env={"PATH": os.getenv("PATH") or ""}
16
        ).returncode
17
    except (subprocess.CalledProcessError, FileNotFoundError):
×
18
        return False
×
19
    return True
11✔
20

21

22
def _parse_go_version(version_string: str) -> tuple[int, int] | None:
11✔
23
    """Parse 'go1.24.3' output to (1, 24)."""
UNCOV
24
    if not version_string.startswith("go"):
×
25
        return None
×
26

27
    # Strip "go" prefix and split on "."
UNCOV
28
    version_parts = version_string[2:].split(".")
×
29

UNCOV
30
    if len(version_parts) < 2:
×
31
        return None
×
32

UNCOV
33
    try:
×
UNCOV
34
        major = int(version_parts[0])
×
UNCOV
35
        minor = int(version_parts[1])
×
UNCOV
36
        return (major, minor)
×
37
    except (ValueError, IndexError):
×
38
        return None
×
39

40

41
@lru_cache(None)
11✔
42
def _discover_go_binaries() -> list[tuple[str, tuple[int, int]]]:
11✔
43
    """Discover all Go binaries in PATH and return sorted by version (newest first)."""
UNCOV
44
    path_env = os.getenv("PATH", "")
×
UNCOV
45
    if not path_env:
×
46
        return []
×
47

UNCOV
48
    go_binaries = []
×
UNCOV
49
    seen_paths = set()
×
50

UNCOV
51
    for path_dir in path_env.split(os.pathsep):
×
UNCOV
52
        if not path_dir or not os.path.isdir(path_dir):
×
UNCOV
53
            continue
×
54

UNCOV
55
        go_binary = os.path.join(path_dir, "go")
×
UNCOV
56
        if os.path.isfile(go_binary) and os.access(go_binary, os.X_OK):
×
57
            # Resolve symlinks to avoid duplicates.
UNCOV
58
            real_path = os.path.realpath(go_binary)
×
UNCOV
59
            if real_path in seen_paths:
×
UNCOV
60
                continue
×
UNCOV
61
            seen_paths.add(real_path)
×
62

UNCOV
63
            try:
×
UNCOV
64
                result = subprocess.run(
×
65
                    [go_binary, "env", "GOVERSION"],
66
                    capture_output=True,
67
                    text=True,
68
                    timeout=5,
69
                    check=False,
70
                )
UNCOV
71
                if result.returncode == 0:
×
UNCOV
72
                    version = _parse_go_version(result.stdout.strip())
×
UNCOV
73
                    if version:
×
UNCOV
74
                        go_binaries.append((go_binary, version))
×
75
            except (subprocess.TimeoutExpired, FileNotFoundError, PermissionError):
×
76
                continue
×
77

78
    # Sort by version (newest first).
UNCOV
79
    go_binaries.sort(key=lambda x: x[1], reverse=True)
×
UNCOV
80
    return go_binaries
×
81

82

83
def pytest_generate_tests(metafunc):
11✔
84
    """Parametrize tests that require specific Go versions."""
85
    # Check if the test has the require_go_version_max marker.
86
    marker = metafunc.definition.get_closest_marker("require_go_version_max")
11✔
87
    if not marker:
11✔
88
        return
11✔
89

90
    # Extract the maximum version from the marker.
UNCOV
91
    if len(marker.args) < 2:
×
92
        pytest.fail(
×
93
            f"require_go_version_max marker requires 2 args (major, minor), got {marker.args}"
94
        )
95
        return
×
96

UNCOV
97
    max_major, max_minor = marker.args[0], marker.args[1]
×
UNCOV
98
    max_version = (max_major, max_minor)
×
99

100
    # Discover available Go binaries
UNCOV
101
    available_go = _discover_go_binaries()
×
102

103
    # Filter for compatible versions (<= max_version)
UNCOV
104
    compatible_go = [(path, version) for path, version in available_go if version <= max_version]
×
105

106
    # Parametrize the test if it accepts a `go_binary_path` fixture.
UNCOV
107
    if "go_binary_path" in metafunc.fixturenames:
×
UNCOV
108
        if compatible_go:
×
109
            # Use the newest compatible version.
UNCOV
110
            best_go_path, best_version = compatible_go[0]
×
UNCOV
111
            metafunc.parametrize(
×
112
                "go_binary_path",
113
                [best_go_path],
114
                ids=[f"go{best_version[0]}.{best_version[1]}"],
115
            )
116
        else:
117
            # Parametrize with None and mark to skip, so the test shows as skipped rather than uncollected.
118
            available_versions = [f"{v[0]}.{v[1]}" for _, v in available_go]
×
119
            skip_msg = (
×
120
                f"Test requires Go <= {max_major}.{max_minor}, but only found: "
121
                f"{', '.join(available_versions) if available_versions else 'none'}"
122
            )
123
            metafunc.parametrize(
×
124
                "go_binary_path",
125
                [pytest.param(None, marks=pytest.mark.skip(reason=skip_msg))],
126
                ids=["no-compatible-go"],
127
            )
128

129

130
def pytest_configure(config):
11✔
131
    """Register custom markers."""
132
    config.addinivalue_line(
11✔
133
        "markers",
134
        "require_go_version_max(major, minor): mark test to require Go version <= specified",
135
    )
136

137

138
def pytest_runtest_setup(item: pytest.Item) -> None:
11✔
139
    if not _go_present():
11✔
140
        pytest.skip(reason="`go` not present on PATH")
×
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