| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106 |
- """Locate the vendored dependency tree matching this interpreter's platform.
- ``vendor/`` is split into a platform-independent part and one directory per
- supported platform, because three of the vendored packages (pyqwest, protobuf,
- protobuf-py-ext) ship compiled extension modules::
- vendor/
- common/ pure-Python packages, shared by all platforms
- platform/
- linux-x86_64/ compiled extensions for that platform only
- linux-aarch64/
- macos-arm64/
- Both directories are prepended to ``sys.path`` (platform first), so the
- vendored copies win over anything installed system-wide. Run
- ``./vendor_chattolib.sh`` to (re)build the tree.
- """
- from __future__ import annotations
- import platform
- import sys
- from pathlib import Path
- from typing import List
- VENDOR_DIR = Path(__file__).parent / "vendor"
- # Directory name per (system, machine) — keep in sync with PLATFORMS in
- # vendor_chattolib.sh.
- _MACHINE_ALIASES = {
- "x86_64": "x86_64",
- "amd64": "x86_64",
- "aarch64": "aarch64",
- "arm64": "aarch64",
- }
- def _is_musl() -> bool:
- """True when running against musl libc (Alpine) rather than glibc."""
- try:
- libc, _version = platform.libc_ver()
- except OSError:
- return False
- # glibc reports "glibc"; musl reports an empty string.
- return libc != "glibc"
- def platform_tag() -> str:
- """Return the vendor/platform/ subdirectory name for this interpreter."""
- system = platform.system().lower()
- machine = _MACHINE_ALIASES.get(platform.machine().lower(), platform.machine().lower())
- if system == "linux":
- return f"linux-{machine}-musl" if _is_musl() else f"linux-{machine}"
- if system == "darwin":
- return f"macos-{'arm64' if machine == 'aarch64' else machine}"
- if system == "windows":
- return f"windows-{'amd64' if machine == 'x86_64' else machine}"
- return f"{system}-{machine}"
- def available_platforms() -> List[str]:
- """Platform directories that were actually vendored into this checkout."""
- platform_root = VENDOR_DIR / "platform"
- if not platform_root.is_dir():
- return []
- return sorted(p.name for p in platform_root.iterdir() if p.is_dir())
- def setup_vendor_path() -> List[str]:
- """Prepend the vendored packages for this platform to ``sys.path``.
- Returns the directories that were added. Raises ImportError when this
- platform was not vendored — a clear message beats the dlopen error you
- would otherwise get from a foreign-architecture .so file.
- """
- tag = platform_tag()
- platform_dir = VENDOR_DIR / "platform" / tag
- common_dir = VENDOR_DIR / "common"
- # Legacy flat layout (single platform, everything directly in vendor/).
- if not platform_dir.is_dir() and not common_dir.is_dir():
- if (VENDOR_DIR / "chattolib").is_dir():
- paths = [VENDOR_DIR]
- else:
- raise ImportError(
- f"Chatto: no vendored dependencies found in {VENDOR_DIR}. "
- f"Run ./vendor_chattolib.sh to build them."
- )
- elif not platform_dir.is_dir():
- raise ImportError(
- f"Chatto: no vendored dependencies for platform '{tag}'. "
- f"Available: {', '.join(available_platforms()) or 'none'}. "
- f"Run 'PLATFORMS={tag} ./vendor_chattolib.sh' to add it."
- )
- else:
- # Platform-specific extensions must shadow anything in common/.
- paths = [platform_dir, common_dir]
- added = []
- for path in paths:
- entry = str(path)
- if entry not in sys.path:
- sys.path.insert(0, entry)
- added.append(entry)
- return added
|