Skip to content

Commit 8b2f21e

Browse files
committed
fix: add sysconfig prefix-sentinel expansion for Python 2.7 and include standalone wrappers
1 parent 44c8d9c commit 8b2f21e

5 files changed

Lines changed: 145 additions & 15 deletions

File tree

2.7/x86/Dockerfile

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,14 @@ RUN set -eux && ./rpath-patcher.sh
194194
# bottleneck for pip 2.7 users (most Py2 wheels on PyPI are pure-Python
195195
# `py2.py3-none-any`). Users needing native wheels can install with
196196
# `--no-binary :all:`.
197+
#
198+
# What we DO need on 2.7 is the sysconfig prefix-sentinel expansion, so
199+
# that a pip install from source (-> distutils/sysconfig LDFLAGS) resolves
200+
# libffi/libsqlite3/libreadline/… against the install's actual prefix
201+
# after packing-initializer's relocation (and after an end-user move).
202+
COPY ["common/build/wrappers/standalone_python_musl_py2.py", \
203+
"common/build/wrappers/standalone_python_musl_py2.pth", \
204+
"/opt/python/lib/python2.7/site-packages/"]
197205

198206
# =========== final ===========
199207
FROM --platform=linux/386 debian:latest AS final

2.7/x86_64/Dockerfile

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,14 @@ RUN set -eux && ./rpath-patcher.sh
194194
# bottleneck for pip 2.7 users (most Py2 wheels on PyPI are pure-Python
195195
# `py2.py3-none-any`). Users needing native wheels can install with
196196
# `--no-binary :all:`.
197+
#
198+
# What we DO need on 2.7 is the sysconfig prefix-sentinel expansion, so
199+
# that a pip install from source (-> distutils/sysconfig LDFLAGS) resolves
200+
# libffi/libsqlite3/libreadline/… against the install's actual prefix
201+
# after packing-initializer's relocation (and after an end-user move).
202+
COPY ["common/build/wrappers/standalone_python_musl_py2.py", \
203+
"common/build/wrappers/standalone_python_musl_py2.pth", \
204+
"/opt/python/lib/python2.7/site-packages/"]
197205

198206
# =========== final ===========
199207
FROM --platform=linux/amd64 debian:latest AS final

common/build/wrappers/packing-initializer

Lines changed: 29 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -62,32 +62,46 @@ sysconfig_path_rewriter() {
6262
# let standalone_python_musl.py's .pth hook substitute `sys.prefix` for
6363
# the sentinel at runtime, before any caller reads sysconfig.
6464
#
65-
# For py2 we use a fixed `/opt/python/shared_libraries` instead, because
66-
# py2 doesn't ship the .pth runtime hook. py2 relocation has other
67-
# hardcoded-path limitations anyway and is outside the release matrix's
68-
# smoke tests.
65+
# Both py2 and py3 use the same sentinel: their respective .pth hooks
66+
# (standalone_python_musl_py2.py, standalone_python_musl.py) expand
67+
# @STANDALONE_PYTHON_PREFIX@ to the live sys.prefix at interpreter
68+
# startup. This keeps installs fully relocatable (move /opt/python →
69+
# /tmp/whatever and `pip install foo-from-source` still resolves libs
70+
# inside the moved tree) without any ELF rewriting at move time.
6971
find /opt/python/lib \
7072
-type f \
7173
\( -name '_sysconfigdata*.py' \
7274
-o -name 'Makefile' \
7375
-o -name 'python-config.py' \) \
7476
-exec grep -l '/opt/shared_libraries' {} + 2>/dev/null \
7577
| while read -r f; do
76-
case "$f" in
77-
*/python2*/*)
78-
sed -i 's|/opt/shared_libraries|/opt/python/shared_libraries|g' "$f"
79-
echo "Rewrote sysconfig paths (py2 absolute) in $f"
80-
;;
81-
*)
82-
sed -i 's|/opt/shared_libraries|@STANDALONE_PYTHON_PREFIX@/shared_libraries|g' "$f"
83-
echo "Rewrote sysconfig paths (py3 sentinel) in $f"
84-
;;
85-
esac
78+
sed -i 's|/opt/shared_libraries|@STANDALONE_PYTHON_PREFIX@/shared_libraries|g' "$f"
79+
echo "Rewrote sysconfig paths (sentinel) in $f"
8680
done
8781

82+
# Also patch the `python*-config` shell script if it bakes the absolute
83+
# path (py2's config/Makefile-based python-config does; py3's
84+
# python3.N-config is a shim that re-execs the interpreter). Restrict
85+
# to shebanged text files so we never sed a real binary — the new
86+
# token is 22 bytes longer than the old and would corrupt fixed-size
87+
# structures if one of the real interpreter ELFs happened to contain
88+
# the literal path by accident.
89+
for f in /opt/python/bin/*; do
90+
[ -f "$f" ] || continue
91+
head_bytes=$(head -c 2 "$f" 2>/dev/null || true)
92+
[ "$head_bytes" = "#!" ] || continue
93+
grep -q '/opt/shared_libraries' "$f" 2>/dev/null || continue
94+
sed -i 's|/opt/shared_libraries|@STANDALONE_PYTHON_PREFIX@/shared_libraries|g' "$f"
95+
echo "Rewrote sysconfig paths (sentinel) in $f"
96+
done
97+
8898
# Invalidate .pyc caches for any _sysconfigdata*.py we changed — stale
8999
# .pyc would mask the .py edit until the next full compileall.
90-
find /opt/python/lib -path '*/__pycache__/_sysconfigdata*' -delete 2>/dev/null || true
100+
find /opt/python/lib \
101+
\( -path '*/__pycache__/_sysconfigdata*' \
102+
-o -name '_sysconfigdata.pyc' \
103+
-o -name '_sysconfigdata.pyo' \) \
104+
-delete 2>/dev/null || true
91105
}
92106

93107
shebang_rewriter() {
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
import standalone_python_musl_py2
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
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

Comments
 (0)