From d7029b72e17e24e7df6cf8201cec33e947f7bf54 Mon Sep 17 00:00:00 2001 From: Schamper <1254028+Schamper@users.noreply.github.com> Date: Thu, 23 Apr 2026 22:40:53 +0200 Subject: [PATCH] Improve Windows builds --- cpython-windows/build.py | 33 ++++--- pythonbuild/static.py | 207 ++++++++++++++------------------------- src/release.rs | 7 +- 3 files changed, 97 insertions(+), 150 deletions(-) diff --git a/cpython-windows/build.py b/cpython-windows/build.py index 7d37f3e02..ef5097601 100644 --- a/cpython-windows/build.py +++ b/cpython-windows/build.py @@ -30,6 +30,7 @@ convert_to_static_library, copy_link_to_lib, hack_source_files, + link_builtin_extensions_into_executables, remove_from_config_c, remove_from_extension_modules, ) @@ -751,6 +752,8 @@ def hack_project_files( pythoncore_proj = pcbuild_path / "pythoncore.vcxproj" + converted_builtin_extensions: list[str] = [] + for extension, entry in sorted(CONVERT_TO_BUILTIN_EXTENSIONS.items()): if entry.get("ignore_static") or extension in DISABLED_EXTENSIONS: log("ignoring extension %s in static builds" % extension) @@ -760,6 +763,14 @@ def hack_project_files( if convert_to_static_library(cpython_source_path, extension, entry, False): add_to_config_c(cpython_source_path, extension, init_fn) + converted_builtin_extensions.append(extension) + + # Link each built-in extension's static library directly into + # python.exe / pythonw.exe to resolve the PyInit_* references in + # PC/config.c. + link_builtin_extensions_into_executables( + cpython_source_path, converted_builtin_extensions + ) # pythoncore.vcxproj produces libpython. Typically pythonXY.dll. We change # it to produce a static library. @@ -835,6 +846,17 @@ def hack_pystandalone_files(source_path: pathlib.Path, python_version: str): # PYSTANDALONE: add pystandalone module to config.c add_to_config_c(source_path, "_pystandalone", "PyInit__pystandalone") + # Register _pystandalone in pcbuild.proj's ExtensionModules list so + # MSBuild picks it up via ``. + pcbuild_proj_path = source_path / "PCbuild" / "pcbuild.proj" + pcbuild_proj_text = pcbuild_proj_path.read_text(encoding="utf8") + if "_pystandalone" not in pcbuild_proj_text: + static_replace_in_file( + pcbuild_proj_path, + b'", ] - # Ensure the extension project doesn't depend on pythoncore: as a built-in - # extension, pythoncore will depend on it. - - # This logic is a bit hacky. Ideally we'd parse the file as XML and operate - # in the XML domain. But that is more work. The goal here is to strip the - # ... containing the - # {pythoncore ID}. This could leave an item . - # That should be fine. - start_line, end_line = None, None - for i, line in enumerate(lines): - if "{cf7ac3d1-e2df-41d2-bea6-1e2556cdea26}" in line: - for j in range(i, 0, -1): - if "" in lines[j]: - end_line = j - break - - break - - if start_line is not None and end_line is not None: - log("stripping pythoncore dependency from %s" % extension) - for line in lines[start_line : end_line + 1]: - log(line) - - lines = lines[:start_line] + lines[end_line + 1 :] - + # Preserve the natural dependency graph: extensions depend on + # pythoncore. This ensures pythoncore's _UpdatePyconfig target runs + # before any extension compiles, so pyconfig.h exists in pythoncore's + # IntDir and extensions see a consistent, correctly-substituted copy + # via GeneratedPyConfigDir on /I. + # + # pythoncore.lib therefore does not contain extension objs; the final + # executables (python.exe, pythonw.exe) link each extension's .lib + # directly to resolve the PyInit_* symbols referenced by PC/config.c. + # See `link_builtin_extensions_into_executables` below. with proj_path.open("w", encoding="utf8") as fh: fh.write("\n".join(lines)) - # Tell pythoncore to link against the static .lib. - RE_ADDITIONAL_DEPENDENCIES = re.compile( - "([^<]+)" - ) - - pythoncore_path = source_path / "PCbuild" / "pythoncore.vcxproj" - lines = [] - - with pythoncore_path.open("r", encoding="utf8") as fh: - for line in fh: - line = line.rstrip() - - m = RE_ADDITIONAL_DEPENDENCIES.search(line) - - if m: - log("changing pythoncore to link against %s.lib" % extension) - # TODO we shouldn't need this with static linking if the - # project is configured to link library dependencies. - # But removing it results in unresolved external symbols - # when linking the python project. There /might/ be a - # visibility issue with the PyMODINIT_FUNC macro. - line = line.replace( - m.group(1), r"$(OutDir)%s.lib;%s" % (extension, m.group(1)) - ) - - lines.append(line) - - with pythoncore_path.open("w", encoding="utf8") as fh: - fh.write("\n".join(lines)) - - # Change pythoncore to depend on the extension project. - - # pcbuild.proj is the file that matters for msbuild. And order within - # matters. We remove the extension from the "ExtensionModules" set of - # projects. Then we re-add the project to before "pythoncore." - remove_from_extension_modules(source_path, extension) - - pcbuild_proj_path = source_path / "PCbuild" / "pcbuild.proj" - - with pcbuild_proj_path.open("r", encoding="utf8") as fh: - data = fh.read() - - data = data.replace( - '', - ' \n ' - % extension, - ) - - with pcbuild_proj_path.open("w", encoding="utf8") as fh: - fh.write(data) - - # We don't technically need to modify the solution since msbuild doesn't - # use it. But it enables debugging inside Visual Studio, which is - # convenient. - RE_PROJECT = re.compile( - r'Project\("\{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942\}"\) = "([^"]+)", "[^"]+", "{([^\}]+)\}"' - ) - - pcbuild_sln_path = source_path / "PCbuild" / "pcbuild.sln" - lines = [] - - extension_id = None - pythoncore_line = None - - with pcbuild_sln_path.open("r", encoding="utf8") as fh: - # First pass buffers the file, finds the ID of the extension project, - # and finds where the pythoncore project is defined. - for i, line in enumerate(fh): - line = line.rstrip() - - m = RE_PROJECT.search(line) - - if m and m.group(1) == extension: - extension_id = m.group(2) - - if m and m.group(1) == "pythoncore": - pythoncore_line = i - - lines.append(line) - - # Not all projects are in the solution(!!!). Since we don't use the - # solution for building, that's fine to ignore. - if not extension_id: - log("failed to find project %s in solution" % extension) + return True - if not pythoncore_line: - log("failed to find pythoncore project in solution") - if extension_id and pythoncore_line: - log("making pythoncore depend on %s" % extension) +def link_builtin_extensions_into_executables( + source_path: pathlib.Path, extensions: list[str] +): + """Make python.exe / pythonw.exe link against each built-in extension's + static library. + + python.vcxproj / pythonw.vcxproj inherit their link settings from + pyproject.props and do not declare an `` + element. Inject one just before the closing `` listing + `$(OutDir).lib` for every converted extension so the linker + resolves the `PyInit_*` symbols referenced by PC/config.c. + """ - needs_section = ( - not lines[pythoncore_line + 1].lstrip().startswith("ProjectSection") + if not extensions: + return + + additional = ";".join("$(OutDir)%s.lib" % ext for ext in extensions) + injected_line = ( + " " + "%s;%%(AdditionalDependencies)" + "" + ) % additional + + for exe_proj_name in ("python", "pythonw", "_freeze_importlib"): + exe_path = source_path / "PCbuild" / ("%s.vcxproj" % exe_proj_name) + if not exe_path.exists(): + log("skipping %s.vcxproj: not present" % exe_proj_name) + continue + + with exe_path.open("r", encoding="utf8") as fh: + data = fh.read() + + # Detect likely newline style so we can preserve it. + newline = "\r\n" if "\r\n" in data else "\n" + + closing = newline + " " + if closing not in data: + log( + "warning: no tag found in %s.vcxproj; " + "built-in extensions will not link into %s.exe" + % (exe_proj_name, exe_proj_name) + ) + continue + + # Insert only once (idempotent). + if "" in data: + log("skipping %s.vcxproj: already patched" % exe_proj_name) + continue + + replacement = ( + newline + + injected_line + + newline + + " " + + newline + + " " ) - offset = 1 if needs_section else 2 + data = data.replace(closing, replacement, 1) - lines.insert( - pythoncore_line + offset, "\t\t{%s} = {%s}" % (extension_id, extension_id) + log( + "linking %d built-in extension lib(s) into %s.exe" + % (len(extensions), exe_proj_name) ) - - if needs_section: - lines.insert( - pythoncore_line + 1, - "\tProjectSection(ProjectDependencies) = postProject", - ) - lines.insert(pythoncore_line + 3, "\tEndProjectSection") - - with pcbuild_sln_path.open("w", encoding="utf8") as fh: - fh.write("\n".join(lines)) - - return True + with exe_path.open("w", encoding="utf8") as fh: + fh.write(data) def copy_link_to_lib(p: pathlib.Path): diff --git a/src/release.rs b/src/release.rs index e45e9957e..b5b012674 100644 --- a/src/release.rs +++ b/src/release.rs @@ -109,7 +109,6 @@ pub static RELEASE_TRIPLES: Lazy> = Lazy:: ); // Windows. - // PYSTANDALONE: we can only build freethreaded on >= 3.14 h.insert( "i686-pc-windows-msvc", TripleRelease { @@ -118,7 +117,7 @@ pub static RELEASE_TRIPLES: Lazy> = Lazy:: freethreaded_install_only_suffix: "freethreaded+pgo", python_version_requirement: None, conditional_suffixes: vec![ConditionalSuffixes { - python_version_requirement: VersionSpecifier::from_str(">=3.14").unwrap(), + python_version_requirement: VersionSpecifier::from_str(">=3.13").unwrap(), suffixes: vec!["freethreaded+pgo"], }], }, @@ -131,7 +130,7 @@ pub static RELEASE_TRIPLES: Lazy> = Lazy:: freethreaded_install_only_suffix: "freethreaded+pgo", python_version_requirement: None, conditional_suffixes: vec![ConditionalSuffixes { - python_version_requirement: VersionSpecifier::from_str(">=3.14").unwrap(), + python_version_requirement: VersionSpecifier::from_str(">=3.13").unwrap(), suffixes: vec!["freethreaded+pgo"], }], }, @@ -144,7 +143,7 @@ pub static RELEASE_TRIPLES: Lazy> = Lazy:: freethreaded_install_only_suffix: "freethreaded+pgo", python_version_requirement: Some(VersionSpecifier::from_str(">=3.11").unwrap()), conditional_suffixes: vec![ConditionalSuffixes { - python_version_requirement: VersionSpecifier::from_str(">=3.14").unwrap(), + python_version_requirement: VersionSpecifier::from_str(">=3.13").unwrap(), suffixes: vec!["freethreaded+pgo"], }], },