diff --git a/pyproject.toml b/pyproject.toml index 7359a51fa6..ce19d21399 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -128,14 +128,19 @@ huggingfacenotorch = [ ] # torchcodec backend for Gemma audio / datasets>=4 (#7225). # Pick the audio-torch* pin matching your torch minor (see TORCH_TORCHCODEC). +# torchcodec publishes no sdist and only manylinux_2_28_x86_64, macosx_*_arm64 +# and win_amd64 wheels, so Linux aarch64, Windows ARM64 and Intel Mac have +# nothing to resolve and pip fails the whole install rather than skipping audio. +# Gate on the platforms that have a wheel, matching +# PLATFORM_LACKS_TORCHCODEC_WHEEL in studio/install_python_stack.py. audio-torch210 = [ - "torchcodec>=0.10.0,<0.11.0 ; python_version >= '3.10'", + "torchcodec>=0.10.0,<0.11.0 ; python_version >= '3.10' and (((sys_platform == 'linux' or sys_platform == 'win32') and (platform_machine == 'x86_64' or platform_machine == 'AMD64')) or (sys_platform == 'darwin' and platform_machine == 'arm64'))", ] audio-torch290 = [ - "torchcodec>=0.8.0,<0.10.0 ; python_version >= '3.10'", + "torchcodec>=0.8.0,<0.10.0 ; python_version >= '3.10' and (((sys_platform == 'linux' or sys_platform == 'win32') and (platform_machine == 'x86_64' or platform_machine == 'AMD64')) or (sys_platform == 'darwin' and platform_machine == 'arm64'))", ] audio-torch280 = [ - "torchcodec>=0.6.0,<0.8.0 ; python_version >= '3.9'", + "torchcodec>=0.6.0,<0.8.0 ; python_version >= '3.9' and (((sys_platform == 'linux' or sys_platform == 'win32') and (platform_machine == 'x86_64' or platform_machine == 'AMD64')) or (sys_platform == 'darwin' and platform_machine == 'arm64'))", ] huggingface = [ "unsloth[huggingfacenotorch]", diff --git a/tests/python/test_torchcodec_torch_compat.py b/tests/python/test_torchcodec_torch_compat.py index 6ad16a73f4..728a51a321 100644 --- a/tests/python/test_torchcodec_torch_compat.py +++ b/tests/python/test_torchcodec_torch_compat.py @@ -11,12 +11,21 @@ import sys import types from pathlib import Path +import pytest + REPO_ROOT = Path(__file__).resolve().parents[2] PYPROJECT = REPO_ROOT / "pyproject.toml" IMPORT_FIXES_PATH = REPO_ROOT / "unsloth" / "import_fixes.py" +def _tomllib(): + if sys.version_info >= (3, 11): + import tomllib + return tomllib + return pytest.importorskip("tomli") + + def _load_import_fixes_module(): spec = importlib.util.spec_from_file_location( "unsloth_import_fixes_under_test", @@ -127,3 +136,41 @@ def test_import_fixes_loads_on_python39_syntax(): """Regression: module must import on 3.9 (postponed annotations for str | None).""" fixes = _load_import_fixes_module() assert callable(fixes._torchcodec_version_mismatch_hint) + + +def test_audio_extras_are_gated_to_platforms_with_a_torchcodec_wheel(): + """torchcodec publishes no sdist and no wheel for Linux aarch64, Windows ARM64 or + Intel Mac, so an ungated pin makes pip fail the whole install on those hosts instead + of just skipping audio -- and the cu*/rocm*/intel torch 2.10 extras pull it in. + The marker must match PLATFORM_LACKS_TORCHCODEC_WHEEL in install_python_stack.py. + """ + markers = pytest.importorskip("packaging.markers") + tomllib = _tomllib() + extras = tomllib.loads(PYPROJECT.read_text(encoding = "utf-8"))["project"][ + "optional-dependencies" + ] + audio = {n: d for n, d in extras.items() if n.startswith("audio-torch")} + assert audio, "expected audio-torch* extras" + + supported = [ + {"sys_platform": "linux", "platform_machine": "x86_64"}, + {"sys_platform": "win32", "platform_machine": "AMD64"}, + {"sys_platform": "darwin", "platform_machine": "arm64"}, + ] + unsupported = [ + {"sys_platform": "linux", "platform_machine": "aarch64"}, + {"sys_platform": "win32", "platform_machine": "ARM64"}, + {"sys_platform": "darwin", "platform_machine": "x86_64"}, + ] + for name, deps in audio.items(): + for dep in deps: + _, _, marker_text = dep.partition(";") + assert marker_text.strip(), f"{name}: {dep!r} has no marker" + marker = markers.Marker(marker_text.strip()) + env = {"python_version": "3.12"} + for case in supported: + assert marker.evaluate({**env, **case}), f"{name} must install on {case}" + for case in unsupported: + assert not marker.evaluate( + {**env, **case} + ), f"{name} has no wheel for {case} and must not be resolved there"