Prevent building in GIL-less environment (#4327)

* Prevent building in GIL-less environment

* Add change log

* add "yet" to phrasing

* Add testing to build script

* add link to issue

* Fix formatting issues

---------

Co-authored-by: David Hewitt <mail@davidhewitt.dev>
This commit is contained in:
Larry Z 2024-07-10 19:39:39 +01:00 committed by GitHub
parent 90c4799951
commit 6be80647cb
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 41 additions and 4 deletions

View File

@ -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.

View File

@ -9,7 +9,7 @@ import tempfile
from functools import lru_cache from functools import lru_cache
from glob import glob from glob import glob
from pathlib import Path 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
import nox.command import nox.command
@ -655,6 +655,14 @@ def test_version_limits(session: nox.Session):
config_file.set("PyPy", "3.11") config_file.set("PyPy", "3.11")
_run_cargo(session, "check", env=env, expect_error=True) _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") @nox.session(name="check-feature-powerset", venv_backend="none")
def check_feature_powerset(session: nox.Session): def check_feature_powerset(session: nox.Session):
@ -919,7 +927,9 @@ class _ConfigFile:
def __init__(self, config_file) -> None: def __init__(self, config_file) -> None:
self._config_file = config_file 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.""" """Set the contents of this config file to the given implementation and version."""
self._config_file.seek(0) self._config_file.seek(0)
self._config_file.truncate(0) self._config_file.truncate(0)
@ -927,6 +937,7 @@ class _ConfigFile:
f"""\ f"""\
implementation={implementation} implementation={implementation}
version={version} version={version}
build_flags={','.join(build_flags)}
suppress_build_script_link_lines=true suppress_build_script_link_lines=true
""" """
) )

View File

@ -996,6 +996,7 @@ pub enum BuildFlag {
Py_DEBUG, Py_DEBUG,
Py_REF_DEBUG, Py_REF_DEBUG,
Py_TRACE_REFS, Py_TRACE_REFS,
Py_GIL_DISABLED,
COUNT_ALLOCS, COUNT_ALLOCS,
Other(String), Other(String),
} }
@ -1016,6 +1017,7 @@ impl FromStr for BuildFlag {
"Py_DEBUG" => Ok(BuildFlag::Py_DEBUG), "Py_DEBUG" => Ok(BuildFlag::Py_DEBUG),
"Py_REF_DEBUG" => Ok(BuildFlag::Py_REF_DEBUG), "Py_REF_DEBUG" => Ok(BuildFlag::Py_REF_DEBUG),
"Py_TRACE_REFS" => Ok(BuildFlag::Py_TRACE_REFS), "Py_TRACE_REFS" => Ok(BuildFlag::Py_TRACE_REFS),
"Py_GIL_DISABLED" => Ok(BuildFlag::Py_GIL_DISABLED),
"COUNT_ALLOCS" => Ok(BuildFlag::COUNT_ALLOCS), "COUNT_ALLOCS" => Ok(BuildFlag::COUNT_ALLOCS),
other => Ok(BuildFlag::Other(other.to_owned())), other => Ok(BuildFlag::Other(other.to_owned())),
} }
@ -1039,10 +1041,11 @@ impl FromStr for BuildFlag {
pub struct BuildFlags(pub HashSet<BuildFlag>); pub struct BuildFlags(pub HashSet<BuildFlag>);
impl BuildFlags { impl BuildFlags {
const ALL: [BuildFlag; 4] = [ const ALL: [BuildFlag; 5] = [
BuildFlag::Py_DEBUG, BuildFlag::Py_DEBUG,
BuildFlag::Py_REF_DEBUG, BuildFlag::Py_REF_DEBUG,
BuildFlag::Py_TRACE_REFS, BuildFlag::Py_TRACE_REFS,
BuildFlag::Py_GIL_DISABLED,
BuildFlag::COUNT_ALLOCS, BuildFlag::COUNT_ALLOCS,
]; ];

View File

@ -4,8 +4,9 @@ use pyo3_build_config::{
cargo_env_var, env_var, errors::Result, is_linking_libpython, resolve_interpreter_config, cargo_env_var, env_var, errors::Result, is_linking_libpython, resolve_interpreter_config,
InterpreterConfig, PythonVersion, InterpreterConfig, PythonVersion,
}, },
warn, PythonImplementation, warn, BuildFlag, PythonImplementation,
}; };
use std::ops::Not;
/// Minimum Python version PyO3 supports. /// Minimum Python version PyO3 supports.
struct SupportedVersions { struct SupportedVersions {
@ -120,6 +121,24 @@ fn ensure_python_version(interpreter_config: &InterpreterConfig) -> Result<()> {
Ok(()) 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<()> { fn ensure_target_pointer_width(interpreter_config: &InterpreterConfig) -> Result<()> {
if let Some(pointer_width) = interpreter_config.pointer_width { if let Some(pointer_width) = interpreter_config.pointer_width {
// Try to check whether the target architecture matches the python library // 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_python_version(&interpreter_config)?;
ensure_target_pointer_width(&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. // Serialize the whole interpreter config into DEP_PYTHON_PYO3_CONFIG env var.
interpreter_config.to_cargo_dep_env()?; interpreter_config.to_cargo_dep_env()?;