From 52c30aa9d0d9f53b3843c5f1e0f519dbe5b5648d Mon Sep 17 00:00:00 2001 From: Heyuan Zeng Date: Tue, 9 Jul 2024 15:55:31 +0100 Subject: [PATCH 1/6] Prevent building in GIL-less environment --- pyo3-build-config/src/impl_.rs | 5 ++++- pyo3-ffi/build.rs | 21 ++++++++++++++++++++- 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/pyo3-build-config/src/impl_.rs b/pyo3-build-config/src/impl_.rs index 3dc1e912447..d38d41ed552 100644 --- a/pyo3-build-config/src/impl_.rs +++ b/pyo3-build-config/src/impl_.rs @@ -996,6 +996,7 @@ pub enum BuildFlag { Py_DEBUG, Py_REF_DEBUG, Py_TRACE_REFS, + Py_GIL_DISABLED, COUNT_ALLOCS, Other(String), } @@ -1016,6 +1017,7 @@ impl FromStr for BuildFlag { "Py_DEBUG" => Ok(BuildFlag::Py_DEBUG), "Py_REF_DEBUG" => Ok(BuildFlag::Py_REF_DEBUG), "Py_TRACE_REFS" => Ok(BuildFlag::Py_TRACE_REFS), + "Py_GIL_DISABLED" => Ok(BuildFlag::Py_GIL_DISABLED), "COUNT_ALLOCS" => Ok(BuildFlag::COUNT_ALLOCS), other => Ok(BuildFlag::Other(other.to_owned())), } @@ -1039,10 +1041,11 @@ impl FromStr for BuildFlag { pub struct BuildFlags(pub HashSet); impl BuildFlags { - const ALL: [BuildFlag; 4] = [ + const ALL: [BuildFlag; 5] = [ BuildFlag::Py_DEBUG, BuildFlag::Py_REF_DEBUG, BuildFlag::Py_TRACE_REFS, + BuildFlag::Py_GIL_DISABLED, BuildFlag::COUNT_ALLOCS, ]; diff --git a/pyo3-ffi/build.rs b/pyo3-ffi/build.rs index b4521678ba9..1065f526aa9 100644 --- a/pyo3-ffi/build.rs +++ b/pyo3-ffi/build.rs @@ -4,8 +4,9 @@ use pyo3_build_config::{ cargo_env_var, env_var, errors::Result, is_linking_libpython, resolve_interpreter_config, InterpreterConfig, PythonVersion, }, - warn, PythonImplementation, + warn, BuildFlag, PythonImplementation, }; +use std::ops::Not; /// Minimum Python version PyO3 supports. struct SupportedVersions { @@ -120,6 +121,23 @@ fn ensure_python_version(interpreter_config: &InterpreterConfig) -> Result<()> { Ok(()) } +fn ensure_gil_enabled(interpreter_config: &InterpreterConfig) -> Result<()> { + let gil_enabled = interpreter_config + .build_flags + .0 + .contains(&BuildFlag::Py_GIL_DISABLED) + .not(); + ensure!( + gil_enabled || std::env::var("UNSAFE_PYO3_BUILD_FREE_THREADED").map_or(false, |os_str| os_str == "1"), + "the Python interpreter was built with the GIL disabled, which is not supported by PyO3\n\ + = help: please check if an updated version of PyO3 is available. Current version: {}\n\ + = help: set UNSAFE_PYO3_BUILD_FREE_THREADED=1 to suppress this check and build anyway for free-threaded Python", + std::env::var("CARGO_PKG_VERSION").unwrap() + ); + + Ok(()) +} + fn ensure_target_pointer_width(interpreter_config: &InterpreterConfig) -> Result<()> { if let Some(pointer_width) = interpreter_config.pointer_width { // Try to check whether the target architecture matches the python library @@ -185,6 +203,7 @@ fn configure_pyo3() -> Result<()> { ensure_python_version(&interpreter_config)?; ensure_target_pointer_width(&interpreter_config)?; + ensure_gil_enabled(&interpreter_config)?; // Serialize the whole interpreter config into DEP_PYTHON_PYO3_CONFIG env var. interpreter_config.to_cargo_dep_env()?; From 1164ea4d97b91b117677b203d246818881890cf2 Mon Sep 17 00:00:00 2001 From: Heyuan Zeng Date: Tue, 9 Jul 2024 16:10:59 +0100 Subject: [PATCH 2/6] Add change log --- newsfragments/4327.packaging.md | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 newsfragments/4327.packaging.md diff --git a/newsfragments/4327.packaging.md b/newsfragments/4327.packaging.md new file mode 100644 index 00000000000..c98d06f7cba --- /dev/null +++ b/newsfragments/4327.packaging.md @@ -0,0 +1,3 @@ +This PR lets PyO3 checks `Py_GIL_DISABLED` build flag and prevents `pyo3-ffi` crate building against GIL-less Python, +unless +explicitly opt using the `UNSAFE_PYO3_BUILD_FREE_THREADED` environment flag. \ No newline at end of file From 2b013c994e0154e8d1a2a15cb6c958d9087ade17 Mon Sep 17 00:00:00 2001 From: David Hewitt Date: Wed, 10 Jul 2024 12:28:14 +0100 Subject: [PATCH 3/6] add "yet" to phrasing --- pyo3-ffi/build.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyo3-ffi/build.rs b/pyo3-ffi/build.rs index 1065f526aa9..b0b84db0f92 100644 --- a/pyo3-ffi/build.rs +++ b/pyo3-ffi/build.rs @@ -129,7 +129,7 @@ fn ensure_gil_enabled(interpreter_config: &InterpreterConfig) -> Result<()> { .not(); ensure!( gil_enabled || std::env::var("UNSAFE_PYO3_BUILD_FREE_THREADED").map_or(false, |os_str| os_str == "1"), - "the Python interpreter was built with the GIL disabled, which is not supported by PyO3\n\ + "the Python interpreter was built with the GIL disabled, which is not yet supported by PyO3\n\ = help: please check if an updated version of PyO3 is available. Current version: {}\n\ = help: set UNSAFE_PYO3_BUILD_FREE_THREADED=1 to suppress this check and build anyway for free-threaded Python", std::env::var("CARGO_PKG_VERSION").unwrap() From 9e1293d5a9e8082ad1f790ad05febdc9267bbd3c Mon Sep 17 00:00:00 2001 From: Heyuan Zeng Date: Wed, 10 Jul 2024 15:49:44 +0100 Subject: [PATCH 4/6] Add testing to build script --- noxfile.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/noxfile.py b/noxfile.py index 96bd587bee8..b90f0766420 100644 --- a/noxfile.py +++ b/noxfile.py @@ -9,7 +9,7 @@ from functools import lru_cache from glob import glob from pathlib import Path -from typing import Any, Callable, Dict, Iterator, List, Optional, Tuple +from typing import Any, Callable, Dict, Iterable, Iterator, List, Optional, Tuple import nox import nox.command @@ -647,6 +647,14 @@ def test_version_limits(session: nox.Session): config_file.set("PyPy", "3.11") _run_cargo(session, "check", env=env, expect_error=True) + # Python build with GIL disabled should fail building + config_file.set("CPython", "3.13", build_flags=['Py_GIL_DISABLED']) + _run_cargo(session, "check", env=env, expect_error=True) + + # Python build with GIL disabled should pass with env flag on + env["UNSAFE_PYO3_BUILD_FREE_THREADED"] = "1" + _run_cargo(session, "check", env=env) + @nox.session(name="check-feature-powerset", venv_backend="none") def check_feature_powerset(session: nox.Session): @@ -907,7 +915,7 @@ class _ConfigFile: def __init__(self, config_file) -> None: self._config_file = config_file - def set(self, implementation: str, version: str) -> None: + def set(self, implementation: str, version: str, build_flags: Iterable[str] = ()) -> None: """Set the contents of this config file to the given implementation and version.""" self._config_file.seek(0) self._config_file.truncate(0) @@ -915,6 +923,7 @@ def set(self, implementation: str, version: str) -> None: f"""\ implementation={implementation} version={version} +build_flags={','.join(build_flags)} suppress_build_script_link_lines=true """ ) From a34d7fc47971ef31880c1f8943a2b504c9507644 Mon Sep 17 00:00:00 2001 From: David Hewitt Date: Wed, 10 Jul 2024 15:59:59 +0100 Subject: [PATCH 5/6] add link to issue --- pyo3-ffi/build.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/pyo3-ffi/build.rs b/pyo3-ffi/build.rs index b0b84db0f92..83408b31222 100644 --- a/pyo3-ffi/build.rs +++ b/pyo3-ffi/build.rs @@ -130,6 +130,7 @@ fn ensure_gil_enabled(interpreter_config: &InterpreterConfig) -> Result<()> { ensure!( gil_enabled || std::env::var("UNSAFE_PYO3_BUILD_FREE_THREADED").map_or(false, |os_str| os_str == "1"), "the Python interpreter was built with the GIL disabled, which is not yet supported by PyO3\n\ + = help: see https://github.com/PyO3/pyo3/issues/4265 for more information\n\ = help: please check if an updated version of PyO3 is available. Current version: {}\n\ = help: set UNSAFE_PYO3_BUILD_FREE_THREADED=1 to suppress this check and build anyway for free-threaded Python", std::env::var("CARGO_PKG_VERSION").unwrap() From f75db681e70458b46d1ee38452d9136e775c6ca0 Mon Sep 17 00:00:00 2001 From: Heyuan Zeng Date: Wed, 10 Jul 2024 16:27:42 +0100 Subject: [PATCH 6/6] Fix formatting issues --- noxfile.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/noxfile.py b/noxfile.py index b90f0766420..00802de4446 100644 --- a/noxfile.py +++ b/noxfile.py @@ -648,7 +648,7 @@ def test_version_limits(session: nox.Session): _run_cargo(session, "check", env=env, expect_error=True) # Python build with GIL disabled should fail building - config_file.set("CPython", "3.13", build_flags=['Py_GIL_DISABLED']) + config_file.set("CPython", "3.13", build_flags=["Py_GIL_DISABLED"]) _run_cargo(session, "check", env=env, expect_error=True) # Python build with GIL disabled should pass with env flag on @@ -915,7 +915,9 @@ class _ConfigFile: def __init__(self, config_file) -> None: self._config_file = config_file - def set(self, implementation: str, version: str, build_flags: Iterable[str] = ()) -> None: + def set( + self, implementation: str, version: str, build_flags: Iterable[str] = () + ) -> None: """Set the contents of this config file to the given implementation and version.""" self._config_file.seek(0) self._config_file.truncate(0)