mirror of
https://github.com/headroomlabs-ai/headroom.git
synced 2026-08-27 14:17:10 -04:00
fix(build): enable Intel macOS pip installs via ort-load-dynamic (#1538)
## Description
Fix `pip install headroom-ai` on Intel Mac (`x86_64-apple-darwin`).
Source installs failed because `ort-sys 2.0.0-rc.12` (transitive via
`fastembed`) does not ship prebuilt ONNX Runtime binaries for that
target, causing maturin/cargo to exit during the wheel build.
This PR mirrors the existing Windows fix: build the Rust core with
`ort-load-dynamic`, pin `ORT_DYLIB_PATH` to the pip `onnxruntime` native
library at import time, and publish Intel macOS wheels from CI.
Closes #
## Type of Change
- [x] Bug fix (non-breaking change that fixes an issue)
- [ ] New feature (non-breaking change that adds functionality)
- [ ] Breaking change (fix or feature that would cause existing
functionality to change)
- [x] Documentation update
- [ ] Performance improvement
- [ ] Code refactoring (no functional changes)
## Changes Made
- `crates/headroom-core/Cargo.toml`: use `ort-load-dynamic` for
`x86_64-apple-darwin` instead of `ort-download-binaries-rustls-tls`.
- `headroom/_ort.py`: extend the ORT dylib pin hook to Intel macOS
(`darwin` + `x86_64`), resolving `libonnxruntime*.dylib` from the pip
`onnxruntime` package.
- `.github/workflows/release.yml` and `.github/workflows/rust.yml`: add
`macos-15-intel` / `x86_64-apple-darwin` wheel matrix entries.
- `tests/test_release_workflows.py` and
`tests/test_transforms/test_ort_dylib.py`: update/add coverage for the
new target and dylib pin behavior.
- `README.md`: note that prebuilt wheels are published for Intel macOS.
## Testing
- [x] Unit tests pass (`pytest`)
- [ ] Linting passes (`ruff check .`)
- [ ] Type checking passes (`mypy headroom`)
- [x] New tests added for new functionality
- [x] Manual testing performed
### Test Output
```text
$ pytest tests/test_transforms/test_ort_dylib.py \
tests/test_release_workflows.py::test_fastembed_uses_dynamic_ort_on_windows \
tests/test_release_workflows.py::test_build_wheels_matrix_includes_intel_macos_with_dynamic_ort -q
.......................... [100%]
10 passed in 0.18s
$ maturin build --release -o /tmp/headroom-dist
📦 Built wheel for abi3 Python ≥ 3.10 to /tmp/headroom-dist/headroom_ai-0.27.0-cp310-abi3-macosx_10_12_x86_64.whl
$ python3.11 -m venv /tmp/hr-venv && /tmp/hr-venv/bin/pip install .
Successfully built headroom-ai
Successfully installed headroom-ai-0.27.0
$ cd /tmp && /tmp/hr-venv/bin/python -c "import headroom; import headroom._core; print('ok')"
version 0.27.0
_core ok
```
## Real Behavior Proof
- Environment: macOS `x86_64-apple-darwin`, Python 3.11.5, Rust 1.95.0
- Exact command / steps: Reproduced the reported failure with `pip
install headroom-ai` (sdist build dies in `ort-sys` for
`x86_64-apple-darwin`); after this patch ran `maturin build --release`,
then `pip install .` in a clean venv, then `python -c "import
headroom._core"`.
- Observed result: Before fix, cargo/maturin exit 101 on missing ORT
prebuilts; after fix, wheel build succeeds and `headroom._core` imports
cleanly (`version 0.27.0`, `_core ok`).
- Not tested: `macos-15-intel` GitHub Actions wheel matrix row (will be
validated by CI after merge).
## Review Readiness
- [x] I have performed a self-review
- [x] This PR is ready for human review
## Checklist
- [x] My code follows the project's style guidelines
- [x] I have performed a self-review of my code
- [x] I have commented my code, particularly in hard-to-understand areas
- [x] I have made corresponding changes to the documentation
- [ ] My changes generate no new warnings
- [x] I have added tests that prove my fix is effective or that my
feature works
- [x] New and existing unit tests pass locally with my changes
- [ ] I have updated the CHANGELOG.md if applicable
## Additional Notes
- ML features (magika detection, fastembed embeddings) still require
`onnxruntime` at runtime on Intel Mac. Users should install
`headroom-ai[proxy]` or `pip install onnxruntime`; `_ort.py` auto-pins
`ORT_DYLIB_PATH` when that package is present.
- Apple Silicon (`aarch64-apple-darwin`) behavior is unchanged: it
continues to bundle ORT via `ort-download-binaries-rustls-tls`.
- Lint/mypy not re-run locally in this pass; targeted pytest +
maturin/pip install proof covers the changed surface.
---------
Co-authored-by: Bor <you@example.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
248ae0f3e0
commit
32ce99e4b4
7 changed files with 170 additions and 96 deletions
43
.github/workflows/release.yml
vendored
43
.github/workflows/release.yml
vendored
|
|
@ -247,15 +247,14 @@ jobs:
|
|||
- os: ubuntu-24.04-arm
|
||||
target: aarch64-unknown-linux-gnu
|
||||
manylinux: 2_28
|
||||
# NOTE: `macos-15-intel` (x86_64-apple-darwin) is intentionally
|
||||
# NOT in the matrix. `ort-sys 2.0.0-rc.12` (transitive via our
|
||||
# ML compression backend) does not provide prebuilt ONNX Runtime
|
||||
# binaries for `x86_64-apple-darwin`, and building ORT from
|
||||
# source would add CMake + ~5 minutes per build. Apple Silicon
|
||||
# macOS is fully covered below; Intel-mac users install the
|
||||
# platform-independent sdist (also produced by this matrix).
|
||||
# Tracked as a follow-up: switch to `ort-tract` or upstream a
|
||||
# request for x86_64 macOS prebuilts.
|
||||
# Intel macOS x86_64. `ort-sys 2.0.0-rc.12` does not ship
|
||||
# prebuilt ONNX Runtime binaries for this triple, so
|
||||
# `headroom-core/Cargo.toml` selects `ort-load-dynamic` (same
|
||||
# as Windows) and `headroom/_ort.py` pins `ORT_DYLIB_PATH` to
|
||||
# the pip `onnxruntime` dylib at import time.
|
||||
- os: macos-15-intel
|
||||
target: x86_64-apple-darwin
|
||||
manylinux: ""
|
||||
- os: macos-14
|
||||
target: aarch64-apple-darwin
|
||||
manylinux: ""
|
||||
|
|
@ -267,8 +266,9 @@ jobs:
|
|||
# or crates.io). `manylinux` is meaningless on Windows (that tag
|
||||
# is Linux-only) — leave it empty. The ONNX Runtime story is
|
||||
# already handled: `crates/headroom-core/Cargo.toml` selects
|
||||
# `ort-load-dynamic` under `cfg(windows)`, so the wheel does not
|
||||
# bundle/link DirectML SDK libs and loads ORT at runtime instead.
|
||||
# `ort-load-dynamic` under `cfg(windows)` and Intel macOS, so
|
||||
# the wheel does not bundle/link platform ORT SDK libs and loads
|
||||
# ORT at runtime instead.
|
||||
- os: windows-latest
|
||||
target: x86_64-pc-windows-msvc
|
||||
manylinux: ""
|
||||
|
|
@ -507,11 +507,11 @@ jobs:
|
|||
# runners (PR #376) host the container.
|
||||
- { runner: ubuntu-24.04-arm, image: "quay.io/pypa/manylinux_2_28_aarch64", python: "3.11", wheel_target: aarch64-unknown-linux-gnu, wheel_artifact: wheels-ubuntu-24.04-arm-aarch64-unknown-linux-gnu, glibc_label: "2.28-floor" }
|
||||
- { runner: ubuntu-24.04-arm, image: "ubuntu:22.04", python: "3.12", wheel_target: aarch64-unknown-linux-gnu, wheel_artifact: wheels-ubuntu-24.04-arm-aarch64-unknown-linux-gnu, glibc_label: "2.35" }
|
||||
# macOS arm64 — runs natively on the host runner; no
|
||||
# container. Apple Silicon is the only macOS target we
|
||||
# ship today (Intel macOS dropped in PR #371 — ort-sys
|
||||
# has no x86_64-apple-darwin prebuilts).
|
||||
# macOS arm64 — runs natively on the host runner; no container.
|
||||
- { runner: macos-14, image: "", python: "3.13", wheel_target: aarch64-apple-darwin, wheel_artifact: wheels-macos-14-aarch64-apple-darwin, glibc_label: "" }
|
||||
# macOS x86_64 (Intel) — `ort-load-dynamic`; see build-wheels
|
||||
# matrix comment for rationale.
|
||||
- { runner: macos-15-intel, image: "", python: "3.12", wheel_target: x86_64-apple-darwin, wheel_artifact: wheels-macos-15-intel-x86_64-apple-darwin, glibc_label: "" }
|
||||
# Windows x86_64 — runs natively on the host runner; no
|
||||
# container. `image: ""` routes this row past the Linux docker
|
||||
# path; a dedicated PowerShell step below installs + imports the
|
||||
|
|
@ -680,16 +680,21 @@ jobs:
|
|||
if: matrix.image == '' && runner.os == 'macOS'
|
||||
env:
|
||||
PYTHON_VERSION: ${{ matrix.python }}
|
||||
WHEEL_TARGET: ${{ matrix.wheel_target }}
|
||||
run: |
|
||||
set -e
|
||||
py_tag=cp$(echo "$PYTHON_VERSION" | tr -d .)
|
||||
# macOS wheels are tagged macosx_*_arm64 (Apple Silicon only).
|
||||
whl=$(find dist -maxdepth 1 -type f -name "headroom_ai-*-${py_tag}-${py_tag}-macosx_*_arm64.whl" -print -quit)
|
||||
case "$WHEEL_TARGET" in
|
||||
aarch64-apple-darwin) mac_arch=arm64 ;;
|
||||
x86_64-apple-darwin) mac_arch=x86_64 ;;
|
||||
*) echo "ERROR: unknown macOS wheel target $WHEEL_TARGET" >&2; exit 1 ;;
|
||||
esac
|
||||
whl=$(find dist -maxdepth 1 -type f -name "headroom_ai-*-${py_tag}-${py_tag}-macosx_*_${mac_arch}.whl" -print -quit)
|
||||
if [ -z "$whl" ]; then
|
||||
# Stable-ABI (abi3) fallback — see the Linux path above. A
|
||||
# cp3<floor>-abi3 wheel installs on any CPython >= floor.
|
||||
py_minor=${PYTHON_VERSION#*.}
|
||||
abi3=$(find dist -maxdepth 1 -type f -name "headroom_ai-*-abi3-macosx_*_arm64.whl" -print -quit)
|
||||
abi3=$(find dist -maxdepth 1 -type f -name "headroom_ai-*-abi3-macosx_*_${mac_arch}.whl" -print -quit)
|
||||
if [ -n "$abi3" ]; then
|
||||
abi3_base=${abi3##*/}
|
||||
abi3_floor=${abi3_base#*-cp3}
|
||||
|
|
@ -699,7 +704,7 @@ jobs:
|
|||
fi
|
||||
fi
|
||||
if [ -z "$whl" ]; then
|
||||
echo "ERROR: no macOS wheel matching python=$PYTHON_VERSION"
|
||||
echo "ERROR: no macOS wheel matching python=$PYTHON_VERSION arch=$mac_arch target=$WHEEL_TARGET"
|
||||
find dist -maxdepth 1 -type f -exec basename {} \;
|
||||
exit 1
|
||||
fi
|
||||
|
|
|
|||
13
.github/workflows/rust.yml
vendored
13
.github/workflows/rust.yml
vendored
|
|
@ -75,14 +75,11 @@ jobs:
|
|||
- os: macos-14
|
||||
target: aarch64-apple-darwin
|
||||
maturin-target: aarch64-apple-darwin
|
||||
# macOS x86_64 (Intel) is NOT in this matrix.
|
||||
# `fastembed` → `ort` → `ort-sys` does not publish prebuilt ONNX
|
||||
# Runtime binaries for `x86_64-apple-darwin`; building from source
|
||||
# in CI is a multi-hour cmake job. Apple Silicon has been the
|
||||
# default macOS target since 2020 and is sufficient for the wheels
|
||||
# we ship. If a customer needs Intel macOS, build from source
|
||||
# locally (the toolchain works; only prebuilt distribution skips
|
||||
# this target).
|
||||
- os: macos-15-intel
|
||||
target: x86_64-apple-darwin
|
||||
maturin-target: x86_64-apple-darwin
|
||||
# Intel macOS uses `ort-load-dynamic` (no prebuilt ORT from ort-sys);
|
||||
# Apple Silicon bundles ORT via `ort-download-binaries-rustls-tls`.
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/setup-python@v6
|
||||
|
|
|
|||
|
|
@ -380,8 +380,8 @@ winget install Rustlang.Rustup && rustup default stable
|
|||
Restart your shell, then `pip install "headroom-ai[all]"`. A prebuilt wheel avoids the Rust
|
||||
build entirely where available: `pip install --only-binary headroom-ai headroom-ai`. Prebuilt
|
||||
wheels are published for Windows (`win_amd64`), Linux (`x86_64` / `aarch64`), and macOS
|
||||
(Apple Silicon), so installs on those platforms never need a local Rust toolchain — the
|
||||
Rust-first dance above is only for the platform-independent sdist fallback (e.g. Intel macOS).
|
||||
(Apple Silicon and Intel), so installs on those platforms never need a local Rust toolchain — the
|
||||
Rust-first dance above is only for the platform-independent sdist fallback when no wheel matches.
|
||||
|
||||
Two runtime assets are fetched over TLS; if they are blocked, trust your corporate CA via
|
||||
`REQUESTS_CA_BUNDLE` / `SSL_CERT_FILE` / `CURL_CA_BUNDLE`:
|
||||
|
|
|
|||
|
|
@ -122,13 +122,25 @@ redis = { version = "0.27", optional = true, default-features = false }
|
|||
# cycling through the proxy crate. Tiny crate (no I/O, just types).
|
||||
http = "1"
|
||||
|
||||
[target.'cfg(not(windows))'.dependencies]
|
||||
[target.'cfg(all(not(windows), not(all(target_os = "macos", target_arch = "x86_64"))))'.dependencies]
|
||||
fastembed = { version = "5", default-features = false, features = [
|
||||
"hf-hub-rustls-tls",
|
||||
"ort-download-binaries-rustls-tls",
|
||||
"image-models",
|
||||
] }
|
||||
|
||||
[target.'cfg(all(target_os = "macos", target_arch = "x86_64"))'.dependencies]
|
||||
# `ort-sys 2.0.0-rc.12` does not ship prebuilt ONNX Runtime binaries for
|
||||
# `x86_64-apple-darwin`. Building ORT from source in CI would add CMake and
|
||||
# several minutes per wheel. Load the pip `onnxruntime` dylib at runtime
|
||||
# instead (same approach as Windows below). `headroom/_ort.py` pins
|
||||
# `ORT_DYLIB_PATH` before `headroom._core` imports.
|
||||
fastembed = { version = "5", default-features = false, features = [
|
||||
"hf-hub-rustls-tls",
|
||||
"ort-load-dynamic",
|
||||
"image-models",
|
||||
] }
|
||||
|
||||
[target.'cfg(windows)'.dependencies]
|
||||
# `ort-download-binaries-*` emits DirectML link libs on Windows (`DXCORE`,
|
||||
# `DXGI`, `D3D12`, `DirectML`). Users installing `headroom-ai[all]` from
|
||||
|
|
|
|||
|
|
@ -1,31 +1,37 @@
|
|||
"""Pin the ONNX Runtime dylib for the Rust core on Windows.
|
||||
"""Pin the ONNX Runtime dylib for the Rust core on dynamic-ORT platforms.
|
||||
|
||||
Why this module exists
|
||||
----------------------
|
||||
On Windows, ``headroom._core`` consumers of the ``ort`` crate (magika
|
||||
content detection, fastembed embeddings) are built with
|
||||
``ort-load-dynamic``: the native ``onnxruntime.dll`` is resolved at
|
||||
*runtime*. Unless ``ORT_DYLIB_PATH`` is set, ort falls back to a bare
|
||||
``LoadLibrary("onnxruntime.dll")`` and the Windows DLL search order
|
||||
applies — and ``C:\\Windows\\System32`` wins.
|
||||
On Windows and Intel macOS (``x86_64-apple-darwin``), ``headroom._core``
|
||||
consumers of the ``ort`` crate (magika content detection, fastembed
|
||||
embeddings) are built with ``ort-load-dynamic``: the native ONNX Runtime
|
||||
library is resolved at *runtime*.
|
||||
|
||||
Windows 11 24H2+ ships ``System32\\onnxruntime.dll`` as part of Windows
|
||||
ML (observed: 1.17.2603 "os-germanium"). Initializing an ort 2.x
|
||||
session against that OS build does not fail — it deadlocks
|
||||
indefinitely at 0% CPU, which the tiered detection fallback cannot
|
||||
catch (a hang is not an ``Err``). Reproduced and bracketed with
|
||||
``scripts/diag_magika_windows.py``: the identical session inits in
|
||||
~400ms when ``ORT_DYLIB_PATH`` points at the ``onnxruntime`` pip
|
||||
package's DLL (which ``headroom-ai[proxy]`` already depends on).
|
||||
Windows: unless ``ORT_DYLIB_PATH`` is set, ort falls back to a bare
|
||||
``LoadLibrary("onnxruntime.dll")`` and the Windows DLL search order
|
||||
applies — and ``C:\\Windows\\System32`` wins. Windows 11 24H2+ ships
|
||||
``System32\\onnxruntime.dll`` as part of Windows ML (observed:
|
||||
1.17.2603 "os-germanium"). Initializing an ort 2.x session against that
|
||||
OS build does not fail — it deadlocks indefinitely at 0% CPU, which the
|
||||
tiered detection fallback cannot catch (a hang is not an ``Err``).
|
||||
Reproduced and bracketed with ``scripts/diag_magika_windows.py``: the
|
||||
identical session inits in ~400ms when ``ORT_DYLIB_PATH`` points at the
|
||||
``onnxruntime`` pip package's DLL (which ``headroom-ai[proxy]`` already
|
||||
depends on).
|
||||
|
||||
Intel macOS: ``ort-sys 2.0.0-rc.12`` does not ship prebuilt ONNX Runtime
|
||||
binaries for ``x86_64-apple-darwin``, so the wheel/sdist build uses
|
||||
``ort-load-dynamic`` and expects a pip-installed ``onnxruntime`` dylib
|
||||
at runtime (same contract as Windows).
|
||||
|
||||
The fix: before anything can import ``headroom._core``, resolve the
|
||||
pip-installed ``onnxruntime\\capi\\onnxruntime.dll`` and export it via
|
||||
pip-installed ``onnxruntime`` native library and export it via
|
||||
``ORT_DYLIB_PATH``. ``headroom/__init__.py`` calls this hook, which
|
||||
guarantees ordering for every package-level consumer.
|
||||
|
||||
Behavior contract
|
||||
-----------------
|
||||
- Windows-only; a no-op everywhere else.
|
||||
- Active on Windows and Intel macOS only; a no-op elsewhere.
|
||||
- Respects a pre-set ``ORT_DYLIB_PATH`` (user override wins).
|
||||
- Locates the ``onnxruntime`` package via ``find_spec`` WITHOUT
|
||||
importing it (importing would load its native code; this hook must
|
||||
|
|
@ -41,6 +47,7 @@ from __future__ import annotations
|
|||
import importlib.util
|
||||
import logging
|
||||
import os
|
||||
import platform
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
|
|
@ -54,11 +61,12 @@ _pinned: object = _UNSET
|
|||
|
||||
|
||||
def ensure_ort_dylib_pinned() -> str | None:
|
||||
"""Export ``ORT_DYLIB_PATH`` for the Rust core's ort runtime (Windows).
|
||||
"""Export ``ORT_DYLIB_PATH`` for the Rust core's ort runtime.
|
||||
|
||||
Returns the effective dylib path (pinned now or already present in
|
||||
the environment), or ``None`` when no pin applies (non-Windows, or
|
||||
no ``onnxruntime`` package to point at). Idempotent and exception-free.
|
||||
the environment), or ``None`` when no pin applies (platforms that
|
||||
bundle ORT at build time, or no ``onnxruntime`` package to point at).
|
||||
Idempotent and exception-free.
|
||||
"""
|
||||
global _pinned
|
||||
if _pinned is not _UNSET:
|
||||
|
|
@ -67,8 +75,23 @@ def ensure_ort_dylib_pinned() -> str | None:
|
|||
return _pinned # type: ignore[return-value]
|
||||
|
||||
|
||||
def _needs_ort_dylib_pin() -> bool:
|
||||
if sys.platform.startswith("win"):
|
||||
return True
|
||||
return sys.platform == "darwin" and platform.machine() == "x86_64"
|
||||
|
||||
|
||||
def _resolve_ort_native_library(capi_dir: Path) -> Path | None:
|
||||
if sys.platform.startswith("win"):
|
||||
candidate = capi_dir / "onnxruntime.dll"
|
||||
return candidate if candidate.is_file() else None
|
||||
|
||||
matches = sorted(capi_dir.glob("libonnxruntime*.dylib"))
|
||||
return matches[0] if matches else None
|
||||
|
||||
|
||||
def _resolve_and_pin() -> str | None:
|
||||
if not sys.platform.startswith("win"):
|
||||
if not _needs_ort_dylib_pin():
|
||||
return None
|
||||
|
||||
try:
|
||||
|
|
@ -80,26 +103,26 @@ def _resolve_and_pin() -> str | None:
|
|||
spec = importlib.util.find_spec("onnxruntime")
|
||||
if spec is None or not spec.origin:
|
||||
logger.debug(
|
||||
"onnxruntime package not found; %s left unset. The Rust ML detection "
|
||||
"may pick up the Windows ML System32 onnxruntime.dll, which is known "
|
||||
"to deadlock ort init on Windows 11 24H2+ (it then degrades to non-ML "
|
||||
"tiers via HEADROOM_MAGIKA_INIT_TIMEOUT_SECS). Install onnxruntime or "
|
||||
"set %s explicitly.",
|
||||
"onnxruntime package not found; %s left unset. Rust ML detection "
|
||||
"needs a pip-installed onnxruntime on this platform (install "
|
||||
"headroom-ai[proxy] or set %s explicitly).",
|
||||
_ENV_VAR,
|
||||
_ENV_VAR,
|
||||
)
|
||||
return None
|
||||
|
||||
dll = Path(spec.origin).parent / "capi" / "onnxruntime.dll"
|
||||
if not dll.is_file():
|
||||
native = _resolve_ort_native_library(Path(spec.origin).parent / "capi")
|
||||
if native is None:
|
||||
logger.debug(
|
||||
"onnxruntime package found but %s is missing; %s left unset", dll, _ENV_VAR
|
||||
"onnxruntime package found but no native library under %s; %s left unset",
|
||||
Path(spec.origin).parent / "capi",
|
||||
_ENV_VAR,
|
||||
)
|
||||
return None
|
||||
|
||||
os.environ[_ENV_VAR] = str(dll)
|
||||
logger.info("Pinned %s to bundled ONNX Runtime: %s", _ENV_VAR, dll)
|
||||
return str(dll)
|
||||
os.environ[_ENV_VAR] = str(native)
|
||||
logger.info("Pinned %s to bundled ONNX Runtime: %s", _ENV_VAR, native)
|
||||
return str(native)
|
||||
except Exception as exc: # never break `import headroom` over an accelerator pin
|
||||
logger.debug("ort dylib pin skipped: %s: %s", type(exc).__name__, exc)
|
||||
return None
|
||||
|
|
|
|||
|
|
@ -200,24 +200,25 @@ def test_fastembed_uses_rustls_features() -> None:
|
|||
|
||||
|
||||
def test_fastembed_uses_dynamic_ort_on_windows() -> None:
|
||||
"""Windows sdist builds must not link Pyke's DirectML ORT binaries.
|
||||
"""Windows and Intel macOS sdist builds must not link Pyke's ORT binaries.
|
||||
|
||||
`ort-download-binaries-*` emits DXCORE/DXGI/D3D12/DirectML link libs on
|
||||
Windows. Those SDK libs are not present on many Python build hosts, so the
|
||||
Windows target must use ORT dynamic loading instead.
|
||||
`ort-download-binaries-*` emits platform SDK link libs (DirectML on
|
||||
Windows; unavailable prebuilts on `x86_64-apple-darwin`). Those targets
|
||||
must use ORT dynamic loading instead.
|
||||
"""
|
||||
|
||||
cargo = (ROOT / "crates" / "headroom-core" / "Cargo.toml").read_text(encoding="utf-8")
|
||||
assert "[target.'cfg(windows)'.dependencies]" in cargo
|
||||
windows_section = cargo.split("[target.'cfg(windows)'.dependencies]", 1)[1].split(
|
||||
"\n[",
|
||||
1,
|
||||
)[0]
|
||||
windows_dependency_lines = "\n".join(
|
||||
line for line in windows_section.splitlines() if not line.lstrip().startswith("#")
|
||||
)
|
||||
assert '"ort-load-dynamic"' in windows_section
|
||||
assert "ort-download-binaries" not in windows_dependency_lines
|
||||
for section_marker in (
|
||||
"[target.'cfg(windows)'.dependencies]",
|
||||
'[target.\'cfg(all(target_os = "macos", target_arch = "x86_64"))\'.dependencies]',
|
||||
):
|
||||
assert section_marker in cargo, f"missing Cargo target section: {section_marker}"
|
||||
section = cargo.split(section_marker, 1)[1].split("\n[", 1)[0]
|
||||
dependency_lines = "\n".join(
|
||||
line for line in section.splitlines() if not line.lstrip().startswith("#")
|
||||
)
|
||||
assert '"ort-load-dynamic"' in section
|
||||
assert "ort-download-binaries" not in dependency_lines
|
||||
|
||||
|
||||
def test_dockerfiles_no_longer_install_openssl_devel() -> None:
|
||||
|
|
@ -289,16 +290,13 @@ def test_release_yml_does_not_install_openssl_or_perl_for_wheels() -> None:
|
|||
)
|
||||
|
||||
|
||||
def test_build_wheels_matrix_excludes_intel_macos() -> None:
|
||||
"""`ort-sys 2.0.0-rc.12` (transitive via the ML compression backend)
|
||||
def test_build_wheels_matrix_includes_intel_macos_with_dynamic_ort() -> None:
|
||||
"""Intel macOS wheels use `ort-load-dynamic` because `ort-sys 2.0.0-rc.12`
|
||||
has no prebuilt ONNX Runtime binaries for `x86_64-apple-darwin`.
|
||||
Building ORT from source would add CMake + ~5 minutes per build.
|
||||
Apple Silicon macOS is fully covered; Intel-mac users install from
|
||||
the platform-independent sdist this matrix also produces.
|
||||
|
||||
We assert against the actual matrix entry shape (`target: <triple>`
|
||||
on a non-comment line) so explanatory comments mentioning the
|
||||
excluded triple don't false-positive.
|
||||
on a non-comment line) so explanatory comments mentioning other
|
||||
triples don't false-positive.
|
||||
"""
|
||||
content = (ROOT / ".github" / "workflows" / "release.yml").read_text(encoding="utf-8")
|
||||
|
||||
|
|
@ -319,14 +317,10 @@ def test_build_wheels_matrix_excludes_intel_macos() -> None:
|
|||
assert "aarch64-apple-darwin" in matrix_targets, "Apple Silicon must stay in the matrix"
|
||||
assert "x86_64-unknown-linux-gnu" in matrix_targets
|
||||
assert "aarch64-unknown-linux-gnu" in matrix_targets
|
||||
|
||||
# Intel macOS must NOT be a matrix entry — re-add only after switching
|
||||
# off ort-sys (e.g., to ort-tract) or adding a CMake-from-source step.
|
||||
assert "x86_64-apple-darwin" not in matrix_targets, (
|
||||
f"x86_64-apple-darwin must not be a wheel-matrix target; got {matrix_targets}"
|
||||
assert "x86_64-apple-darwin" in matrix_targets, (
|
||||
f"x86_64-apple-darwin must be a wheel-matrix target; got {matrix_targets}"
|
||||
)
|
||||
|
||||
# The runner OS itself shouldn't appear as a configured `os:` either.
|
||||
matrix_os: list[str] = []
|
||||
for raw in body.splitlines():
|
||||
stripped = raw.lstrip()
|
||||
|
|
@ -334,7 +328,27 @@ def test_build_wheels_matrix_excludes_intel_macos() -> None:
|
|||
continue
|
||||
if stripped.startswith("os:"):
|
||||
matrix_os.append(stripped.split(":", 1)[1].strip())
|
||||
assert "macos-15-intel" not in matrix_os
|
||||
elif stripped.startswith("- os:"):
|
||||
matrix_os.append(stripped.split(":", 1)[1].strip())
|
||||
assert "macos-15-intel" in matrix_os
|
||||
|
||||
|
||||
def test_smoke_import_macos_selects_wheel_arch_from_target() -> None:
|
||||
"""The macOS smoke-import step must pick the wheel tag from the matrix
|
||||
target (arm64 for Apple Silicon, x86_64 for Intel) instead of
|
||||
hardcoding `_arm64` for every macOS row."""
|
||||
content = (ROOT / ".github" / "workflows" / "release.yml").read_text(encoding="utf-8")
|
||||
|
||||
step_start = content.index("- name: Smoke-import wheel on macOS host")
|
||||
step_end = content.index("- name: Smoke-import wheel on Windows host", step_start)
|
||||
macos_block = content[step_start:step_end]
|
||||
|
||||
assert "WHEEL_TARGET: ${{ matrix.wheel_target }}" in macos_block
|
||||
assert "aarch64-apple-darwin) mac_arch=arm64" in macos_block
|
||||
assert "x86_64-apple-darwin) mac_arch=x86_64" in macos_block
|
||||
assert "macosx_*_${mac_arch}.whl" in macos_block
|
||||
assert "headroom_ai-*-${py_tag}-${py_tag}-macosx_*_arm64.whl" not in macos_block
|
||||
assert "headroom_ai-*-abi3-macosx_*_arm64.whl" not in macos_block
|
||||
|
||||
|
||||
def test_aarch64_wheel_uses_native_arm64_runner() -> None:
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
"""Tests for headroom._ort — the Windows ORT_DYLIB_PATH auto-pin.
|
||||
"""Tests for headroom._ort — the ORT_DYLIB_PATH auto-pin.
|
||||
|
||||
The resolver guards the Rust core against the Windows DLL search picking
|
||||
up the Windows ML System32 onnxruntime.dll (deadlocks ort session init on
|
||||
Win11 24H2+, see headroom/_ort.py). The platform gate is monkeypatched so
|
||||
the full logic runs on any CI OS.
|
||||
The resolver guards the Rust core on platforms that use `ort-load-dynamic`
|
||||
(Windows and Intel macOS). On Windows it avoids the System32 onnxruntime.dll
|
||||
deadlock (Win11 24H2+, see headroom/_ort.py). Platform gates are
|
||||
monkeypatched so the full logic runs on any CI OS.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
|
@ -27,6 +27,11 @@ def _force_windows(monkeypatch):
|
|||
monkeypatch.setattr(sys, "platform", "win32")
|
||||
|
||||
|
||||
def _force_intel_macos(monkeypatch):
|
||||
monkeypatch.setattr(sys, "platform", "darwin")
|
||||
monkeypatch.setattr(_ort.platform, "machine", lambda: "x86_64")
|
||||
|
||||
|
||||
def _fake_spec_for(monkeypatch, package_dir):
|
||||
"""Make find_spec('onnxruntime') resolve to a fake package directory."""
|
||||
spec = SimpleNamespace(origin=str(package_dir / "__init__.py"))
|
||||
|
|
@ -37,11 +42,16 @@ def _fake_spec_for(monkeypatch, package_dir):
|
|||
)
|
||||
|
||||
|
||||
def test_noop_on_non_windows(monkeypatch):
|
||||
def test_noop_on_non_dynamic_platforms(monkeypatch):
|
||||
monkeypatch.setattr(sys, "platform", "linux")
|
||||
assert _ort.ensure_ort_dylib_pinned() is None
|
||||
assert "ORT_DYLIB_PATH" not in _ort.os.environ
|
||||
|
||||
monkeypatch.setattr(sys, "platform", "darwin")
|
||||
monkeypatch.setattr(_ort.platform, "machine", lambda: "arm64")
|
||||
assert _ort.ensure_ort_dylib_pinned() is None
|
||||
assert "ORT_DYLIB_PATH" not in _ort.os.environ
|
||||
|
||||
|
||||
def test_respects_existing_env(monkeypatch):
|
||||
_force_windows(monkeypatch)
|
||||
|
|
@ -63,6 +73,19 @@ def test_pins_to_package_capi_dll(monkeypatch, tmp_path):
|
|||
assert _ort.os.environ["ORT_DYLIB_PATH"] == str(dll)
|
||||
|
||||
|
||||
def test_pins_to_package_capi_dylib_on_intel_macos(monkeypatch, tmp_path):
|
||||
_force_intel_macos(monkeypatch)
|
||||
pkg = tmp_path / "onnxruntime"
|
||||
capi = pkg / "capi"
|
||||
capi.mkdir(parents=True)
|
||||
dylib = capi / "libonnxruntime.1.23.2.dylib"
|
||||
dylib.write_bytes(b"not really a dylib")
|
||||
_fake_spec_for(monkeypatch, pkg)
|
||||
|
||||
assert _ort.ensure_ort_dylib_pinned() == str(dylib)
|
||||
assert _ort.os.environ["ORT_DYLIB_PATH"] == str(dylib)
|
||||
|
||||
|
||||
def test_idempotent_after_first_resolution(monkeypatch, tmp_path):
|
||||
_force_windows(monkeypatch)
|
||||
pkg = tmp_path / "onnxruntime"
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue