diff --git a/newsfragments/4327.packaging.md b/newsfragments/4327.packaging.md new file mode 100644 index 00000000..c98d06f7 --- /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 diff --git a/noxfile.py b/noxfile.py index 4757a282..5d8123c7 100644 --- a/noxfile.py +++ b/noxfile.py @@ -9,7 +9,7 @@ import tempfile 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 @@ -655,6 +655,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): @@ -919,7 +927,9 @@ 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) @@ -927,6 +937,7 @@ class _ConfigFile: f"""\ implementation={implementation} version={version} +build_flags={','.join(build_flags)} suppress_build_script_link_lines=true """ ) diff --git a/pyo3-build-config/src/impl_.rs b/pyo3-build-config/src/impl_.rs index 3dc1e912..d38d41ed 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 b4521678..83408b31 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,24 @@ 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 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() + ); + + 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 +204,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()?;