Skip to content

Commit 51d355f

Browse files
authored
Build libtcl, libtk, and _tkinter as shared objects, and remove Tix (astral-sh#676)
Several important third-party packages, including matplotlib in its tkagg backend and Pillow, use tkinter as a way of locating libtcl and libtk and making direct C API calls to those libraries. For more details, see the analysis in astral-sh#129 (comment) To make these packages work, we need to expose the full libtcl and libtk dynamic symbol ABI; we can't just statically link them into our own binary. It seems most robust to also expose these as separate libraries under their usual filenames to match the behavior of other Python distributions. Build shared libraries for the _tkinter module and for libtcl and libtk, and set up rpaths so we find our copies of them. libX11 continues to be statically linked, but it's linked into libtk. Just as with the build of Python itself, use --exclude-libs=ALL to prevent the dependencies' symbols from being exported. Stop building Tix because it's broken (astral-sh#723) and it would need to be changed to dynamic linking. Configure libX11 with --disable-loadable-xcursor to fix astral-sh#146, which I ran into while running tests. Add zlib as a build-dep of Tcl/Tk so that they can statically link libz.a. I think we were previously picking up the zlib headers from the OS, which wasn't a problem when libtcl and libtk were static libraries - they got linked into CPython itself which also linked zlib.a. But now libtcl.so and libtk.so need zlib.a. Fixes astral-sh#129 Fixes astral-sh#533
1 parent 7c96f41 commit 51d355f

File tree

15 files changed

+189
-157
lines changed

15 files changed

+189
-157
lines changed

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
name = "pythonbuild"
33
version = "0.1.0"
44
authors = ["Gregory Szorc <[email protected]>"]
5-
edition = "2021"
5+
edition = "2024"
66

77
[dependencies]
88
anyhow = "1.0.80"

cpython-unix/Makefile

Lines changed: 6 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -192,18 +192,14 @@ $(OUTDIR)/patchelf-$(PATCHELF_VERSION)-$(PACKAGE_SUFFIX).tar: $(PYTHON_DEP_DEPEN
192192
$(OUTDIR)/sqlite-$(SQLITE_VERSION)-$(PACKAGE_SUFFIX).tar: $(PYTHON_DEP_DEPENDS) $(HERE)/build-sqlite.sh
193193
$(RUN_BUILD) --docker-image $(DOCKER_IMAGE_BUILD) sqlite
194194

195-
$(OUTDIR)/tcl-$(TCL_VERSION)-$(PACKAGE_SUFFIX).tar: $(PYTHON_DEP_DEPENDS) $(HERE)/build-tcl.sh
196-
$(RUN_BUILD) --docker-image $(DOCKER_IMAGE_BUILD) tcl
197-
198-
TIX_DEPENDS = \
199-
$(HERE)/build-tix.sh \
200-
$(OUTDIR)/tcl-$(TCL_VERSION)-$(PACKAGE_SUFFIX).tar \
201-
$(OUTDIR)/tk-$(TK_VERSION)-$(PACKAGE_SUFFIX).tar \
202-
$(if $(NEED_LIBX11),$(OUTDIR)/libX11-$(LIBX11_VERSION)-$(PACKAGE_SUFFIX).tar) \
195+
TCL_DEPENDS = \
196+
$(PYTHON_DEP_DEPENDS) \
197+
$(HERE)/build-tcl.sh \
198+
$(OUTDIR)/zlib-$(ZLIB_VERSION)-$(PACKAGE_SUFFIX).tar \
203199
$(NULL)
204200

205-
$(OUTDIR)/tix-$(TIX_VERSION)-$(PACKAGE_SUFFIX).tar: $(TIX_DEPENDS)
206-
$(RUN_BUILD) --docker-image $(DOCKER_IMAGE_BUILD) tix
201+
$(OUTDIR)/tcl-$(TCL_VERSION)-$(PACKAGE_SUFFIX).tar: $(TCL_DEPENDS)
202+
$(RUN_BUILD) --docker-image $(DOCKER_IMAGE_BUILD) tcl
207203

208204
TK_DEPENDS = \
209205
$(HOST_PYTHON_DEPENDS) \
@@ -271,7 +267,6 @@ PYTHON_DEPENDS_$(1) := \
271267
$$(if $$(NEED_SQLITE),$$(OUTDIR)/sqlite-$$(SQLITE_VERSION)-$$(PACKAGE_SUFFIX).tar) \
272268
$$(if $$(NEED_TCL),$$(OUTDIR)/tcl-$$(TCL_VERSION)-$$(PACKAGE_SUFFIX).tar) \
273269
$$(if $$(NEED_TK),$$(OUTDIR)/tk-$$(TK_VERSION)-$$(PACKAGE_SUFFIX).tar) \
274-
$$(if $$(NEED_TIX),$$(OUTDIR)/tix-$$(TIX_VERSION)-$$(PACKAGE_SUFFIX).tar) \
275270
$$(if $$(NEED_UUID),$$(OUTDIR)/uuid-$$(UUID_VERSION)-$$(PACKAGE_SUFFIX).tar) \
276271
$$(if $$(NEED_XZ),$$(OUTDIR)/xz-$$(XZ_VERSION)-$$(PACKAGE_SUFFIX).tar) \
277272
$$(if $$(NEED_ZLIB),$$(OUTDIR)/zlib-$$(ZLIB_VERSION)-$$(PACKAGE_SUFFIX).tar) \

cpython-unix/build-cpython.sh

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ sed "${sed_args[@]}" "s|/tools/host|${TOOLS_PATH}/host|g" ${TOOLS_PATH}/host/sha
4444
# We force linking of external static libraries by removing the shared
4545
# libraries. This is hacky. But we're building in a temporary container
4646
# and it gets the job done.
47-
find ${TOOLS_PATH}/deps -name '*.so*' -exec rm {} \;
47+
find ${TOOLS_PATH}/deps -name '*.so*' -a \! \( -name 'libtcl*.so*' -or -name 'libtk*.so*' \) -exec rm {} \;
4848

4949
tar -xf Python-${PYTHON_VERSION}.tar.xz
5050

@@ -693,6 +693,8 @@ if [ "${PYBUILD_SHARED}" = "1" ]; then
693693
${ROOT}/out/python/install/bin/python${PYTHON_MAJMIN_VERSION}
694694

695695
# Python's build system doesn't make this file writable.
696+
# TODO(geofft): @executable_path/ is a weird choice here, who is
697+
# relying on it? Should probably be @loader_path.
696698
chmod 755 ${ROOT}/out/python/install/lib/${LIBPYTHON_SHARED_LIBRARY_BASENAME}
697699
install_name_tool \
698700
-change /install/lib/${LIBPYTHON_SHARED_LIBRARY_BASENAME} @executable_path/${LIBPYTHON_SHARED_LIBRARY_BASENAME} \
@@ -711,6 +713,13 @@ if [ "${PYBUILD_SHARED}" = "1" ]; then
711713
-change /install/lib/${LIBPYTHON_SHARED_LIBRARY_BASENAME} @executable_path/../lib/${LIBPYTHON_SHARED_LIBRARY_BASENAME} \
712714
${ROOT}/out/python/install/bin/python${PYTHON_MAJMIN_VERSION}${PYTHON_BINARY_SUFFIX}
713715
fi
716+
717+
# At the moment, python3 and libpython don't have shared-library
718+
# dependencies, but at some point we will want to run this for
719+
# them too.
720+
for module in ${ROOT}/out/python/install/lib/python*/lib-dynload/*.so; do
721+
install_name_tool -add_rpath @loader_path/../.. "$module"
722+
done
714723
else # (not macos)
715724
LIBPYTHON_SHARED_LIBRARY_BASENAME=libpython${PYTHON_MAJMIN_VERSION}${PYTHON_BINARY_SUFFIX}.so.1.0
716725
LIBPYTHON_SHARED_LIBRARY=${ROOT}/out/python/install/lib/${LIBPYTHON_SHARED_LIBRARY_BASENAME}
@@ -1232,16 +1241,20 @@ fi
12321241
rm -f ${ROOT}/out/python/build/lib/{libdb-6.0,libxcb-*,libX11-xcb}.a
12331242

12341243
if [ -d "${TOOLS_PATH}/deps/lib/tcl8" ]; then
1235-
# Copy tcl/tk/tix resources needed by tkinter.
1244+
# Copy tcl/tk resources needed by tkinter.
12361245
mkdir ${ROOT}/out/python/install/lib/tcl
12371246
# Keep this list in sync with tcl_library_paths.
12381247
for source in ${TOOLS_PATH}/deps/lib/{itcl4.2.4,tcl8,tcl8.6,thread2.8.9,tk8.6}; do
12391248
cp -av $source ${ROOT}/out/python/install/lib/
12401249
done
12411250

1242-
if [[ "${PYBUILD_PLATFORM}" != macos* ]]; then
1243-
cp -av ${TOOLS_PATH}/deps/lib/Tix8.4.3 ${ROOT}/out/python/install/lib/
1244-
fi
1251+
(
1252+
shopt -s nullglob
1253+
dylibs=(${TOOLS_PATH}/deps/lib/lib*.dylib ${TOOLS_PATH}/deps/lib/lib*.so)
1254+
if [ "${#dylibs[@]}" -gt 0 ]; then
1255+
cp -av "${dylibs[@]}" ${ROOT}/out/python/install/lib/
1256+
fi
1257+
)
12451258
fi
12461259

12471260
# Copy the terminfo database if present.

cpython-unix/build-libX11.sh

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,11 @@ if [ -n "${CROSS_COMPILING}" ]; then
9999
esac
100100
fi
101101

102+
# Avoid dlopen("libXcursor.so.1") from the OS, which can go horribly wrong. We
103+
# might not need to avoid this if we switch to shipping X11 as shared
104+
# libraries, and ideally if we ship libXcursor ourselves.
105+
EXTRA_FLAGS="${EXTRA_FLAGS} --disable-loadable-xcursor"
106+
102107
# CC_FOR_BUILD is here because configure doesn't look for `clang` when
103108
# cross-compiling. So we force it.
104109
CFLAGS="${EXTRA_TARGET_CFLAGS} -fPIC -I/tools/deps/include" \

cpython-unix/build-main.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -182,7 +182,7 @@ def main():
182182
# because we can get some speedup from parallel operations. But we also don't
183183
# share a make job server with each build. So if we didn't limit the
184184
# parallelism we could easily oversaturate the CPU. Higher levels of
185-
# parallelism don't result in meaningful build speedups because tk/tix has
185+
# parallelism don't result in meaningful build speedups because tk has
186186
# a long, serial dependency chain that can't be built in parallel.
187187
parallelism = min(1 if args.serial else 4, multiprocessing.cpu_count())
188188

cpython-unix/build-tcl.sh

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,12 @@ set -ex
77

88
ROOT=`pwd`
99

10+
# Force linking to static libraries from our dependencies.
11+
# TODO(geofft): This is copied from build-cpython.sh. Really this should
12+
# be done at the end of the build of each dependency, rather than before
13+
# the build of each consumer.
14+
find ${TOOLS_PATH}/deps -name '*.so*' -exec rm {} \;
15+
1016
export PATH=${TOOLS_PATH}/${TOOLCHAIN}/bin:${TOOLS_PATH}/host/bin:$PATH
1117
export PKG_CONFIG_PATH=${TOOLS_PATH}/deps/share/pkgconfig:${TOOLS_PATH}/deps/lib/pkgconfig
1218

@@ -20,9 +26,8 @@ if [ -n "${STATIC}" ]; then
2026
# `checking whether musl-clang accepts -g...` fails with a duplicate definition error
2127
TARGET_TRIPLE="$(echo "${TARGET_TRIPLE}" | sed -e 's/-unknown-linux-musl/-unknown-linux-gnu/g')"
2228
fi
23-
fi
2429

25-
patch -p1 << 'EOF'
30+
patch -p1 << 'EOF'
2631
diff --git a/unix/Makefile.in b/unix/Makefile.in
2732
--- a/unix/Makefile.in
2833
+++ b/unix/Makefile.in
@@ -36,24 +41,31 @@ diff --git a/unix/Makefile.in b/unix/Makefile.in
3641
fi; \
3742
fi; \
3843
EOF
44+
fi
3945

4046
# Remove packages we don't care about and can pull in unwanted symbols.
4147
rm -rf pkgs/sqlite* pkgs/tdbc*
4248

4349
pushd unix
4450

4551
CFLAGS="${EXTRA_TARGET_CFLAGS} -fPIC -I${TOOLS_PATH}/deps/include"
52+
LDFLAGS="${EXTRA_TARGET_CFLAGS} -L${TOOLS_PATH}/deps/lib"
53+
if [[ "${PYBUILD_PLATFORM}" != macos* ]]; then
54+
LDFLAGS="${LDFLAGS} -Wl,--exclude-libs,ALL"
55+
fi
4656

47-
CFLAGS="${CFLAGS}" CPPFLAGS="${CFLAGS}" LDFLAGS="${EXTRA_TARGET_LDFLAGS}" ./configure \
57+
CFLAGS="${CFLAGS}" CPPFLAGS="${CFLAGS}" LDFLAGS="${LDFLAGS}" ./configure \
4858
--build=${BUILD_TRIPLE} \
4959
--host=${TARGET_TRIPLE} \
5060
--prefix=/tools/deps \
51-
--enable-shared=no \
61+
--enable-shared"${STATIC:+=no}" \
5262
--enable-threads
5363

54-
make -j ${NUM_CPUS}
55-
make -j ${NUM_CPUS} install DESTDIR=${ROOT}/out
64+
make -j ${NUM_CPUS} DYLIB_INSTALL_DIR=@rpath
65+
make -j ${NUM_CPUS} install DESTDIR=${ROOT}/out DYLIB_INSTALL_DIR=@rpath
5666
make -j ${NUM_CPUS} install-private-headers DESTDIR=${ROOT}/out
5767

58-
# For some reason libtcl*.a have weird permissions. Fix that.
59-
chmod 644 ${ROOT}/out/tools/deps/lib/libtcl*.a
68+
if [ -n "${STATIC}" ]; then
69+
# For some reason libtcl*.a have weird permissions. Fix that.
70+
chmod 644 ${ROOT}/out/tools/deps/lib/libtcl*.a
71+
fi

cpython-unix/build-tk.sh

Lines changed: 28 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,12 @@ set -ex
77

88
ROOT=`pwd`
99

10+
# Force linking to static libraries from our dependencies.
11+
# TODO(geofft): This is copied from build-cpython.sh. Really this should
12+
# be done at the end of the build of each dependency, rather than before
13+
# the build of each consumer.
14+
find ${TOOLS_PATH}/deps -name '*.so*' -exec rm {} \;
15+
1016
export PATH=${TOOLS_PATH}/deps/bin:${TOOLS_PATH}/${TOOLCHAIN}/bin:${TOOLS_PATH}/host/bin:$PATH
1117
export PKG_CONFIG_PATH=${TOOLS_PATH}/deps/share/pkgconfig:${TOOLS_PATH}/deps/lib/pkgconfig
1218

@@ -23,6 +29,7 @@ if [[ "${PYBUILD_PLATFORM}" = macos* ]]; then
2329
LDFLAGS="-L${TOOLS_PATH}/deps/lib"
2430
EXTRA_CONFIGURE_FLAGS="--enable-aqua=yes --without-x"
2531
else
32+
LDFLAGS="${LDFLAGS} -Wl,--exclude-libs,ALL"
2633
EXTRA_CONFIGURE_FLAGS="--x-includes=${TOOLS_PATH}/deps/include --x-libraries=${TOOLS_PATH}/deps/lib"
2734
fi
2835

@@ -31,29 +38,38 @@ CFLAGS="${CFLAGS}" CPPFLAGS="${CFLAGS}" LDFLAGS="${LDFLAGS}" ./configure \
3138
--host=${TARGET_TRIPLE} \
3239
--prefix=/tools/deps \
3340
--with-tcl=${TOOLS_PATH}/deps/lib \
34-
--enable-shared=no \
41+
--enable-shared"${STATIC:+=no}" \
3542
--enable-threads \
3643
${EXTRA_CONFIGURE_FLAGS}
3744

3845
# Remove wish, since we don't need it.
39-
if [[ "${PYBUILD_PLATFORM}" != macos* ]]; then
40-
sed -i 's/all: binaries libraries doc/all: libraries/' Makefile
41-
sed -i 's/install-binaries: $(TK_STUB_LIB_FILE) $(TK_LIB_FILE) ${WISH_EXE}/install-binaries: $(TK_STUB_LIB_FILE) $(TK_LIB_FILE)/' Makefile
46+
if [[ "${PYBUILD_PLATFORM}" = macos* ]]; then
47+
sed_args=(-i '' -e)
48+
else
49+
sed_args=(-i)
4250
fi
51+
sed "${sed_args[@]}" 's/all: binaries libraries doc/all: libraries/' Makefile
52+
sed "${sed_args[@]}" 's/install-binaries: $(TK_STUB_LIB_FILE) $(TK_LIB_FILE) ${WISH_EXE}/install-binaries: $(TK_STUB_LIB_FILE) $(TK_LIB_FILE)/' Makefile
4353

44-
# For some reason musl isn't link libXau and libxcb. So we hack the Makefile
45-
# to do what we want.
46-
if [ "${CC}" = "musl-clang" ]; then
47-
sed -i 's/-ldl -lpthread /-ldl -lpthread -lXau -lxcb/' tkConfig.sh
48-
sed -i 's/-lpthread $(X11_LIB_SWITCHES) -ldl -lpthread/-lpthread $(X11_LIB_SWITCHES) -ldl -lpthread -lXau -lxcb/' Makefile
54+
# We are statically linking libX11, and static libraries do not carry
55+
# information about dependencies. pkg-config --static does, but Tcl/Tk's
56+
# build system apparently is too old for that. So we need to manually
57+
# inform the build process that libX11.a needs libxcb.a and libXau.a.
58+
# Note that the order is significant, for static libraries: X11 requires
59+
# xcb, which requires Xau.
60+
MAKE_VARS=(DYLIB_INSTALL_DIR=@rpath)
61+
if [[ "${PYBUILD_PLATFORM}" != macos* ]]; then
62+
MAKE_VARS+=(X11_LIB_SWITCHES="-lX11 -lxcb -lXau")
4963
fi
5064

51-
make -j ${NUM_CPUS}
65+
make -j ${NUM_CPUS} "${MAKE_VARS[@]}"
5266
touch wish
53-
make -j ${NUM_CPUS} install DESTDIR=${ROOT}/out
67+
make -j ${NUM_CPUS} install DESTDIR=${ROOT}/out "${MAKE_VARS[@]}"
5468
make -j ${NUM_CPUS} install-private-headers DESTDIR=${ROOT}/out
5569

5670
# For some reason libtk*.a have weird permissions. Fix that.
57-
chmod 644 /${ROOT}/out/tools/deps/lib/libtk*.a
71+
if [ -n "${STATIC}" ]; then
72+
chmod 644 /${ROOT}/out/tools/deps/lib/libtk*.a
73+
fi
5874

5975
rm ${ROOT}/out/tools/deps/bin/wish*

cpython-unix/build.py

Lines changed: 5 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -379,48 +379,6 @@ def build_libedit(
379379
build_env.get_tools_archive(dest_archive, "deps")
380380

381381

382-
def build_tix(
383-
settings, client, image, host_platform, target_triple, build_options, dest_archive
384-
):
385-
tcl_archive = download_entry("tcl", DOWNLOADS_PATH)
386-
tk_archive = download_entry("tk", DOWNLOADS_PATH)
387-
tix_archive = download_entry("tix", DOWNLOADS_PATH)
388-
389-
with build_environment(client, image) as build_env:
390-
if settings.get("needs_toolchain"):
391-
build_env.install_toolchain(
392-
BUILD,
393-
host_platform,
394-
target_triple,
395-
binutils=install_binutils(host_platform),
396-
clang=True,
397-
musl="musl" in target_triple,
398-
static="static" in build_options,
399-
)
400-
401-
depends = {"tcl", "tk"}
402-
if not host_platform.startswith("macos_"):
403-
depends |= {"libX11", "xorgproto"}
404-
405-
for p in sorted(depends):
406-
build_env.install_artifact_archive(BUILD, p, target_triple, build_options)
407-
408-
for p in (tcl_archive, tk_archive, tix_archive, SUPPORT / "build-tix.sh"):
409-
build_env.copy_file(p)
410-
411-
env = {
412-
"TOOLCHAIN": "clang-%s" % host_platform,
413-
"TCL_VERSION": DOWNLOADS["tcl"]["version"],
414-
"TIX_VERSION": DOWNLOADS["tix"]["version"],
415-
"TK_VERSION": DOWNLOADS["tk"]["version"],
416-
}
417-
418-
add_target_env(env, host_platform, target_triple, build_env)
419-
420-
build_env.run("build-tix.sh", environment=env)
421-
build_env.get_tools_archive(dest_archive, "deps")
422-
423-
424382
def build_cpython_host(
425383
client,
426384
image,
@@ -946,9 +904,6 @@ def build_cpython(
946904
"tk8.6",
947905
]
948906

949-
if "-apple" not in target_triple:
950-
python_info["tcl_library_paths"].append("Tix8.4.3")
951-
952907
if "-apple" in target_triple:
953908
python_info["apple_sdk_platform"] = env["APPLE_SDK_PLATFORM"]
954909
python_info["apple_sdk_version"] = env["APPLE_SDK_VERSION"]
@@ -1166,6 +1121,9 @@ def main():
11661121
"zstd",
11671122
):
11681123
tools_path = "host" if action in ("m4", "patchelf") else "deps"
1124+
extra_archives = {
1125+
"tcl": {"zlib"},
1126+
}.get(action)
11691127

11701128
simple_build(
11711129
settings,
@@ -1176,6 +1134,7 @@ def main():
11761134
target_triple=target_triple,
11771135
build_options=build_options,
11781136
dest_archive=dest_archive,
1137+
extra_archives=extra_archives,
11791138
tools_path=tools_path,
11801139
)
11811140

@@ -1239,19 +1198,8 @@ def main():
12391198
python_host_version=python_host_version,
12401199
)
12411200

1242-
elif action == "tix":
1243-
build_tix(
1244-
settings,
1245-
client,
1246-
get_image(client, ROOT, BUILD, docker_image, host_platform),
1247-
host_platform=host_platform,
1248-
target_triple=target_triple,
1249-
build_options=build_options,
1250-
dest_archive=dest_archive,
1251-
)
1252-
12531201
elif action == "tk":
1254-
extra_archives = {"tcl"}
1202+
extra_archives = {"tcl", "zlib"}
12551203
if not host_platform.startswith("macos_"):
12561204
extra_archives |= {
12571205
"libX11",

0 commit comments

Comments
 (0)