Skip to content

Commit 54bf7c6

Browse files
committed
unix: build a dynamic libpython unless targeting musl
python-build-standalone had its origins in supporting a single file, statically linked Python interpreter. So its defaults were to not produce a libpython shared library. Fast forward several months. The PYTHON.json file format is now sufficiently defined to the point where it is possible for a single distribution to contain both a static and shared library and for downstream consumers to be smart about which one they consume. If a consumer wants a static libpython, they can statically link it. If a consumer wants a shared libpython, they can use the shared library version. If a consumer wants a custom libpython, they can grab the set of object files and library dependencies that are needed and produce a custom one, linking statically or dynamically as they desire. Furthermore, the lack of a dynamic libpython was non-standard and caused issues in some environments. It would be nice for our Python distributions to be more usable. This commit teaches the UNIX build system to produce a dynamic libpython unless building for musl. As part of making macOS work, I discovered that libz.1.dylib was being searched for in a non-sensical directory. So that was fixed. I also discovered that CPython's build system links module libraries against the `python` executable instead of the library. Why, I'm not sure. To make the produced distributions more portable, we need to hack up RPATH in binaries linking against libpython so they can find the library relative to the executable: no LD_LIBRARY_PATH mucking required. As part of this work, I noticed we weren't populating shared_lib and static_lib in the build info metadata. So I added this as well. Closes #44.
1 parent 580b4d7 commit 54bf7c6

File tree

3 files changed

+155
-7
lines changed

3 files changed

+155
-7
lines changed

cpython-unix/build-cpython.sh

Lines changed: 127 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,78 @@ diff --git a/Modules/makesetup b/Modules/makesetup
6060
*) DEFS="SHAREDMODS=$SHAREDMODS$NL$DEFS";;
6161
EOF
6262

63+
# The default build rule for the macOS dylib doesn't pick up libraries
64+
# from modules / makesetup. So patch it accordingly.
65+
if [ "${PYTHON_MAJMIN_VERSION}" = "3.7" ]; then
66+
patch -p1 << "EOF"
67+
diff --git a/Makefile.pre.in b/Makefile.pre.in
68+
--- a/Makefile.pre.in
69+
+++ b/Makefile.pre.in
70+
@@ -643,7 +643,7 @@ libpython3.so: libpython$(LDVERSION).so
71+
$(BLDSHARED) $(NO_AS_NEEDED) -o $@ -Wl,-h$@ $^
72+
73+
libpython$(LDVERSION).dylib: $(LIBRARY_OBJS)
74+
- $(CC) -dynamiclib -Wl,-single_module $(PY_CORE_LDFLAGS) -undefined dynamic_lookup -Wl,-install_name,$(prefix)/lib/libpython$(LDVERSION).dylib -Wl,-compatibility_version,$(VERSION) -Wl,-current_version,$(VERSION) -o $@ $(LIBRARY_OBJS) $(SHLIBS) $(LIBC) $(LIBM) $(LDLAST); \
75+
+ $(CC) -dynamiclib -Wl,-single_module $(PY_CORE_LDFLAGS) -undefined dynamic_lookup -Wl,-install_name,$(prefix)/lib/libpython$(LDVERSION).dylib -Wl,-compatibility_version,$(VERSION) -Wl,-current_version,$(VERSION) -o $@ $(LIBRARY_OBJS) $(MODLIBS) $(SHLIBS) $(LIBC) $(LIBM) $(LDLAST); \
76+
77+
78+
libpython$(VERSION).sl: $(LIBRARY_OBJS)
79+
EOF
80+
else
81+
patch -p1 << "EOF"
82+
diff --git a/Makefile.pre.in b/Makefile.pre.in
83+
--- a/Makefile.pre.in
84+
+++ b/Makefile.pre.in
85+
@@ -628,7 +628,7 @@ libpython3.so: libpython$(LDVERSION).so
86+
$(BLDSHARED) $(NO_AS_NEEDED) -o $@ -Wl,-h$@ $^
87+
88+
libpython$(LDVERSION).dylib: $(LIBRARY_OBJS)
89+
- $(CC) -dynamiclib -Wl,-single_module $(PY_CORE_LDFLAGS) -undefined dynamic_lookup -Wl,-install_name,$(prefix)/lib/libpython$(LDVERSION).dylib -Wl,-compatibility_version,$(VERSION) -Wl,-current_version,$(VERSION) -o $@ $(LIBRARY_OBJS) $(DTRACE_OBJS) $(SHLIBS) $(LIBC) $(LIBM); \
90+
+ $(CC) -dynamiclib -Wl,-single_module $(PY_CORE_LDFLAGS) -undefined dynamic_lookup -Wl,-install_name,$(prefix)/lib/libpython$(LDVERSION).dylib -Wl,-compatibility_version,$(VERSION) -Wl,-current_version,$(VERSION) -o $@ $(LIBRARY_OBJS) $(DTRACE_OBJS) $(MODLIBS) $(SHLIBS) $(LIBC) $(LIBM); \
91+
92+
93+
libpython$(VERSION).sl: $(LIBRARY_OBJS)
94+
EOF
95+
fi
96+
97+
# Also on macOS, the `python` executable is linked against libraries defined by statically
98+
# linked modules. But those libraries should only get linked into libpython, not the
99+
# executable. This behavior is kinda suspect on all platforms, as it could be adding
100+
# library dependencies that shouldn't need to be there.
101+
if [ "${PYBUILD_PLATFORM}" = "macos" ]; then
102+
if [ "${PYTHON_MAJMIN_VERSION}" = "3.7" ]; then
103+
patch -p1 <<"EOF"
104+
diff --git a/Makefile.pre.in b/Makefile.pre.in
105+
--- a/Makefile.pre.in
106+
+++ b/Makefile.pre.in
107+
@@ -578,7 +578,7 @@ clinic: check-clean-src $(srcdir)/Modules/_blake2/blake2s_impl.c
108+
109+
# Build the interpreter
110+
$(BUILDPYTHON): Programs/python.o $(LIBRARY) $(LDLIBRARY) $(PY3LIBRARY)
111+
- $(LINKCC) $(PY_CORE_LDFLAGS) $(LINKFORSHARED) -o $@ Programs/python.o $(BLDLIBRARY) $(LIBS) $(MODLIBS) $(SYSLIBS) $(LDLAST)
112+
+ $(LINKCC) $(PY_CORE_LDFLAGS) $(LINKFORSHARED) -o $@ Programs/python.o $(BLDLIBRARY) $(LIBS) $(SYSLIBS) $(LDLAST)
113+
114+
platform: $(BUILDPYTHON) pybuilddir.txt
115+
$(RUNSHARED) $(PYTHON_FOR_BUILD) -c 'import sys ; from sysconfig import get_platform ; print("%s-%d.%d" % (get_platform(), *sys.version_info[:2]))' >platform
116+
EOF
117+
else
118+
patch -p1 <<"EOF"
119+
diff --git a/Makefile.pre.in b/Makefile.pre.in
120+
--- a/Makefile.pre.in
121+
+++ b/Makefile.pre.in
122+
@@ -563,7 +563,7 @@ clinic: check-clean-src $(srcdir)/Modules/_blake2/blake2s_impl.c
123+
124+
# Build the interpreter
125+
$(BUILDPYTHON): Programs/python.o $(LIBRARY) $(LDLIBRARY) $(PY3LIBRARY)
126+
- $(LINKCC) $(PY_CORE_LDFLAGS) $(LINKFORSHARED) -o $@ Programs/python.o $(BLDLIBRARY) $(LIBS) $(MODLIBS) $(SYSLIBS)
127+
+ $(LINKCC) $(PY_CORE_LDFLAGS) $(LINKFORSHARED) -o $@ Programs/python.o $(BLDLIBRARY) $(LIBS) $(SYSLIBS)
128+
129+
platform: $(BUILDPYTHON) pybuilddir.txt
130+
$(RUNSHARED) $(PYTHON_FOR_BUILD) -c 'import sys ; from sysconfig import get_platform ; print("%s-%d.%d" % (get_platform(), *sys.version_info[:2]))' >platform
131+
EOF
132+
fi
133+
fi
134+
63135
# Code that runs at ctypes module import time does not work with
64136
# non-dynamic binaries. Patch Python to work around this.
65137
# See https://bugs.python.org/issue37060.
@@ -152,14 +224,18 @@ fi
152224
CPPFLAGS=$CFLAGS
153225
LDFLAGS="-L${TOOLS_PATH}/deps/lib"
154226

227+
CONFIGURE_FLAGS="--prefix=/install --with-openssl=${TOOLS_PATH}/deps --without-ensurepip"
228+
155229
if [ "${CC}" = "musl-clang" ]; then
156230
CFLAGS="${CFLAGS} -static"
157231
CPPFLAGS="${CPPFLAGS} -static"
158232
LDFLAGS="${LDFLAGS} -static"
233+
PYBUILD_SHARED=0
234+
else
235+
CONFIGURE_FLAGS="${CONFIGURE_FLAGS} --enable-shared"
236+
PYBUILD_SHARED=1
159237
fi
160238

161-
CONFIGURE_FLAGS="--prefix=/install --with-openssl=${TOOLS_PATH}/deps --without-ensurepip"
162-
163239
if [ -n "${CPYTHON_DEBUG}" ]; then
164240
CONFIGURE_FLAGS="${CONFIGURE_FLAGS} --with-pydebug"
165241
fi
@@ -188,6 +264,51 @@ cat ../Makefile.extra >> Makefile
188264
make -j ${NUM_CPUS}
189265
make -j ${NUM_CPUS} install DESTDIR=${ROOT}/out/python
190266

267+
if [ "${PYTHON_MAJMIN_VERSION}" = "3.7" ]; then
268+
PYTHON_BINARY_SUFFIX=m
269+
else
270+
PYTHON_BINARY_SUFFIX=
271+
fi
272+
273+
# If we're building a shared library hack some binaries so rpath is set.
274+
# This ensures we can run the binary in any location without
275+
# LD_LIBRARY_PATH pointing to the directory containing libpython.
276+
if [ "${PYBUILD_SHARED}" = "1" ]; then
277+
if [ "${PYBUILD_PLATFORM}" = "macos" ]; then
278+
# There's only 1 dylib produced on macOS and it has the binary suffix.
279+
install_name_tool \
280+
-change /install/lib/libpython${PYTHON_MAJMIN_VERSION}${PYTHON_BINARY_SUFFIX}.dylib @executable_path/../lib/libpython${PYTHON_MAJMIN_VERSION}${PYTHON_BINARY_SUFFIX}.dylib \
281+
${ROOT}/out/python/install/bin/python${PYTHON_MAJMIN_VERSION}
282+
283+
# Python 3.7's build system doesn't make this file writable.
284+
chmod 755 ${ROOT}/out/python/install/lib/libpython${PYTHON_MAJMIN_VERSION}${PYTHON_BINARY_SUFFIX}.dylib
285+
install_name_tool \
286+
-change /install/lib/libpython${PYTHON_MAJMIN_VERSION}${PYTHON_BINARY_SUFFIX}.dylib @executable_path/libpython${PYTHON_MAJMIN_VERSION}${PYTHON_BINARY_SUFFIX}.dylib \
287+
${ROOT}/out/python/install/lib/libpython${PYTHON_MAJMIN_VERSION}${PYTHON_BINARY_SUFFIX}.dylib
288+
289+
# We also normalize /tools/deps/lib/libz.1.dylib to the system location.
290+
install_name_tool \
291+
-change /tools/deps/lib/libz.1.dylib /usr/lib/libz.1.dylib \
292+
${ROOT}/out/python/install/bin/python${PYTHON_MAJMIN_VERSION}
293+
install_name_tool \
294+
-change /tools/deps/lib/libz.1.dylib /usr/lib/libz.1.dylib \
295+
${ROOT}/out/python/install/lib/libpython${PYTHON_MAJMIN_VERSION}${PYTHON_BINARY_SUFFIX}.dylib
296+
297+
if [ -n "${PYTHON_BINARY_SUFFIX}" ]; then
298+
install_name_tool \
299+
-change /install/lib/libpython${PYTHON_MAJMIN_VERSION}.dylib @executable_path/../lib/libpython${PYTHON_MAJMIN_VERSION}.dylib \
300+
${ROOT}/out/python/install/bin/python${PYTHON_MAJMIN_VERSION}${PYTHON_BINARY_SUFFIX}
301+
fi
302+
else
303+
patchelf --set-rpath '$ORIGIN/../lib' ${ROOT}/out/python/install/bin/python${PYTHON_MAJMIN_VERSION}
304+
patchelf --set-rpath '$ORIGIN/../lib' ${ROOT}/out/python/install/lib/libpython3.so
305+
306+
if [ -n "${PYTHON_BINARY_SUFFIX}" ]; then
307+
patchelf --set-rpath '$ORIGIN/../lib' ${ROOT}/out/python/install/bin/python${PYTHON_MAJMIN_VERSION}${PYTHON_BINARY_SUFFIX}
308+
fi
309+
fi
310+
fi
311+
191312
# Install pip so we can patch it to work with non-dynamic executables
192313
# and work around https://github.com/pypa/pip/issues/6543. But pip's bundled
193314
# setuptools has the same bug! So we need to install a patched version.
@@ -316,21 +437,21 @@ fi
316437
# Symlink libpython so we don't have 2 copies. We only need to do
317438
# this on Python 3.7, as 3.8 dropped the m ABI suffix from binary names.
318439

319-
if [ "${PYTHON_MAJMIN_VERSION}" = "3.7" ]; then
440+
if [ -n "${PYTHON_BINARY_SUFFIX}" ]; then
320441
if [ "${PYBUILD_PLATFORM}" = "macos" ]; then
321442
PYTHON_ARCH="darwin"
322443
else
323444
PYTHON_ARCH="x86_64-linux-gnu"
324445
fi
325446

326-
LIBPYTHON=libpython${PYTHON_MAJMIN_VERSION}m.a
447+
LIBPYTHON=libpython${PYTHON_MAJMIN_VERSION}${PYTHON_BINARY_SUFFIX}.a
327448
ln -sf \
328-
python${PYTHON_MAJMIN_VERSION}/config-${PYTHON_MAJMIN_VERSION}m-${PYTHON_ARCH}/${LIBPYTHON} \
449+
python${PYTHON_MAJMIN_VERSION}/config-${PYTHON_MAJMIN_VERSION}${PYTHON_BINARY_SUFFIX}-${PYTHON_ARCH}/${LIBPYTHON} \
329450
${ROOT}/out/python/install/lib/${LIBPYTHON}
330451

331452
# Ditto for Python executable.
332453
ln -sf \
333-
python${PYTHON_MAJMIN_VERSION}m \
454+
python${PYTHON_MAJMIN_VERSION}${PYTHON_BINARY_SUFFIX} \
334455
${ROOT}/out/python/install/bin/python${PYTHON_MAJMIN_VERSION}
335456
fi
336457

cpython-unix/build.Dockerfile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ RUN apt-get install \
44
libc6-dev \
55
make \
66
patch \
7+
patchelf \
78
perl \
89
pkg-config \
910
tar \

cpython-unix/build.py

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -367,6 +367,7 @@ def python_build_info(
367367
build_env,
368368
version,
369369
platform,
370+
musl,
370371
optimizations,
371372
config_c_in,
372373
setup_dist,
@@ -379,12 +380,36 @@ def python_build_info(
379380

380381
bi = {"core": {"objs": [], "links": []}, "extensions": {}}
381382

383+
binary_suffix = "m" if version == "3.7" else ""
384+
382385
if platform == "linux64":
386+
bi["core"][
387+
"static_lib"
388+
] = "install/lib/python{version}/config-{version}{binary_suffix}-x86_64-linux-gnu/libpython{version}{binary_suffix}.a".format(
389+
version=version, binary_suffix=binary_suffix
390+
)
391+
392+
if not musl:
393+
bi["core"]["shared_lib"] = "install/lib/libpython%s%s.so.1.0" % (
394+
version,
395+
binary_suffix,
396+
)
397+
383398
if optimizations in ("lto", "pgo+lto"):
384399
object_file_format = "llvm-bitcode:%s" % DOWNLOADS["llvm"]["version"]
385400
else:
386401
object_file_format = "elf"
387402
elif platform == "macos":
403+
bi["core"][
404+
"static_lib"
405+
] = "install/lib/python{version}/config-{version}{binary_suffix}-darwin/libpython{version}{binary_suffix}.a".format(
406+
version=version, binary_suffix=binary_suffix
407+
)
408+
bi["core"]["shared_lib"] = "install/lib/libpython%s%s.dylib" % (
409+
version,
410+
binary_suffix,
411+
)
412+
388413
if optimizations in ("lto", "pgo+lto"):
389414
object_file_format = "llvm-bitcode:%s" % DOWNLOADS["llvm"]["version"]
390415
else:
@@ -730,13 +755,14 @@ def build_cpython(
730755
"python_stdlib_test_packages": sorted(STDLIB_TEST_PACKAGES),
731756
"python_symbol_visibility": python_symbol_visibility,
732757
"python_extension_module_loading": extension_module_loading,
733-
"libpython_link_mode": "static",
758+
"libpython_link_mode": "static" if "musl" in target_triple else "shared",
734759
"crt_features": crt_features,
735760
"run_tests": "build/run_tests.py",
736761
"build_info": python_build_info(
737762
build_env,
738763
version,
739764
host_platform,
765+
"musl" in target_triple,
740766
optimizations,
741767
config_c_in,
742768
setup_dist_content,

0 commit comments

Comments
 (0)