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/Lib/test/test_tools/test_getbuildinfo.py b/Lib/test/test_tools/test_getbuildinfo.py new file mode 100644 index 00000000000000..e4e4071afc0018 --- /dev/null +++ b/Lib/test/test_tools/test_getbuildinfo.py @@ -0,0 +1,195 @@ +"""Test 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 + + +test_tools.skip_if_missing("build") + +with test_tools.imports_under_tool('build'): + import generate_getbuildinfo + +SRC_DIR = os.path.dirname(test_tools.toolsdir) +SCRIPT_NAME = 'generate_getbuildinfo.py' + + +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 = os.path.join('Programs', '_getcompiler') + MOCK_COMPILER = 'MOCK_COMPILER' + CC = 'gcc -std=c11' + + for hostrunner in ('', 'node'): + for use_cc in (False, True): + 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 + compact_compiler = generate_getbuildinfo.compact_compiler + + def check(compiler, expected): + self.assertEqual(compact_compiler(compiler), expected) + + def check_unchanged(compiler): + self.assertEqual(compact_compiler(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, 'Android 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('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)') + + 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) + + 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'{SCRIPT_NAME}: {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/Makefile.pre.in b/Makefile.pre.in index 42480f28ad7f31..121d571c9e63aa 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: $(srcdir)/Programs/_getcompiler.c + $(LINKCC) $(PY_CPPFLAGS) -o $@ $(srcdir)/Programs/_getcompiler.c + ############################################################################ # "Bootstrap Python" used to run Programs/_freeze_module.py @@ -1867,18 +1866,17 @@ 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: $(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)`" + +Modules/getbuildinfo.o: $(srcdir)/Modules/getbuildinfo.c 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) $(CC) -c $(PY_CORE_CFLAGS) -DPYTHONPATH='"$(PYTHONPATH)"' \ @@ -2028,9 +2026,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..303ce6e4e4ed9e 100644 --- a/Modules/getbuildinfo.c +++ b/Modules/getbuildinfo.c @@ -1,81 +1,71 @@ -#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 * _Py_gitversion(void) { - return GITVERSION; + return GIT_VERSION; } 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 BUILD_INFO; +} + +// 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 = 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/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/PCbuild/_freeze_module.vcxproj b/PCbuild/_freeze_module.vcxproj index 36a1d768486a3f..057168d245e568 100644 --- a/PCbuild/_freeze_module.vcxproj +++ b/PCbuild/_freeze_module.vcxproj @@ -222,11 +222,7 @@ - - - - @@ -470,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()) + + 1 + 0 + + + + + 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 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.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 e2cac3158831ee..e5ff49ea6f5265 100644 --- a/PCbuild/pcbuild.sln +++ b/PCbuild/pcbuild.sln @@ -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} @@ -112,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 @@ -161,6 +165,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 +1752,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 c2d8f64a72b119..f532d6b894cc47 100644 --- a/PCbuild/pythoncore.vcxproj +++ b/PCbuild/pythoncore.vcxproj @@ -649,11 +649,7 @@ - - - - @@ -735,29 +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()) - - - - - GITVERSION="$(GitVersion)";GITTAG="$(GitTag)";GITBRANCH="$(GitBranch)";%(PreprocessorDefinitions) - - - 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/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/Python/getcompiler.c b/Programs/_getcompiler.c similarity index 52% rename from Python/getcompiler.c rename to Programs/_getcompiler.c index baa03679d79fb8..05df97de6da5a5 100644 --- a/Python/getcompiler.c +++ b/Programs/_getcompiler.c @@ -1,7 +1,8 @@ - -/* Return the compiler identification, if possible. */ +// On Windows, do not link the program to python3x.lib +#define Py_NO_LINK_LIB #include "Python.h" +#include // printf() // PC/pyconfig.h defines _Py_COMPILER #ifdef _Py_COMPILER @@ -13,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 */ -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..5755c77e3b4d67 --- /dev/null +++ b/Tools/build/generate_getbuildinfo.py @@ -0,0 +1,266 @@ +# Script to generate Modules/getbuildinfo.h + +import argparse +import locale +import os +import re +import shlex +import subprocess +import sys +import sysconfig +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 +"(.*)"$', 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' +# '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 log(msg): + print(f"{SCRIPT_NAME}: {msg}") + + +def exit_error(msg): + log(msg) + sys.exit(1) + + +def get_py_version(): + filename = os.path.join(SRC_DIR, 'Include', 'patchlevel.h') + with open(filename, encoding='utf8') as fp: + code = fp.read() + match = PY_VERSION_REGEX.search(code) + if not match: + exit_error(f"ERROR: Unable to locate PY_VERSION in {filename}") + return match.group(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: + 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' + CC = parse_file('CC', makefile, CC_REGEX) + return CC.rstrip() + + +def get_gil_disabled(): + # Look in the current working directory + pyconfig_h = '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(): + 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) + + day = time.strftime("%d", time_tuple) + if day.startswith("0"): + day = " " + day[1:] + 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 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: + git_id = git_branch + if not git_id: + git_id = "main" + + sep = ":" if git_version else "" + build_info = f"{git_id}{sep}{git_version}, {build_date}, {build_time}" + return (build_info, git_id) + + +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('--free-threading', type=int) + return parser.parse_args() + + +def run_command(cmd, *, check=True): + cmd_str = shlex.join(cmd) + log(f"+ {cmd_str}") + try: + proc = subprocess.run(cmd, stdout=subprocess.PIPE, text=True) + except OSError as exc: + error = f'with: {exc!r}' + else: + exitcode = proc.returncode + if exitcode: + error = f'with exit code {exitcode}' + else: + error = None + + if error: + msg = f"Command {cmd_str} failed {error}" + if check: + exit_error(msg) + else: + log(msg) + return None + + return proc.stdout.rstrip() + + +def _get_compiler(): + # Run _getcompiler program + getcompiler = os.path.join('Programs', '_getcompiler') + HOSTRUNNER = get_hostrunner() + 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 the compiler with --version + CC = get_makefile_cc() + if not CC: + exit_error(f"ERROR: Unable to locate CC in Makefile") + + cmd = shlex.split(CC) + output = run_command([*cmd, '--version'], check=False) + if output: + # Get the first line + return output.splitlines()[0] + + return None + + +def compact_compiler(compiler): + match = CLANG_VERSION_REGEX.search(compiler) + if match: + vendor = match.group(1) + version = match.group(2) + if vendor: + compiler = f'{vendor} Clang {version}' + else: + compiler = f'Clang {version}' + + return compiler + + +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' + + 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}]' + + +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 + free_threading = args.free_threading + + if not output_filename: + # Write to the current directory + output_filename = os.path.join('Modules', 'getbuildinfo.h') + if not platform: + platform = "unknown" + + compiler = get_compiler(args.compiler) + + 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 free_threading is None: + # Get Py_GIL_DISABLED macro from pyconfig.h + free_threading = get_gil_disabled() + + if free_threading: + version = f"{PY_VERSION} free-threading build ({build_info}) {compiler}" + else: + version = f"{PY_VERSION} ({build_info}) {compiler}" + + 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('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) + log(f"{output_filename} updated") + + +if __name__ == "__main__": + main() 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 -