Skip to content

Commit 6d86d82

Browse files
committedMar 26, 2025··
pythonGH-131556: calculate PYBUILDDIR in configure instead of sysconfig
Right now, this value is being calculated during the build process when calling `python -m sysconfig --generate-posix-vars`. This command, ammong other things, calculates the PYBUILDDIR value and writes it to a pybuilddir.txt, making it available to the Makefile this way. This is problematic because it makes it impossible to write Makefile rules with targets under PYBUILDDIR — the target and prerequisites of rule are always expanded immediatily as the Makefile is read, only the rule recipe is deferred [1], meaning that all targets must be known before any rule is executed. Since PYBUILDDIR is only known in the middle of the build process — after the target list has been built — we cannot write rules for files under it. We have had to worked around this limitation in a couple ways: - Extension modules, which need to be present in PYBUILDDIR to execute the interpreter in-tree, are built in the source tree, and afterwards symlinked to PYBUILDDIR once its value is known. - Instead of the sysconfigdata module and the sysconfig vars JSON file, instead of having theirn own target, are generated by the pybuilddir.txt target. Additionally, this limitation is also prone to cause issues such as pythonGH-131556. That said, on top of the Makefile issues, PYBUILDDIR being calculated by sysconfig also creates its own additional complications, necessitating more workarounds and introducing unecessary complexity to the sysconfig data generation code — there's a chicken-and-egg problem in certain systems, where we need to know PYBUILDDIR to install the sysconfigdata module, but the sysconfigdata module is necessary to be avaible to calculate the value PYBUILDDIR [2]. We currently handle this by manually building a module object for sysconfigdata and inject it to sys.modules. By defining PYBUILDDIR directly in Makefile.pre.in, we solve all these problems. The current build system design is the result of a sucession small fixes adapting the original sysconfig code from over 15 years ago to work with the rest of codebase as it evolved. That is to say — I don't think there's any technical resoning behind this particular design, I believe it is simply the result of technical debt. Therefore, I don't see any reason why not to move the PYBUILDDIR definition over to Makefile.pre.in, which to me seems to make more sense [1] https://www.gnu.org/software/make/manual/html_node/Reading-Makefiles.html [2] https://github.com/python/cpython/blob/898e6b395e63ad7f8bbe421adf0af8947d429925/Lib/sysconfig/__main__.py#L206-L221 Signed-off-by: Filipe Laíns <lains@riseup.net>
1 parent 898e6b3 commit 6d86d82

File tree

7 files changed

+79
-80
lines changed

7 files changed

+79
-80
lines changed
 

‎Lib/sysconfig/__main__.py

+3-27
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,12 @@
11
import json
22
import os
33
import sys
4-
import types
54
from sysconfig import (
65
_ALWAYS_STR,
6+
_PROJECT_BASE,
77
_PYTHON_BUILD,
88
_get_sysconfigdata_name,
99
get_config_h_filename,
10-
get_config_var,
1110
get_config_vars,
1211
get_default_scheme,
1312
get_makefile_filename,
@@ -161,10 +160,8 @@ def _print_config_dict(d, stream):
161160

162161

163162
def _get_pybuilddir():
164-
pybuilddir = f'build/lib.{get_platform()}-{get_python_version()}'
165-
if get_config_var('Py_DEBUG') == '1':
166-
pybuilddir += '-pydebug'
167-
return pybuilddir
163+
with open(os.path.join(_PROJECT_BASE, 'pybuilddir.txt')) as f:
164+
return f.read()
168165

169166

170167
def _get_json_data_name():
@@ -203,23 +200,6 @@ def _generate_posix_vars():
203200

204201
name = _get_sysconfigdata_name()
205202

206-
# There's a chicken-and-egg situation on OS X with regards to the
207-
# _sysconfigdata module after the changes introduced by #15298:
208-
# get_config_vars() is called by get_platform() as part of the
209-
# `make pybuilddir.txt` target -- which is a precursor to the
210-
# _sysconfigdata.py module being constructed. Unfortunately,
211-
# get_config_vars() eventually calls _init_posix(), which attempts
212-
# to import _sysconfigdata, which we won't have built yet. In order
213-
# for _init_posix() to work, if we're on Darwin, just mock up the
214-
# _sysconfigdata module manually and populate it with the build vars.
215-
# This is more than sufficient for ensuring the subsequent call to
216-
# get_platform() succeeds.
217-
# GH-127178: Since we started generating a .json file, we also need this to
218-
# be able to run sysconfig.get_config_vars().
219-
module = types.ModuleType(name)
220-
module.build_time_vars = vars
221-
sys.modules[name] = module
222-
223203
pybuilddir = _get_pybuilddir()
224204
os.makedirs(pybuilddir, exist_ok=True)
225205
destfile = os.path.join(pybuilddir, name + '.py')
@@ -243,10 +223,6 @@ def _generate_posix_vars():
243223

244224
print(f'Written {jsonfile}')
245225

246-
# Create file used for sys.path fixup -- see Modules/getpath.c
247-
with open('pybuilddir.txt', 'w', encoding='utf8') as f:
248-
f.write(pybuilddir)
249-
250226

251227
def _print_dict(title, data):
252228
for index, (key, value) in enumerate(sorted(data.items())):

‎Makefile.pre.in

+41-35
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ srcdir= @srcdir@
3232
VPATH= @srcdir@
3333
abs_srcdir= @abs_srcdir@
3434
abs_builddir= @abs_builddir@
35+
PYBUILDDIR= build/lib.@MACHDEP@-@MULTIARCH@-@VERSION@-@ABIFLAGS@
3536

3637

3738
CC= @CC@
@@ -135,6 +136,10 @@ MACHDEP= @MACHDEP@
135136
MULTIARCH= @MULTIARCH@
136137
MULTIARCH_CPPFLAGS = @MULTIARCH_CPPFLAGS@
137138

139+
# Sysconfig data
140+
SYSCONFIGDATA_NAME= @SYSCONFIGDATA_NAME@
141+
SYSCONFIGVARS_JSON_NAME= @SYSCONFIGVARS_JSON_NAME@
142+
138143
# Install prefix for architecture-independent files
139144
prefix= @prefix@
140145

@@ -730,11 +735,12 @@ list-targets:
730735

731736
.PHONY: build_all
732737
build_all: check-clean-src check-app-store-compliance $(BUILDPYTHON) platform sharedmods \
733-
gdbhooks Programs/_testembed scripts checksharedmods rundsymutil build-details.json
738+
gdbhooks Programs/_testembed scripts checksharedmods rundsymutil pybuilddir.txt \
739+
$(PYBUILDDIR)/build-details.json $(PYBUILDDIR)/$(SYSCONFIGDATA_NAME).py
734740

735741
.PHONY: build_wasm
736742
build_wasm: check-clean-src $(BUILDPYTHON) platform sharedmods \
737-
python-config checksharedmods
743+
$(PYBUILDDIR)/$(SYSCONFIGDATA_NAME).py python-config checksharedmods
738744

739745
.PHONY: build_emscripten
740746
build_emscripten: build_wasm web_example
@@ -917,27 +923,37 @@ clinic-tests: check-clean-src $(srcdir)/Lib/test/clinic.test.c
917923
$(BUILDPYTHON): Programs/python.o $(LINK_PYTHON_DEPS)
918924
$(LINKCC) $(PY_CORE_LDFLAGS) $(LINKFORSHARED) -o $@ Programs/python.o $(LINK_PYTHON_OBJS) $(LIBS) $(MODLIBS) $(SYSLIBS)
919925

920-
platform: $(PYTHON_FOR_BUILD_DEPS) pybuilddir.txt
926+
platform: $(PYTHON_FOR_BUILD_DEPS) $(PYBUILDDIR)/$(SYSCONFIGDATA_NAME).py
921927
$(RUNSHARED) $(PYTHON_FOR_BUILD) -c 'import sys ; from sysconfig import get_platform ; print("%s-%d.%d" % (get_platform(), *sys.version_info[:2]))' >platform
922928

923-
# Create build directory and generate the sysconfig build-time data there.
924-
# pybuilddir.txt contains the name of the build dir and is used for
925-
# sys.path fixup -- see Modules/getpath.c.
926-
# Since this step runs before shared modules are built, try to avoid bootstrap
927-
# problems by creating a dummy pybuilddir.txt just to allow interpreter
928-
# initialization to succeed. It will be overwritten by generate-posix-vars
929-
# or removed in case of failure.
930-
pybuilddir.txt: $(PYTHON_FOR_BUILD_DEPS)
931-
@echo "none" > ./pybuilddir.txt
932-
$(RUNSHARED) $(PYTHON_FOR_BUILD) -S -m sysconfig --generate-posix-vars ;\
933-
if test $$? -ne 0 ; then \
934-
echo "generate-posix-vars failed" ; \
935-
rm -f ./pybuilddir.txt ; \
936-
exit 1 ; \
937-
fi
929+
# Create build directory and pybuilddir.txt.
930+
# pybuilddir.txt which is used by Modules/getpath.c to detect the build directory.
931+
pybuilddir.txt:
932+
mkdir -p $(PYBUILDDIR)
933+
printf $(PYBUILDDIR) >pybuilddir.txt
934+
935+
SYSCONFIG_SRC= \
936+
$(srcdir)/Lib/sysconfig/__init__.py \
937+
$(srcdir)/Lib/sysconfig/__main__.py
938+
SYSCONFIGVARS_DEPS= \
939+
$(SYSCONFIG_SRC) \
940+
Makefile \
941+
Python/sysmodule.o \
942+
Modules/posixmodule.o
943+
944+
# Generate the sysconfig data module.
945+
$(PYBUILDDIR)/$(SYSCONFIGDATA_NAME).py $(PYBUILDDIR)/$(SYSCONFIGVARS_JSON_NAME): $(SYSCONFIGVARS_DEPS) $(PYTHON_FOR_BUILD_DEPS) pybuilddir.txt
946+
$(RUNSHARED) $(PYTHON_FOR_BUILD) -S -m sysconfig --generate-posix-vars
947+
@# Sanity check, to make sure sysconfig._get_sysconfigdata_name() returns the same value.
948+
@for file in $@; do \
949+
if test ! -f $<; then \
950+
echo "generate-posix-vars didn't generate '$(PYBUILDDIR)/$(SYSCONFIGDATA_NAME).py'" \
951+
exit 1; \
952+
fi; \
953+
done
938954

939-
build-details.json: pybuilddir.txt
940-
$(RUNSHARED) $(PYTHON_FOR_BUILD) $(srcdir)/Tools/build/generate-build-details.py `cat pybuilddir.txt`/build-details.json
955+
$(PYBUILDDIR)/build-details.json: $(PYTHON_FOR_BUILD_DEPS) $(PYBUILDDIR)/$(SYSCONFIGDATA_NAME).py Makefile pybuilddir.txt
956+
$(RUNSHARED) $(PYTHON_FOR_BUILD) $(srcdir)/Tools/build/generate-build-details.py $@
941957

942958
# Build static library
943959
$(LIBRARY): $(LIBRARY_OBJS)
@@ -1443,18 +1459,8 @@ $(LIBHACL_BLAKE2_A): $(LIBHACL_BLAKE2_OBJS)
14431459
-rm -f $@
14441460
$(AR) $(ARFLAGS) $@ $(LIBHACL_BLAKE2_OBJS)
14451461

1446-
# create relative links from build/lib.platform/egg.so to Modules/egg.so
1447-
# pybuilddir.txt is created too late. We cannot use it in Makefile
1448-
# targets. ln --relative is not portable.
14491462
.PHONY: sharedmods
1450-
sharedmods: $(SHAREDMODS) pybuilddir.txt
1451-
@target=`cat pybuilddir.txt`; \
1452-
$(MKDIR_P) $$target; \
1453-
for mod in X $(SHAREDMODS); do \
1454-
if test $$mod != X; then \
1455-
$(LN) -sf ../../$$mod $$target/`basename $$mod`; \
1456-
fi; \
1457-
done
1463+
sharedmods: $(SHAREDMODS)
14581464

14591465
# dependency on BUILDPYTHON ensures that the target is run last
14601466
.PHONY: checksharedmods
@@ -2656,9 +2662,9 @@ libinstall: all $(srcdir)/Modules/xxmodule.c
26562662
esac; \
26572663
done; \
26582664
done
2659-
$(INSTALL_DATA) `cat pybuilddir.txt`/_sysconfigdata_$(ABIFLAGS)_$(MACHDEP)_$(MULTIARCH).py $(DESTDIR)$(LIBDEST); \
2660-
$(INSTALL_DATA) `cat pybuilddir.txt`/_sysconfig_vars_$(ABIFLAGS)_$(MACHDEP)_$(MULTIARCH).json $(DESTDIR)$(LIBDEST); \
2661-
$(INSTALL_DATA) `cat pybuilddir.txt`/build-details.json $(DESTDIR)$(LIBDEST); \
2665+
$(INSTALL_DATA) $(PYBUILDDIR)/_sysconfigdata_$(ABIFLAGS)_$(MACHDEP)_$(MULTIARCH).py $(DESTDIR)$(LIBDEST); \
2666+
$(INSTALL_DATA) $(PYBUILDDIR)/_sysconfig_vars_$(ABIFLAGS)_$(MACHDEP)_$(MULTIARCH).json $(DESTDIR)$(LIBDEST); \
2667+
$(INSTALL_DATA) $(PYBUILDDIR)/build-details.json $(DESTDIR)$(LIBDEST); \
26622668
$(INSTALL_DATA) $(srcdir)/LICENSE $(DESTDIR)$(LIBDEST)/LICENSE.txt
26632669
@ # If app store compliance has been configured, apply the patch to the
26642670
@ # installed library code. The patch has been previously validated against
@@ -3063,7 +3069,7 @@ clean-retain-profile: pycremoval
30633069
find build -name 'fficonfig.h' -exec rm -f {} ';' || true
30643070
find build -name '*.py' -exec rm -f {} ';' || true
30653071
find build -name '*.py[co]' -exec rm -f {} ';' || true
3066-
-rm -f pybuilddir.txt
3072+
-rm -rf pybuilddir.txt $(PYBUILDDIR)
30673073
-rm -f _bootstrap_python
30683074
-rm -rf web_example python.mjs python.wasm python*.symbols python*.map
30693075
-rm -f Programs/_testembed Programs/_freeze_module

‎Modules/makesetup

+2-2
Original file line numberDiff line numberDiff line change
@@ -266,15 +266,15 @@ sed -e 's/[ ]*#.*//' -e '/^[ ]*$/d' |
266266
esac
267267
for mod in $mods
268268
do
269-
file="$srcdir/$mod\$(EXT_SUFFIX)"
269+
file="\$(PYBUILDDIR)/$mod\$(EXT_SUFFIX)"
270270
case $doconfig in
271271
no)
272272
SHAREDMODS="$SHAREDMODS $file"
273273
BUILT_SHARED="$BUILT_SHARED $mod"
274274
;;
275275
esac
276276
rule="$file: $objs"
277-
rule="$rule; \$(BLDSHARED) $objs $libs \$(LIBPYTHON) -o $file"
277+
rule="$rule; @mkdir -p \$(PYBUILDDIR); \$(BLDSHARED) $objs $libs \$(LIBPYTHON) -o $file"
278278
echo "$rule" >>$rulesf
279279
done
280280
done

‎Tools/wasm/emscripten/__main__.py

+10-6
Original file line numberDiff line numberDiff line change
@@ -195,11 +195,10 @@ def configure_emscripten_python(context, working_dir):
195195
assert (
196196
len(lib_dirs) == 1
197197
), f"Expected a single lib.* directory in {python_build_dir}"
198-
lib_dir = os.fsdecode(lib_dirs[0])
199-
pydebug = lib_dir.endswith("-pydebug")
200-
python_version = lib_dir.removesuffix("-pydebug").rpartition("-")[-1]
198+
_, python_version, abiflags = os.fsdecode(lib_dirs[0]).rsplit('-', maxsplit=2)
201199
sysconfig_data = (
202-
f"{emscripten_build_dir}/build/lib.emscripten-wasm32-{python_version}"
200+
f"{emscripten_build_dir}/build/"
201+
f"lib.emscripten-wasm32-emscripten-{python_version}-{abiflags}"
203202
)
204203
if pydebug:
205204
sysconfig_data += "-pydebug"
@@ -227,8 +226,13 @@ def configure_emscripten_python(context, working_dir):
227226
"--enable-wasm-dynamic-linking",
228227
f"--prefix={PREFIX_DIR}",
229228
]
230-
if pydebug:
231-
configure.append("--with-pydebug")
229+
for flag in abiflags:
230+
if flag == 'd':
231+
configure.append("--with-pydebug")
232+
elif flag == 't':
233+
configure.append("--disable-gil")
234+
else:
235+
raise ValueError(f"Unknown ABI flag: {flag}")
232236
if context.args:
233237
configure.extend(context.args)
234238
call(

‎Tools/wasm/wasi.py

+9-8
Original file line numberDiff line numberDiff line change
@@ -212,12 +212,8 @@ def configure_wasi_python(context, working_dir):
212212
python_build_dir = BUILD_DIR / "build"
213213
lib_dirs = list(python_build_dir.glob("lib.*"))
214214
assert len(lib_dirs) == 1, f"Expected a single lib.* directory in {python_build_dir}"
215-
lib_dir = os.fsdecode(lib_dirs[0])
216-
pydebug = lib_dir.endswith("-pydebug")
217-
python_version = lib_dir.removesuffix("-pydebug").rpartition("-")[-1]
218-
sysconfig_data = f"{wasi_build_dir}/build/lib.wasi-wasm32-{python_version}"
219-
if pydebug:
220-
sysconfig_data += "-pydebug"
215+
_, python_version, abiflags = os.fsdecode(lib_dirs[0]).rsplit('-', maxsplit=2)
216+
sysconfig_data = f"{wasi_build_dir}/build/lib.wasi-wasm32-wasi-{python_version}-{abiflags}"
221217

222218
# Use PYTHONPATH to include sysconfig data which must be anchored to the
223219
# WASI guest's `/` directory.
@@ -244,8 +240,13 @@ def configure_wasi_python(context, working_dir):
244240
f"--host={context.host_triple}",
245241
f"--build={build_platform()}",
246242
f"--with-build-python={build_python}"]
247-
if pydebug:
248-
configure.append("--with-pydebug")
243+
for flag in abiflags:
244+
if flag == 'd':
245+
configure.append("--with-pydebug")
246+
elif flag == 't':
247+
configure.append("--disable-gil")
248+
else:
249+
raise ValueError(f"Unknown ABI flag: {flag}")
249250
if context.args:
250251
configure.extend(context.args)
251252
call(configure,

‎configure

+8-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎configure.ac

+6-1
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,7 @@ AC_ARG_WITH([build-python],
164164
dnl Build Python interpreter is used for regeneration and freezing.
165165
ac_cv_prog_PYTHON_FOR_REGEN=$with_build_python
166166
PYTHON_FOR_FREEZE="$with_build_python"
167-
PYTHON_FOR_BUILD='_PYTHON_PROJECT_BASE=$(abs_builddir) _PYTHON_HOST_PLATFORM=$(_PYTHON_HOST_PLATFORM) PYTHONPATH=$(srcdir)/Lib _PYTHON_SYSCONFIGDATA_NAME=_sysconfigdata_$(ABIFLAGS)_$(MACHDEP)_$(MULTIARCH) _PYTHON_SYSCONFIGDATA_PATH=$(shell test -f pybuilddir.txt && echo $(abs_builddir)/`cat pybuilddir.txt`) '$with_build_python
167+
PYTHON_FOR_BUILD='_PYTHON_PROJECT_BASE=$(abs_builddir) _PYTHON_HOST_PLATFORM=$(_PYTHON_HOST_PLATFORM) PYTHONPATH=$(srcdir)/Lib _PYTHON_SYSCONFIGDATA_NAME=$(SYSCONFIGDATA_NAME) _PYTHON_SYSCONFIGDATA_PATH=$(abs_builddir)/$(PYBUILDDIR) '$with_build_python
168168
AC_MSG_RESULT([$with_build_python])
169169
], [
170170
AS_VAR_IF([cross_compiling], [yes],
@@ -176,6 +176,11 @@ AC_ARG_WITH([build-python],
176176
)
177177
AC_SUBST([PYTHON_FOR_BUILD])
178178

179+
SYSCONFIGDATA_NAME='_sysconfigdata_$(ABIFLAGS)_$(MACHDEP)_$(MULTIARCH)'
180+
SYSCONFIGVARS_JSON_NAME='_sysconfigvars_$(ABIFLAGS)_$(MACHDEP)_$(MULTIARCH).json'
181+
AC_SUBST([SYSCONFIGDATA_NAME])
182+
AC_SUBST([SYSCONFIGVARS_JSON_NAME])
183+
179184
AC_MSG_CHECKING([for Python interpreter freezing])
180185
AC_MSG_RESULT([$PYTHON_FOR_FREEZE])
181186
AC_SUBST([PYTHON_FOR_FREEZE])

0 commit comments

Comments
 (0)
Please sign in to comment.