| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495 |
- #!/usr/bin/env python3
- """Reject runtime executables that require newer macOS than their wheel tag."""
- from __future__ import annotations
- import argparse
- import re
- import runpy
- import subprocess
- from pathlib import Path
- ROOT = Path(__file__).resolve().parents[1]
- RELEASE = runpy.run_path(str(ROOT / "scripts" / "build-python-release.py"))
- MACOS_PLATFORM_TAG = RELEASE["PLATFORMS"]["macos-arm64"][0]
- def parse_version(value: str) -> tuple[int, ...]:
- """Parse a dot-separated numeric deployment version."""
- if re.fullmatch(r"\d+(?:\.\d+)*", value) is None:
- raise ValueError(f"invalid macOS deployment version: {value!r}")
- return tuple(int(part) for part in value.split("."))
- def claimed_version(platform_tag: str) -> tuple[int, ...]:
- """Return the minimum macOS version encoded by a wheel platform tag."""
- match = re.fullmatch(r"macosx_(\d+)_(\d+)_arm64", platform_tag)
- if match is None:
- raise ValueError(f"unsupported macOS wheel platform tag: {platform_tag!r}")
- return int(match.group(1)), int(match.group(2))
- def parse_otool_deployment_target(output: str) -> tuple[int, ...]:
- """Return the newest deployment target from one or more Mach-O slices."""
- versions = [
- parse_version(match.group(1))
- for match in re.finditer(r"^\s*minos\s+(\d+(?:\.\d+)*)\s*$", output, re.MULTILINE)
- ]
- if not versions:
- raise ValueError("otool output contains no LC_BUILD_VERSION deployment target")
- return max(versions)
- def deployment_target(executable: Path) -> tuple[int, ...]:
- """Read one Mach-O executable's deployment target with ``otool``."""
- if not executable.is_file():
- raise FileNotFoundError(f"runtime executable does not exist: {executable}")
- result = subprocess.run(
- ["otool", "-l", str(executable)],
- check=True,
- capture_output=True,
- text=True,
- )
- try:
- return parse_otool_deployment_target(result.stdout)
- except ValueError as error:
- raise ValueError(f"{executable}: {error}") from error
- def ensure_compatible(
- executable: Path, actual: tuple[int, ...], platform_tag: str
- ) -> None:
- """Reject an executable whose deployment target exceeds its wheel claim."""
- claimed = claimed_version(platform_tag)
- width = max(len(actual), len(claimed))
- padded_actual = actual + (0,) * (width - len(actual))
- padded_claimed = claimed + (0,) * (width - len(claimed))
- if padded_actual > padded_claimed:
- rendered = ".".join(str(part) for part in actual)
- raise RuntimeError(
- f"{executable} requires macOS {rendered} but the wheel claims {platform_tag}"
- )
- def validate_deployment_targets(
- executables: list[Path], platform_tag: str = MACOS_PLATFORM_TAG
- ) -> list[tuple[Path, tuple[int, ...]]]:
- """Validate every executable and return its measured deployment target."""
- measured = [(executable, deployment_target(executable)) for executable in executables]
- for executable, actual in measured:
- ensure_compatible(executable, actual, platform_tag)
- return measured
- def main() -> None:
- parser = argparse.ArgumentParser(description=__doc__)
- parser.add_argument("executables", type=Path, nargs="+")
- args = parser.parse_args()
- for executable, version in validate_deployment_targets(args.executables):
- rendered = ".".join(str(part) for part in version)
- print(f"{executable}: macOS {rendered} <= {MACOS_PLATFORM_TAG}")
- if __name__ == "__main__":
- main()
|