From a6f0efbd0d95d053f0646058efef3717dec1600e Mon Sep 17 00:00:00 2001 From: "Daniel A. Wozniak" Date: Fri, 23 Oct 2026 17:45:34 -0700 Subject: [PATCH 1/2] Skip xcs.c include when CPython already compiles it The earlier fix on this branch appended `#include "xcs.c"` to xmlparse.c whenever the dropped-in expat tarball shipped xcs.c, on the assumption that Python's build system only compiles xmlparse.c/xmlrole.c/xmltok.c. That assumption holds for the setup.py-era Linux/macOS builds and for Python 3.10-3.13's PCbuild/pyexpat.vcxproj, but not for 3.14.9: its PCbuild/pyexpat.vcxproj and _elementtree.vcxproj already list xcs.c as ClCompile entries. The relenv-added include then defined xcslen/xcscmp/ xcsncmp twice -- once in xcs.obj (CPython's build), once in xmlparse.obj (our include) -- and the Windows link failed with LNK2005/LNK1169 on _elementtree.pyd and pyexpat.pyd. Gate the include on PCbuild/pyexpat.vcxproj not already listing xcs.c. Pre-3.14 Windows and Linux/macOS behavior is unchanged. --- relenv/build/windows.py | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/relenv/build/windows.py b/relenv/build/windows.py index dd2b7ce6..7602255e 100644 --- a/relenv/build/windows.py +++ b/relenv/build/windows.py @@ -447,11 +447,19 @@ def update_expat(dirs: Dirs, env: EnvMapping) -> None: f.write('#if defined(HAVE_ARC4RANDOM)\n#include "random_arc4random.c"\n#endif\n') f.write('#if !defined(_WIN32) && defined(XML_DEV_URANDOM)\n#include "random_dev_urandom.c"\n#endif\n') # expat >= 2.9.0 moved xcslen/xcscmp/xcsncmp into a separate TU - # (xcs.c). Python's build system only compiles xmlparse.c, - # xmlrole.c, and xmltok.c, so pull xcs.c into xmlparse.c's TU to - # avoid undefined-reference failures when linking pyexpat. + # (xcs.c). On Python versions whose PCbuild/pyexpat.vcxproj does + # not already compile xcs.c (3.10-3.13), pull it into xmlparse.c's + # TU so pyexpat links. On 3.14+ the vcxproj already lists xcs.c as + # a ClCompile entry — including it here would produce duplicate + # symbols at link time. if (expat_dir / "xcs.c").exists(): - f.write('#include "xcs.c"\n') + pyexpat_vcxproj = dirs.source / "PCbuild" / "pyexpat.vcxproj" + vcxproj_compiles_xcs = ( + pyexpat_vcxproj.exists() + and "expat\\xcs.c" in pyexpat_vcxproj.read_text() + ) + if not vcxproj_compiles_xcs: + f.write('#include "xcs.c"\n') # Update SBOM with correct checksums for updated expat files files_to_update = {f"Modules/expat/{f.name}": f for f in updated_files} From bffbaa1efafd6d973a4e77a6005094e667f7244b Mon Sep 17 00:00:00 2001 From: "Daniel A. Wozniak" Date: Sat, 24 Oct 2026 14:05:55 -0700 Subject: [PATCH 2/2] Collapse vcxproj_compiles_xcs onto one line for ruff-format --- relenv/build/windows.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/relenv/build/windows.py b/relenv/build/windows.py index 7602255e..317c11e0 100644 --- a/relenv/build/windows.py +++ b/relenv/build/windows.py @@ -454,10 +454,7 @@ def update_expat(dirs: Dirs, env: EnvMapping) -> None: # symbols at link time. if (expat_dir / "xcs.c").exists(): pyexpat_vcxproj = dirs.source / "PCbuild" / "pyexpat.vcxproj" - vcxproj_compiles_xcs = ( - pyexpat_vcxproj.exists() - and "expat\\xcs.c" in pyexpat_vcxproj.read_text() - ) + vcxproj_compiles_xcs = pyexpat_vcxproj.exists() and "expat\\xcs.c" in pyexpat_vcxproj.read_text() if not vcxproj_compiles_xcs: f.write('#include "xcs.c"\n')