Skip to content

Commit

Permalink
Prevent building in GIL-less environment (#4327)
Browse files Browse the repository at this point in the history
* 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>
  • Loading branch information
FlickerSoul and davidhewitt committed Jul 16, 2024
1 parent 4b7c092 commit 05cf847
Show file tree
Hide file tree
Showing 4 changed files with 41 additions and 4 deletions.
3 changes: 3 additions & 0 deletions newsfragments/4327.packaging.md
Original file line number Diff line number Diff line change
@@ -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.
15 changes: 13 additions & 2 deletions noxfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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):
Expand Down Expand Up @@ -919,14 +927,17 @@ 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)
self._config_file.write(
f"""\
implementation={implementation}
version={version}
build_flags={','.join(build_flags)}
suppress_build_script_link_lines=true
"""
)
Expand Down
5 changes: 4 additions & 1 deletion pyo3-build-config/src/impl_.rs
Original file line number Diff line number Diff line change
Expand Up @@ -996,6 +996,7 @@ pub enum BuildFlag {
Py_DEBUG,
Py_REF_DEBUG,
Py_TRACE_REFS,
Py_GIL_DISABLED,
COUNT_ALLOCS,
Other(String),
}
Expand All @@ -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())),
}
Expand All @@ -1039,10 +1041,11 @@ impl FromStr for BuildFlag {
pub struct BuildFlags(pub HashSet<BuildFlag>);

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,
];

Expand Down
22 changes: 21 additions & 1 deletion pyo3-ffi/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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()?;
Expand Down

0 comments on commit 05cf847

Please sign in to comment.