Skip to content
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

Add basic support for GraalPy #1645

Merged
merged 2 commits into from
Jun 4, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ _formerly pyo3-pack_
Build and publish crates with pyo3, rust-cpython, cffi and uniffi bindings as well as rust binaries as python packages.

This project is meant as a zero configuration replacement for [setuptools-rust](https://github.com/PyO3/setuptools-rust) and [milksnake](https://github.com/getsentry/milksnake).
It supports building wheels for python 3.5+ on windows, linux, mac and freebsd, can upload them to [pypi](https://pypi.org/) and has basic pypy support.
It supports building wheels for python 3.5+ on windows, linux, mac and freebsd, can upload them to [pypi](https://pypi.org/) and has basic pypy and graalpy support.

Check out the [User Guide](https://maturin.rs/)!

Expand Down
2 changes: 1 addition & 1 deletion guide/src/bindings.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ specify which bindings to use.

[pyo3](https://github.com/PyO3/pyo3) is Rust bindings for Python,
including tools for creating native Python extension modules.
It supports both CPython and PyPy.
It supports CPython, PyPy, and GraalPy.

maturin automatically detects pyo3 bindings when it's added as a dependency in `Cargo.toml`.

Expand Down
2 changes: 1 addition & 1 deletion guide/src/platform_support.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ supported by [manylinux](https://github.com/pypa/manylinux).
CPython 3.7 to 3.10 are supported and tested on CI, though the entire 3.x series should work.
This will be changed as new python versions are released and others have their end of life.

PyPy 3.6 and later also works.
PyPy 3.6 and later also works, as does GraalPy 23.0 and later.

## Manylinux/Musllinux

Expand Down
6 changes: 4 additions & 2 deletions src/build_context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ pub enum BridgeModel {
/// providing crate, e.g. pyo3, the number is the minimum minor python version
Bindings(String, usize),
/// `Bindings`, but specifically for pyo3 with feature flags that allow building a single wheel
/// for all cpython versions (pypy still needs multiple versions).
/// for all cpython versions (pypy & graalpy still need multiple versions).
/// The numbers are the minimum major and minor version
BindingsAbi3(u8, u8),
/// A native module with c bindings, i.e. `#[no_mangle] extern "C" <some item>`
Expand Down Expand Up @@ -234,7 +234,9 @@ impl BuildContext {
let interp_names: HashSet<_> = non_abi3_interps
.iter()
.map(|interp| match interp.interpreter_kind {
InterpreterKind::CPython => interp.implementation_name.to_string(),
InterpreterKind::CPython | InterpreterKind::GraalPy => {
interp.implementation_name.to_string()
}
InterpreterKind::PyPy => "PyPy".to_string(),
})
.collect();
Expand Down
7 changes: 7 additions & 0 deletions src/build_options.rs
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,8 @@ impl BuildOptions {
Some(InterpreterKind::PyPy)
} else if tag.starts_with("cpython") {
Some(InterpreterKind::CPython)
} else if tag.starts_with("graalpy") {
Some(InterpreterKind::GraalPy)
} else {
None
}
Expand Down Expand Up @@ -1133,6 +1135,11 @@ fn find_interpreter_in_sysconfig(
let python = interp.display().to_string();
let (python_impl, python_ver) = if let Some(ver) = python.strip_prefix("pypy") {
(InterpreterKind::PyPy, ver.strip_prefix('-').unwrap_or(ver))
} else if let Some(ver) = python.strip_prefix("graalpy") {
(
InterpreterKind::GraalPy,
ver.strip_prefix('-').unwrap_or(ver),
)
} else if let Some(ver) = python.strip_prefix("python") {
(
InterpreterKind::CPython,
Expand Down
9 changes: 5 additions & 4 deletions src/compile.rs
Original file line number Diff line number Diff line change
Expand Up @@ -350,10 +350,10 @@ fn compile_target(
}

if let BridgeModel::BindingsAbi3(_, _) = bridge_model {
let is_pypy = python_interpreter
.map(|p| p.interpreter_kind.is_pypy())
let is_pypy_or_graalpy = python_interpreter
.map(|p| p.interpreter_kind.is_pypy() || p.interpreter_kind.is_graalpy())
.unwrap_or(false);
if !is_pypy && !target.is_windows() {
if !is_pypy_or_graalpy && !target.is_windows() {
let pyo3_ver = pyo3_version(&context.cargo_metadata)
.context("Failed to get pyo3 version from cargo metadata")?;
if pyo3_ver < PYO3_ABI3_NO_PYTHON_VERSION {
Expand Down Expand Up @@ -389,7 +389,8 @@ fn compile_target(
} else if (bridge_model.is_bindings("pyo3")
|| bridge_model.is_bindings("pyo3-ffi")
|| (matches!(bridge_model, BridgeModel::BindingsAbi3(_, _))
&& interpreter.interpreter_kind.is_pypy()))
&& (interpreter.interpreter_kind.is_pypy()
|| interpreter.interpreter_kind.is_graalpy())))
&& env::var_os("PYO3_CONFIG_FILE").is_none()
{
let pyo3_config = interpreter.pyo3_config_file();
Expand Down
16 changes: 14 additions & 2 deletions src/python_interpreter/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ use std::io::{BufRead, BufReader};
use std::path::Path;

const PYPY_ABI_TAG: &str = "pp73";
const GRAALPY_ABI_TAG: &str = "graalpy230_310_native";
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what's the graalpy abi versioning policy? would be good to have that as a comment somewhere


/// Some of the sysconfigdata of Python interpreter we care about
#[derive(Debug, Clone, Deserialize, Eq, PartialEq)]
Expand All @@ -17,7 +18,7 @@ pub struct InterpreterConfig {
pub major: usize,
/// Python's minor version
pub minor: usize,
/// cpython or pypy
/// cpython, pypy, or graalpy
#[serde(rename = "interpreter")]
pub interpreter_kind: InterpreterKind,
/// For linux and mac, this contains the value of the abiflags, e.g. "m"
Expand Down Expand Up @@ -68,7 +69,7 @@ impl InterpreterConfig {
target.target_env().to_string().replace("musl", "gnu")
}
}
PyPy => "gnu".to_string(),
PyPy | GraalPy => "gnu".to_string(),
};
match (target.target_os(), python_impl) {
(Os::Linux, CPython) => {
Expand Down Expand Up @@ -316,6 +317,7 @@ impl InterpreterConfig {
}
}
InterpreterKind::PyPy => abi_tag.unwrap_or_else(|| PYPY_ABI_TAG.to_string()),
InterpreterKind::GraalPy => abi_tag.unwrap_or_else(|| GRAALPY_ABI_TAG.to_string()),
};
let file_ext = if target.is_windows() { "pyd" } else { "so" };
let ext_suffix = if target.is_linux() || target.is_macos() {
Expand Down Expand Up @@ -350,6 +352,16 @@ impl InterpreterConfig {
file_ext,
)
}),
InterpreterKind::GraalPy => ext_suffix.unwrap_or_else(|| {
// e.g. .graalpy230-310-native-x86_64-linux.so
format!(
".{}-{}-{}.{}",
abi_tag.replace('_', "-"),
target.get_python_arch(),
target.get_python_os(),
file_ext,
)
}),
}
} else if target.is_emscripten() && matches!(interpreter_kind, InterpreterKind::CPython) {
ext_suffix.unwrap_or_else(|| {
Expand Down
19 changes: 18 additions & 1 deletion src/python_interpreter/get_interpreter_metadata.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,23 @@
else:
ext_suffix = sysconfig.get_config_var("EXT_SUFFIX")


def get_abi_tag():
# This should probably return the ABI tag based on EXT_SUFFIX in the same
# way as pypa/packaging. See https://github.com/pypa/packaging/pull/607.
# For simplicity, we just fix it up for GraalPy for now and leave the logic
# for the other interpreters untouched, but this should be fixed properly
# down the road.
Comment on lines +24 to +28
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i'd be happy to switch this over to what pypa/packaging does entirely, their logic is authoritative for all other tools anyway

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's tracked here: #1545

if platform.python_implementation() == "GraalVM":
ext_suffix = sysconfig.get_config_var("EXT_SUFFIX")
parts = ext_suffix.split(".")
soabi = parts[1]
abi = "-".join(soabi.split("-")[:3])
return abi.replace(".", "_").replace("-", "_")
else:
return (sysconfig.get_config_var("SOABI") or "-").split("-")[1]


metadata = {
# sys.implementation.name can differ from platform.python_implementation(), for example
# Pyston has sys.implementation.name == "pyston" while platform.python_implementation() == cpython
Expand All @@ -30,7 +47,7 @@
"interpreter": platform.python_implementation().lower(),
"ext_suffix": ext_suffix,
"soabi": sysconfig.get_config_var("SOABI") or None,
"abi_tag": (sysconfig.get_config_var("SOABI") or "-").split("-")[1] or None,
"abi_tag": get_abi_tag() or None,
"platform": sysconfig.get_platform(),
# This one isn't technically necessary, but still very useful for sanity checks
"system": platform.system().lower(),
Expand Down
34 changes: 30 additions & 4 deletions src/python_interpreter/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -291,6 +291,7 @@ fn windows_python_info(executable: &Path) -> Result<Option<InterpreterConfig>> {
pub enum InterpreterKind {
CPython,
PyPy,
GraalPy,
}

impl InterpreterKind {
Expand All @@ -303,13 +304,19 @@ impl InterpreterKind {
pub fn is_pypy(&self) -> bool {
matches!(self, InterpreterKind::PyPy)
}

/// Is this a GraalPy interpreter?
pub fn is_graalpy(&self) -> bool {
matches!(self, InterpreterKind::GraalPy)
}
}

impl fmt::Display for InterpreterKind {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
InterpreterKind::CPython => write!(f, "CPython"),
InterpreterKind::PyPy => write!(f, "PyPy"),
InterpreterKind::GraalPy => write!(f, "GraalPy"),
}
}
}
Expand All @@ -321,6 +328,7 @@ impl FromStr for InterpreterKind {
match s.to_ascii_lowercase().as_str() {
"cpython" => Ok(InterpreterKind::CPython),
"pypy" => Ok(InterpreterKind::PyPy),
"graalvm" | "graalpy" => Ok(InterpreterKind::GraalPy),
unknown => Err(format!("Unknown interpreter kind '{unknown}'")),
}
}
Expand Down Expand Up @@ -407,8 +415,8 @@ fn fun_with_abiflags(
);
}

if message.interpreter == "pypy" {
// pypy does not specify abi flags
if message.interpreter == "pypy" || message.interpreter == "graalvm" {
// pypy and graalpy do not specify abi flags
Ok("".to_string())
} else if message.system == "windows" {
if matches!(message.abiflags.as_deref(), Some("") | None) {
Expand Down Expand Up @@ -438,7 +446,7 @@ impl PythonInterpreter {
} else {
match self.interpreter_kind {
InterpreterKind::CPython => true,
InterpreterKind::PyPy => false,
InterpreterKind::PyPy | InterpreterKind::GraalPy => false,
}
}
}
Expand Down Expand Up @@ -516,6 +524,23 @@ impl PythonInterpreter {
platform = platform,
)
}
InterpreterKind::GraalPy => {
// GraalPy suffers from pypa/packaging#614, where
// packaging misdetects it as a 32-bit implementation,
// so GraalPy adds the correct platform itself, e.g.
// graalpy 3.10 23.1 => numpy-1.23.5-graalpy310-graalpy231_310_native_x86_64_linux-linux_i686.whl
format!(
"graalpy{major}{minor}-{abi_tag}_{arch}_{os}-{os}_i686",
major = self.major,
minor = self.minor,
abi_tag = self
.abi_tag
.clone()
.expect("GraalPy's syconfig didn't define an `EXT_SUFFIX` ಠ_ಠ"),
os = target.get_python_os(),
arch = target.get_python_arch(),
)
}
}
};
Ok(tag)
Expand Down Expand Up @@ -635,6 +660,7 @@ impl PythonInterpreter {
let interpreter = match message.interpreter.as_str() {
"cpython" => InterpreterKind::CPython,
"pypy" => InterpreterKind::PyPy,
"graalvm" | "graalpy" => InterpreterKind::GraalPy,
other => {
bail!("Unsupported interpreter {}", other);
}
Expand Down Expand Up @@ -888,7 +914,7 @@ impl PythonInterpreter {
pub fn get_venv_site_package(&self, venv_base: impl AsRef<Path>, target: &Target) -> PathBuf {
if target.is_unix() {
match self.interpreter_kind {
InterpreterKind::CPython => {
InterpreterKind::CPython | InterpreterKind::GraalPy => {
let python_dir = format!("python{}.{}", self.major, self.minor);
venv_base
.as_ref()
Expand Down
4 changes: 2 additions & 2 deletions tests/run.rs
Original file line number Diff line number Diff line change
Expand Up @@ -158,8 +158,8 @@ fn integration_pyo3_bin() {
target.get_python()
});
let python_implementation = get_python_implementation(&python).unwrap();
if python_implementation == "pypy" {
// PyPy doesn't support the 'auto-initialize' feature of pyo3
if python_implementation == "pypy" || python_implementation == "graalpy" {
// PyPy & GraalPy do not support the 'auto-initialize' feature of pyo3
return;
}

Expand Down