diff --git a/cpython-windows/build.py b/cpython-windows/build.py index 24c5885a..7f3be23d 100644 --- a/cpython-windows/build.py +++ b/cpython-windows/build.py @@ -845,6 +845,7 @@ def run_msbuild( platform: str, python_version: str, windows_sdk_version: str, + freethreaded: bool, ): args = [ str(msbuild), @@ -867,6 +868,9 @@ def run_msbuild( f"/property:DefaultWindowsSDKVersion={windows_sdk_version}", ] + if freethreaded: + args.append("/property:DisableGil=true") + exec_and_log(args, str(pcbuild_path), os.environ) @@ -1118,6 +1122,7 @@ def collect_python_build_artifacts( arch: str, config: str, openssl_entry: str, + freethreaded: bool, ): """Collect build artifacts from Python. @@ -1243,6 +1248,20 @@ def find_additional_dependencies(project: pathlib.Path): return set() + if arch == "amd64": + abi_platform = "win_amd64" + elif arch == "win32": + abi_platform = "win32" + else: + raise ValueError("unhandled arch: %s" % arch) + + if freethreaded: + abi_tag = ".cp%st-%s" % (python_majmin, abi_platform) + lib_suffix = "t" + else: + abi_tag = "" + lib_suffix = "" + # Copy object files for core sources into their own directory. core_dir = out_dir / "build" / "core" core_dir.mkdir(parents=True) @@ -1263,12 +1282,12 @@ def find_additional_dependencies(project: pathlib.Path): exts = ("lib", "exp") for ext in exts: - source = outputs_path / ("python%s.%s" % (python_majmin, ext)) - dest = core_dir / ("python%s.%s" % (python_majmin, ext)) + source = outputs_path / ("python%s%s.%s" % (python_majmin, lib_suffix, ext)) + dest = core_dir / ("python%s%s.%s" % (python_majmin, lib_suffix, ext)) log("copying %s" % source) shutil.copyfile(source, dest) - res["core"]["shared_lib"] = "install/python%s.dll" % python_majmin + res["core"]["shared_lib"] = "install/python%s%s.dll" % (python_majmin, lib_suffix) # We hack up pythoncore.vcxproj and the list in it when this function # runs isn't totally accurate. We hardcode the list from the CPython @@ -1354,12 +1373,15 @@ def find_additional_dependencies(project: pathlib.Path): res["extensions"][ext] = [entry] # Copy the extension static library. - ext_static = outputs_path / ("%s.lib" % ext) - dest = dest_dir / ("%s.lib" % ext) + ext_static = outputs_path / ("%s%s.lib" % (ext, abi_tag)) + dest = dest_dir / ("%s%s.lib" % (ext, abi_tag)) log("copying static extension %s" % ext_static) shutil.copyfile(ext_static, dest) - res["extensions"][ext][0]["shared_lib"] = "install/DLLs/%s.pyd" % ext + res["extensions"][ext][0]["shared_lib"] = "install/DLLs/%s%s.pyd" % ( + ext, + abi_tag, + ) lib_dir = out_dir / "build" / "lib" lib_dir.mkdir() @@ -1394,6 +1416,7 @@ def build_cpython( ) -> pathlib.Path: parsed_build_options = set(build_options.split("+")) pgo = "pgo" in parsed_build_options + freethreaded = "freethreaded" in parsed_build_options msbuild = find_msbuild(msvc_version) log("found MSBuild at %s" % msbuild) @@ -1425,6 +1448,12 @@ def build_cpython( # as we do for Unix builds. mpdecimal_archive = None + if freethreaded: + (major, minor, _) = python_version.split(".") + python_exe = f"python{major}.{minor}t.exe" + else: + python_exe = "python.exe" + if arch == "amd64": build_platform = "x64" build_directory = "amd64" @@ -1507,6 +1536,7 @@ def build_cpython( platform=build_platform, python_version=python_version, windows_sdk_version=windows_sdk_version, + freethreaded=freethreaded, ) # build-windows.py sets some environment variables which cause the @@ -1526,7 +1556,7 @@ def build_cpython( # test execution. We work around this by invoking the test harness # separately for each test. instrumented_python = ( - pcbuild_path / build_directory / "instrumented" / "python.exe" + pcbuild_path / build_directory / "instrumented" / python_exe ) tests = subprocess.run( @@ -1572,6 +1602,7 @@ def build_cpython( platform=build_platform, python_version=python_version, windows_sdk_version=windows_sdk_version, + freethreaded=freethreaded, ) artifact_config = "PGUpdate" @@ -1583,6 +1614,7 @@ def build_cpython( platform=build_platform, python_version=python_version, windows_sdk_version=windows_sdk_version, + freethreaded=freethreaded, ) artifact_config = "Release" @@ -1615,6 +1647,9 @@ def build_cpython( "--include-venv", ] + if freethreaded: + args.append("--include-freethreaded") + # CPython 3.12 removed distutils. if not meets_python_minimum_version(python_version, "3.12"): args.append("--include-distutils") @@ -1639,7 +1674,7 @@ def build_cpython( # Install pip and setuptools. exec_and_log( [ - str(install_dir / "python.exe"), + str(install_dir / python_exe), "-m", "pip", "install", @@ -1656,7 +1691,7 @@ def build_cpython( if meets_python_maximum_version(python_version, "3.11"): exec_and_log( [ - str(install_dir / "python.exe"), + str(install_dir / python_exe), "-m", "pip", "install", @@ -1691,6 +1726,7 @@ def build_cpython( build_directory, artifact_config, openssl_entry=openssl_entry, + freethreaded=freethreaded, ) for ext, init_fn in sorted(builtin_extensions.items()): @@ -1775,7 +1811,7 @@ def build_cpython( } # Collect information from running Python script. - python_exe = out_dir / "python" / "install" / "python.exe" + python_exe = out_dir / "python" / "install" / python_exe metadata_path = td / "metadata.json" env = dict(os.environ) env["ROOT"] = str(out_dir / "python") diff --git a/cpython-windows/generate_metadata.py b/cpython-windows/generate_metadata.py index 5e5a583e..c6341b64 100644 --- a/cpython-windows/generate_metadata.py +++ b/cpython-windows/generate_metadata.py @@ -26,7 +26,7 @@ ).decode("ascii"), "python_paths": {}, "python_paths_abstract": sysconfig.get_paths(expand=False), - "python_exe": "install/python.exe", + "python_exe": f"install/{os.path.basename(sys.executable)}", "python_major_minor_version": sysconfig.get_python_version(), "python_config_vars": {k: str(v) for k, v in sysconfig.get_config_vars().items()}, } diff --git a/src/validation.rs b/src/validation.rs index 3b8a81d4..765745b4 100644 --- a/src/validation.rs +++ b/src/validation.rs @@ -126,6 +126,7 @@ const PE_ALLOWED_LIBRARIES: &[&str] = &[ "python311.dll", "python312.dll", "python313.dll", + "python313t.dll", "sqlite3.dll", "tcl86t.dll", "tk86t.dll", @@ -2087,6 +2088,7 @@ fn verify_distribution_behavior(dist_path: &Path) -> Result> { .stdout_to_stderr() .unchecked() .env("TARGET_TRIPLE", &python_json.target_triple) + .env("BUILD_OPTIONS", &python_json.build_options) .run()?; if !output.status.success() { diff --git a/src/verify_distribution.py b/src/verify_distribution.py index 8ee1b2db..5f039f09 100644 --- a/src/verify_distribution.py +++ b/src/verify_distribution.py @@ -141,6 +141,20 @@ def test_ssl(self): ssl.create_default_context() + @unittest.skipIf( + sys.version_info[:2] < (3, 13), + "Free-threaded builds are only available in 3.13+", + ) + def test_gil_disabled(self): + import sysconfig + + if "freethreaded" in os.environ.get("BUILD_OPTIONS", "").split("+"): + wanted = 1 + else: + wanted = 0 + + self.assertEqual(sysconfig.get_config_var("Py_GIL_DISABLED"), wanted) + @unittest.skipIf("TCL_LIBRARY" not in os.environ, "TCL_LIBRARY not set") @unittest.skipIf("DISPLAY" not in os.environ, "DISPLAY not set") def test_tkinter(self):