From ae13acf849f77c832eb8a5a71bed4677b7a66ce6 Mon Sep 17 00:00:00 2001 From: "Jonathan J. Helmus" Date: Wed, 20 May 2026 15:44:03 -0500 Subject: [PATCH 1/4] build openssl with no-uplink on Windows for Python 3.12+ Uplink support was dropping upstream when building OpenSSL in CPython 3.12. Follow this behavior but keep uplink support in 3.10 and 3.11. --- cpython-windows/build.py | 65 ++++++++++++++++++++++++++-------------- 1 file changed, 42 insertions(+), 23 deletions(-) diff --git a/cpython-windows/build.py b/cpython-windows/build.py index d7201f53f..9f98a9657 100644 --- a/cpython-windows/build.py +++ b/cpython-windows/build.py @@ -674,17 +674,17 @@ def hack_project_files( pass # Our custom OpenSSL build has applink.c in a different location from the - # binary OpenSSL distribution. This is no longer relevant for 3.12+ per - # https://github.com/python/cpython/pull/131839, so we allow it to fail.swe - try: + # binary OpenSSL distribution. + # Starting with 3.12 CPython on Windows is built without uplink support + # so this modification is not needed. + # https://github.com/python/cpython/pull/131839 + if meets_python_maximum_version(python_version, "3.11"): ssl_proj = pcbuild_path / "_ssl.vcxproj" static_replace_in_file( ssl_proj, rb'', rb'', ) - except NoSearchStringError: - pass # Python 3.12+ uses the the pre-built tk-windows-bin 8.6.12 which doesn't # have a standalone zlib DLL, so we remove references to it. For Python @@ -791,6 +791,7 @@ def build_openssl_for_arch( build_root: pathlib.Path, *, jom_archive, + with_uplink: bool = False, ): nasm_version = DOWNLOADS["nasm-windows-bin"]["version"] @@ -810,14 +811,15 @@ def build_openssl_for_arch( source_root = build_root / ("openssl-%s" % openssl_version) - # uplink.c tries to find the OPENSSL_Applink function exported from the current - # executable. However, it is exported from _ssl[_d].pyd in shared builds. So - # update its sounce to look for it from there. - static_replace_in_file( - source_root / "ms" / "uplink.c", - b"((h = GetModuleHandle(NULL)) == NULL)", - b'((h = GetModuleHandleA("_ssl.pyd")) == NULL) if ((h = GetModuleHandleA("_ssl_d.pyd")) == NULL) if ((h = GetModuleHandle(NULL)) == NULL)', - ) + if with_uplink: + # uplink.c tries to find the OPENSSL_Applink function exported from the + # current executable. However, it is exported from _ssl[_d].pyd in shared + # builds. So update its source to look for it from there. + static_replace_in_file( + source_root / "ms" / "uplink.c", + b"((h = GetModuleHandle(NULL)) == NULL)", + b'((h = GetModuleHandleA("_ssl.pyd")) == NULL) if ((h = GetModuleHandleA("_ssl_d.pyd")) == NULL) if ((h = GetModuleHandle(NULL)) == NULL)', + ) if arch == "x86": configure = "VC-WIN32" @@ -841,16 +843,22 @@ def build_openssl_for_arch( env["DESTDIR"] = str(dest_dir) install_root = dest_dir / prefix + configure_args = [ + str(perl_path), + "Configure", + configure, + "no-idea", + "no-mdc2", + "no-tests", + "--prefix=/%s" % prefix, + ] + if with_uplink: + log("building OpenSSL with uplink support for Python <3.12") + else: + configure_args.append("no-uplink") + exec_and_log( - [ - str(perl_path), - "Configure", - configure, - "no-idea", - "no-mdc2", - "no-tests", - "--prefix=/%s" % prefix, - ], + configure_args, source_root, { **env, @@ -889,6 +897,7 @@ def build_openssl( perl_path: pathlib.Path, arch: str, dest_archive: pathlib.Path, + with_uplink: bool = False, ): """Build OpenSSL from sources using the Perl executable specified.""" @@ -916,6 +925,7 @@ def build_openssl( nasm_archive, root_32, jom_archive=jom_archive, + with_uplink=with_uplink, ) elif arch == "amd64": root_64.mkdir() @@ -927,6 +937,7 @@ def build_openssl( nasm_archive, root_64, jom_archive=jom_archive, + with_uplink=with_uplink, ) elif arch == "arm64": root_arm64.mkdir() @@ -938,6 +949,7 @@ def build_openssl( nasm_archive, root_arm64, jom_archive=jom_archive, + with_uplink=with_uplink, ) else: raise Exception("unhandled architecture: %s" % arch) @@ -1987,8 +1999,14 @@ def main() -> None: else: openssl_entry = "openssl-3.5" + openssl_with_uplink = args.python in ["cpython-3.10", "cpython-3.11"] + if openssl_with_uplink: + openssl_build_options = f"{build_options}-uplink" + else: + openssl_build_options = f"{build_options}-no-uplink" + openssl_archive = BUILD / ( - "%s-%s-%s.tar" % (openssl_entry, target_triple, build_options) + "%s-%s-%s.tar" % (openssl_entry, target_triple, openssl_build_options) ) if not openssl_archive.exists(): perl_path = fetch_strawberry_perl() / "perl" / "bin" / "perl.exe" @@ -1998,6 +2016,7 @@ def main() -> None: perl_path, arch, dest_archive=openssl_archive, + with_uplink=openssl_with_uplink, ) libffi_archive = BUILD / ("libffi-%s-%s.tar" % (target_triple, build_options)) From aee21a59173b80f74ee3c085d6b3ae56492c2895 Mon Sep 17 00:00:00 2001 From: "Jonathan J. Helmus" Date: Wed, 20 May 2026 15:45:32 -0500 Subject: [PATCH 2/4] Remove outdated comment from cpython-windows/build.py --- cpython-windows/build.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/cpython-windows/build.py b/cpython-windows/build.py index 9f98a9657..fed9b06ff 100644 --- a/cpython-windows/build.py +++ b/cpython-windows/build.py @@ -833,11 +833,6 @@ def build_openssl_for_arch( else: raise Exception("unhandled architecture: %s" % arch) - # The official CPython OpenSSL builds hack ms/uplink.c to change the - # ``GetModuleHandle(NULL)`` invocation to load things from _ssl.pyd - # instead. But since we statically link the _ssl extension, this hackery - # is not required. - # Set DESTDIR to affect install location. dest_dir = build_root / "install" env["DESTDIR"] = str(dest_dir) From 579f06aec487c46b2284cc8efb7d0751db7fc69c Mon Sep 17 00:00:00 2001 From: "Jonathan J. Helmus" Date: Wed, 20 May 2026 16:49:17 -0500 Subject: [PATCH 3/4] add test for SSLKEYLOGFILE --- pythonbuild/disttests/__init__.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/pythonbuild/disttests/__init__.py b/pythonbuild/disttests/__init__.py index f7186e13d..141443afe 100644 --- a/pythonbuild/disttests/__init__.py +++ b/pythonbuild/disttests/__init__.py @@ -217,6 +217,26 @@ def test_ssl(self): ssl.create_default_context() + @unittest.skipIf(os.name != "nt", "Windows-specific OpenSSL uplink regression") + def test_ssl_with_keylogfile(self): + # Validate that a SSLContext can be created when SSLKEYLOGFILE is set + # https://github.com/astral-sh/python-build-standalone/issues/640 + import ssl + + with tempfile.TemporaryDirectory() as td: + keylog_path = str(Path(td) / "sslkeylog.log") + original_keylog_path = os.environ.get("SSLKEYLOGFILE") + os.environ["SSLKEYLOGFILE"] = keylog_path + try: + context = ssl.create_default_context() + self.assertEqual(context.keylog_filename, keylog_path) + self.assertGreater(len(context.get_ciphers()), 0) + finally: + if original_keylog_path is None: + os.environ.pop("SSLKEYLOGFILE", None) + else: + os.environ["SSLKEYLOGFILE"] = original_keylog_path + @unittest.skipIf( sys.version_info[:2] < (3, 13), "Free-threaded builds are only available in 3.13+", From d85f0c6e9ae596618219c33ef3cc33fe07946bfa Mon Sep 17 00:00:00 2001 From: "Jonathan J. Helmus" Date: Thu, 21 May 2026 09:35:24 -0500 Subject: [PATCH 4/4] make test_ssl_with_keylogfile more robust Allow cleanup of test_ssl_with_keylogfile to fail and remove the context which should allow the file to be deleted. --- pythonbuild/disttests/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pythonbuild/disttests/__init__.py b/pythonbuild/disttests/__init__.py index 141443afe..652f16f26 100644 --- a/pythonbuild/disttests/__init__.py +++ b/pythonbuild/disttests/__init__.py @@ -223,14 +223,14 @@ def test_ssl_with_keylogfile(self): # https://github.com/astral-sh/python-build-standalone/issues/640 import ssl - with tempfile.TemporaryDirectory() as td: + with tempfile.TemporaryDirectory(ignore_cleanup_errors=True) as td: keylog_path = str(Path(td) / "sslkeylog.log") original_keylog_path = os.environ.get("SSLKEYLOGFILE") os.environ["SSLKEYLOGFILE"] = keylog_path try: context = ssl.create_default_context() self.assertEqual(context.keylog_filename, keylog_path) - self.assertGreater(len(context.get_ciphers()), 0) + del context finally: if original_keylog_path is None: os.environ.pop("SSLKEYLOGFILE", None)