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"],
}],
},