Skip to content

Fix freethreaded include on Windows #368

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 13 commits into from
Oct 15, 2024
Merged
56 changes: 46 additions & 10 deletions cpython-windows/build.py
Original file line number Diff line number Diff line change
Expand Up @@ -845,6 +845,7 @@ def run_msbuild(
platform: str,
python_version: str,
windows_sdk_version: str,
freethreaded: bool,
):
args = [
str(msbuild),
Expand All @@ -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)


Expand Down Expand Up @@ -1118,6 +1122,7 @@ def collect_python_build_artifacts(
arch: str,
config: str,
openssl_entry: str,
freethreaded: bool,
):
"""Collect build artifacts from Python.

Expand Down Expand Up @@ -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)
Expand All @@ -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
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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"
Expand Down Expand Up @@ -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
Expand All @@ -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(
Expand Down Expand Up @@ -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"

Expand All @@ -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"

Expand Down Expand Up @@ -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")
Expand All @@ -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",
Expand All @@ -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",
Expand Down Expand Up @@ -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()):
Expand Down Expand Up @@ -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")
Expand Down
2 changes: 1 addition & 1 deletion cpython-windows/generate_metadata.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()},
}
Expand Down
2 changes: 2 additions & 0 deletions src/validation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,7 @@ const PE_ALLOWED_LIBRARIES: &[&str] = &[
"python311.dll",
"python312.dll",
"python313.dll",
"python313t.dll",
"sqlite3.dll",
"tcl86t.dll",
"tk86t.dll",
Expand Down Expand Up @@ -2087,6 +2088,7 @@ fn verify_distribution_behavior(dist_path: &Path) -> Result<Vec<String>> {
.stdout_to_stderr()
.unchecked()
.env("TARGET_TRIPLE", &python_json.target_triple)
.env("BUILD_OPTIONS", &python_json.build_options)
.run()?;

if !output.status.success() {
Expand Down
14 changes: 14 additions & 0 deletions src/verify_distribution.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down