|
| 1 | +# Python 2.7 companion to standalone_python_musl.py. |
| 2 | +# |
| 3 | +# Responsibilities |
| 4 | +# ---------------- |
| 5 | +# The py3 hook does two things: (1) patch packaging's _get_musl_version to |
| 6 | +# advertise the shipped musl version for correct wheel selection, and (2) |
| 7 | +# expand an install-prefix sentinel inside sysconfig so `pip install |
| 8 | +# foo-from-source` works after the install tree is relocated (e.g. from |
| 9 | +# /opt/python to /tmp/mypack/python). |
| 10 | +# |
| 11 | +# For py2.7 we only need (2). Justification for dropping (1): |
| 12 | +# - pip 20.3.4 (the last py2-compatible release, pinned in |
| 13 | +# install_pip_py2.sh) predates PEP 656 musllinux tag support, so |
| 14 | +# there is no _get_musl_version to patch. |
| 15 | +# - The py2 Dockerfiles already document that users needing native |
| 16 | +# wheels on 2.7 should pass --no-binary :all:. |
| 17 | +# |
| 18 | +# Relocatability design |
| 19 | +# --------------------- |
| 20 | +# packing-initializer rewrites `/opt/shared_libraries` (the build-time |
| 21 | +# prefix inherited from build_python_py2.sh's LDFLAGS) to the sentinel |
| 22 | +# `@STANDALONE_PYTHON_PREFIX@/shared_libraries` in every file that baked |
| 23 | +# the old absolute path: `_sysconfigdata.py` (py2's single-module |
| 24 | +# equivalent of py3's _sysconfigdata__<platform>_<triple>.py), |
| 25 | +# `config/Makefile`, and `python2.7-config`. This .pth-loaded module then |
| 26 | +# substitutes `sys.prefix` for the sentinel on every interpreter startup, |
| 27 | +# before any caller (pip / distutils / setuptools) reads the config vars. |
| 28 | +# |
| 29 | +# py2 caches to patch |
| 30 | +# ------------------- |
| 31 | +# Two stdlib caches consume `_sysconfigdata.build_time_vars`: |
| 32 | +# * sysconfig._CONFIG_VARS (Lib/sysconfig.py) |
| 33 | +# * distutils.sysconfig._config_vars (Lib/distutils/sysconfig.py) |
| 34 | +# Both call `_init_posix(vars)` which does `from _sysconfigdata import |
| 35 | +# build_time_vars; vars.update(build_time_vars)` on first read. Patching |
| 36 | +# build_time_vars before either reads it propagates to both lazily; |
| 37 | +# patching a cache that's already been populated (e.g. by another .pth |
| 38 | +# that raced us) handles the non-default ordering. The cost is O(keys) |
| 39 | +# per dict — negligible at startup. |
| 40 | +# |
| 41 | +# Intentionally minimal: no try/except importlib.abc dance (py2's |
| 42 | +# importlib predates the hooks the py3 file uses, and there's no module |
| 43 | +# to lazily patch on import). Plain function, plain imports. |
| 44 | + |
| 45 | +import sys |
| 46 | + |
| 47 | + |
| 48 | +_SYSCONFIG_PREFIX_SENTINEL = "@STANDALONE_PYTHON_PREFIX@" |
| 49 | + |
| 50 | +# py2.7 stores config-var values as either `str` (bytes) or `unicode` |
| 51 | +# depending on how the value was initialized; values that came through |
| 52 | +# pprint-serialized _sysconfigdata are normally `str`, but entries a |
| 53 | +# site.py hook might have injected could be `unicode`. Match both. |
| 54 | +try: |
| 55 | + _TEXT_TYPES = (str, unicode) # noqa: F821 — py2 only |
| 56 | +except NameError: |
| 57 | + _TEXT_TYPES = (str,) |
| 58 | + |
| 59 | + |
| 60 | +def _patch_dict(d, prefix): |
| 61 | + if not isinstance(d, dict): |
| 62 | + return |
| 63 | + for k, v in list(d.items()): |
| 64 | + if isinstance(v, _TEXT_TYPES) and _SYSCONFIG_PREFIX_SENTINEL in v: |
| 65 | + d[k] = v.replace(_SYSCONFIG_PREFIX_SENTINEL, prefix) |
| 66 | + |
| 67 | + |
| 68 | +def _expand_sysconfig_prefix(): |
| 69 | + prefix = sys.prefix |
| 70 | + if not prefix: |
| 71 | + return |
| 72 | + |
| 73 | + # Patch build_time_vars directly (source of truth). If _sysconfigdata |
| 74 | + # isn't importable we still try the two caches below — they may have |
| 75 | + # been populated via an alternate path such as a frozen Makefile parse. |
| 76 | + try: |
| 77 | + import _sysconfigdata |
| 78 | + btv = getattr(_sysconfigdata, "build_time_vars", None) |
| 79 | + _patch_dict(btv, prefix) |
| 80 | + except ImportError: |
| 81 | + pass |
| 82 | + |
| 83 | + # sysconfig (2.7+) cache. |
| 84 | + try: |
| 85 | + import sysconfig |
| 86 | + _patch_dict(getattr(sysconfig, "_CONFIG_VARS", None), prefix) |
| 87 | + except ImportError: |
| 88 | + pass |
| 89 | + |
| 90 | + # distutils.sysconfig cache — what pip 20.x actually reads when |
| 91 | + # compiling a source distribution, via distutils.sysconfig.customize_compiler. |
| 92 | + try: |
| 93 | + from distutils import sysconfig as _dsc |
| 94 | + _patch_dict(getattr(_dsc, "_config_vars", None), prefix) |
| 95 | + except ImportError: |
| 96 | + pass |
| 97 | + |
| 98 | + |
| 99 | +_expand_sysconfig_prefix() |
0 commit comments