Skip to content

Commit 381b64f

Browse files
fix(packaging): guard transitive torch extras on intel macos
1 parent fc0b462 commit 381b64f

3 files changed

Lines changed: 85 additions & 12 deletions

File tree

pyproject.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ ml = [
116116
# [memory]) fail on any machine without a compiler — see #1368.
117117
memory = [
118118
"sqlite-vec>=0.1.6",
119-
"sentence-transformers>=2.2.0,<6.0",
119+
"sentence-transformers>=2.2.0,<6.0; sys_platform != 'darwin' or platform_machine != 'x86_64'",
120120
]
121121
# Optional HNSW vector backend. Needs a C++ toolchain to build hnswlib, so it is
122122
# kept out of [memory] and [all]; opt in with `pip install headroom-ai[vector]`
@@ -225,7 +225,7 @@ voice-train = [
225225
# Evaluation framework
226226
evals = [
227227
"datasets>=2.14.0",
228-
"sentence-transformers>=2.2.0,<6.0",
228+
"sentence-transformers>=2.2.0,<6.0; sys_platform != 'darwin' or platform_machine != 'x86_64'",
229229
"numpy>=1.24.0",
230230
"scikit-learn>=1.3.0",
231231
"anthropic>=0.18.0",

tests/test_optional_dependencies.py

Lines changed: 77 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,31 +2,104 @@
22

33
from pathlib import Path
44

5+
from packaging.markers import default_environment
6+
from packaging.requirements import Requirement
7+
58
try:
69
import tomllib
710
except ModuleNotFoundError: # pragma: no cover - Python 3.10 fallback
811
import tomli as tomllib # type: ignore[no-redef]
912

1013

1114
ROOT = Path(__file__).resolve().parents[1]
15+
ALL_EXTRA = "all"
16+
HEADROOM_PACKAGE_NAME = "headroom-ai"
1217
MACOS_X86_64_TORCH_GUARD = "sys_platform != 'darwin' or platform_machine != 'x86_64'"
18+
MACOS_X86_64_SYS_PLATFORM = "darwin"
19+
MACOS_X86_64_PLATFORM_MACHINE = "x86_64"
20+
PYPROJECT_FILE = "pyproject.toml"
21+
SYS_PLATFORM_MARKER = "sys_platform"
22+
PLATFORM_MACHINE_MARKER = "platform_machine"
23+
TORCH_PACKAGE_NAME = "torch"
24+
TORCH_TRANSITIVE_PACKAGE_NAMES = frozenset({"sentence-transformers"})
25+
UV_LOCK_FILE = "uv.lock"
26+
27+
28+
def _selected_dependency_names_for_extra(
29+
optional_deps: dict[str, list[str]],
30+
extra_name: str,
31+
environment: dict[str, str],
32+
visited: set[str] | None = None,
33+
) -> set[str]:
34+
selected: set[str] = set()
35+
visited = visited or set()
36+
if extra_name in visited:
37+
return selected
38+
visited.add(extra_name)
39+
40+
for dependency in optional_deps[extra_name]:
41+
requirement = Requirement(dependency)
42+
if requirement.marker is not None and not requirement.marker.evaluate(environment):
43+
continue
44+
if requirement.name == HEADROOM_PACKAGE_NAME:
45+
for nested_extra in requirement.extras:
46+
selected.update(
47+
_selected_dependency_names_for_extra(
48+
optional_deps,
49+
nested_extra,
50+
environment,
51+
visited,
52+
)
53+
)
54+
else:
55+
selected.add(requirement.name)
56+
57+
return selected
58+
59+
60+
def _locked_dependency_names(package_name: str) -> set[str]:
61+
lock = tomllib.loads((ROOT / UV_LOCK_FILE).read_text(encoding="utf-8"))
62+
for package in lock["package"]:
63+
if package["name"] == package_name:
64+
return {dependency["name"] for dependency in package.get("dependencies", [])}
65+
raise AssertionError(f"{package_name} not found in {UV_LOCK_FILE}")
1366

1467

1568
def test_all_extra_does_not_require_torch_on_macos_x86_64() -> None:
1669
"""Keep `headroom-ai[all]` resolvable where PyTorch publishes no wheel."""
1770

18-
pyproject = tomllib.loads((ROOT / "pyproject.toml").read_text(encoding="utf-8"))
71+
pyproject = tomllib.loads((ROOT / PYPROJECT_FILE).read_text(encoding="utf-8"))
1972
optional_deps = pyproject["project"]["optional-dependencies"]
73+
macos_x86_64_environment = default_environment()
74+
macos_x86_64_environment.update(
75+
{
76+
SYS_PLATFORM_MARKER: MACOS_X86_64_SYS_PLATFORM,
77+
PLATFORM_MACHINE_MARKER: MACOS_X86_64_PLATFORM_MACHINE,
78+
}
79+
)
2080

21-
assert "ml" in optional_deps["all"][0]
22-
assert "voice" in optional_deps["all"][0]
81+
assert "ml" in optional_deps[ALL_EXTRA][0]
82+
assert "voice" in optional_deps[ALL_EXTRA][0]
2383

2484
torch_deps = [
2585
dep
2686
for extra_name in ("ml", "voice")
2787
for dep in optional_deps[extra_name]
28-
if dep.startswith("torch")
88+
if dep.startswith(TORCH_PACKAGE_NAME)
2989
]
90+
selected_all_dependency_names = _selected_dependency_names_for_extra(
91+
optional_deps,
92+
ALL_EXTRA,
93+
macos_x86_64_environment,
94+
)
95+
locked_torch_transitive_dependency_names = {
96+
package_name
97+
for package_name in TORCH_TRANSITIVE_PACKAGE_NAMES
98+
if TORCH_PACKAGE_NAME in _locked_dependency_names(package_name)
99+
}
30100

31101
assert torch_deps
32102
assert all(MACOS_X86_64_TORCH_GUARD in dep for dep in torch_deps)
103+
assert locked_torch_transitive_dependency_names
104+
assert TORCH_PACKAGE_NAME not in selected_all_dependency_names
105+
assert selected_all_dependency_names.isdisjoint(locked_torch_transitive_dependency_names)

uv.lock

Lines changed: 6 additions & 6 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)