From 54ba2c02f8ed3d776fa51116ca5449b4fbf22129 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Tue, 6 Oct 2026 23:03:46 +0200 Subject: [PATCH 01/29] gh-158948: Add auto-generated Module/getbuildinfo.h Merge Python/getcompiler.c, Python/getcopyright.c, Python/getplatform.c and Python/getversion.c into Modules/getbuildinfo.c. Add Tools/build/generate_getbuildinfo.py to auto-generate a new Module/getbuildinfo.h header file which contains build information strings. --- .gitignore | 3 + Include/internal/pycore_pylifecycle.h | 2 - Makefile.pre.in | 39 +++--- Modules/getbuildinfo.c | 104 +++++++------- Modules/main.c | 1 - Objects/bytesobject.c | 23 ++- PCbuild/pythoncore.vcxproj | 4 - PCbuild/pythoncore.vcxproj.filters | 12 -- .../getcompiler.c => Programs/_getcompiler.c | 11 +- Python/getcopyright.c | 23 --- Python/getplatform.c | 12 -- Python/getversion.c | 79 ----------- Python/pylifecycle.c | 5 - Tools/build/generate_getbuildinfo.py | 131 ++++++++++++++++++ 14 files changed, 224 insertions(+), 225 deletions(-) rename Python/getcompiler.c => Programs/_getcompiler.c (79%) delete mode 100644 Python/getcopyright.c delete mode 100644 Python/getplatform.c delete mode 100644 Python/getversion.c create mode 100644 Tools/build/generate_getbuildinfo.py diff --git a/.gitignore b/.gitignore index 00813f9d1b6b00..0878694847a2e5 100644 --- a/.gitignore +++ b/.gitignore @@ -99,8 +99,11 @@ Modules/Setup.config Modules/Setup.local Modules/Setup.stdlib Modules/config.c +# Generated by Tools/build/generate_getbuildinfo.py +Modules/getbuildinfo.h Modules/ld_so_aix Programs/_freeze_module +Programs/_getcompiler Programs/_testembed PC/python_nt*.h PC/pythonnt_rc*.h diff --git a/Include/internal/pycore_pylifecycle.h b/Include/internal/pycore_pylifecycle.h index 99d09c5e0d4e11..416f6a4e729340 100644 --- a/Include/internal/pycore_pylifecycle.h +++ b/Include/internal/pycore_pylifecycle.h @@ -23,7 +23,6 @@ extern int _Py_IsLocaleCoercionTarget(const char *ctype_loc); /* Various one-time initializers */ -extern PyStatus _Py_GetVersion_Init(void); extern PyStatus _PyFaulthandler_Init(int enable); extern PyObject * _PyBuiltin_Init(PyInterpreterState *interp); extern int _PyBuiltin_InitPythonFunctions(PyObject *dict); @@ -43,7 +42,6 @@ extern PyStatus _PyDateTime_InitTypes(PyInterpreterState *interp); /* Various internal finalizers */ -extern void _Py_GetVersion_Fini(void); extern int _PySignal_Init(int install_signal_handlers); extern void _PySignal_Fini(void); diff --git a/Makefile.pre.in b/Makefile.pre.in index 42480f28ad7f31..6c8cba44a114d7 100644 --- a/Makefile.pre.in +++ b/Makefile.pre.in @@ -461,10 +461,6 @@ PYTHON_OBJS= \ Python/gc_free_threading.o \ Python/gc_gil.o \ Python/getargs.o \ - Python/getcompiler.o \ - Python/getcopyright.o \ - Python/getplatform.o \ - Python/getversion.o \ Python/ceval_gil.o \ Python/hamt.o \ Python/hashtable.o \ @@ -1617,6 +1613,9 @@ regen-re: $(BUILDPYTHON) Programs/_testembed: Programs/_testembed.o $(LINK_PYTHON_DEPS) $(LINKCC) $(PY_CORE_EXE_LDFLAGS) $(LINKFORSHARED) -o $@ Programs/_testembed.o $(LINK_PYTHON_OBJS) $(LIBS) $(MODLIBS) $(SYSLIBS) +Programs/_getcompiler: Programs/_getcompiler.c + $(LINKCC) $(PY_CORE_EXE_LDFLAGS) $(LINKFORSHARED) $(PY_CPPFLAGS) -o $@ $< + ############################################################################ # "Bootstrap Python" used to run Programs/_freeze_module.py @@ -1867,18 +1866,23 @@ regen-all: regen-cases regen-slots \ ############################################################################ # Special rules for object files -Modules/getbuildinfo.o: $(PARSER_OBJS) \ - $(OBJECT_OBJS) \ - $(PYTHON_OBJS) \ - $(MODULE_OBJS) \ - $(MODOBJS) \ - $(DTRACE_OBJS) \ - $(srcdir)/Modules/getbuildinfo.c - $(CC) -c $(PY_CORE_CFLAGS) \ - -DGITVERSION="\"`LC_ALL=C $(GITVERSION)`\"" \ - -DGITTAG="\"`LC_ALL=C $(GITTAG)`\"" \ - -DGITBRANCH="\"`LC_ALL=C $(GITBRANCH)`\"" \ - -o $@ $(srcdir)/Modules/getbuildinfo.c +Modules/getbuildinfo.h: $(PARSER_OBJS) \ + $(OBJECT_OBJS) \ + $(PYTHON_OBJS) \ + $(MODULE_OBJS) \ + $(MODOBJS) \ + $(DTRACE_OBJS) \ + Programs/_getcompiler \ + $(srcdir)/Tools/build/generate_getbuildinfo.py + $(PYTHON_FOR_REGEN) $(srcdir)/Tools/build/generate_getbuildinfo.py $@ \ + "$(MACHDEP)" \ + `LC_ALL=C $(GITVERSION)` \ + `LC_ALL=C $(GITTAG)` \ + `LC_ALL=C $(GITBRANCH)` \ + "`Programs/_getcompiler`" + +Modules/getbuildinfo.o: $(srcdir)/Modules/getbuildinfo.c Modules/getbuildinfo.h + $(CC) -c $(PY_CORE_CFLAGS) -o $@ $(srcdir)/Modules/getbuildinfo.c Modules/getpath.o: $(srcdir)/Modules/getpath.c Python/frozen_modules/getpath.h Makefile $(PYTHON_HEADERS) $(CC) -c $(PY_CORE_CFLAGS) -DPYTHONPATH='"$(PYTHONPATH)"' \ @@ -2028,9 +2032,6 @@ regen-sre: Python/compile.o Python/codegen.o Python/symtable.o Python/ast_unparse.o Python/ast.o Python/future.o: $(srcdir)/Include/internal/pycore_ast.h $(srcdir)/Include/internal/pycore_ast.h -Python/getplatform.o: $(srcdir)/Python/getplatform.c - $(CC) -c $(PY_CORE_CFLAGS) -DPLATFORM='"$(MACHDEP)"' -o $@ $(srcdir)/Python/getplatform.c - Python/importdl.o: $(srcdir)/Python/importdl.c $(CC) -c $(PY_CORE_CFLAGS) -I$(DLINCLDIR) -o $@ $(srcdir)/Python/importdl.c diff --git a/Modules/getbuildinfo.c b/Modules/getbuildinfo.c index 006043d7dc3ba6..c3690fb9068a6a 100644 --- a/Modules/getbuildinfo.c +++ b/Modules/getbuildinfo.c @@ -1,63 +1,40 @@ -#ifndef Py_BUILD_CORE_BUILTIN -# define Py_BUILD_CORE_MODULE 1 -#endif +/* Functions to get information about the Python build */ #include "Python.h" -#include "pycore_pylifecycle.h" // _Py_gitidentifier() -#ifndef DONT_HAVE_STDIO_H -#include -#endif +#include "getbuildinfo.h" -#ifndef DATE -#ifdef __DATE__ -#define DATE __DATE__ -#else -#define DATE "Jan 01 1970" -#endif -#endif +static const char copyright[] = +"\ +Copyright (c) 2001 Python Software Foundation.\n\ +All Rights Reserved.\n\ +\n\ +Copyright (c) 2000 BeOpen.com.\n\ +All Rights Reserved.\n\ +\n\ +Copyright (c) 1995-2001 Corporation for National Research Initiatives.\n\ +All Rights Reserved.\n\ +\n\ +Copyright (c) 1991-1995 Stichting Mathematisch Centrum, Amsterdam.\n\ +All Rights Reserved."; -#ifndef TIME -#ifdef __TIME__ -#define TIME __TIME__ -#else -#define TIME "00:00:00" -#endif -#endif -/* XXX Only unix build process has been tested */ -#ifndef GITVERSION -#define GITVERSION "" -#endif -#ifndef GITTAG -#define GITTAG "" -#endif -#ifndef GITBRANCH -#define GITBRANCH "" -#endif +const char * +Py_GetCopyright(void) +{ + return copyright; +} -static int initialized = 0; -static char buildinfo[50 + sizeof(GITVERSION) + - ((sizeof(GITTAG) > sizeof(GITBRANCH)) ? - sizeof(GITTAG) : sizeof(GITBRANCH))]; +const char * +Py_GetCompiler(void) +{ + return COMPILER; +} const char * -Py_GetBuildInfo(void) +Py_GetPlatform(void) { - if (initialized) { - return buildinfo; - } - initialized = 1; - const char *revision = _Py_gitversion(); - const char *sep = *revision ? ":" : ""; - const char *gitid = _Py_gitidentifier(); - if (!(*gitid)) { - gitid = "main"; - } - PyOS_snprintf(buildinfo, sizeof(buildinfo), - "%s%s%s, %.20s, %.9s", gitid, sep, revision, - DATE, TIME); - return buildinfo; + return PLATFORM; } const char * @@ -69,13 +46,26 @@ _Py_gitversion(void) const char * _Py_gitidentifier(void) { - const char *gittag, *gitid; - gittag = GITTAG; - if ((*gittag) && strcmp(gittag, "undefined") != 0) - gitid = gittag; - else - gitid = GITBRANCH; - return gitid; + return GIT_IDENTIFIER; +} + +const char * +Py_GetBuildInfo(void) +{ + return BUILDINFO; +} + +// Export the Python hex version as a constant. +const unsigned long Py_Version = PY_VERSION_HEX; + +// Keep the 'version' variable for backward compatibility. +// Some debuggers inspect directly the variable. +static const char *version = GET_VERSION; + +const char * +Py_GetVersion(void) +{ + return version; } diff --git a/Modules/main.c b/Modules/main.c index 097c10504d69a3..44a025d500e35e 100644 --- a/Modules/main.c +++ b/Modules/main.c @@ -847,7 +847,6 @@ pymain_free(void) _PyPathConfig_ClearGlobal(); _Py_ClearArgcArgv(); _PyRuntime_Finalize(); - _Py_GetVersion_Fini(); } diff --git a/Objects/bytesobject.c b/Objects/bytesobject.c index 1917055d25198f..5d55376b982f3e 100644 --- a/Objects/bytesobject.c +++ b/Objects/bytesobject.c @@ -3831,13 +3831,23 @@ byteswriter_resize(PyBytesWriter *writer, Py_ssize_t new_size, int resize) } Py_ssize_t alloc = new_size; - if (resize && writer->overallocate) { - if (alloc <= (PY_SSIZE_T_MAX - alloc / OVERALLOCATE_FACTOR)) { - alloc += alloc / OVERALLOCATE_FACTOR; - } - } if (writer->obj != NULL) { + if (resize && writer->overallocate) { + if (alloc <= (PY_SSIZE_T_MAX - alloc / OVERALLOCATE_FACTOR)) { + alloc += alloc / OVERALLOCATE_FACTOR; + } + } + + if (new_size <= old_allocated + old_allocated / 8) { + /* Moderate upsize; overallocate similar to list_resize() */ + alloc = new_size + (new_size >> 3); + } + else { + /* Major upsize; resize up to exact size */ + alloc = new_size; + } + if (writer->use_bytearray) { if (PyByteArray_Resize(writer->obj, alloc)) { #ifdef Py_DEBUG @@ -3860,6 +3870,9 @@ byteswriter_resize(PyBytesWriter *writer, Py_ssize_t new_size, int resize) assert(writer->obj != NULL); } else { + // Do not overallocate when the first bytes/bytearray object is + // allocated + char *data; if (writer->use_bytearray) { writer->obj = PyByteArray_FromStringAndSize(NULL, alloc); diff --git a/PCbuild/pythoncore.vcxproj b/PCbuild/pythoncore.vcxproj index c2d8f64a72b119..ca3ac8112ce8f6 100644 --- a/PCbuild/pythoncore.vcxproj +++ b/PCbuild/pythoncore.vcxproj @@ -649,11 +649,7 @@ - - - - diff --git a/PCbuild/pythoncore.vcxproj.filters b/PCbuild/pythoncore.vcxproj.filters index 2536fc971700de..f20983f84889ec 100644 --- a/PCbuild/pythoncore.vcxproj.filters +++ b/PCbuild/pythoncore.vcxproj.filters @@ -1469,21 +1469,9 @@ Python - - Python - - - Python - Python - - Python - - - Python - Python diff --git a/Python/getcompiler.c b/Programs/_getcompiler.c similarity index 79% rename from Python/getcompiler.c rename to Programs/_getcompiler.c index baa03679d79fb8..c5e310c3d1c858 100644 --- a/Python/getcompiler.c +++ b/Programs/_getcompiler.c @@ -1,7 +1,5 @@ - -/* Return the compiler identification, if possible. */ - #include "Python.h" +#include // printf() // PC/pyconfig.h defines _Py_COMPILER #ifdef _Py_COMPILER @@ -25,8 +23,9 @@ #endif /* !COMPILER */ -const char * -Py_GetCompiler(void) +int main() { - return COMPILER; + const char *compiler = COMPILER; + printf("%s\n", compiler); + return 0; } diff --git a/Python/getcopyright.c b/Python/getcopyright.c deleted file mode 100644 index 964584ddf7998e..00000000000000 --- a/Python/getcopyright.c +++ /dev/null @@ -1,23 +0,0 @@ -/* Return the copyright string. This is updated manually. */ - -#include "Python.h" - -static const char cprt[] = -"\ -Copyright (c) 2001 Python Software Foundation.\n\ -All Rights Reserved.\n\ -\n\ -Copyright (c) 2000 BeOpen.com.\n\ -All Rights Reserved.\n\ -\n\ -Copyright (c) 1995-2001 Corporation for National Research Initiatives.\n\ -All Rights Reserved.\n\ -\n\ -Copyright (c) 1991-1995 Stichting Mathematisch Centrum, Amsterdam.\n\ -All Rights Reserved."; - -const char * -Py_GetCopyright(void) -{ - return cprt; -} diff --git a/Python/getplatform.c b/Python/getplatform.c deleted file mode 100644 index 81a0f7ac5378bd..00000000000000 --- a/Python/getplatform.c +++ /dev/null @@ -1,12 +0,0 @@ - -#include "Python.h" - -#ifndef PLATFORM -#define PLATFORM "unknown" -#endif - -const char * -Py_GetPlatform(void) -{ - return PLATFORM; -} diff --git a/Python/getversion.c b/Python/getversion.c deleted file mode 100644 index 5c05b841de6fc8..00000000000000 --- a/Python/getversion.c +++ /dev/null @@ -1,79 +0,0 @@ - -/* Return the full version string. */ - -#include "Python.h" -#include "pycore_initconfig.h" // _PyStatus_NO_MEMORY() - - -static int initialized = 0; -// Use the smallest possible buffer: it's only needed if malloc() fails *and* -// Py_GetVersion() is called before Py_Initialize(). The buffer should be big -// enough to store "3.16.0a0 " string. -static char static_version[20]; -static char *heap_version = NULL; - - -PyStatus -_Py_GetVersion_Init(void) -{ - if (initialized) { - return _PyStatus_OK(); - } - -#ifdef Py_GIL_DISABLED - const char *format = "%s free-threading build (%s) %s"; - size_t format_len = strlen(" free-threading build () "); -#else - const char *format = "%s (%s) %s"; - size_t format_len = strlen(" () "); -#endif - const char *version_str = PY_VERSION; - const char *buildinfo = Py_GetBuildInfo(); - const char *compiler = Py_GetCompiler(); - // +1 for the trailing NUL byte - size_t len = (format_len + strlen(version_str) + strlen(buildinfo) - + strlen(compiler) + 1); - - // Always format the static version - PyOS_snprintf(static_version, sizeof(static_version), format, - version_str, buildinfo, compiler); - - heap_version = malloc(len); - if (heap_version == NULL) { - // If malloc() failed, don't set initialized to 1, so next - // Py_GetVersion() will try again to allocate memory. - return _PyStatus_NO_MEMORY(); - } - - PyOS_snprintf(heap_version, len, format, - version_str, buildinfo, compiler); - initialized = 1; - return _PyStatus_OK(); -} - -void -_Py_GetVersion_Fini(void) -{ - if (heap_version) { - free(heap_version); - heap_version = NULL; - } -} - -const char * -Py_GetVersion(void) -{ - PyStatus status = _Py_GetVersion_Init(); - // Ignore error: Py_GetVersion() API cannot report error - (void)status; - - if (heap_version) { - return heap_version; - } - else { - return static_version; - } -} - -// Export the Python hex version as a constant. -const unsigned long Py_Version = PY_VERSION_HEX; diff --git a/Python/pylifecycle.c b/Python/pylifecycle.c index ba1b94c47984ab..19af7c48bb8ad0 100644 --- a/Python/pylifecycle.c +++ b/Python/pylifecycle.c @@ -559,11 +559,6 @@ pycore_init_runtime(_PyRuntimeState *runtime, */ _PyRuntimeState_SetFinalizing(runtime, NULL); - status = _Py_GetVersion_Init(); - if (_PyStatus_EXCEPTION(status)) { - return status; - } - _Py_DumpTraceback_Init(); status = _Py_HashRandomization_Init(config); diff --git a/Tools/build/generate_getbuildinfo.py b/Tools/build/generate_getbuildinfo.py new file mode 100644 index 00000000000000..1638a2da5ae44f --- /dev/null +++ b/Tools/build/generate_getbuildinfo.py @@ -0,0 +1,131 @@ +# Script to generate Modules/getbuildinfo.h + +import locale +import os +import re +import sys +import time + + +SRC_DIR = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..')) +SCRIPT_NAME = os.path.basename(__file__) +SCRIPT_FULLNAME = f'Tools/build/{SCRIPT_NAME}' + +# Get PY_VERSION from Include/patchlevel.h +PY_VERSION_REGEX = re.compile(r'#define PY_VERSION +"(.*)"') + +# Py_GIL_DISABLED from pyconfig.h +Py_GIL_DISABLED_DEF_REGEX = re.compile(r'#define Py_GIL_DISABLED 1') +Py_GIL_DISABLED_UNDEF_REGEX = re.compile(r'/\* #undef Py_GIL_DISABLED \*/') + + +def get_py_version(): + patchlevel_h = os.path.join(SRC_DIR, 'Include', 'patchlevel.h') + with open(patchlevel_h, encoding='utf8') as fp: + code = fp.read() + match = PY_VERSION_REGEX.search(code) + if not match: + print(f"ERROR: Unable to locate PY_VERSION in {patchlevel_h}") + sys.exit(1) + return match.group(1) + + +def get_gil_disable(): + pyconfig_h = os.path.join(SRC_DIR, 'pyconfig.h') + with open(pyconfig_h, encoding='utf8') as fp: + code = fp.read() + + if Py_GIL_DISABLED_DEF_REGEX.search(code): + return True + if Py_GIL_DISABLED_UNDEF_REGEX.search(code): + return False + + print(f"ERROR: Unable to locate Py_GIL_DISABLED in {patchlevel_h}") + sys.exit(1) + + +def get_date_time(): + if os.environ.get('SOURCE_DATE_EPOCH'): + ts = int(os.environ['SOURCE_DATE_EPOCH']) + else: + ts = time.time() + + time_tuple = time.localtime(ts) + day = time.strftime("%d", time_tuple) + if day.startswith("0"): + day = " " + day[1:] + DATE = time.strftime(f"%b {day} %Y", time_tuple) + TIME = time.strftime("%H:%M:%S", time_tuple) + return (DATE, TIME) + + +def main(): + if len(sys.argv) != 7: + print(f"usage: {SCRIPT_NAME} " + "OUTPUT PLATFORM GITVERSION GITTAG GITBRANCH COMPILER") + print(sys.argv) + print(len(sys.argv)) + sys.exit(1) + + OUTPUT = sys.argv[1] + PLATFORM = sys.argv[2] + GITVERSION = sys.argv[3] + GITTAG = sys.argv[4] + GITBRANCH = sys.argv[5] + COMPILER = sys.argv[6] + + if not PLATFORM: + PLATFORM = "unknown" + + # Force the C locale to format date as English + locale.setlocale(locale.LC_ALL, 'C') + + gittag = GITTAG + if gittag and gittag != "undefined": + gitid = gittag + else: + gitid = GITBRANCH + + revision = GITVERSION + sep = ":" if revision else "" + if not gitid: + gitid = "main" + + DATE, TIME = get_date_time() + PY_VERSION = get_py_version() + Py_GIL_DISABLED = get_gil_disable() + BUILDINFO = f"{gitid}{sep}{revision}, {DATE:.20s}, {TIME:.9s}" + + if Py_GIL_DISABLED: + GET_VERSION = f"{PY_VERSION} free-threading build ({BUILDINFO}) {COMPILER}" + else: + GET_VERSION = f"{PY_VERSION} ({BUILDINFO}) {COMPILER}" + + new_filename = OUTPUT + ".new" + with open(new_filename, "w", encoding="utf8") as fp: + def write_macro(name, value): + print(f'#define {name} "{value}"', file=fp) + + print(f'// Header file auto-generated by {SCRIPT_FULLNAME}', file=fp) + print(file=fp) + write_macro('DATE', DATE) + write_macro('TIME', TIME) + write_macro('COMPILER', COMPILER) + print(file=fp) + write_macro('PY_VERSION', PY_VERSION) + write_macro('PLATFORM', PLATFORM) + print(file=fp) + write_macro('GITBRANCH', GITBRANCH) + write_macro('GITTAG', GITTAG) + write_macro('GITVERSION', GITVERSION) + write_macro('GIT_IDENTIFIER', gitid) + print(file=fp) + write_macro('BUILDINFO', BUILDINFO) + write_macro('GET_VERSION', GET_VERSION) + + os.replace(new_filename, OUTPUT) + print(f"{OUTPUT} updated") + + +if __name__ == "__main__": + main() From 508e3caa41b993dffd5ee68d8d880c6ecb73f62a Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Wed, 7 Oct 2026 04:59:30 +0200 Subject: [PATCH 02/29] Fix and reforamt generate_getbuildinfo.py --- Tools/build/generate_getbuildinfo.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Tools/build/generate_getbuildinfo.py b/Tools/build/generate_getbuildinfo.py index 1638a2da5ae44f..be5e5bee86ac48 100644 --- a/Tools/build/generate_getbuildinfo.py +++ b/Tools/build/generate_getbuildinfo.py @@ -6,7 +6,6 @@ import sys import time - SRC_DIR = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..')) SCRIPT_NAME = os.path.basename(__file__) SCRIPT_FULLNAME = f'Tools/build/{SCRIPT_NAME}' @@ -40,7 +39,7 @@ def get_gil_disable(): if Py_GIL_DISABLED_UNDEF_REGEX.search(code): return False - print(f"ERROR: Unable to locate Py_GIL_DISABLED in {patchlevel_h}") + print(f"ERROR: Unable to locate Py_GIL_DISABLED in {pyconfig_h}") sys.exit(1) From 8e7fe032961dda43ed94a654fdf9130b2ce6848a Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Wed, 7 Oct 2026 05:17:06 +0200 Subject: [PATCH 03/29] Look for pyconfig.h in the current working directory --- Tools/build/generate_getbuildinfo.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Tools/build/generate_getbuildinfo.py b/Tools/build/generate_getbuildinfo.py index be5e5bee86ac48..0f3c43925d5626 100644 --- a/Tools/build/generate_getbuildinfo.py +++ b/Tools/build/generate_getbuildinfo.py @@ -30,7 +30,8 @@ def get_py_version(): def get_gil_disable(): - pyconfig_h = os.path.join(SRC_DIR, 'pyconfig.h') + # Look in the current working directory + pyconfig_h = 'pyconfig.h' with open(pyconfig_h, encoding='utf8') as fp: code = fp.read() From 06cc3482e7bda8f767afa00d9854fadef0b86bae Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Wed, 7 Oct 2026 05:20:30 +0200 Subject: [PATCH 04/29] Fix out-of-tree build --- Makefile.pre.in | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Makefile.pre.in b/Makefile.pre.in index 6c8cba44a114d7..3bbdfb82570fb0 100644 --- a/Makefile.pre.in +++ b/Makefile.pre.in @@ -1882,7 +1882,8 @@ Modules/getbuildinfo.h: $(PARSER_OBJS) \ "`Programs/_getcompiler`" Modules/getbuildinfo.o: $(srcdir)/Modules/getbuildinfo.c Modules/getbuildinfo.h - $(CC) -c $(PY_CORE_CFLAGS) -o $@ $(srcdir)/Modules/getbuildinfo.c + # Add local Modules/ to include directories to get Modules/getbuildinfo.h + $(CC) -c $(PY_CORE_CFLAGS) -IModules/ -o $@ $(srcdir)/Modules/getbuildinfo.c Modules/getpath.o: $(srcdir)/Modules/getpath.c Python/frozen_modules/getpath.h Makefile $(PYTHON_HEADERS) $(CC) -c $(PY_CORE_CFLAGS) -DPYTHONPATH='"$(PYTHONPATH)"' \ From 7c10d0b71f48c6fe6c6ed9d47bba45196d62cfb0 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Wed, 7 Oct 2026 05:23:07 +0200 Subject: [PATCH 05/29] Short comment --- Makefile.pre.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile.pre.in b/Makefile.pre.in index 3bbdfb82570fb0..fcd3c201f8eb94 100644 --- a/Makefile.pre.in +++ b/Makefile.pre.in @@ -1882,7 +1882,7 @@ Modules/getbuildinfo.h: $(PARSER_OBJS) \ "`Programs/_getcompiler`" Modules/getbuildinfo.o: $(srcdir)/Modules/getbuildinfo.c Modules/getbuildinfo.h - # Add local Modules/ to include directories to get Modules/getbuildinfo.h + # Add local Modules/ include directory to get Modules/getbuildinfo.h $(CC) -c $(PY_CORE_CFLAGS) -IModules/ -o $@ $(srcdir)/Modules/getbuildinfo.c Modules/getpath.o: $(srcdir)/Modules/getpath.c Python/frozen_modules/getpath.h Makefile $(PYTHON_HEADERS) From 8c2dcced07c126ab5ac727b5ada6e9602caccb30 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Wed, 7 Oct 2026 05:29:41 +0200 Subject: [PATCH 06/29] Update PCbuild/_freeze_module.vcxproj --- PCbuild/_freeze_module.vcxproj | 4 ---- PCbuild/_freeze_module.vcxproj.filters | 12 ------------ 2 files changed, 16 deletions(-) diff --git a/PCbuild/_freeze_module.vcxproj b/PCbuild/_freeze_module.vcxproj index 36a1d768486a3f..7e354c20f798af 100644 --- a/PCbuild/_freeze_module.vcxproj +++ b/PCbuild/_freeze_module.vcxproj @@ -222,11 +222,7 @@ - - - - diff --git a/PCbuild/_freeze_module.vcxproj.filters b/PCbuild/_freeze_module.vcxproj.filters index b358e09950a443..9d50d0a73a75f4 100644 --- a/PCbuild/_freeze_module.vcxproj.filters +++ b/PCbuild/_freeze_module.vcxproj.filters @@ -196,24 +196,12 @@ Source Files - - Source Files - - - Source Files - Source Files Source Files - - Source Files - - - Source Files - Source Files From 78823312335a6a263d04b7490c99c38c7bb17371 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Wed, 7 Oct 2026 05:32:30 +0200 Subject: [PATCH 07/29] Update Tools/c-analyzer/cpython/ignored.tsv --- Tools/c-analyzer/cpython/ignored.tsv | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/Tools/c-analyzer/cpython/ignored.tsv b/Tools/c-analyzer/cpython/ignored.tsv index 220e84ef1f60c2..a65aec69d52c5d 100644 --- a/Tools/c-analyzer/cpython/ignored.tsv +++ b/Tools/c-analyzer/cpython/ignored.tsv @@ -45,11 +45,7 @@ Objects/unicodeobject.c _init_global_state initialized - ## other values (Python-specific) ## internal state - set before/during first init -Modules/getbuildinfo.c - buildinfo - -Modules/getbuildinfo.c - initialized - -Python/getversion.c - initialized - -Python/getversion.c - static_version - -Python/getversion.c - heap_version - +Modules/getbuildinfo.c - version - ## public C-API - set during first init Python/bootstrap_hash.c - _Py_HashSecret_Initialized - From 22d1a6b7c1e6acea03dda56063b5cd3c7013934e Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Wed, 7 Oct 2026 05:35:05 +0200 Subject: [PATCH 08/29] Don't redefine PY_VERSION It's only needed in the Python script. Python.h defines PY_VERSION (via patchlevel.h). --- Tools/build/generate_getbuildinfo.py | 1 - 1 file changed, 1 deletion(-) diff --git a/Tools/build/generate_getbuildinfo.py b/Tools/build/generate_getbuildinfo.py index 0f3c43925d5626..140ba41948fd60 100644 --- a/Tools/build/generate_getbuildinfo.py +++ b/Tools/build/generate_getbuildinfo.py @@ -112,7 +112,6 @@ def write_macro(name, value): write_macro('TIME', TIME) write_macro('COMPILER', COMPILER) print(file=fp) - write_macro('PY_VERSION', PY_VERSION) write_macro('PLATFORM', PLATFORM) print(file=fp) write_macro('GITBRANCH', GITBRANCH) From 61ddc420547486d07774802f8b814f4f4d7531d4 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Wed, 7 Oct 2026 05:37:10 +0200 Subject: [PATCH 09/29] Remove useless getbuildinfo.h dependencies --- Makefile.pre.in | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/Makefile.pre.in b/Makefile.pre.in index fcd3c201f8eb94..f6befa4ca6374e 100644 --- a/Makefile.pre.in +++ b/Makefile.pre.in @@ -1866,14 +1866,7 @@ regen-all: regen-cases regen-slots \ ############################################################################ # Special rules for object files -Modules/getbuildinfo.h: $(PARSER_OBJS) \ - $(OBJECT_OBJS) \ - $(PYTHON_OBJS) \ - $(MODULE_OBJS) \ - $(MODOBJS) \ - $(DTRACE_OBJS) \ - Programs/_getcompiler \ - $(srcdir)/Tools/build/generate_getbuildinfo.py +Modules/getbuildinfo.h: $(srcdir)/Tools/build/generate_getbuildinfo.py Programs/_getcompiler $(PYTHON_FOR_REGEN) $(srcdir)/Tools/build/generate_getbuildinfo.py $@ \ "$(MACHDEP)" \ `LC_ALL=C $(GITVERSION)` \ From 1ae6b29b4481f5c3a7ac58f4bbef5f18c2763c09 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Wed, 7 Oct 2026 19:50:06 +0200 Subject: [PATCH 10/29] Add _getcompiler project on Windows --- Makefile.pre.in | 3 +- PCbuild/_getcompiler.vcxproj | 116 +++++++++++++++++++++++++++ PCbuild/_getcompiler.vcxproj.filters | 21 +++++ PCbuild/pcbuild.sln | 41 +++++++++- PCbuild/python.props | 1 + PCbuild/pythoncore.vcxproj | 21 +++-- PCbuild/readme.txt | 3 + Programs/_getcompiler.c | 3 + Tools/build/generate_getbuildinfo.py | 15 ++-- 9 files changed, 207 insertions(+), 17 deletions(-) create mode 100644 PCbuild/_getcompiler.vcxproj create mode 100644 PCbuild/_getcompiler.vcxproj.filters diff --git a/Makefile.pre.in b/Makefile.pre.in index f6befa4ca6374e..2437890a16a1ac 100644 --- a/Makefile.pre.in +++ b/Makefile.pre.in @@ -1872,7 +1872,8 @@ Modules/getbuildinfo.h: $(srcdir)/Tools/build/generate_getbuildinfo.py Programs/ `LC_ALL=C $(GITVERSION)` \ `LC_ALL=C $(GITTAG)` \ `LC_ALL=C $(GITBRANCH)` \ - "`Programs/_getcompiler`" + "`Programs/_getcompiler`" \ + use_pyconfig Modules/getbuildinfo.o: $(srcdir)/Modules/getbuildinfo.c Modules/getbuildinfo.h # Add local Modules/ include directory to get Modules/getbuildinfo.h diff --git a/PCbuild/_getcompiler.vcxproj b/PCbuild/_getcompiler.vcxproj new file mode 100644 index 00000000000000..9705433ebfb5d8 --- /dev/null +++ b/PCbuild/_getcompiler.vcxproj @@ -0,0 +1,116 @@ + + + + + Debug + ARM + + + Debug + ARM64 + + + Debug + Win32 + + + Debug + x64 + + + PGInstrument + ARM + + + PGInstrument + ARM64 + + + PGInstrument + Win32 + + + PGInstrument + x64 + + + PGUpdate + ARM + + + PGUpdate + ARM64 + + + PGUpdate + Win32 + + + PGUpdate + x64 + + + Release + ARM + + + Release + ARM64 + + + Release + Win32 + + + Release + x64 + + + + {E762E8EE-5316-4D66-9529-7074F3B6E74A} + _getcompiler + false + + + + + _getcompiler + Application + MultiByte + + + + + + ClCompile + + + + + + + $(PyExeName).exe + + + + EXENAME=L"$(ExeName)";_CONSOLE;%(PreprocessorDefinitions) + MultiThreaded + + + PY_ICON;%(PreprocessorDefinitions) + + + pathcch.lib;%(AdditionalDependencies) + Console + + + + + + + + + + + + diff --git a/PCbuild/_getcompiler.vcxproj.filters b/PCbuild/_getcompiler.vcxproj.filters new file mode 100644 index 00000000000000..6d5498df5c0993 --- /dev/null +++ b/PCbuild/_getcompiler.vcxproj.filters @@ -0,0 +1,21 @@ + + + + + {8f3ab79e-3cba-4e6d-82b2-559ce946de58} + + + {4a2423af-e5d1-4c88-b308-d71b768977df} + + + + + Resource Files + + + + + Source Files + + + diff --git a/PCbuild/pcbuild.sln b/PCbuild/pcbuild.sln index e2cac3158831ee..4f48c0fea6060f 100644 --- a/PCbuild/pcbuild.sln +++ b/PCbuild/pcbuild.sln @@ -1,6 +1,6 @@ Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 17 -VisualStudioVersion = 17.8.34525.116 +# Visual Studio Version 18 +VisualStudioVersion = 18.10.12224.181 stable MinimumVisualStudioVersion = 10.0.40219.1 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{553EC33E-9816-4996-A660-5D6186A0B0B3}" ProjectSection(SolutionItems) = preProject @@ -33,8 +33,8 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "python", "python.vcxproj", {78D80A15-BD8C-44E2-B49E-1F05B0A0A687} = {78D80A15-BD8C-44E2-B49E-1F05B0A0A687} {86937F53-C189-40EF-8CE8-8759D8E7D480} = {86937F53-C189-40EF-8CE8-8759D8E7D480} {885D4898-D08D-4091-9C40-C700CFE3FC5A} = {885D4898-D08D-4091-9C40-C700CFE3FC5A} - {947BB5F5-6025-4A4F-8182-1B175469F8D2} = {947BB5F5-6025-4A4F-8182-1B175469F8D2} {900342D7-516A-4469-B1AD-59A66E49A25F} = {900342D7-516A-4469-B1AD-59A66E49A25F} + {947BB5F5-6025-4A4F-8182-1B175469F8D2} = {947BB5F5-6025-4A4F-8182-1B175469F8D2} {9E48B300-37D1-11DD-8C41-005056C00008} = {9E48B300-37D1-11DD-8C41-005056C00008} {9EC7190A-249F-4180-A900-548FDCF3055F} = {9EC7190A-249F-4180-A900-548FDCF3055F} {A2697BD3-28C1-4AEC-9106-8B748639FD16} = {A2697BD3-28C1-4AEC-9106-8B748639FD16} @@ -43,6 +43,7 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "python", "python.vcxproj", {C6E20F84-3247-4AD6-B051-B073268F73BA} = {C6E20F84-3247-4AD6-B051-B073268F73BA} {CB435430-EBB1-478B-8F4E-C256F6838F55} = {CB435430-EBB1-478B-8F4E-C256F6838F55} {D06B6426-4762-44CC-8BAD-D79052507F2F} = {D06B6426-4762-44CC-8BAD-D79052507F2F} + {E762E8EE-5316-4D66-9529-7074F3B6E74A} = {E762E8EE-5316-4D66-9529-7074F3B6E74A} {EB6E69DD-04BF-4543-9B92-49FAABCEAC2E} = {EB6E69DD-04BF-4543-9B92-49FAABCEAC2E} {ECC7CEAC-A5E5-458E-BB9E-2413CC847881} = {ECC7CEAC-A5E5-458E-BB9E-2413CC847881} {F749B822-B489-4CA5-A3AD-CE078F5F338A} = {F749B822-B489-4CA5-A3AD-CE078F5F338A} @@ -161,6 +162,8 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "_zstd", "_zstd.vcxproj", "{ EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "xxlimited_35", "xxlimited_35.vcxproj", "{FB868EA7-F93A-4D9B-BE78-CA4E9BA14FFF}" EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "_getcompiler", "_getcompiler.vcxproj", "{E762E8EE-5316-4D66-9529-7074F3B6E74A}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|ARM = Debug|ARM @@ -1746,6 +1749,38 @@ Global {FB868EA7-F93A-4D9B-BE78-CA4E9BA14FFF}.Release|Win32.Build.0 = Release|Win32 {FB868EA7-F93A-4D9B-BE78-CA4E9BA14FFF}.Release|x64.ActiveCfg = Release|x64 {FB868EA7-F93A-4D9B-BE78-CA4E9BA14FFF}.Release|x64.Build.0 = Release|x64 + {E762E8EE-5316-4D66-9529-7074F3B6E74A}.Debug|ARM.ActiveCfg = Debug|ARM + {E762E8EE-5316-4D66-9529-7074F3B6E74A}.Debug|ARM.Build.0 = Debug|ARM + {E762E8EE-5316-4D66-9529-7074F3B6E74A}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {E762E8EE-5316-4D66-9529-7074F3B6E74A}.Debug|ARM64.Build.0 = Debug|ARM64 + {E762E8EE-5316-4D66-9529-7074F3B6E74A}.Debug|Win32.ActiveCfg = Debug|Win32 + {E762E8EE-5316-4D66-9529-7074F3B6E74A}.Debug|Win32.Build.0 = Debug|Win32 + {E762E8EE-5316-4D66-9529-7074F3B6E74A}.Debug|x64.ActiveCfg = Debug|x64 + {E762E8EE-5316-4D66-9529-7074F3B6E74A}.Debug|x64.Build.0 = Debug|x64 + {E762E8EE-5316-4D66-9529-7074F3B6E74A}.PGInstrument|ARM.ActiveCfg = PGInstrument|ARM + {E762E8EE-5316-4D66-9529-7074F3B6E74A}.PGInstrument|ARM.Build.0 = PGInstrument|ARM + {E762E8EE-5316-4D66-9529-7074F3B6E74A}.PGInstrument|ARM64.ActiveCfg = PGInstrument|ARM64 + {E762E8EE-5316-4D66-9529-7074F3B6E74A}.PGInstrument|ARM64.Build.0 = PGInstrument|ARM64 + {E762E8EE-5316-4D66-9529-7074F3B6E74A}.PGInstrument|Win32.ActiveCfg = PGInstrument|Win32 + {E762E8EE-5316-4D66-9529-7074F3B6E74A}.PGInstrument|Win32.Build.0 = PGInstrument|Win32 + {E762E8EE-5316-4D66-9529-7074F3B6E74A}.PGInstrument|x64.ActiveCfg = PGInstrument|x64 + {E762E8EE-5316-4D66-9529-7074F3B6E74A}.PGInstrument|x64.Build.0 = PGInstrument|x64 + {E762E8EE-5316-4D66-9529-7074F3B6E74A}.PGUpdate|ARM.ActiveCfg = PGUpdate|ARM + {E762E8EE-5316-4D66-9529-7074F3B6E74A}.PGUpdate|ARM.Build.0 = PGUpdate|ARM + {E762E8EE-5316-4D66-9529-7074F3B6E74A}.PGUpdate|ARM64.ActiveCfg = PGUpdate|ARM64 + {E762E8EE-5316-4D66-9529-7074F3B6E74A}.PGUpdate|ARM64.Build.0 = PGUpdate|ARM64 + {E762E8EE-5316-4D66-9529-7074F3B6E74A}.PGUpdate|Win32.ActiveCfg = PGUpdate|Win32 + {E762E8EE-5316-4D66-9529-7074F3B6E74A}.PGUpdate|Win32.Build.0 = PGUpdate|Win32 + {E762E8EE-5316-4D66-9529-7074F3B6E74A}.PGUpdate|x64.ActiveCfg = PGUpdate|x64 + {E762E8EE-5316-4D66-9529-7074F3B6E74A}.PGUpdate|x64.Build.0 = PGUpdate|x64 + {E762E8EE-5316-4D66-9529-7074F3B6E74A}.Release|ARM.ActiveCfg = Release|ARM + {E762E8EE-5316-4D66-9529-7074F3B6E74A}.Release|ARM.Build.0 = Release|ARM + {E762E8EE-5316-4D66-9529-7074F3B6E74A}.Release|ARM64.ActiveCfg = Release|ARM64 + {E762E8EE-5316-4D66-9529-7074F3B6E74A}.Release|ARM64.Build.0 = Release|ARM64 + {E762E8EE-5316-4D66-9529-7074F3B6E74A}.Release|Win32.ActiveCfg = Release|Win32 + {E762E8EE-5316-4D66-9529-7074F3B6E74A}.Release|Win32.Build.0 = Release|Win32 + {E762E8EE-5316-4D66-9529-7074F3B6E74A}.Release|x64.ActiveCfg = Release|x64 + {E762E8EE-5316-4D66-9529-7074F3B6E74A}.Release|x64.Build.0 = Release|x64 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/PCbuild/python.props b/PCbuild/python.props index 4c49decd7490d1..e7b2c72cfb38a7 100644 --- a/PCbuild/python.props +++ b/PCbuild/python.props @@ -258,6 +258,7 @@ python $(BuildPath)$(PyExeName)$(PyDebugExt).exe pythonw + $(BuildPath)_getcompiler$(PyDebugExt).exe python$(MajorVersionNumber)$(MinorVersionNumber)t$(PyDebugExt) diff --git a/PCbuild/pythoncore.vcxproj b/PCbuild/pythoncore.vcxproj index ca3ac8112ce8f6..681404f86f5514 100644 --- a/PCbuild/pythoncore.vcxproj +++ b/PCbuild/pythoncore.vcxproj @@ -731,7 +731,10 @@ - + git <_GIT>$(GIT) @@ -742,18 +745,22 @@ + $([System.IO.File]::ReadAllText('$(IntDir)gitbranch.txt').Trim()) $([System.IO.File]::ReadAllText('$(IntDir)gitversion.txt').Trim()) $([System.IO.File]::ReadAllText('$(IntDir)gittag.txt').Trim()) + $([System.IO.File]::ReadAllText('$(IntDir)compiler.txt').Trim()) + + 0 + 1 - - - - GITVERSION="$(GitVersion)";GITTAG="$(GitTag)";GITBRANCH="$(GitBranch)";%(PreprocessorDefinitions) - - + + + + diff --git a/PCbuild/readme.txt b/PCbuild/readme.txt index d54c0ed3e3c633..a592dda5bb0ceb 100644 --- a/PCbuild/readme.txt +++ b/PCbuild/readme.txt @@ -143,6 +143,9 @@ pythonw _testembed _testembed.exe, a small program that embeds Python for testing purposes, used by test_capi.py +_getcompiler + _getcompiler.exe, a small program that writes the compiler name and version + to stdout. Used to build Modules\getbuildinfo.h. These are miscellaneous sub-projects that don't really fit the other categories: diff --git a/Programs/_getcompiler.c b/Programs/_getcompiler.c index c5e310c3d1c858..bbc19297c0f0b3 100644 --- a/Programs/_getcompiler.c +++ b/Programs/_getcompiler.c @@ -1,3 +1,6 @@ +// Do not link to python3x.lib +#define Py_NO_LINK_LIB + #include "Python.h" #include // printf() diff --git a/Tools/build/generate_getbuildinfo.py b/Tools/build/generate_getbuildinfo.py index 140ba41948fd60..4b06693ba92b54 100644 --- a/Tools/build/generate_getbuildinfo.py +++ b/Tools/build/generate_getbuildinfo.py @@ -60,11 +60,9 @@ def get_date_time(): def main(): - if len(sys.argv) != 7: + if len(sys.argv) != 8: print(f"usage: {SCRIPT_NAME} " - "OUTPUT PLATFORM GITVERSION GITTAG GITBRANCH COMPILER") - print(sys.argv) - print(len(sys.argv)) + "OUTPUT PLATFORM GITVERSION GITTAG GITBRANCH COMPILER GIL_ENABLED") sys.exit(1) OUTPUT = sys.argv[1] @@ -73,6 +71,7 @@ def main(): GITTAG = sys.argv[4] GITBRANCH = sys.argv[5] COMPILER = sys.argv[6] + GIL_ENABLED = sys.argv[7] if not PLATFORM: PLATFORM = "unknown" @@ -93,10 +92,14 @@ def main(): DATE, TIME = get_date_time() PY_VERSION = get_py_version() - Py_GIL_DISABLED = get_gil_disable() + if GIL_ENABLED == 'use_pyconfig': + Py_GIL_DISABLED = get_gil_disable() + GIL_ENABLED = not Py_GIL_DISABLED + else: + GIL_ENABLED = int(GIL_ENABLED) BUILDINFO = f"{gitid}{sep}{revision}, {DATE:.20s}, {TIME:.9s}" - if Py_GIL_DISABLED: + if not GIL_ENABLED: GET_VERSION = f"{PY_VERSION} free-threading build ({BUILDINFO}) {COMPILER}" else: GET_VERSION = f"{PY_VERSION} ({BUILDINFO}) {COMPILER}" From cd00cdb5ba1e289ea1cfd9b5fde8aa2adf2579df Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Wed, 7 Oct 2026 19:55:05 +0200 Subject: [PATCH 11/29] Temporary workaround for cross-compilation --- Makefile.pre.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile.pre.in b/Makefile.pre.in index 2437890a16a1ac..b47af5cf75db1a 100644 --- a/Makefile.pre.in +++ b/Makefile.pre.in @@ -1872,7 +1872,7 @@ Modules/getbuildinfo.h: $(srcdir)/Tools/build/generate_getbuildinfo.py Programs/ `LC_ALL=C $(GITVERSION)` \ `LC_ALL=C $(GITTAG)` \ `LC_ALL=C $(GITBRANCH)` \ - "`Programs/_getcompiler`" \ + "`Programs/_getcompiler || echo COMPILER`" \ use_pyconfig Modules/getbuildinfo.o: $(srcdir)/Modules/getbuildinfo.c Modules/getbuildinfo.h From 9a56153eb041fbfaff37e536e571ad2bdc90cd29 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Wed, 7 Oct 2026 20:48:10 +0200 Subject: [PATCH 12/29] Update PCbuild/pcbuild.proj Fix also dependencies. --- PCbuild/_freeze_module.vcxproj | 28 ++++++++++++++++++++++++++++ PCbuild/pcbuild.proj | 2 +- PCbuild/pcbuild.sln | 3 +++ PCbuild/pythoncore.vcxproj | 30 ------------------------------ 4 files changed, 32 insertions(+), 31 deletions(-) diff --git a/PCbuild/_freeze_module.vcxproj b/PCbuild/_freeze_module.vcxproj index 7e354c20f798af..6dcedca4d245fa 100644 --- a/PCbuild/_freeze_module.vcxproj +++ b/PCbuild/_freeze_module.vcxproj @@ -466,6 +466,34 @@ + + + git + <_GIT>$(GIT) + <_GIT Condition="$(GIT.Contains(` `))">"$(GIT)" + + + + + + + + + $([System.IO.File]::ReadAllText('$(IntDir)gitbranch.txt').Trim()) + $([System.IO.File]::ReadAllText('$(IntDir)gitversion.txt').Trim()) + $([System.IO.File]::ReadAllText('$(IntDir)gittag.txt').Trim()) + $([System.IO.File]::ReadAllText('$(IntDir)compiler.txt').Trim()) + + 0 + 1 + + + + + diff --git a/PCbuild/pcbuild.proj b/PCbuild/pcbuild.proj index 53aec0276beaee..7d0af6b3ad79ff 100644 --- a/PCbuild/pcbuild.proj +++ b/PCbuild/pcbuild.proj @@ -87,7 +87,7 @@ - + diff --git a/PCbuild/pcbuild.sln b/PCbuild/pcbuild.sln index 4f48c0fea6060f..aa29f6d06c8e3d 100644 --- a/PCbuild/pcbuild.sln +++ b/PCbuild/pcbuild.sln @@ -113,6 +113,9 @@ EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "_testbuffer", "_testbuffer.vcxproj", "{A2697BD3-28C1-4AEC-9106-8B748639FD16}" EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "_freeze_module", "_freeze_module.vcxproj", "{19C0C13F-47CA-4432-AFF3-799A296A4DDC}" + ProjectSection(ProjectDependencies) = postProject + {E762E8EE-5316-4D66-9529-7074F3B6E74A} = {E762E8EE-5316-4D66-9529-7074F3B6E74A} + EndProjectSection EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "_overlapped", "_overlapped.vcxproj", "{EB6E69DD-04BF-4543-9B92-49FAABCEAC2E}" EndProject diff --git a/PCbuild/pythoncore.vcxproj b/PCbuild/pythoncore.vcxproj index 681404f86f5514..f532d6b894cc47 100644 --- a/PCbuild/pythoncore.vcxproj +++ b/PCbuild/pythoncore.vcxproj @@ -731,36 +731,6 @@ - - - git - <_GIT>$(GIT) - <_GIT Condition="$(GIT.Contains(` `))">"$(GIT)" - - - - - - - - - $([System.IO.File]::ReadAllText('$(IntDir)gitbranch.txt').Trim()) - $([System.IO.File]::ReadAllText('$(IntDir)gitversion.txt').Trim()) - $([System.IO.File]::ReadAllText('$(IntDir)gittag.txt').Trim()) - $([System.IO.File]::ReadAllText('$(IntDir)compiler.txt').Trim()) - - 0 - 1 - - - - - - From eab1b0e91c83422d5f741a5ef0f39e1e4a76dc68 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Wed, 7 Oct 2026 21:24:39 +0200 Subject: [PATCH 13/29] Fix build when there is not .git/ directory (test_tools) --- Makefile.pre.in | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Makefile.pre.in b/Makefile.pre.in index b47af5cf75db1a..a0a737625214cd 100644 --- a/Makefile.pre.in +++ b/Makefile.pre.in @@ -1869,9 +1869,9 @@ regen-all: regen-cases regen-slots \ Modules/getbuildinfo.h: $(srcdir)/Tools/build/generate_getbuildinfo.py Programs/_getcompiler $(PYTHON_FOR_REGEN) $(srcdir)/Tools/build/generate_getbuildinfo.py $@ \ "$(MACHDEP)" \ - `LC_ALL=C $(GITVERSION)` \ - `LC_ALL=C $(GITTAG)` \ - `LC_ALL=C $(GITBRANCH)` \ + "`LC_ALL=C $(GITVERSION)`" \ + "`LC_ALL=C $(GITTAG)`" \ + "`LC_ALL=C $(GITBRANCH)`" \ "`Programs/_getcompiler || echo COMPILER`" \ use_pyconfig From a0780d4aa9c2f41d50e3176bd6c5222fd7c0bf0c Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Wed, 7 Oct 2026 23:26:27 +0200 Subject: [PATCH 14/29] Enhance generate_getbuildinfo.py * Use argparse for command line parsing. * Run _getcompiler if --compiler option is omitted. * Rename getbuildinfo.h variables. --- Makefile.pre.in | 13 +- Modules/getbuildinfo.c | 6 +- PCbuild/_freeze_module.vcxproj | 2 +- Tools/build/generate_getbuildinfo.py | 174 +++++++++++++++++---------- 4 files changed, 123 insertions(+), 72 deletions(-) diff --git a/Makefile.pre.in b/Makefile.pre.in index a0a737625214cd..687fe9fd1a18d9 100644 --- a/Makefile.pre.in +++ b/Makefile.pre.in @@ -1867,13 +1867,12 @@ regen-all: regen-cases regen-slots \ # Special rules for object files Modules/getbuildinfo.h: $(srcdir)/Tools/build/generate_getbuildinfo.py Programs/_getcompiler - $(PYTHON_FOR_REGEN) $(srcdir)/Tools/build/generate_getbuildinfo.py $@ \ - "$(MACHDEP)" \ - "`LC_ALL=C $(GITVERSION)`" \ - "`LC_ALL=C $(GITTAG)`" \ - "`LC_ALL=C $(GITBRANCH)`" \ - "`Programs/_getcompiler || echo COMPILER`" \ - use_pyconfig + $(PYTHON_FOR_REGEN) $(srcdir)/Tools/build/generate_getbuildinfo.py \ + -o $@ \ + --platform="$(MACHDEP)" \ + --git-version="`LC_ALL=C $(GITVERSION)`" \ + --git-tag="`LC_ALL=C $(GITTAG)`" \ + --git-branch="`LC_ALL=C $(GITBRANCH)`" Modules/getbuildinfo.o: $(srcdir)/Modules/getbuildinfo.c Modules/getbuildinfo.h # Add local Modules/ include directory to get Modules/getbuildinfo.h diff --git a/Modules/getbuildinfo.c b/Modules/getbuildinfo.c index c3690fb9068a6a..303ce6e4e4ed9e 100644 --- a/Modules/getbuildinfo.c +++ b/Modules/getbuildinfo.c @@ -40,7 +40,7 @@ Py_GetPlatform(void) const char * _Py_gitversion(void) { - return GITVERSION; + return GIT_VERSION; } const char * @@ -52,7 +52,7 @@ _Py_gitidentifier(void) const char * Py_GetBuildInfo(void) { - return BUILDINFO; + return BUILD_INFO; } // Export the Python hex version as a constant. @@ -60,7 +60,7 @@ const unsigned long Py_Version = PY_VERSION_HEX; // Keep the 'version' variable for backward compatibility. // Some debuggers inspect directly the variable. -static const char *version = GET_VERSION; +static const char *version = VERSION; const char * Py_GetVersion(void) diff --git a/PCbuild/_freeze_module.vcxproj b/PCbuild/_freeze_module.vcxproj index 6dcedca4d245fa..db34b84401d1f3 100644 --- a/PCbuild/_freeze_module.vcxproj +++ b/PCbuild/_freeze_module.vcxproj @@ -491,7 +491,7 @@ - diff --git a/Tools/build/generate_getbuildinfo.py b/Tools/build/generate_getbuildinfo.py index 4b06693ba92b54..c4e73e0e0ed3c5 100644 --- a/Tools/build/generate_getbuildinfo.py +++ b/Tools/build/generate_getbuildinfo.py @@ -1,8 +1,11 @@ # Script to generate Modules/getbuildinfo.h +import argparse import locale import os import re +import shlex +import subprocess import sys import time @@ -11,24 +14,38 @@ SCRIPT_FULLNAME = f'Tools/build/{SCRIPT_NAME}' # Get PY_VERSION from Include/patchlevel.h -PY_VERSION_REGEX = re.compile(r'#define PY_VERSION +"(.*)"') +PY_VERSION_REGEX = re.compile(r'^#define PY_VERSION +"(.*)"$', re.MULTILINE) + +# Get HOSTRUNNER from Makefile +HOSTRUNNER_REGEX = re.compile(r'^HOSTRUNNER *= *(.*)$', re.MULTILINE) # Py_GIL_DISABLED from pyconfig.h -Py_GIL_DISABLED_DEF_REGEX = re.compile(r'#define Py_GIL_DISABLED 1') -Py_GIL_DISABLED_UNDEF_REGEX = re.compile(r'/\* #undef Py_GIL_DISABLED \*/') +Py_GIL_DISABLED_DEF_REGEX = re.compile(r'^#define Py_GIL_DISABLED 1$', re.MULTILINE) +Py_GIL_DISABLED_UNDEF_REGEX = re.compile(r'^/\* #undef Py_GIL_DISABLED \*/$', re.MULTILINE) -def get_py_version(): - patchlevel_h = os.path.join(SRC_DIR, 'Include', 'patchlevel.h') - with open(patchlevel_h, encoding='utf8') as fp: +def parse_file(variable_name, filename, regex): + with open(filename, encoding='utf8') as fp: code = fp.read() - match = PY_VERSION_REGEX.search(code) + match = regex.search(code) if not match: - print(f"ERROR: Unable to locate PY_VERSION in {patchlevel_h}") + print(f"ERROR: Unable to locate {variable_name} in {filename}") sys.exit(1) return match.group(1) +def get_py_version(): + patchlevel_h = os.path.join(SRC_DIR, 'Include', 'patchlevel.h') + return parse_file('PY_VERSION ', patchlevel_h, PY_VERSION_REGEX) + + +def get_host_runner(): + # Look in the current working directory + makefile = 'Makefile' + HOSTRUNNER = parse_file('HOSTRUNNER', makefile, HOSTRUNNER_REGEX) + return HOSTRUNNER.rstrip() + + def get_gil_disable(): # Look in the current working directory pyconfig_h = 'pyconfig.h' @@ -54,79 +71,114 @@ def get_date_time(): day = time.strftime("%d", time_tuple) if day.startswith("0"): day = " " + day[1:] - DATE = time.strftime(f"%b {day} %Y", time_tuple) - TIME = time.strftime("%H:%M:%S", time_tuple) - return (DATE, TIME) + build_date = time.strftime(f"%b {day} %Y", time_tuple) + build_time = time.strftime("%H:%M:%S", time_tuple) + return (build_date, build_time) -def main(): - if len(sys.argv) != 8: - print(f"usage: {SCRIPT_NAME} " - "OUTPUT PLATFORM GITVERSION GITTAG GITBRANCH COMPILER GIL_ENABLED") - sys.exit(1) +def get_build_info(git_tag, git_branch, git_version, build_date, build_time): + if git_tag and git_tag != "undefined": + git_id = git_tag + else: + git_id = git_branch + if not git_id: + git_id = "main" - OUTPUT = sys.argv[1] - PLATFORM = sys.argv[2] - GITVERSION = sys.argv[3] - GITTAG = sys.argv[4] - GITBRANCH = sys.argv[5] - COMPILER = sys.argv[6] - GIL_ENABLED = sys.argv[7] + sep = ":" if git_version else "" - if not PLATFORM: - PLATFORM = "unknown" + build_info = f"{git_id}{sep}{git_version}, {build_date:.20s}, {build_time:.9s}" + return (build_info, git_id) - # Force the C locale to format date as English - locale.setlocale(locale.LC_ALL, 'C') - gittag = GITTAG - if gittag and gittag != "undefined": - gitid = gittag +def parse_args(): + parser = argparse.ArgumentParser() + parser.add_argument('-o', '--output', type=str) + parser.add_argument('--platform', type=str, required=True) + parser.add_argument('--git-version', type=str, required=True) + parser.add_argument('--git-tag', type=str, required=True) + parser.add_argument('--git-branch', type=str, required=True) + parser.add_argument('--compiler', type=str) + parser.add_argument('--gil-enabled', type=int) + return parser.parse_args() + + +def run_command(cmd): + try: + proc = subprocess.run(cmd, stdout=subprocess.PIPE, text=True) + except OSError as exc: + error = f'with: {exc!r}' else: - gitid = GITBRANCH + exitcode = proc.returncode + if exitcode: + error = f'with exit code {exitcode}' + else: + error = None + + if error: + print(f"Command {shlex.join(cmd)} failed {error}") + sys.exit(1) + return proc.stdout.rstrip() - revision = GITVERSION - sep = ":" if revision else "" - if not gitid: - gitid = "main" - DATE, TIME = get_date_time() +def main(): + # Force the C locale to format date as English + locale.setlocale(locale.LC_ALL, 'C') + + args = parse_args() + output_filename = args.output + platform = args.platform + git_version = args.git_version + git_tag = args.git_tag + git_branch = args.git_branch + compiler = args.compiler + gil_enabled = args.gil_enabled + + if not output_filename: + # In the current directory + output_filename = os.path.join('Modules', 'getbuildinfo.h') + if not platform: + platform = "unknown" + + if not compiler: + getcompiler = os.path.join('Programs', '_getcompiler') + HOSTRUNNER = get_host_runner() + if HOSTRUNNER: + runner = shlex.split(HOSTRUNNER)[0] + compiler = run_command([runner, getcompiler]) + else: + compiler = run_command([getcompiler]) + + build_date, build_time = get_date_time() + build_info, git_id = get_build_info(git_tag, git_branch, git_version, build_date, build_time) + + # Get PY_VERSION macro from Include/patchlevel.h PY_VERSION = get_py_version() - if GIL_ENABLED == 'use_pyconfig': + if gil_enabled is None: + # Get Py_GIL_DISABLED macro from pyconfig.h (defined or undefined) Py_GIL_DISABLED = get_gil_disable() - GIL_ENABLED = not Py_GIL_DISABLED - else: - GIL_ENABLED = int(GIL_ENABLED) - BUILDINFO = f"{gitid}{sep}{revision}, {DATE:.20s}, {TIME:.9s}" + gil_enabled = not Py_GIL_DISABLED - if not GIL_ENABLED: - GET_VERSION = f"{PY_VERSION} free-threading build ({BUILDINFO}) {COMPILER}" + if not gil_enabled: + version = f"{PY_VERSION} free-threading build ({build_info}) {compiler}" else: - GET_VERSION = f"{PY_VERSION} ({BUILDINFO}) {COMPILER}" + version = f"{PY_VERSION} ({build_info}) {compiler}" - new_filename = OUTPUT + ".new" + new_filename = output_filename + ".new" with open(new_filename, "w", encoding="utf8") as fp: def write_macro(name, value): print(f'#define {name} "{value}"', file=fp) print(f'// Header file auto-generated by {SCRIPT_FULLNAME}', file=fp) print(file=fp) - write_macro('DATE', DATE) - write_macro('TIME', TIME) - write_macro('COMPILER', COMPILER) - print(file=fp) - write_macro('PLATFORM', PLATFORM) - print(file=fp) - write_macro('GITBRANCH', GITBRANCH) - write_macro('GITTAG', GITTAG) - write_macro('GITVERSION', GITVERSION) - write_macro('GIT_IDENTIFIER', gitid) - print(file=fp) - write_macro('BUILDINFO', BUILDINFO) - write_macro('GET_VERSION', GET_VERSION) - - os.replace(new_filename, OUTPUT) - print(f"{OUTPUT} updated") + write_macro('PLATFORM', platform) + write_macro('COMPILER', compiler) + write_macro('GIT_VERSION', git_version) + write_macro('GIT_IDENTIFIER', git_id) + write_macro('BUILD_INFO', build_info) + write_macro('VERSION', version) + + os.replace(new_filename, output_filename) + print(f"{output_filename} updated") if __name__ == "__main__": From 0c8ad79a0cc901c78b5e9c689dcc96110362ea81 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Thu, 8 Oct 2026 00:18:49 +0200 Subject: [PATCH 15/29] Run directly the compiler (--version) if _getcompiler failed --- Programs/_getcompiler.c | 2 +- Tools/build/generate_getbuildinfo.py | 72 +++++++++++++++++++++------- 2 files changed, 56 insertions(+), 18 deletions(-) diff --git a/Programs/_getcompiler.c b/Programs/_getcompiler.c index bbc19297c0f0b3..0a7dabbc7d9c75 100644 --- a/Programs/_getcompiler.c +++ b/Programs/_getcompiler.c @@ -1,4 +1,4 @@ -// Do not link to python3x.lib +// On Windows, do not link the program to python3x.lib #define Py_NO_LINK_LIB #include "Python.h" diff --git a/Tools/build/generate_getbuildinfo.py b/Tools/build/generate_getbuildinfo.py index c4e73e0e0ed3c5..7ab00018b08028 100644 --- a/Tools/build/generate_getbuildinfo.py +++ b/Tools/build/generate_getbuildinfo.py @@ -16,21 +16,30 @@ # Get PY_VERSION from Include/patchlevel.h PY_VERSION_REGEX = re.compile(r'^#define PY_VERSION +"(.*)"$', re.MULTILINE) -# Get HOSTRUNNER from Makefile -HOSTRUNNER_REGEX = re.compile(r'^HOSTRUNNER *= *(.*)$', re.MULTILINE) +def create_makefile_regex(name): + return re.compile(fr'^{name}[ \t]*=[ \t]*(.*)$', re.MULTILINE) + +# Get HOSTRUNNER and CC from Makefile +HOSTRUNNER_REGEX = create_makefile_regex('HOSTRUNNER') +CC_REGEX = create_makefile_regex('CC') # Py_GIL_DISABLED from pyconfig.h Py_GIL_DISABLED_DEF_REGEX = re.compile(r'^#define Py_GIL_DISABLED 1$', re.MULTILINE) Py_GIL_DISABLED_UNDEF_REGEX = re.compile(r'^/\* #undef Py_GIL_DISABLED \*/$', re.MULTILINE) +def exit_error(msg): + print(msg) + print("cwd: {os.getcwd()}") + sys.exit(1) + + def parse_file(variable_name, filename, regex): with open(filename, encoding='utf8') as fp: code = fp.read() match = regex.search(code) if not match: - print(f"ERROR: Unable to locate {variable_name} in {filename}") - sys.exit(1) + exit_error(f"ERROR: Unable to locate {variable_name} in {filename}") return match.group(1) @@ -46,6 +55,13 @@ def get_host_runner(): return HOSTRUNNER.rstrip() +def get_makefile_cc(): + # Look in the current working directory + makefile = 'Makefile' + CC = parse_file('CC', makefile, CC_REGEX) + return CC.rstrip() + + def get_gil_disable(): # Look in the current working directory pyconfig_h = 'pyconfig.h' @@ -57,8 +73,7 @@ def get_gil_disable(): if Py_GIL_DISABLED_UNDEF_REGEX.search(code): return False - print(f"ERROR: Unable to locate Py_GIL_DISABLED in {pyconfig_h}") - sys.exit(1) + exit_error(f"ERROR: Unable to locate Py_GIL_DISABLED in {pyconfig_h}") def get_date_time(): @@ -102,7 +117,9 @@ def parse_args(): return parser.parse_args() -def run_command(cmd): +def run_command(cmd, *, check=True): + cmd_str = shlex.join(cmd) + print(f"+ {cmd_str}") try: proc = subprocess.run(cmd, stdout=subprocess.PIPE, text=True) except OSError as exc: @@ -115,11 +132,38 @@ def run_command(cmd): error = None if error: - print(f"Command {shlex.join(cmd)} failed {error}") - sys.exit(1) + msg = f"Command {cmd_str} failed {error}" + if check: + exit_error(msg) + else: + print(msg) + return None + return proc.stdout.rstrip() +def get_compiler(): + # Run _getcompiler program + getcompiler = os.path.join('Programs', '_getcompiler') + HOSTRUNNER = get_host_runner() + if HOSTRUNNER: + # Cross-compilation + runner = shlex.split(HOSTRUNNER)[0] + compiler = run_command([runner, getcompiler], check=False) + else: + compiler = run_command([getcompiler], check=False) + + if compiler: + return compiler + + # Running _getcompiler failed, run directly the compiler (--version) + cc = get_makefile_cc() + cc = shlex.split(cc) + output = run_command([*cc, '--version']) + output = output.splitlines()[0] + return f'[{output}]' + + def main(): # Force the C locale to format date as English locale.setlocale(locale.LC_ALL, 'C') @@ -134,19 +178,13 @@ def main(): gil_enabled = args.gil_enabled if not output_filename: - # In the current directory + # Write to the current directory output_filename = os.path.join('Modules', 'getbuildinfo.h') if not platform: platform = "unknown" if not compiler: - getcompiler = os.path.join('Programs', '_getcompiler') - HOSTRUNNER = get_host_runner() - if HOSTRUNNER: - runner = shlex.split(HOSTRUNNER)[0] - compiler = run_command([runner, getcompiler]) - else: - compiler = run_command([getcompiler]) + compiler = get_compiler() build_date, build_time = get_date_time() build_info, git_id = get_build_info(git_tag, git_branch, git_version, build_date, build_time) From 0d50e2e7364e099f02a74fa6e675da8e5eeb1a94 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Thu, 8 Oct 2026 00:37:28 +0200 Subject: [PATCH 16/29] Reuse sysconfig parsers --- PCbuild/_freeze_module.vcxproj | 6 +-- Tools/build/generate_getbuildinfo.py | 63 ++++++++++------------------ 2 files changed, 25 insertions(+), 44 deletions(-) diff --git a/PCbuild/_freeze_module.vcxproj b/PCbuild/_freeze_module.vcxproj index db34b84401d1f3..057168d245e568 100644 --- a/PCbuild/_freeze_module.vcxproj +++ b/PCbuild/_freeze_module.vcxproj @@ -486,12 +486,12 @@ $([System.IO.File]::ReadAllText('$(IntDir)gittag.txt').Trim()) $([System.IO.File]::ReadAllText('$(IntDir)compiler.txt').Trim()) - 0 - 1 + 1 + 0 - diff --git a/Tools/build/generate_getbuildinfo.py b/Tools/build/generate_getbuildinfo.py index 7ab00018b08028..720053f2239415 100644 --- a/Tools/build/generate_getbuildinfo.py +++ b/Tools/build/generate_getbuildinfo.py @@ -7,6 +7,7 @@ import shlex import subprocess import sys +import sysconfig.__main__ import time SRC_DIR = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..')) @@ -16,17 +17,6 @@ # Get PY_VERSION from Include/patchlevel.h PY_VERSION_REGEX = re.compile(r'^#define PY_VERSION +"(.*)"$', re.MULTILINE) -def create_makefile_regex(name): - return re.compile(fr'^{name}[ \t]*=[ \t]*(.*)$', re.MULTILINE) - -# Get HOSTRUNNER and CC from Makefile -HOSTRUNNER_REGEX = create_makefile_regex('HOSTRUNNER') -CC_REGEX = create_makefile_regex('CC') - -# Py_GIL_DISABLED from pyconfig.h -Py_GIL_DISABLED_DEF_REGEX = re.compile(r'^#define Py_GIL_DISABLED 1$', re.MULTILINE) -Py_GIL_DISABLED_UNDEF_REGEX = re.compile(r'^/\* #undef Py_GIL_DISABLED \*/$', re.MULTILINE) - def exit_error(msg): print(msg) @@ -48,32 +38,18 @@ def get_py_version(): return parse_file('PY_VERSION ', patchlevel_h, PY_VERSION_REGEX) -def get_host_runner(): - # Look in the current working directory - makefile = 'Makefile' - HOSTRUNNER = parse_file('HOSTRUNNER', makefile, HOSTRUNNER_REGEX) - return HOSTRUNNER.rstrip() - - -def get_makefile_cc(): +def get_makefile_vars(): # Look in the current working directory makefile = 'Makefile' - CC = parse_file('CC', makefile, CC_REGEX) - return CC.rstrip() + return sysconfig.__main__._parse_makefile(makefile) def get_gil_disable(): # Look in the current working directory pyconfig_h = 'pyconfig.h' - with open(pyconfig_h, encoding='utf8') as fp: - code = fp.read() - - if Py_GIL_DISABLED_DEF_REGEX.search(code): - return True - if Py_GIL_DISABLED_UNDEF_REGEX.search(code): - return False - - exit_error(f"ERROR: Unable to locate Py_GIL_DISABLED in {pyconfig_h}") + with open(pyconfig_h, encoding="utf-8") as fp: + config_vars = sysconfig.parse_config_h(fp) + return bool(config_vars['Py_GIL_DISABLED']) def get_date_time(): @@ -91,7 +67,9 @@ def get_date_time(): return (build_date, build_time) -def get_build_info(git_tag, git_branch, git_version, build_date, build_time): +def get_build_info(git_tag, git_branch, git_version): + build_date, build_time = get_date_time() + if git_tag and git_tag != "undefined": git_id = git_tag else: @@ -113,7 +91,7 @@ def parse_args(): parser.add_argument('--git-tag', type=str, required=True) parser.add_argument('--git-branch', type=str, required=True) parser.add_argument('--compiler', type=str) - parser.add_argument('--gil-enabled', type=int) + parser.add_argument('--free-threading', type=int) return parser.parse_args() @@ -143,9 +121,11 @@ def run_command(cmd, *, check=True): def get_compiler(): + makefile_vars = get_makefile_vars() + # Run _getcompiler program getcompiler = os.path.join('Programs', '_getcompiler') - HOSTRUNNER = get_host_runner() + HOSTRUNNER = makefile_vars.get('HOSTRUNNER') if HOSTRUNNER: # Cross-compilation runner = shlex.split(HOSTRUNNER)[0] @@ -157,7 +137,10 @@ def get_compiler(): return compiler # Running _getcompiler failed, run directly the compiler (--version) - cc = get_makefile_cc() + CC = makefile_vars.get('CC') + if not CC: + exit_error(f"ERROR: Unable to locate CC in Makefile") + cc = shlex.split(cc) output = run_command([*cc, '--version']) output = output.splitlines()[0] @@ -175,7 +158,7 @@ def main(): git_tag = args.git_tag git_branch = args.git_branch compiler = args.compiler - gil_enabled = args.gil_enabled + free_threading = args.free_threading if not output_filename: # Write to the current directory @@ -186,17 +169,15 @@ def main(): if not compiler: compiler = get_compiler() - build_date, build_time = get_date_time() - build_info, git_id = get_build_info(git_tag, git_branch, git_version, build_date, build_time) + build_info, git_id = get_build_info(git_tag, git_branch, git_version) # Get PY_VERSION macro from Include/patchlevel.h PY_VERSION = get_py_version() - if gil_enabled is None: + if free_threading is None: # Get Py_GIL_DISABLED macro from pyconfig.h (defined or undefined) - Py_GIL_DISABLED = get_gil_disable() - gil_enabled = not Py_GIL_DISABLED + free_threading = get_gil_disable() - if not gil_enabled: + if free_threading: version = f"{PY_VERSION} free-threading build ({build_info}) {compiler}" else: version = f"{PY_VERSION} ({build_info}) {compiler}" From 98519e17eb278a629a5bc45ada8ecf7b1aac3866 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Thu, 8 Oct 2026 00:48:30 +0200 Subject: [PATCH 17/29] Simplify _getcompiler build command --- Makefile.pre.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile.pre.in b/Makefile.pre.in index 687fe9fd1a18d9..4544dfbd112c7b 100644 --- a/Makefile.pre.in +++ b/Makefile.pre.in @@ -1614,7 +1614,7 @@ Programs/_testembed: Programs/_testembed.o $(LINK_PYTHON_DEPS) $(LINKCC) $(PY_CORE_EXE_LDFLAGS) $(LINKFORSHARED) -o $@ Programs/_testembed.o $(LINK_PYTHON_OBJS) $(LIBS) $(MODLIBS) $(SYSLIBS) Programs/_getcompiler: Programs/_getcompiler.c - $(LINKCC) $(PY_CORE_EXE_LDFLAGS) $(LINKFORSHARED) $(PY_CPPFLAGS) -o $@ $< + $(LINKCC) $(PY_CPPFLAGS) -o $@ $< ############################################################################ # "Bootstrap Python" used to run Programs/_freeze_module.py From 05a97797e4de7c91cccb222ee10d16b36b1395b7 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Thu, 8 Oct 2026 00:52:47 +0200 Subject: [PATCH 18/29] Fix typo --- Tools/build/generate_getbuildinfo.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Tools/build/generate_getbuildinfo.py b/Tools/build/generate_getbuildinfo.py index 720053f2239415..abd3d203bb5183 100644 --- a/Tools/build/generate_getbuildinfo.py +++ b/Tools/build/generate_getbuildinfo.py @@ -141,8 +141,8 @@ def get_compiler(): if not CC: exit_error(f"ERROR: Unable to locate CC in Makefile") - cc = shlex.split(cc) - output = run_command([*cc, '--version']) + cmd = shlex.split(CC) + output = run_command([*cmd, '--version']) output = output.splitlines()[0] return f'[{output}]' From 11cd830165a4125d62dbf007f8eaa5aa7e081b95 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Thu, 8 Oct 2026 01:14:40 +0200 Subject: [PATCH 19/29] Strip URL from verbose Clang version --- Tools/build/generate_getbuildinfo.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/Tools/build/generate_getbuildinfo.py b/Tools/build/generate_getbuildinfo.py index abd3d203bb5183..aea5998e05923f 100644 --- a/Tools/build/generate_getbuildinfo.py +++ b/Tools/build/generate_getbuildinfo.py @@ -17,6 +17,11 @@ # Get PY_VERSION from Include/patchlevel.h PY_VERSION_REGEX = re.compile(r'^#define PY_VERSION +"(.*)"$', re.MULTILINE) +# Parse verbose Clang version (truncated here with '...'): +# 'Clang 24.0.0git (https:/github.com/llvm/llvm-project a06...8bf)' +# 'Android (13691557, +pgo, ...) clang version 18.0.4 (https://android.googlesource.com/toolchain/llvm-project d80...262)' +CLANG_VERBOSE_VERSION = re.compile(r'(Clang .*|clang version .*) \(https:/.*\)') + def exit_error(msg): print(msg) @@ -169,6 +174,11 @@ def main(): if not compiler: compiler = get_compiler() + # Make verbose Clang version shorter: strip the prefix and URL + match = CLANG_VERBOSE_VERSION.search(compiler) + if match: + compiler = match.group(1) + build_info, git_id = get_build_info(git_tag, git_branch, git_version) # Get PY_VERSION macro from Include/patchlevel.h From dc6d476923c02190c871060b58505e0ab4fca377 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Thu, 8 Oct 2026 01:54:08 +0200 Subject: [PATCH 20/29] Strip '[ and ']' from COMPILER and _Py_COMPILER --- PC/pyconfig.h | 16 ++++++++-------- Programs/_getcompiler.c | 9 +++++---- Tools/build/generate_getbuildinfo.py | 10 +++++----- 3 files changed, 18 insertions(+), 17 deletions(-) diff --git a/PC/pyconfig.h b/PC/pyconfig.h index 5b1df0fc146c39..71a2e4ee994ada 100644 --- a/PC/pyconfig.h +++ b/PC/pyconfig.h @@ -123,9 +123,9 @@ WIN32 is still required for the locale module. * on macro arguments, and doesn't evaluate macros passed *as* arguments. */ #define _Py_PASTE_VERSION(SUFFIX) \ - ("[MSC v." _Py_STRINGIZE(_MSC_VER) " " SUFFIX "]") + ("MSC v." _Py_STRINGIZE(_MSC_VER) " " SUFFIX "") /* e.g., this produces, after compile-time string catenation, - * ("[MSC v.1900 64 bit (Intel)]") + * ("MSC v.1900 64 bit (Intel)") * * _Py_STRINGIZE(_MSC_VER) expands to * _Py_STRINGIZE1(_MSC_VER) and this second macro call is scanned @@ -150,9 +150,9 @@ WIN32 is still required for the locale module. #ifdef __clang__ #define _Py_CLANG_COMPILER(platform) ( \ - "[Clang " _Py_STRINGIZE(__clang_major__) "." _Py_STRINGIZE(__clang_minor__) \ + "Clang " _Py_STRINGIZE(__clang_major__) "." _Py_STRINGIZE(__clang_minor__) \ "." _Py_STRINGIZE(__clang_patchlevel__) " " platform \ - " with MSC v." _Py_STRINGIZE(_MSC_VER) " CRT]") + " with MSC v." _Py_STRINGIZE(_MSC_VER) " CRT") #endif /* set the _Py_COMPILER and support tier @@ -168,7 +168,7 @@ WIN32 is still required for the locale module. #define _Py_COMPILER _Py_CLANG_COMPILER("64 bit (AMD64)") #define PY_SUPPORT_TIER 0 #elif defined(__INTEL_COMPILER) -#define _Py_COMPILER ("[ICC v." _Py_STRINGIZE(__INTEL_COMPILER) " 64 bit (amd64) with MSC v." _Py_STRINGIZE(_MSC_VER) " CRT]") +#define _Py_COMPILER ("ICC v." _Py_STRINGIZE(__INTEL_COMPILER) " 64 bit (amd64) with MSC v." _Py_STRINGIZE(_MSC_VER) " CRT") #define PY_SUPPORT_TIER 0 #else #define _Py_COMPILER _Py_PASTE_VERSION("64 bit (AMD64)") @@ -235,7 +235,7 @@ typedef _W64 int Py_ssize_t; #define _Py_COMPILER _Py_CLANG_COMPILER("32 bit (Intel)") #define PY_SUPPORT_TIER 0 #elif defined(__INTEL_COMPILER) -#define _Py_COMPILER ("[ICC v." _Py_STRINGIZE(__INTEL_COMPILER) " 32 bit (Intel) with MSC v." _Py_STRINGIZE(_MSC_VER) " CRT]") +#define _Py_COMPILER ("ICC v." _Py_STRINGIZE(__INTEL_COMPILER) " 32 bit (Intel) with MSC v." _Py_STRINGIZE(_MSC_VER) " CRT") #define PY_SUPPORT_TIER 0 #else #define _Py_COMPILER _Py_PASTE_VERSION("32 bit (Intel)") @@ -285,7 +285,7 @@ typedef int pid_t; #warning "Please use an up-to-date version of gcc! (>2.91 recommended)" #endif -#define _Py_COMPILER "[gcc]" +#define _Py_COMPILER "gcc" #define PY_LONG_LONG long long #define PY_LLONG_MIN LLONG_MIN #define PY_LLONG_MAX LLONG_MAX @@ -298,7 +298,7 @@ typedef int pid_t; /* XXX These defines are likely incomplete, but should be easy to fix. They should be complete enough to build extension modules. */ -#define _Py_COMPILER "[lcc-win32]" +#define _Py_COMPILER "lcc-win32" typedef int pid_t; /* __declspec() is supported here too - do nothing to get the defaults */ diff --git a/Programs/_getcompiler.c b/Programs/_getcompiler.c index 0a7dabbc7d9c75..92a3ed4c135bb7 100644 --- a/Programs/_getcompiler.c +++ b/Programs/_getcompiler.c @@ -14,20 +14,21 @@ // Note the __clang__ conditional has to come before the __GNUC__ one because // clang pretends to be GCC. #if defined(__clang__) -# define COMPILER "[Clang " __clang_version__ "]" +# define COMPILER "Clang " __clang_version__ #elif defined(__GNUC__) -# define COMPILER "[GCC " __VERSION__ "]" +# define COMPILER "GCC " __VERSION__ // Generic fallbacks. #elif defined(__cplusplus) -# define COMPILER "[C++]" +# define COMPILER "C++" #else -# define COMPILER "[C]" +# define COMPILER "C" #endif #endif /* !COMPILER */ int main() { +return 1; const char *compiler = COMPILER; printf("%s\n", compiler); return 0; diff --git a/Tools/build/generate_getbuildinfo.py b/Tools/build/generate_getbuildinfo.py index aea5998e05923f..d3d9d0921eef00 100644 --- a/Tools/build/generate_getbuildinfo.py +++ b/Tools/build/generate_getbuildinfo.py @@ -148,8 +148,8 @@ def get_compiler(): cmd = shlex.split(CC) output = run_command([*cmd, '--version']) - output = output.splitlines()[0] - return f'[{output}]' + # Get the first line + return output.splitlines()[0] def main(): @@ -188,9 +188,9 @@ def main(): free_threading = get_gil_disable() if free_threading: - version = f"{PY_VERSION} free-threading build ({build_info}) {compiler}" + version = f"{PY_VERSION} free-threading build ({build_info}) [{compiler}]" else: - version = f"{PY_VERSION} ({build_info}) {compiler}" + version = f"{PY_VERSION} ({build_info}) [{compiler}]" new_filename = output_filename + ".new" with open(new_filename, "w", encoding="utf8") as fp: @@ -200,7 +200,7 @@ def write_macro(name, value): print(f'// Header file auto-generated by {SCRIPT_FULLNAME}', file=fp) print(file=fp) write_macro('PLATFORM', platform) - write_macro('COMPILER', compiler) + write_macro('COMPILER', f'[{compiler}]') write_macro('GIT_VERSION', git_version) write_macro('GIT_IDENTIFIER', git_id) write_macro('BUILD_INFO', build_info) From ad75107a38f510c509fd6820e0e7e6be3fca825b Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Thu, 8 Oct 2026 01:55:54 +0200 Subject: [PATCH 21/29] Tolerate "cc --version" failure, use "C" compiler by default --- Programs/_getcompiler.c | 1 - Tools/build/generate_getbuildinfo.py | 12 ++++++++---- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/Programs/_getcompiler.c b/Programs/_getcompiler.c index 92a3ed4c135bb7..05df97de6da5a5 100644 --- a/Programs/_getcompiler.c +++ b/Programs/_getcompiler.c @@ -28,7 +28,6 @@ int main() { -return 1; const char *compiler = COMPILER; printf("%s\n", compiler); return 0; diff --git a/Tools/build/generate_getbuildinfo.py b/Tools/build/generate_getbuildinfo.py index d3d9d0921eef00..895b7f0487e527 100644 --- a/Tools/build/generate_getbuildinfo.py +++ b/Tools/build/generate_getbuildinfo.py @@ -137,7 +137,6 @@ def get_compiler(): compiler = run_command([runner, getcompiler], check=False) else: compiler = run_command([getcompiler], check=False) - if compiler: return compiler @@ -147,9 +146,14 @@ def get_compiler(): exit_error(f"ERROR: Unable to locate CC in Makefile") cmd = shlex.split(CC) - output = run_command([*cmd, '--version']) - # Get the first line - return output.splitlines()[0] + output = run_command([*cmd, '--version'], check=False) + if output: + # Get the first line + return output.splitlines()[0] + + # Default compiler name when everything else failed + # (see Programs/_getcompiler.c) + return 'C' def main(): From 4281ae9f6ca637d08bbb625cafd1eb93adc5fb63 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Thu, 8 Oct 2026 02:00:19 +0200 Subject: [PATCH 22/29] Cleanup get_compiler() code --- Tools/build/generate_getbuildinfo.py | 37 +++++++++++++++++----------- 1 file changed, 22 insertions(+), 15 deletions(-) diff --git a/Tools/build/generate_getbuildinfo.py b/Tools/build/generate_getbuildinfo.py index 895b7f0487e527..f13a45a4e9ad72 100644 --- a/Tools/build/generate_getbuildinfo.py +++ b/Tools/build/generate_getbuildinfo.py @@ -125,7 +125,7 @@ def run_command(cmd, *, check=True): return proc.stdout.rstrip() -def get_compiler(): +def _get_compiler(): makefile_vars = get_makefile_vars() # Run _getcompiler program @@ -151,9 +151,23 @@ def get_compiler(): # Get the first line return output.splitlines()[0] - # Default compiler name when everything else failed - # (see Programs/_getcompiler.c) - return 'C' + return None + + +def get_compiler(compiler): + if not compiler: + compiler = _get_compiler() + if not compiler: + # Default compiler name when everything else failed + # (see Programs/_getcompiler.c) + compiler = 'C' + + # Make verbose Clang version shorter: strip the prefix and URL + match = CLANG_VERBOSE_VERSION.search(compiler) + if match: + compiler = match.group(1) + + return f'[{compiler}]' def main(): @@ -166,7 +180,6 @@ def main(): git_version = args.git_version git_tag = args.git_tag git_branch = args.git_branch - compiler = args.compiler free_threading = args.free_threading if not output_filename: @@ -175,13 +188,7 @@ def main(): if not platform: platform = "unknown" - if not compiler: - compiler = get_compiler() - - # Make verbose Clang version shorter: strip the prefix and URL - match = CLANG_VERBOSE_VERSION.search(compiler) - if match: - compiler = match.group(1) + compiler = get_compiler(args.compiler) build_info, git_id = get_build_info(git_tag, git_branch, git_version) @@ -192,9 +199,9 @@ def main(): free_threading = get_gil_disable() if free_threading: - version = f"{PY_VERSION} free-threading build ({build_info}) [{compiler}]" + version = f"{PY_VERSION} free-threading build ({build_info}) {compiler}" else: - version = f"{PY_VERSION} ({build_info}) [{compiler}]" + version = f"{PY_VERSION} ({build_info}) {compiler}" new_filename = output_filename + ".new" with open(new_filename, "w", encoding="utf8") as fp: @@ -204,7 +211,7 @@ def write_macro(name, value): print(f'// Header file auto-generated by {SCRIPT_FULLNAME}', file=fp) print(file=fp) write_macro('PLATFORM', platform) - write_macro('COMPILER', f'[{compiler}]') + write_macro('COMPILER', compiler) write_macro('GIT_VERSION', git_version) write_macro('GIT_IDENTIFIER', git_id) write_macro('BUILD_INFO', build_info) From d6fd2d4088cce1a19ec0e69ec5ff034c99d39965 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Thu, 8 Oct 2026 03:00:15 +0200 Subject: [PATCH 23/29] Shorter clang version; add tests --- Lib/test/test_tools/test_getbuildinfo.py | 175 +++++++++++++++++++++++ Tools/build/generate_getbuildinfo.py | 40 ++++-- 2 files changed, 201 insertions(+), 14 deletions(-) create mode 100644 Lib/test/test_tools/test_getbuildinfo.py diff --git a/Lib/test/test_tools/test_getbuildinfo.py b/Lib/test/test_tools/test_getbuildinfo.py new file mode 100644 index 00000000000000..63162a4d4b3eb8 --- /dev/null +++ b/Lib/test/test_tools/test_getbuildinfo.py @@ -0,0 +1,175 @@ +"""Tests Tools/build/generate_getbuildinfo.py.""" + +import locale +import os +import subprocess +import sys +import textwrap +import unittest +from unittest import mock +from test import support +from test.support import os_helper +from test import test_tools + +SRC_DIR = os.path.dirname(test_tools.toolsdir) + +with test_tools.imports_under_tool('build'): + import generate_getbuildinfo + + +class TestGetBuildInfo(unittest.TestCase): + def test_get_py_version(self): + with os_helper.change_cwd(SRC_DIR): + PY_VERSION = generate_getbuildinfo.get_py_version() + + # "./python -V" writes "Python {PY_VERSION}" + proc = subprocess.run([sys.executable, '-V'], + stdout=subprocess.PIPE, + text=True, check=True) + python_version = proc.stdout.rstrip() + self.assertEqual(f'Python {PY_VERSION}', python_version) + + def test_get_date_time(self): + # Test get_date_time() with SOURCE_DATE_EPOCH env var + def check(timestamp, date, time): + with os_helper.EnvironmentVarGuard() as env: + env['SOURCE_DATE_EPOCH'] = str(timestamp) + result = generate_getbuildinfo.get_date_time() + + self.assertEqual(result, (date, time)) + + check(0, 'Jan 1 1970', '00:00:00') + check(1791419852, 'Oct 8 2026', '00:37:32') + + def test_get_build_info(self): + get_build_info = generate_getbuildinfo.get_build_info + + with os_helper.EnvironmentVarGuard() as env: + env['SOURCE_DATE_EPOCH'] = '0' + date_time = 'Jan 1 1970, 00:00:00' + # all set + self.assertEqual(get_build_info('tag', 'branch', 'version'), + (f'tag:version, {date_time}', 'tag')) + # missing version + self.assertEqual(get_build_info('tag', 'branch', ''), + (f'tag, {date_time}', 'tag')) + # Missing tag + self.assertEqual(get_build_info('', 'branch', 'version'), + (f'branch:version, {date_time}', 'branch')) + # undefined tag + self.assertEqual(get_build_info('undefined', 'branch', 'version'), + (f'branch:version, {date_time}', 'branch')) + # all empty + self.assertEqual(get_build_info('', '', ''), + (f'main, {date_time}', 'main')) + + def test_get_compiler(self): + getcompiler = 'Programs/_getcompiler' + MOCK_COMPILER = 'MOCK_COMPILER' + + for hostrunner in ('', 'node'): + for use_cc in (False, True): + makefile_vars = { + 'HOSTRUNNER': hostrunner, + 'CC': 'gcc -std=c11', + } + with mock.patch.object(generate_getbuildinfo, + 'get_makefile_vars', + return_value=makefile_vars): + with mock.patch.object(generate_getbuildinfo, + 'run_command') as mock_run_command: + if use_cc: + mock_run_command.side_effect = ('', MOCK_COMPILER) + else: + mock_run_command.return_value = MOCK_COMPILER + compiler = generate_getbuildinfo._get_compiler() + self.assertEqual(compiler, MOCK_COMPILER) + if hostrunner: + cmd = [hostrunner, getcompiler] + else: + cmd = [getcompiler] + if use_cc: + self.assertEqual(mock_run_command.call_args_list, + [ + mock.call(cmd, check=False), + mock.call(['gcc', '-std=c11', '--version'], check=False), + ] + ) + else: + mock_run_command.assert_called_once_with(cmd, check=False) + + def test_shorter_clang_version(self): + shorter_clang_version = generate_getbuildinfo.shorter_clang_version + + def check(compiler, expected): + self.assertEqual(shorter_clang_version(compiler), expected) + + def check_unchanged(compiler): + self.assertEqual(shorter_clang_version(compiler), compiler) + + compiler = ( + 'Android (13691557, +pgo, +bolt, +lto, +mlgo, based on r522817d) ' + 'clang version 18.0.4 ' + '(https://android.googlesource.com/toolchain/llvm-project' + ' d8003a456d14a3deb8054cdaa529ffbf02d9b262)' + ) + check(compiler, 'Clang 18.0.4') + + compiler = ( + 'Clang 18.0.4 ' + '(https://android.googlesource.com/toolchain/llvm-project' + ' d8003a456d14a3deb8054cdaa529ffbf02d9b262)' + ) + check(compiler, 'Clang 18.0.4') + + compiler = ( + 'Clang 24.0.0git ' + '(https:/github.com/llvm/llvm-project' + ' a06d9165905ce89b5ffef2bbb84c886d60a9b8bf)' + ) + check(compiler, 'Clang 24.0.0git') + + check_unchanged('Clang 21.0.0 (clang-2100.1.1.101)') + check_unchanged('Apple clang version 21.0.0 (clang-2100.1.1.101)') + + def test_functional(self): + old_locale = locale.setlocale(locale.LC_ALL) + self.addCleanup(locale.setlocale, locale.LC_ALL, old_locale) + + filename = 'output.txt' + self.addCleanup(os.unlink, filename) + + argv = [ + 'program', + '--output', filename, + '--platform', 'SET_PLATFORM', + '--git-version', 'VERSION', + '--git-tag', 'TAG', + '--git-branch', 'BRANCH', + '--compiler', 'COMPILER', + '--free-threading', '1', + ] + with support.swap_attr(sys, 'argv', argv): + with os_helper.EnvironmentVarGuard() as env: + env['SOURCE_DATE_EPOCH'] = str(1791419852) + with support.captured_stdout() as stdout: + generate_getbuildinfo.main() + self.assertEqual(stdout.getvalue(), f'{filename} updated\n') + + with open(filename) as fp: + output = fp.read() + expected = textwrap.dedent(''' + // Header file auto-generated by Tools/build/generate_getbuildinfo.py + + #define PLATFORM "SET_PLATFORM" + #define COMPILER "[COMPILER]" + #define GIT_VERSION "VERSION" + #define GIT_IDENTIFIER "TAG" + #define BUILD_INFO "TAG:VERSION, Oct 8 2026, 00:37:32" + #define VERSION "3.16.0a0 free-threading build (TAG:VERSION, Oct 8 2026, 00:37:32) [COMPILER]" + ''').lstrip() + self.assertEqual(output, expected) + + +if __name__ == "__main__": + unittest.main() diff --git a/Tools/build/generate_getbuildinfo.py b/Tools/build/generate_getbuildinfo.py index f13a45a4e9ad72..e02df77637bdd8 100644 --- a/Tools/build/generate_getbuildinfo.py +++ b/Tools/build/generate_getbuildinfo.py @@ -22,6 +22,9 @@ # 'Android (13691557, +pgo, ...) clang version 18.0.4 (https://android.googlesource.com/toolchain/llvm-project d80...262)' CLANG_VERBOSE_VERSION = re.compile(r'(Clang .*|clang version .*) \(https:/.*\)') +# Parse short Clang version: 'clang version 18.0.4' +CLANG_SHORT_VERSION = re.compile(r'clang version (.*)') + def exit_error(msg): print(msg) @@ -29,20 +32,16 @@ def exit_error(msg): sys.exit(1) -def parse_file(variable_name, filename, regex): +def get_py_version(): + filename = os.path.join(SRC_DIR, 'Include', 'patchlevel.h') with open(filename, encoding='utf8') as fp: code = fp.read() - match = regex.search(code) + match = PY_VERSION_REGEX.search(code) if not match: - exit_error(f"ERROR: Unable to locate {variable_name} in {filename}") + exit_error(f"ERROR: Unable to locate PY_VERSION in {filename}") return match.group(1) -def get_py_version(): - patchlevel_h = os.path.join(SRC_DIR, 'Include', 'patchlevel.h') - return parse_file('PY_VERSION ', patchlevel_h, PY_VERSION_REGEX) - - def get_makefile_vars(): # Look in the current working directory makefile = 'Makefile' @@ -60,10 +59,11 @@ def get_gil_disable(): def get_date_time(): if os.environ.get('SOURCE_DATE_EPOCH'): ts = int(os.environ['SOURCE_DATE_EPOCH']) + time_tuple = time.gmtime(ts) else: ts = time.time() + time_tuple = time.localtime(ts) - time_tuple = time.localtime(ts) day = time.strftime("%d", time_tuple) if day.startswith("0"): day = " " + day[1:] @@ -154,6 +154,20 @@ def _get_compiler(): return None +def shorter_clang_version(compiler): + # Make verbose Clang version shorter: strip the prefix and URL + match = CLANG_VERBOSE_VERSION.search(compiler) + if match: + compiler = match.group(1) + + # Use a full match: don't replace 'Apple clang version 21.0.0' for example + match = CLANG_SHORT_VERSION.fullmatch(compiler) + if match: + compiler = 'Clang ' + match.group(1) + + return compiler + + def get_compiler(compiler): if not compiler: compiler = _get_compiler() @@ -162,10 +176,7 @@ def get_compiler(compiler): # (see Programs/_getcompiler.c) compiler = 'C' - # Make verbose Clang version shorter: strip the prefix and URL - match = CLANG_VERBOSE_VERSION.search(compiler) - if match: - compiler = match.group(1) + compiler = shorter_clang_version(compiler) return f'[{compiler}]' @@ -194,8 +205,9 @@ def main(): # Get PY_VERSION macro from Include/patchlevel.h PY_VERSION = get_py_version() + if free_threading is None: - # Get Py_GIL_DISABLED macro from pyconfig.h (defined or undefined) + # Get Py_GIL_DISABLED macro from pyconfig.h free_threading = get_gil_disable() if free_threading: From aa9a4aa5e0b25fadd3f4cda36b91f1fca163b12d Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Thu, 8 Oct 2026 03:39:37 +0200 Subject: [PATCH 24/29] Fix test * Fix test on Windows * Skip test if missing (tools are not installed) --- Lib/test/test_tools/test_getbuildinfo.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/Lib/test/test_tools/test_getbuildinfo.py b/Lib/test/test_tools/test_getbuildinfo.py index 63162a4d4b3eb8..8e34c73b64c85e 100644 --- a/Lib/test/test_tools/test_getbuildinfo.py +++ b/Lib/test/test_tools/test_getbuildinfo.py @@ -11,11 +11,14 @@ from test.support import os_helper from test import test_tools -SRC_DIR = os.path.dirname(test_tools.toolsdir) + +test_tools.skip_if_missing("build") with test_tools.imports_under_tool('build'): import generate_getbuildinfo +SRC_DIR = os.path.dirname(test_tools.toolsdir) + class TestGetBuildInfo(unittest.TestCase): def test_get_py_version(self): @@ -64,7 +67,7 @@ def test_get_build_info(self): (f'main, {date_time}', 'main')) def test_get_compiler(self): - getcompiler = 'Programs/_getcompiler' + getcompiler = os.path.join('Programs', '_getcompiler') MOCK_COMPILER = 'MOCK_COMPILER' for hostrunner in ('', 'node'): From b036e721e4423471208de91acc16f659791b6284 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Thu, 8 Oct 2026 04:21:33 +0200 Subject: [PATCH 25/29] Keep the vendor (Android or Apple) in Clang version --- Lib/test/test_tools/test_getbuildinfo.py | 32 ++++++++++------- PCbuild/pcbuild.sln | 4 +-- Tools/build/generate_getbuildinfo.py | 45 ++++++++++++------------ 3 files changed, 45 insertions(+), 36 deletions(-) diff --git a/Lib/test/test_tools/test_getbuildinfo.py b/Lib/test/test_tools/test_getbuildinfo.py index 8e34c73b64c85e..587c802786260b 100644 --- a/Lib/test/test_tools/test_getbuildinfo.py +++ b/Lib/test/test_tools/test_getbuildinfo.py @@ -1,4 +1,4 @@ -"""Tests Tools/build/generate_getbuildinfo.py.""" +"""Test Tools/build/generate_getbuildinfo.py.""" import locale import os @@ -71,11 +71,11 @@ def test_get_compiler(self): MOCK_COMPILER = 'MOCK_COMPILER' for hostrunner in ('', 'node'): + makefile_vars = { + 'HOSTRUNNER': hostrunner, + 'CC': 'gcc -std=c11', + } for use_cc in (False, True): - makefile_vars = { - 'HOSTRUNNER': hostrunner, - 'CC': 'gcc -std=c11', - } with mock.patch.object(generate_getbuildinfo, 'get_makefile_vars', return_value=makefile_vars): @@ -101,14 +101,14 @@ def test_get_compiler(self): else: mock_run_command.assert_called_once_with(cmd, check=False) - def test_shorter_clang_version(self): - shorter_clang_version = generate_getbuildinfo.shorter_clang_version + def test_compact_compiler(self): + compact_compiler = generate_getbuildinfo.compact_compiler def check(compiler, expected): - self.assertEqual(shorter_clang_version(compiler), expected) + self.assertEqual(compact_compiler(compiler), expected) def check_unchanged(compiler): - self.assertEqual(shorter_clang_version(compiler), compiler) + self.assertEqual(compact_compiler(compiler), compiler) compiler = ( 'Android (13691557, +pgo, +bolt, +lto, +mlgo, based on r522817d) ' @@ -116,7 +116,7 @@ def check_unchanged(compiler): '(https://android.googlesource.com/toolchain/llvm-project' ' d8003a456d14a3deb8054cdaa529ffbf02d9b262)' ) - check(compiler, 'Clang 18.0.4') + check(compiler, 'Android Clang 18.0.4') compiler = ( 'Clang 18.0.4 ' @@ -132,8 +132,16 @@ def check_unchanged(compiler): ) check(compiler, 'Clang 24.0.0git') - check_unchanged('Clang 21.0.0 (clang-2100.1.1.101)') - check_unchanged('Apple clang version 21.0.0 (clang-2100.1.1.101)') + check('Clang 21.0.0 (clang-2100.1.1.101)', + 'Clang 21.0.0') + check('Apple clang version 21.0.0 (clang-2100.1.1.101)', + 'Apple Clang 21.0.0') + check('Custom Vendor (something) Clang 22.1.0+dev', + 'Custom Vendor Clang 22.1.0+dev') + + check_unchanged('GCC 16.2.1 20260819 (Red Hat 16.2.1-2)') + check_unchanged('GCC 15.2.0') + check_unchanged('MSC v.1951 32 bit (Intel)') def test_functional(self): old_locale = locale.setlocale(locale.LC_ALL) diff --git a/PCbuild/pcbuild.sln b/PCbuild/pcbuild.sln index aa29f6d06c8e3d..e5ff49ea6f5265 100644 --- a/PCbuild/pcbuild.sln +++ b/PCbuild/pcbuild.sln @@ -1,6 +1,6 @@ Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 18 -VisualStudioVersion = 18.10.12224.181 stable +# Visual Studio Version 17 +VisualStudioVersion = 17.8.34525.116 MinimumVisualStudioVersion = 10.0.40219.1 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{553EC33E-9816-4996-A660-5D6186A0B0B3}" ProjectSection(SolutionItems) = preProject diff --git a/Tools/build/generate_getbuildinfo.py b/Tools/build/generate_getbuildinfo.py index e02df77637bdd8..8107f0d7699699 100644 --- a/Tools/build/generate_getbuildinfo.py +++ b/Tools/build/generate_getbuildinfo.py @@ -17,13 +17,16 @@ # Get PY_VERSION from Include/patchlevel.h PY_VERSION_REGEX = re.compile(r'^#define PY_VERSION +"(.*)"$', re.MULTILINE) -# Parse verbose Clang version (truncated here with '...'): -# 'Clang 24.0.0git (https:/github.com/llvm/llvm-project a06...8bf)' -# 'Android (13691557, +pgo, ...) clang version 18.0.4 (https://android.googlesource.com/toolchain/llvm-project d80...262)' -CLANG_VERBOSE_VERSION = re.compile(r'(Clang .*|clang version .*) \(https:/.*\)') - -# Parse short Clang version: 'clang version 18.0.4' -CLANG_SHORT_VERSION = re.compile(r'clang version (.*)') +# Parse Clang version (truncated examples using '...'): +# 'Clang 21.0.0 (clang-2100.1.1.101)' +# 'clang version 18.0.4' +# 'Apple clang version 21.0.0 (clang-2100.1.1.101)' +# 'Clang 18.0.4 (https://android.googlesource.com/toolchain/llvm-project d80...62)' +CLANG_VERSION_REGEX = re.compile( + r'(?:([A-Za-z_-][A-Za-z_ -]+) .*)?' # Vendor name: 'Android' or 'Apple' + r'(?:Clang|clang version) ' # 'Clang' or 'clang version' + r'([0-9]+\.[0-9a-z.+]+)' # version +) def exit_error(msg): @@ -48,7 +51,7 @@ def get_makefile_vars(): return sysconfig.__main__._parse_makefile(makefile) -def get_gil_disable(): +def get_gil_disabled(): # Look in the current working directory pyconfig_h = 'pyconfig.h' with open(pyconfig_h, encoding="utf-8") as fp: @@ -83,8 +86,7 @@ def get_build_info(git_tag, git_branch, git_version): git_id = "main" sep = ":" if git_version else "" - - build_info = f"{git_id}{sep}{git_version}, {build_date:.20s}, {build_time:.9s}" + build_info = f"{git_id}{sep}{git_version}, {build_date}, {build_time}" return (build_info, git_id) @@ -140,7 +142,7 @@ def _get_compiler(): if compiler: return compiler - # Running _getcompiler failed, run directly the compiler (--version) + # Running _getcompiler failed, run the compiler with --version CC = makefile_vars.get('CC') if not CC: exit_error(f"ERROR: Unable to locate CC in Makefile") @@ -154,16 +156,15 @@ def _get_compiler(): return None -def shorter_clang_version(compiler): - # Make verbose Clang version shorter: strip the prefix and URL - match = CLANG_VERBOSE_VERSION.search(compiler) +def compact_compiler(compiler): + match = CLANG_VERSION_REGEX.search(compiler) if match: - compiler = match.group(1) - - # Use a full match: don't replace 'Apple clang version 21.0.0' for example - match = CLANG_SHORT_VERSION.fullmatch(compiler) - if match: - compiler = 'Clang ' + match.group(1) + vendor = match.group(1) + version = match.group(2) + if vendor: + compiler = f'{vendor} Clang {version}' + else: + compiler = f'Clang {version}' return compiler @@ -176,7 +177,7 @@ def get_compiler(compiler): # (see Programs/_getcompiler.c) compiler = 'C' - compiler = shorter_clang_version(compiler) + compiler = compact_compiler(compiler) return f'[{compiler}]' @@ -208,7 +209,7 @@ def main(): if free_threading is None: # Get Py_GIL_DISABLED macro from pyconfig.h - free_threading = get_gil_disable() + free_threading = get_gil_disabled() if free_threading: version = f"{PY_VERSION} free-threading build ({build_info}) {compiler}" From 1d9bb17e85fe38b878ca2ed80e9c9dc2a854b45e Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Thu, 8 Oct 2026 04:40:47 +0200 Subject: [PATCH 26/29] Revert unrelated changes --- Objects/bytesobject.c | 23 +++++------------------ 1 file changed, 5 insertions(+), 18 deletions(-) diff --git a/Objects/bytesobject.c b/Objects/bytesobject.c index 5d55376b982f3e..1917055d25198f 100644 --- a/Objects/bytesobject.c +++ b/Objects/bytesobject.c @@ -3831,23 +3831,13 @@ byteswriter_resize(PyBytesWriter *writer, Py_ssize_t new_size, int resize) } Py_ssize_t alloc = new_size; - - if (writer->obj != NULL) { - if (resize && writer->overallocate) { - if (alloc <= (PY_SSIZE_T_MAX - alloc / OVERALLOCATE_FACTOR)) { - alloc += alloc / OVERALLOCATE_FACTOR; - } - } - - if (new_size <= old_allocated + old_allocated / 8) { - /* Moderate upsize; overallocate similar to list_resize() */ - alloc = new_size + (new_size >> 3); - } - else { - /* Major upsize; resize up to exact size */ - alloc = new_size; + if (resize && writer->overallocate) { + if (alloc <= (PY_SSIZE_T_MAX - alloc / OVERALLOCATE_FACTOR)) { + alloc += alloc / OVERALLOCATE_FACTOR; } + } + if (writer->obj != NULL) { if (writer->use_bytearray) { if (PyByteArray_Resize(writer->obj, alloc)) { #ifdef Py_DEBUG @@ -3870,9 +3860,6 @@ byteswriter_resize(PyBytesWriter *writer, Py_ssize_t new_size, int resize) assert(writer->obj != NULL); } else { - // Do not overallocate when the first bytes/bytearray object is - // allocated - char *data; if (writer->use_bytearray) { writer->obj = PyByteArray_FromStringAndSize(NULL, alloc); From ebaadbc73d6d32d4c025991ab5a3ec9d8280b503 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Thu, 8 Oct 2026 05:19:14 +0200 Subject: [PATCH 27/29] Log the truncated compiler string Add the script name as prefix to all logged messages. --- Lib/test/test_tools/test_getbuildinfo.py | 11 ++++++++++- Tools/build/generate_getbuildinfo.py | 18 ++++++++++++------ 2 files changed, 22 insertions(+), 7 deletions(-) diff --git a/Lib/test/test_tools/test_getbuildinfo.py b/Lib/test/test_tools/test_getbuildinfo.py index 587c802786260b..2f7d15fb5775a6 100644 --- a/Lib/test/test_tools/test_getbuildinfo.py +++ b/Lib/test/test_tools/test_getbuildinfo.py @@ -18,6 +18,7 @@ import generate_getbuildinfo SRC_DIR = os.path.dirname(test_tools.toolsdir) +SCRIPT_NAME = 'generate_getbuildinfo.py' class TestGetBuildInfo(unittest.TestCase): @@ -102,6 +103,7 @@ def test_get_compiler(self): mock_run_command.assert_called_once_with(cmd, check=False) def test_compact_compiler(self): + get_compiler = generate_getbuildinfo.get_compiler compact_compiler = generate_getbuildinfo.compact_compiler def check(compiler, expected): @@ -143,6 +145,13 @@ def check_unchanged(compiler): check_unchanged('GCC 15.2.0') check_unchanged('MSC v.1951 32 bit (Intel)') + with support.captured_stdout() as stdout: + compiler = get_compiler('Clang 21.0.0 (clang-2100.1.1.101)') + self.assertEqual(compiler, '[Clang 21.0.0]') + self.assertEqual(stdout.getvalue(), + f"{SCRIPT_NAME}: Truncate compiler string 'Clang 21.0.0 " + "(clang-2100.1.1.101)' to 'Clang 21.0.0'\n") + def test_functional(self): old_locale = locale.setlocale(locale.LC_ALL) self.addCleanup(locale.setlocale, locale.LC_ALL, old_locale) @@ -165,7 +174,7 @@ def test_functional(self): env['SOURCE_DATE_EPOCH'] = str(1791419852) with support.captured_stdout() as stdout: generate_getbuildinfo.main() - self.assertEqual(stdout.getvalue(), f'{filename} updated\n') + self.assertEqual(stdout.getvalue(), f'{SCRIPT_NAME}: {filename} updated\n') with open(filename) as fp: output = fp.read() diff --git a/Tools/build/generate_getbuildinfo.py b/Tools/build/generate_getbuildinfo.py index 8107f0d7699699..51dbbd1314c468 100644 --- a/Tools/build/generate_getbuildinfo.py +++ b/Tools/build/generate_getbuildinfo.py @@ -29,9 +29,12 @@ ) +def log(msg): + print(f"{SCRIPT_NAME}: {msg}") + + def exit_error(msg): - print(msg) - print("cwd: {os.getcwd()}") + log(msg) sys.exit(1) @@ -104,7 +107,7 @@ def parse_args(): def run_command(cmd, *, check=True): cmd_str = shlex.join(cmd) - print(f"+ {cmd_str}") + log(f"+ {cmd_str}") try: proc = subprocess.run(cmd, stdout=subprocess.PIPE, text=True) except OSError as exc: @@ -121,7 +124,7 @@ def run_command(cmd, *, check=True): if check: exit_error(msg) else: - print(msg) + log(msg) return None return proc.stdout.rstrip() @@ -177,7 +180,10 @@ def get_compiler(compiler): # (see Programs/_getcompiler.c) compiler = 'C' - compiler = compact_compiler(compiler) + old_compiler = compiler + compiler = compact_compiler(old_compiler) + if compiler != old_compiler: + log(f"Truncate compiler string {old_compiler!r} to {compiler!r}") return f'[{compiler}]' @@ -231,7 +237,7 @@ def write_macro(name, value): write_macro('VERSION', version) os.replace(new_filename, output_filename) - print(f"{output_filename} updated") + log(f"{output_filename} updated") if __name__ == "__main__": From 2c9bafb43c2b11bbb27b5b2c8ee1d58843659c7b Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Thu, 8 Oct 2026 05:52:34 +0200 Subject: [PATCH 28/29] Support old sysconfig versions --- Lib/test/test_tools/test_getbuildinfo.py | 56 ++++++++++++------------ Tools/build/generate_getbuildinfo.py | 36 ++++++++++++--- 2 files changed, 57 insertions(+), 35 deletions(-) diff --git a/Lib/test/test_tools/test_getbuildinfo.py b/Lib/test/test_tools/test_getbuildinfo.py index 2f7d15fb5775a6..e4e4071afc0018 100644 --- a/Lib/test/test_tools/test_getbuildinfo.py +++ b/Lib/test/test_tools/test_getbuildinfo.py @@ -70,37 +70,37 @@ def test_get_build_info(self): def test_get_compiler(self): getcompiler = os.path.join('Programs', '_getcompiler') MOCK_COMPILER = 'MOCK_COMPILER' + CC = 'gcc -std=c11' for hostrunner in ('', 'node'): - makefile_vars = { - 'HOSTRUNNER': hostrunner, - 'CC': 'gcc -std=c11', - } for use_cc in (False, True): - with mock.patch.object(generate_getbuildinfo, - 'get_makefile_vars', - return_value=makefile_vars): - with mock.patch.object(generate_getbuildinfo, - 'run_command') as mock_run_command: - if use_cc: - mock_run_command.side_effect = ('', MOCK_COMPILER) - else: - mock_run_command.return_value = MOCK_COMPILER - compiler = generate_getbuildinfo._get_compiler() - self.assertEqual(compiler, MOCK_COMPILER) - if hostrunner: - cmd = [hostrunner, getcompiler] - else: - cmd = [getcompiler] - if use_cc: - self.assertEqual(mock_run_command.call_args_list, - [ - mock.call(cmd, check=False), - mock.call(['gcc', '-std=c11', '--version'], check=False), - ] - ) - else: - mock_run_command.assert_called_once_with(cmd, check=False) + with (mock.patch.object(generate_getbuildinfo, + 'get_hostrunner', + return_value=hostrunner), + mock.patch.object(generate_getbuildinfo, + 'get_makefile_cc', + return_value=CC), + mock.patch.object(generate_getbuildinfo, + 'run_command') as mock_run_command): + if use_cc: + mock_run_command.side_effect = ('', MOCK_COMPILER) + else: + mock_run_command.return_value = MOCK_COMPILER + compiler = generate_getbuildinfo._get_compiler() + self.assertEqual(compiler, MOCK_COMPILER) + if hostrunner: + cmd = [hostrunner, getcompiler] + else: + cmd = [getcompiler] + if use_cc: + self.assertEqual(mock_run_command.call_args_list, + [ + mock.call(cmd, check=False), + mock.call(['gcc', '-std=c11', '--version'], check=False), + ] + ) + else: + mock_run_command.assert_called_once_with(cmd, check=False) def test_compact_compiler(self): get_compiler = generate_getbuildinfo.get_compiler diff --git a/Tools/build/generate_getbuildinfo.py b/Tools/build/generate_getbuildinfo.py index 51dbbd1314c468..5755c77e3b4d67 100644 --- a/Tools/build/generate_getbuildinfo.py +++ b/Tools/build/generate_getbuildinfo.py @@ -7,7 +7,7 @@ import shlex import subprocess import sys -import sysconfig.__main__ +import sysconfig import time SRC_DIR = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..')) @@ -17,6 +17,13 @@ # Get PY_VERSION from Include/patchlevel.h PY_VERSION_REGEX = re.compile(r'^#define PY_VERSION +"(.*)"$', re.MULTILINE) +def create_makefile_regex(name): + return re.compile(fr'^{name}[ \t]*=[ \t]*(.*)$', re.MULTILINE) + +# Get HOSTRUNNER and CC from Makefile +HOSTRUNNER_REGEX = create_makefile_regex('HOSTRUNNER') +CC_REGEX = create_makefile_regex('CC') + # Parse Clang version (truncated examples using '...'): # 'Clang 21.0.0 (clang-2100.1.1.101)' # 'clang version 18.0.4' @@ -48,10 +55,27 @@ def get_py_version(): return match.group(1) -def get_makefile_vars(): +def parse_file(variable_name, filename, regex): + with open(filename, encoding='utf8') as fp: + code = fp.read() + match = regex.search(code) + if not match: + exit_error(f"ERROR: Unable to locate {variable_name} in {filename}") + return match.group(1) + + +def get_hostrunner(): + # Look in the current working directory + makefile = 'Makefile' + HOSTRUNNER = parse_file('HOSTRUNNER', makefile, HOSTRUNNER_REGEX) + return HOSTRUNNER.rstrip() + + +def get_makefile_cc(): # Look in the current working directory makefile = 'Makefile' - return sysconfig.__main__._parse_makefile(makefile) + CC = parse_file('CC', makefile, CC_REGEX) + return CC.rstrip() def get_gil_disabled(): @@ -131,11 +155,9 @@ def run_command(cmd, *, check=True): def _get_compiler(): - makefile_vars = get_makefile_vars() - # Run _getcompiler program getcompiler = os.path.join('Programs', '_getcompiler') - HOSTRUNNER = makefile_vars.get('HOSTRUNNER') + HOSTRUNNER = get_hostrunner() if HOSTRUNNER: # Cross-compilation runner = shlex.split(HOSTRUNNER)[0] @@ -146,7 +168,7 @@ def _get_compiler(): return compiler # Running _getcompiler failed, run the compiler with --version - CC = makefile_vars.get('CC') + CC = get_makefile_cc() if not CC: exit_error(f"ERROR: Unable to locate CC in Makefile") From 8431d21905b7bd0a60bee3aa1eb81d2259e1830f Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Thu, 8 Oct 2026 06:12:18 +0200 Subject: [PATCH 29/29] Fix Makefile (_getcompiler) for BSD make Adjust also getbuildinfo.h indentation --- Makefile.pre.in | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/Makefile.pre.in b/Makefile.pre.in index 4544dfbd112c7b..121d571c9e63aa 100644 --- a/Makefile.pre.in +++ b/Makefile.pre.in @@ -1613,8 +1613,8 @@ regen-re: $(BUILDPYTHON) Programs/_testembed: Programs/_testembed.o $(LINK_PYTHON_DEPS) $(LINKCC) $(PY_CORE_EXE_LDFLAGS) $(LINKFORSHARED) -o $@ Programs/_testembed.o $(LINK_PYTHON_OBJS) $(LIBS) $(MODLIBS) $(SYSLIBS) -Programs/_getcompiler: Programs/_getcompiler.c - $(LINKCC) $(PY_CPPFLAGS) -o $@ $< +Programs/_getcompiler: $(srcdir)/Programs/_getcompiler.c + $(LINKCC) $(PY_CPPFLAGS) -o $@ $(srcdir)/Programs/_getcompiler.c ############################################################################ # "Bootstrap Python" used to run Programs/_freeze_module.py @@ -1867,12 +1867,12 @@ regen-all: regen-cases regen-slots \ # Special rules for object files Modules/getbuildinfo.h: $(srcdir)/Tools/build/generate_getbuildinfo.py Programs/_getcompiler - $(PYTHON_FOR_REGEN) $(srcdir)/Tools/build/generate_getbuildinfo.py \ - -o $@ \ - --platform="$(MACHDEP)" \ - --git-version="`LC_ALL=C $(GITVERSION)`" \ - --git-tag="`LC_ALL=C $(GITTAG)`" \ - --git-branch="`LC_ALL=C $(GITBRANCH)`" + $(PYTHON_FOR_REGEN) $(srcdir)/Tools/build/generate_getbuildinfo.py \ + -o $@ \ + --platform="$(MACHDEP)" \ + --git-version="`LC_ALL=C $(GITVERSION)`" \ + --git-tag="`LC_ALL=C $(GITTAG)`" \ + --git-branch="`LC_ALL=C $(GITBRANCH)`" Modules/getbuildinfo.o: $(srcdir)/Modules/getbuildinfo.c Modules/getbuildinfo.h # Add local Modules/ include directory to get Modules/getbuildinfo.h