Skip to content

Commit a01e91c

Browse files
committed
unix: build host CPython separately
Before, we built a host CPython inside build-cpython.sh for build configurations that required it. This commit extracts the host CPython build to its own build script and make target. The impetus for this is that iterating on changes to the main CPython build script is slow due to having to recompile the host CPython all the time. I've been wanting to make this change for a while to speed up that common development workflow. As part of this refactor we now sometimes compile the host CPython when we don't need to. But since we always the host CPython for 3.11+, eventually that waste is reduced to 0. So I'm fine with the overhead.
1 parent b686fe2 commit a01e91c

File tree

4 files changed

+178
-58
lines changed

4 files changed

+178
-58
lines changed

cpython-unix/Makefile

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -240,10 +240,30 @@ $(OUTDIR)/xz-$(XZ_VERSION)-$(PACKAGE_SUFFIX).tar: $(PYTHON_DEP_DEPENDS) $(HERE)/
240240
$(OUTDIR)/zlib-$(ZLIB_VERSION)-$(PACKAGE_SUFFIX).tar: $(PYTHON_DEP_DEPENDS) $(HERE)/build-zlib.sh
241241
$(RUN_BUILD) --docker-image $(DOCKER_IMAGE_BUILD) zlib
242242

243+
PYTHON_HOST_DEPENDS := \
244+
$(PYTHON_DEP_DEPENDS) \
245+
$(HERE)/build-cpython-host.sh \
246+
$(OUTDIR)/autoconf-$(AUTOCONF_VERSION)-$(PACKAGE_SUFFIX).tar \
247+
$(OUTDIR)/m4-$(M4_VERSION)-$(PACKAGE_SUFFIX).tar \
248+
$(NULL)
249+
250+
$(OUTDIR)/cpython-3.8-$(CPYTHON_3.8_VERSION)-$(HOST_PLATFORM).tar: $(PYTHON_HOST_DEPENDS)
251+
$(RUN_BUILD) --docker-image $(DOCKER_IMAGE_BUILD) cpython-3.8-host
252+
253+
$(OUTDIR)/cpython-3.9-$(CPYTHON_3.9_VERSION)-$(HOST_PLATFORM).tar: $(PYTHON_HOST_DEPENDS)
254+
$(RUN_BUILD) --docker-image $(DOCKER_IMAGE_BUILD) cpython-3.9-host
255+
256+
$(OUTDIR)/cpython-3.10-$(CPYTHON_3.10_VERSION)-$(HOST_PLATFORM).tar: $(PYTHON_HOST_DEPENDS)
257+
$(RUN_BUILD) --docker-image $(DOCKER_IMAGE_BUILD) cpython-3.10-host
258+
259+
$(OUTDIR)/cpython-3.11-$(CPYTHON_3.11_VERSION)-$(HOST_PLATFORM).tar: $(PYTHON_HOST_DEPENDS)
260+
$(RUN_BUILD) --docker-image $(DOCKER_IMAGE_BUILD) cpython-3.11-host
261+
243262
PYTHON_DEPENDS := \
244263
$(PYTHON_SUPPORT_FILES) \
245264
$(OUTDIR)/versions/VERSION.pip \
246265
$(OUTDIR)/versions/VERSION.setuptools \
266+
$(OUTDIR)/cpython-$(PYBUILD_PYTHON_MAJOR_VERSION)-$(PYBUILD_PYTHON_VERSION)-$(HOST_PLATFORM).tar \
247267
$(if $(NEED_AUTOCONF),$(OUTDIR)/autoconf-$(AUTOCONF_VERSION)-$(PACKAGE_SUFFIX).tar) \
248268
$(if $(NEED_BDB),$(OUTDIR)/bdb-$(BDB_VERSION)-$(PACKAGE_SUFFIX).tar) \
249269
$(if $(NEED_BZIP2),$(OUTDIR)/bzip2-$(BZIP2_VERSION)-$(PACKAGE_SUFFIX).tar) \

cpython-unix/build-cpython-host.sh

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
#!/usr/bin/env bash
2+
# This Source Code Form is subject to the terms of the Mozilla Public
3+
# License, v. 2.0. If a copy of the MPL was not distributed with this
4+
# file, You can obtain one at https://mozilla.org/MPL/2.0/.
5+
6+
set -ex
7+
8+
export ROOT=`pwd`
9+
10+
export PATH=${TOOLS_PATH}/${TOOLCHAIN}/bin:${TOOLS_PATH}/host/bin:${TOOLS_PATH}/deps/bin:$PATH
11+
12+
# autoconf has some paths hardcoded into scripts. These paths just work in
13+
# the containerized build environment. But from macOS the paths are wrong.
14+
# Explicitly point to the proper path via environment variable overrides.
15+
export AUTOCONF=${TOOLS_PATH}/host/bin/autoconf
16+
export AUTOHEADER=${TOOLS_PATH}/host/bin/autoheader
17+
export AUTOM4TE=${TOOLS_PATH}/host/bin/autom4te
18+
export autom4te_perllibdir=${TOOLS_PATH}/host/share/autoconf
19+
export AC_MACRODIR=${TOOLS_PATH}/host/share/autoconf
20+
export M4=${TOOLS_PATH}/host/bin/m4
21+
export trailer_m4=${TOOLS_PATH}/host/share/autoconf/autoconf/trailer.m4
22+
23+
# The share/autoconf/autom4te.cfg file also hard-codes some paths. Rewrite
24+
# those to the real tools path.
25+
if [ "${PYBUILD_PLATFORM}" = "macos" ]; then
26+
sed_args="-i '' -e"
27+
else
28+
sed_args="-i"
29+
fi
30+
31+
sed ${sed_args} "s|/tools/host|${TOOLS_PATH}/host|g" ${TOOLS_PATH}/host/share/autoconf/autom4te.cfg
32+
33+
tar -xf Python-${PYTHON_VERSION}.tar.xz
34+
35+
pushd "Python-${PYTHON_VERSION}"
36+
37+
# Clang 13 actually prints something with --print-multiarch, confusing CPython's
38+
# configure. This is reported as https://bugs.python.org/issue45405. We nerf the
39+
# check since we know what we're doing.
40+
if [ "${CC}" = "clang" ]; then
41+
if [ -n "${PYTHON_MEETS_MINIMUM_VERSION_3_9}" ]; then
42+
patch -p1 -i ${ROOT}/patch-disable-multiarch.patch
43+
else
44+
patch -p1 -i ${ROOT}/patch-disable-multiarch-legacy.patch
45+
fi
46+
fi
47+
48+
autoconf
49+
50+
# When cross-compiling, we need to build a host Python that has working zlib
51+
# and ctypes extensions, otherwise various things fail. (`make install` fails
52+
# without zlib and setuptools / pip used by target install fail due to missing
53+
# ctypes.)
54+
#
55+
# On Apple, the dependencies are present in the Apple SDK and missing extensions
56+
# are built properly by setup.py. However, on other platforms, we need to teach
57+
# the host build system where to find things.
58+
#
59+
# Adding /usr paths on Linux is a bit funky. This is a side-effect or our
60+
# custom Clang purposefully omitting default system search paths to help
61+
# prevent unwanted dependencies from sneaking in.
62+
case "${BUILD_TRIPLE}" in
63+
x86_64-unknown-linux-gnu)
64+
EXTRA_HOST_CFLAGS="${EXTRA_HOST_CFLAGS} -I/usr/include/x86_64-linux-gnu"
65+
EXTRA_HOST_CPPFLAGS="${EXTRA_HOST_CPPFLAGS} -I/usr/include/x86_64-linux-gnu"
66+
EXTRA_HOST_LDFLAGS="${EXTRA_HOST_LDFLAGS} -L/usr/lib/x86_64-linux-gnu"
67+
;;
68+
*)
69+
;;
70+
esac
71+
72+
CC="${HOST_CC}" CXX="${HOST_CXX}" CFLAGS="${EXTRA_HOST_CFLAGS}" CPPFLAGS="${EXTRA_HOST_CFLAGS}" LDFLAGS="${EXTRA_HOST_LDFLAGS}" ./configure \
73+
--prefix /tools/host \
74+
--without-ensurepip
75+
76+
make -j "${NUM_CPUS}" install DESTDIR=${ROOT}/out
77+
78+
popd

cpython-unix/build-cpython.sh

Lines changed: 1 addition & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -64,63 +64,6 @@ zip -r "${PIP_WHEEL}" *
6464
popd
6565
rm -rf pip-tmp
6666

67-
# If we are cross-compiling, we need to build a host Python to use during
68-
# the build.
69-
#
70-
# We also build a host Python for 3.11+ to avoid complexity with building a
71-
# bootstrap Python during regular build.
72-
if [[ -n "${CROSS_COMPILING}" || -n "${PYTHON_MEETS_MINIMUM_VERSION_3_11}" ]]; then
73-
pushd "Python-${PYTHON_VERSION}"
74-
75-
# Same patch as below. See comment there.
76-
if [ "${CC}" = "clang" ]; then
77-
if [ -n "${PYTHON_MEETS_MINIMUM_VERSION_3_9}" ]; then
78-
patch -p1 -i ${ROOT}/patch-disable-multiarch.patch
79-
else
80-
patch -p1 -i ${ROOT}/patch-disable-multiarch-legacy.patch
81-
fi
82-
fi
83-
84-
autoconf
85-
86-
# When cross-compiling, we need to build a host Python that has working zlib
87-
# and ctypes extensions, otherwise various things fail. (`make install` fails
88-
# without zlib and setuptools / pip used by target install fail due to missing
89-
# ctypes.)
90-
#
91-
# On Apple, the dependencies are present in the Apple SDK and missing extensions
92-
# are built properly by setup.py. However, on other platforms, we need to teach
93-
# the host build system where to find things.
94-
#
95-
# Adding /usr paths on Linux is a bit funky. This is a side-effect or our
96-
# custom Clang purposefully omitting default system search paths to help
97-
# prevent unwanted dependencies from sneaking in.
98-
case "${BUILD_TRIPLE}" in
99-
x86_64-unknown-linux-gnu)
100-
EXTRA_HOST_CFLAGS="${EXTRA_HOST_CFLAGS} -I/usr/include/x86_64-linux-gnu"
101-
EXTRA_HOST_CPPFLAGS="${EXTRA_HOST_CPPFLAGS} -I/usr/include/x86_64-linux-gnu"
102-
EXTRA_HOST_LDFLAGS="${EXTRA_HOST_LDFLAGS} -L/usr/lib/x86_64-linux-gnu"
103-
;;
104-
*)
105-
;;
106-
esac
107-
108-
CC="${HOST_CC}" CXX="${HOST_CXX}" CFLAGS="${EXTRA_HOST_CFLAGS}" CPPFLAGS="${EXTRA_HOST_CFLAGS}" LDFLAGS="${EXTRA_HOST_LDFLAGS}" ./configure \
109-
--prefix "${TOOLS_PATH}/pyhost" \
110-
--without-ensurepip
111-
112-
make -j "${NUM_CPUS}" install
113-
114-
# configure will look for a pythonX.Y executable. Install our host Python
115-
# at the front of PATH.
116-
export PATH="${TOOLS_PATH}/pyhost/bin:${PATH}"
117-
118-
popd
119-
# Nuke and re-pave the source directory out of paranoia.
120-
rm -rf "Python-${PYTHON_VERSION}"
121-
tar -xf "Python-${PYTHON_VERSION}.tar.xz"
122-
fi
123-
12467
cat Setup.local
12568
mv Setup.local Python-${PYTHON_VERSION}/Modules/Setup.local
12669

@@ -361,7 +304,7 @@ fi
361304
# It is required when cross-compiling. But we always build a host Python
362305
# to avoid complexity with the bootstrap Python binary.
363306
if [ -n "${PYTHON_MEETS_MINIMUM_VERSION_3_11}" ]; then
364-
CONFIGURE_FLAGS="${CONFIGURE_FLAGS} --with-build-python=${TOOLS_PATH}/pyhost/bin/python${PYTHON_MAJMIN_VERSION}"
307+
CONFIGURE_FLAGS="${CONFIGURE_FLAGS} --with-build-python=${TOOLS_PATH}/host/bin/python${PYTHON_MAJMIN_VERSION}"
365308
fi
366309

367310
if [ "${PYBUILD_PLATFORM}" = "macos" ]; then

cpython-unix/build.py

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -404,6 +404,70 @@ def build_tix(
404404
build_env.get_tools_archive(dest_archive, "deps")
405405

406406

407+
def build_cpython_host(
408+
client,
409+
image,
410+
entry,
411+
host_platform: str,
412+
target_triple: str,
413+
optimizations: str,
414+
dest_archive,
415+
):
416+
"""Build binutils in the Docker image."""
417+
archive = download_entry(entry, DOWNLOADS_PATH)
418+
419+
with build_environment(client, image) as build_env:
420+
python_version = DOWNLOADS[entry]["version"]
421+
422+
build_env.install_toolchain(
423+
BUILD,
424+
host_platform,
425+
target_triple,
426+
binutils=install_binutils(host_platform),
427+
clang=True,
428+
)
429+
430+
build_env.copy_file(archive)
431+
432+
support = {
433+
"build-cpython-host.sh",
434+
"patch-disable-multiarch.patch",
435+
"patch-disable-multiarch-legacy.patch",
436+
}
437+
for s in sorted(support):
438+
build_env.copy_file(SUPPORT / s)
439+
440+
packages = {
441+
"autoconf",
442+
"m4",
443+
}
444+
for p in sorted(packages):
445+
build_env.install_artifact_archive(BUILD, p, target_triple, optimizations)
446+
447+
env = {
448+
"PYTHON_VERSION": python_version,
449+
}
450+
451+
add_target_env(env, host_platform, target_triple, build_env)
452+
453+
# Set environment variables allowing convenient testing for Python
454+
# version ranges.
455+
for v in ("3.8", "3.9", "3.10", "3.11"):
456+
normal_version = v.replace(".", "_")
457+
458+
if meets_python_minimum_version(python_version, v):
459+
env[f"PYTHON_MEETS_MINIMUM_VERSION_{normal_version}"] = "1"
460+
if meets_python_maximum_version(python_version, v):
461+
env[f"PYTHON_MEETS_MAXIMUM_VERSION_{normal_version}"] = "1"
462+
463+
build_env.run(
464+
"build-cpython-host.sh",
465+
environment=env,
466+
)
467+
468+
build_env.get_tools_archive(dest_archive, "host")
469+
470+
407471
def python_build_info(
408472
build_env,
409473
version,
@@ -659,6 +723,8 @@ def build_cpython(
659723
for p in sorted(packages):
660724
build_env.install_artifact_archive(BUILD, p, target_triple, optimizations)
661725

726+
build_env.install_toolchain_archive(BUILD, entry_name, host_platform)
727+
662728
for p in (
663729
python_archive,
664730
setuptools_archive,
@@ -877,6 +943,8 @@ def main():
877943
log_name = "image-%s" % action
878944
elif args.toolchain:
879945
log_name = "%s-%s" % (action, host_platform)
946+
elif args.action.startswith("cpython-") and args.action.endswith("-host"):
947+
log_name = args.action
880948
else:
881949
entry = DOWNLOADS[action]
882950
log_name = "%s-%s-%s-%s" % (
@@ -1080,6 +1148,17 @@ def main():
10801148
extra_archives=extra_archives,
10811149
)
10821150

1151+
elif action.startswith("cpython-") and action.endswith("-host"):
1152+
build_cpython_host(
1153+
client,
1154+
get_image(client, ROOT, BUILD, docker_image),
1155+
action[:-5],
1156+
host_platform=host_platform,
1157+
target_triple=target_triple,
1158+
optimizations=optimizations,
1159+
dest_archive=dest_archive,
1160+
)
1161+
10831162
elif action in ("cpython-3.8", "cpython-3.9", "cpython-3.10", "cpython-3.11"):
10841163
build_cpython(
10851164
settings,

0 commit comments

Comments
 (0)