Skip to content

Commit 22d4f43

Browse files
Add minimal capi lifecycle support (#7648)
* Add minimal capi lifecycle support * Force enable `threading` on `stdlib` --------- Co-authored-by: Jeong, YunWon <jeong@youknowone.org>
1 parent 02a2b19 commit 22d4f43

21 files changed

Lines changed: 418 additions & 71 deletions

.cspell.dict/cpython.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,7 @@ Pyfunc
161161
pylifecycle
162162
pymain
163163
pyrepl
164+
pystate
164165
PYTHONTRACEMALLOC
165166
PYTHONUTF8
166167
pythonw

.cspell.dict/python-more.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,7 @@ pycodecs
189189
pycs
190190
pydatetime
191191
pyexpat
192+
PYGILSTATE
192193
pyio
193194
pymain
194195
PYTHONAPI

Cargo.lock

Lines changed: 10 additions & 7 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ repository.workspace = true
1010
license.workspace = true
1111

1212
[features]
13+
capi = ["dep:rustpython-capi", "threading"]
1314
default = ["threading", "stdlib", "stdio", "importlib", "ssl-rustls", "host_env"]
1415
host_env = ["rustpython-vm/host_env", "rustpython-stdlib?/host_env"]
1516
importlib = ["rustpython-vm/importlib"]
@@ -31,6 +32,7 @@ tkinter = ["rustpython-stdlib/tkinter"]
3132
winresource = "0.1"
3233

3334
[dependencies]
35+
rustpython-capi = { workspace = true, optional = true }
3436
rustpython-compiler = { workspace = true }
3537
rustpython-pylib = { workspace = true, optional = true }
3638
rustpython-stdlib = { workspace = true, optional = true, features = ["compiler"] }
@@ -140,6 +142,7 @@ repository = "https://github.com/RustPython/RustPython"
140142
license = "MIT"
141143

142144
[workspace.dependencies]
145+
rustpython-capi = { path = "crates/capi", version = "0.5.0" }
143146
rustpython-compiler-core = { path = "crates/compiler-core", version = "0.5.0" }
144147
rustpython-compiler = { path = "crates/compiler", version = "0.5.0" }
145148
rustpython-codegen = { path = "crates/codegen", version = "0.5.0" }
@@ -257,7 +260,6 @@ rustls-platform-verifier = "0.7"
257260
rustyline = "18"
258261
serde = { package = "serde_core", version = "1.0.225", default-features = false, features = ["alloc"] }
259262
schannel = "0.1.29"
260-
scoped-tls = "1"
261263
scopeguard = "1"
262264
sha-1 = "0.10.0"
263265
sha2 = "0.10.2"

build.rs

Lines changed: 25 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,29 @@
11
fn main() {
2-
if std::env::var("CARGO_CFG_TARGET_OS").unwrap() == "windows" {
3-
println!("cargo:rerun-if-changed=logo.ico");
4-
let mut res = winresource::WindowsResource::new();
5-
if std::path::Path::new("logo.ico").exists() {
6-
res.set_icon("logo.ico");
7-
} else {
8-
println!("cargo:warning=logo.ico not found, skipping icon embedding");
9-
return;
2+
let target = std::env::var("CARGO_CFG_TARGET_OS").unwrap();
3+
let capi_enabled = std::env::var_os("CARGO_FEATURE_CAPI").is_some();
4+
5+
match target.as_str() {
6+
"linux" if capi_enabled => {
7+
println!("cargo:rustc-link-arg-bin=rustpython=-Wl,--export-dynamic");
108
}
11-
res.compile()
12-
.map_err(|e| {
13-
println!("cargo:warning=Failed to compile Windows resources: {e}");
14-
})
15-
.ok();
9+
"macos" if capi_enabled => {
10+
println!("cargo:rustc-link-arg-bin=rustpython=-Wl,-export_dynamic");
11+
}
12+
"windows" => {
13+
println!("cargo:rerun-if-changed=logo.ico");
14+
let mut res = winresource::WindowsResource::new();
15+
if std::path::Path::new("logo.ico").exists() {
16+
res.set_icon("logo.ico");
17+
} else {
18+
println!("cargo:warning=logo.ico not found, skipping icon embedding");
19+
return;
20+
}
21+
res.compile()
22+
.map_err(|e| {
23+
println!("cargo:warning=Failed to compile Windows resources: {e}");
24+
})
25+
.ok();
26+
}
27+
_ => {}
1628
}
1729
}

crates/capi/.cargo/config.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
[env]
2+
PYO3_CONFIG_FILE = { value = "pyo3-rustpython.config", relative = true }
3+
PYO3_NO_PYTHON = { value = "1" }

crates/capi/Cargo.toml

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
[package]
2+
name = "rustpython-capi"
3+
description = "Minimal CPython C-API compatibility exports for RustPython"
4+
version.workspace = true
5+
authors.workspace = true
6+
edition.workspace = true
7+
rust-version.workspace = true
8+
repository.workspace = true
9+
license.workspace = true
10+
11+
[lib]
12+
crate-type = ["cdylib", "rlib"]
13+
14+
[dependencies]
15+
rustpython-vm = { workspace = true, features = ["threading"] }
16+
rustpython-stdlib = {workspace = true, features = ["threading"] }
17+
18+
[dev-dependencies]
19+
pyo3 = { version = "0.28", features = ["auto-initialize", "abi3"] }
20+
21+
[lints]
22+
workspace = true
23+
24+
[package.metadata.cargo-shear]
25+
# Not a direct dependency (yet), but we need to enable threading support in the stdlib.
26+
ignored = ["rustpython-stdlib"]

crates/capi/pyo3-rustpython.config

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
implementation=CPython
2+
version=3.14
3+
shared=true
4+
abi3=true
5+
suppress_build_script_link_lines=true

crates/capi/src/lib.rs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
#![allow(clippy::missing_safety_doc)]
2+
3+
use crate::pylifecycle::MAIN_INTERP;
4+
use rustpython_vm::Interpreter;
5+
pub use rustpython_vm::PyObject;
6+
use std::sync::MutexGuard;
7+
8+
extern crate alloc;
9+
10+
pub mod pylifecycle;
11+
pub mod pystate;
12+
pub mod refcount;
13+
14+
/// Get main interpreter of this process. Will be None if it has not been initialized yet.
15+
pub fn get_main_interpreter() -> MutexGuard<'static, Option<Interpreter>> {
16+
MAIN_INTERP
17+
.lock()
18+
.expect("Failed to lock interpreter mutex")
19+
}

crates/capi/src/pylifecycle.rs

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
use crate::get_main_interpreter;
2+
use crate::pystate::ensure_thread_has_vm_attached;
3+
use core::ffi::c_int;
4+
use rustpython_vm::Interpreter;
5+
use rustpython_vm::vm::thread::ThreadedVirtualMachine;
6+
use std::sync::Mutex;
7+
8+
pub(crate) static MAIN_INTERP: Mutex<Option<Interpreter>> = Mutex::new(None);
9+
10+
/// Request a thread local vm from the main interpreter
11+
pub(crate) fn request_vm_from_interpreter() -> ThreadedVirtualMachine {
12+
get_main_interpreter()
13+
.as_ref()
14+
.expect("Interpreter not initialized")
15+
.enter(|vm| vm.new_thread())
16+
}
17+
18+
#[unsafe(no_mangle)]
19+
pub extern "C" fn Py_IsInitialized() -> c_int {
20+
get_main_interpreter().is_some() as c_int
21+
}
22+
23+
#[unsafe(no_mangle)]
24+
pub extern "C" fn Py_Initialize() {
25+
Py_InitializeEx(0);
26+
}
27+
28+
#[unsafe(no_mangle)]
29+
pub extern "C" fn Py_InitializeEx(_initsigs: c_int) {
30+
let mut interp = get_main_interpreter();
31+
if interp.is_none() {
32+
*interp = Interpreter::with_init(Default::default(), |_vm| {}).into();
33+
drop(interp);
34+
ensure_thread_has_vm_attached();
35+
}
36+
}
37+
38+
#[unsafe(no_mangle)]
39+
pub extern "C" fn Py_Finalize() {
40+
let _ = Py_FinalizeEx();
41+
}
42+
43+
#[unsafe(no_mangle)]
44+
pub extern "C" fn Py_FinalizeEx() -> c_int {
45+
0
46+
}
47+
48+
#[unsafe(no_mangle)]
49+
pub extern "C" fn Py_IsFinalizing() -> c_int {
50+
0
51+
}

0 commit comments

Comments
 (0)