diff --git a/Cargo.lock b/Cargo.lock index 5fd981fd135..1eca00b9fbb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2643,9 +2643,9 @@ dependencies = [ [[package]] name = "pyo3" -version = "0.28.2" +version = "0.28.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf85e27e86080aafd5a22eae58a162e133a589551542b3e5cee4beb27e54f8e1" +checksum = "91fd8e38a3b50ed1167fb981cd6fd60147e091784c427b8f7183a7ee32c31c12" dependencies = [ "libc", "once_cell", @@ -2657,18 +2657,18 @@ dependencies = [ [[package]] name = "pyo3-build-config" -version = "0.28.2" +version = "0.28.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8bf94ee265674bf76c09fa430b0e99c26e319c945d96ca0d5a8215f31bf81cf7" +checksum = "e368e7ddfdeb98c9bca7f8383be1648fd84ab466bf2bc015e94008db6d35611e" dependencies = [ "target-lexicon", ] [[package]] name = "pyo3-ffi" -version = "0.28.2" +version = "0.28.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "491aa5fc66d8059dd44a75f4580a2962c1862a1c2945359db36f6c2818b748dc" +checksum = "7f29e10af80b1f7ccaf7f69eace800a03ecd13e883acfacc1e5d0988605f651e" dependencies = [ "libc", "pyo3-build-config", @@ -2676,9 +2676,9 @@ dependencies = [ [[package]] name = "pyo3-macros" -version = "0.28.2" +version = "0.28.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5d671734e9d7a43449f8480f8b38115df67bef8d21f76837fa75ee7aaa5e52e" +checksum = "df6e520eff47c45997d2fc7dd8214b25dd1310918bbb2642156ef66a67f29813" dependencies = [ "proc-macro2", "pyo3-macros-backend", @@ -2688,9 +2688,9 @@ dependencies = [ [[package]] name = "pyo3-macros-backend" -version = "0.28.2" +version = "0.28.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22faaa1ce6c430a1f71658760497291065e6450d7b5dc2bcf254d49f66ee700a" +checksum = "c4cdc218d835738f81c2338f822078af45b4afdf8b2e33cbb5916f108b813acb" dependencies = [ "heck", "proc-macro2", @@ -3104,6 +3104,13 @@ dependencies = [ "winresource", ] +[[package]] +name = "rustpython-capi" +version = "0.5.0" +dependencies = [ + "rustpython-vm", +] + [[package]] name = "rustpython-codegen" version = "0.5.0" diff --git a/crates/capi/Cargo.toml b/crates/capi/Cargo.toml new file mode 100644 index 00000000000..964a996143a --- /dev/null +++ b/crates/capi/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "rustpython-capi" +description = "Minimal CPython C-API compatibility exports for RustPython" +version.workspace = true +authors.workspace = true +edition.workspace = true +rust-version.workspace = true +repository.workspace = true +license.workspace = true + +[lib] +crate-type = ["staticlib"] + +[dependencies] +rustpython-vm = { workspace = true, features = ["threading"]} + +[lints] +workspace = true diff --git a/crates/capi/src/bytesobject.rs b/crates/capi/src/bytesobject.rs new file mode 100644 index 00000000000..a5164bc8487 --- /dev/null +++ b/crates/capi/src/bytesobject.rs @@ -0,0 +1,16 @@ +use core::ffi::c_char; +use core::ptr; + +use crate::PyObject; + +#[unsafe(no_mangle)] +pub extern "C" fn PyBytes_Size(_bytes: *mut PyObject) -> isize { + crate::log_stub("PyBytes_Size"); + 0 +} + +#[unsafe(no_mangle)] +pub extern "C" fn PyBytes_AsString(_bytes: *mut PyObject) -> *mut c_char { + crate::log_stub("PyBytes_AsString"); + ptr::null_mut() +} diff --git a/crates/capi/src/import.rs b/crates/capi/src/import.rs new file mode 100644 index 00000000000..29ea7996687 --- /dev/null +++ b/crates/capi/src/import.rs @@ -0,0 +1,9 @@ +use core::ptr; + +use crate::PyObject; + +#[unsafe(no_mangle)] +pub extern "C" fn PyImport_Import(_name: *mut PyObject) -> *mut PyObject { + crate::log_stub("PyImport_Import"); + ptr::null_mut() +} diff --git a/crates/capi/src/lib.rs b/crates/capi/src/lib.rs new file mode 100644 index 00000000000..7080bdbae49 --- /dev/null +++ b/crates/capi/src/lib.rs @@ -0,0 +1,20 @@ +pub use rustpython_vm::PyObject; + +extern crate alloc; + +pub mod bytesobject; +pub mod import; +pub mod longobject; +pub mod object; +pub mod pyerrors; +pub mod pylifecycle; +pub mod pystate; +pub mod refcount; +pub mod traceback; +pub mod tupleobject; +pub mod unicodeobject; + +#[inline] +pub(crate) fn log_stub(name: &str) { + eprintln!("[rustpython-capi stub] {name} called"); +} diff --git a/crates/capi/src/longobject.rs b/crates/capi/src/longobject.rs new file mode 100644 index 00000000000..efaa0fc2cfc --- /dev/null +++ b/crates/capi/src/longobject.rs @@ -0,0 +1,65 @@ +use core::ffi::{c_long, c_longlong, c_ulong, c_ulonglong}; +use core::ptr; + +use crate::PyObject; +use crate::pystate::with_vm; +use rustpython_vm::PyObjectRef; +use rustpython_vm::builtins::PyInt; + +#[unsafe(no_mangle)] +pub extern "C" fn PyLong_FromLong(value: c_long) -> *mut PyObject { + with_vm(|vm| { + let obj: PyObjectRef = vm.ctx.new_int(value).into(); + obj.into_raw().as_ptr() + }) +} + +#[unsafe(no_mangle)] +pub extern "C" fn PyLong_FromLongLong(_value: c_longlong) -> *mut PyObject { + crate::log_stub("PyLong_FromLongLong"); + ptr::null_mut() +} + +#[unsafe(no_mangle)] +pub extern "C" fn PyLong_FromSsize_t(_value: isize) -> *mut PyObject { + crate::log_stub("PyLong_FromSsize_t"); + ptr::null_mut() +} + +#[unsafe(no_mangle)] +pub extern "C" fn PyLong_FromSize_t(_value: usize) -> *mut PyObject { + crate::log_stub("PyLong_FromSize_t"); + ptr::null_mut() +} + +#[unsafe(no_mangle)] +pub extern "C" fn PyLong_FromUnsignedLong(_value: c_ulong) -> *mut PyObject { + crate::log_stub("PyLong_FromUnsignedLong"); + ptr::null_mut() +} + +#[unsafe(no_mangle)] +pub extern "C" fn PyLong_FromUnsignedLongLong(_value: c_ulonglong) -> *mut PyObject { + crate::log_stub("PyLong_FromUnsignedLongLong"); + ptr::null_mut() +} + +#[unsafe(no_mangle)] +pub extern "C" fn PyLong_AsLong(obj: *mut PyObject) -> c_long { + if obj.is_null() { + panic!("PyLong_AsLong called with null object"); + } + + with_vm(|_vm| { + // SAFETY: non-null checked above; caller promises a valid PyObject pointer. + let obj_ref = unsafe { &*obj }; + let int_obj = obj_ref + .downcast_ref::() + .expect("PyLong_AsLong currently only accepts int instances"); + + int_obj + .as_bigint() + .try_into() + .expect("PyLong_AsLong: value out of range for c_long") + }) +} diff --git a/crates/capi/src/object.rs b/crates/capi/src/object.rs new file mode 100644 index 00000000000..f041d2774e3 --- /dev/null +++ b/crates/capi/src/object.rs @@ -0,0 +1,145 @@ +use core::ffi::c_ulong; + +use crate::PyObject; +use rustpython_vm::builtins::PyType; +use rustpython_vm::{AsObject, Context, Py}; +use std::sync::LazyLock; + +pub struct PyTypeObject { + ty: LazyLock<&'static Py>, + flags: c_ulong, +} + +impl PyTypeObject { + const fn new(f: fn() -> &'static Py, flags: c_ulong) -> PyTypeObject { + PyTypeObject { + ty: LazyLock::new(f), + flags, + } + } +} + +const PY_TPFLAGS_HAVE_STACKLESS_EXTENSION: c_ulong = 0; +const PY_TPFLAGS_HAVE_VERSION_TAG: c_ulong = 1 << 18; +const PY_TPFLAGS_DEFAULT: c_ulong = + PY_TPFLAGS_HAVE_STACKLESS_EXTENSION | PY_TPFLAGS_HAVE_VERSION_TAG; +const PY_TPFLAGS_IMMUTABLETYPE: c_ulong = 1 << 8; +const PY_TPFLAGS_BASETYPE: c_ulong = 1 << 10; +const PY_TPFLAGS_LONG_SUBCLASS: c_ulong = 1 << 24; +const PY_TPFLAGS_TUPLE_SUBCLASS: c_ulong = 1 << 26; +const PY_TPFLAGS_UNICODE_SUBCLASS: c_ulong = 1 << 28; +const PY_TPFLAGS_TYPE_SUBCLASS: c_ulong = 1 << 31; + +#[unsafe(no_mangle)] +pub static mut PyType_Type: PyTypeObject = PyTypeObject::new( + || { + let zoo = &Context::genesis().types; + zoo.type_type + }, + PY_TPFLAGS_DEFAULT | PY_TPFLAGS_IMMUTABLETYPE | PY_TPFLAGS_BASETYPE | PY_TPFLAGS_TYPE_SUBCLASS, +); + +#[unsafe(no_mangle)] +pub static mut PyLong_Type: PyTypeObject = PyTypeObject::new( + || { + let zoo = &Context::genesis().types; + zoo.int_type + }, + PY_TPFLAGS_DEFAULT | PY_TPFLAGS_IMMUTABLETYPE | PY_TPFLAGS_BASETYPE | PY_TPFLAGS_LONG_SUBCLASS, +); + +#[unsafe(no_mangle)] +pub static mut PyTuple_Type: PyTypeObject = PyTypeObject::new( + || { + let zoo = &Context::genesis().types; + zoo.tuple_type + }, + PY_TPFLAGS_DEFAULT | PY_TPFLAGS_IMMUTABLETYPE | PY_TPFLAGS_BASETYPE | PY_TPFLAGS_TUPLE_SUBCLASS, +); + +#[unsafe(no_mangle)] +pub static mut PyUnicode_Type: PyTypeObject = PyTypeObject::new( + || { + let zoo = &Context::genesis().types; + zoo.str_type + }, + PY_TPFLAGS_DEFAULT + | PY_TPFLAGS_IMMUTABLETYPE + | PY_TPFLAGS_BASETYPE + | PY_TPFLAGS_UNICODE_SUBCLASS, +); + +#[unsafe(no_mangle)] +pub extern "C" fn Py_TYPE(op: *mut PyObject) -> *mut PyTypeObject { + if op.is_null() { + return std::ptr::null_mut(); + } + + unsafe { + let ty = (*op).class(); + if ty.is(*PyType_Type.ty) { + &raw mut PyType_Type + } else if ty.is(*PyLong_Type.ty) { + &raw mut PyLong_Type + } else if ty.is(*PyTuple_Type.ty) { + &raw mut PyTuple_Type + } else if ty.is(*PyUnicode_Type.ty) { + &raw mut PyUnicode_Type + } else { + todo!("Unsupported type: {:?}", ty.name()); + } + } +} + +#[unsafe(no_mangle)] +#[allow(clippy::not_unsafe_ptr_arg_deref)] +pub extern "C" fn PyType_GetFlags(ty: *mut PyTypeObject) -> c_ulong { + if ty.is_null() { + panic!("PyType_GetFlags called with null type pointer"); + } + + // SAFETY: caller guarantees this is a valid exported type object pointer. + unsafe { (*ty).flags } +} + +#[unsafe(no_mangle)] +pub extern "C" fn PyType_GetName(_ty: *mut PyTypeObject) -> *mut PyObject { + crate::log_stub("PyType_GetName"); + std::ptr::null_mut() +} + +#[unsafe(no_mangle)] +pub extern "C" fn PyType_GetQualName(_ty: *mut PyTypeObject) -> *mut PyObject { + crate::log_stub("PyType_GetQualName"); + std::ptr::null_mut() +} + +#[unsafe(no_mangle)] +pub extern "C" fn PyObject_CallNoArgs(_callable: *mut PyObject) -> *mut PyObject { + crate::log_stub("PyObject_CallNoArgs"); + std::ptr::null_mut() +} + +#[unsafe(no_mangle)] +pub extern "C" fn PyObject_GetAttr(_obj: *mut PyObject, _name: *mut PyObject) -> *mut PyObject { + crate::log_stub("PyObject_GetAttr"); + std::ptr::null_mut() +} + +#[unsafe(no_mangle)] +pub extern "C" fn PyObject_Repr(_obj: *mut PyObject) -> *mut PyObject { + crate::log_stub("PyObject_Repr"); + std::ptr::null_mut() +} + +#[unsafe(no_mangle)] +pub extern "C" fn PyObject_Str(_obj: *mut PyObject) -> *mut PyObject { + crate::log_stub("PyObject_Str"); + std::ptr::null_mut() +} + +#[unsafe(no_mangle)] +pub extern "C" fn Py_GetConstantBorrowed(_constant_id: core::ffi::c_uint) -> *mut PyObject { + crate::log_stub("Py_GetConstantBorrowed"); + std::ptr::null_mut() +} diff --git a/crates/capi/src/pyerrors.rs b/crates/capi/src/pyerrors.rs new file mode 100644 index 00000000000..3a567bcfece --- /dev/null +++ b/crates/capi/src/pyerrors.rs @@ -0,0 +1,61 @@ +use core::ffi::{c_char, c_int}; +use core::ptr; + +use crate::PyObject; + +#[unsafe(no_mangle)] +pub static mut PyExc_BaseException: *mut PyObject = ptr::null_mut(); + +#[unsafe(no_mangle)] +pub static mut PyExc_TypeError: *mut PyObject = ptr::null_mut(); + +#[unsafe(no_mangle)] +pub static mut PyExc_OverflowError: *mut PyObject = ptr::null_mut(); + +#[unsafe(no_mangle)] +pub extern "C" fn PyErr_GetRaisedException() -> *mut PyObject { + crate::log_stub("PyErr_GetRaisedException"); + ptr::null_mut() +} + +#[unsafe(no_mangle)] +pub extern "C" fn PyErr_SetRaisedException(_exc: *mut PyObject) { + crate::log_stub("PyErr_SetRaisedException"); +} + +#[unsafe(no_mangle)] +pub extern "C" fn PyErr_SetObject(_exception: *mut PyObject, _value: *mut PyObject) { + crate::log_stub("PyErr_SetObject"); +} + +#[unsafe(no_mangle)] +pub extern "C" fn PyErr_SetString(_exception: *mut PyObject, _message: *const c_char) { + crate::log_stub("PyErr_SetString"); +} + +#[unsafe(no_mangle)] +pub extern "C" fn PyErr_PrintEx(_set_sys_last_vars: c_int) { + crate::log_stub("PyErr_PrintEx"); +} + +#[unsafe(no_mangle)] +pub extern "C" fn PyErr_WriteUnraisable(_obj: *mut PyObject) { + crate::log_stub("PyErr_WriteUnraisable"); +} + +#[unsafe(no_mangle)] +pub extern "C" fn PyErr_NewExceptionWithDoc( + _name: *const c_char, + _doc: *const c_char, + _base: *mut PyObject, + _dict: *mut PyObject, +) -> *mut PyObject { + crate::log_stub("PyErr_NewExceptionWithDoc"); + ptr::null_mut() +} + +#[unsafe(no_mangle)] +pub extern "C" fn PyException_GetTraceback(_exc: *mut PyObject) -> *mut PyObject { + crate::log_stub("PyException_GetTraceback"); + ptr::null_mut() +} diff --git a/crates/capi/src/pylifecycle.rs b/crates/capi/src/pylifecycle.rs new file mode 100644 index 00000000000..d8b3fc2ae41 --- /dev/null +++ b/crates/capi/src/pylifecycle.rs @@ -0,0 +1,73 @@ +use crate::log_stub; +use crate::pystate::attach_vm_to_thread; +use core::ffi::c_int; +use rustpython_vm::Interpreter; +use rustpython_vm::vm::thread::ThreadedVirtualMachine; +use std::sync::{Once, OnceLock, mpsc}; + +static VM_REQUEST_TX: OnceLock>> = + OnceLock::new(); +static INITIALIZED: Once = Once::new(); + +/// Request a vm from the main interpreter +pub(crate) fn request_vm_from_interpreter() -> ThreadedVirtualMachine { + let tx = VM_REQUEST_TX + .get() + .expect("VM request channel not initialized"); + let (response_tx, response_rx) = mpsc::channel(); + tx.send(response_tx).expect("Failed to send VM request"); + response_rx.recv().expect("Failed to receive VM response") +} + +#[unsafe(no_mangle)] +pub extern "C" fn Py_IsInitialized() -> c_int { + INITIALIZED.is_completed() as _ +} + +#[unsafe(no_mangle)] +pub extern "C" fn Py_Initialize() { + Py_InitializeEx(0); +} + +#[unsafe(no_mangle)] +pub extern "C" fn Py_InitializeEx(_initsigs: c_int) { + if INITIALIZED.is_completed() { + panic!("Initialize called multiple times"); + } + + INITIALIZED.call_once(|| { + let (tx, rx) = mpsc::channel(); + VM_REQUEST_TX + .set(tx) + .expect("VM request channel was already initialized"); + + std::thread::spawn(move || { + let interp = Interpreter::with_init(Default::default(), |_vm| {}); + interp.enter(|vm| { + while let Ok(request) = rx.recv() { + request + .send(vm.new_thread()) + .expect("Failed to send VM response"); + } + }) + }); + }); + + attach_vm_to_thread(); +} + +#[unsafe(no_mangle)] +pub extern "C" fn Py_Finalize() { + let _ = Py_FinalizeEx(); +} + +#[unsafe(no_mangle)] +pub extern "C" fn Py_FinalizeEx() -> c_int { + log_stub("Py_FinalizeEx"); + 0 +} + +#[unsafe(no_mangle)] +pub extern "C" fn Py_IsFinalizing() -> c_int { + 0 +} diff --git a/crates/capi/src/pystate.rs b/crates/capi/src/pystate.rs new file mode 100644 index 00000000000..de25dba790a --- /dev/null +++ b/crates/capi/src/pystate.rs @@ -0,0 +1,53 @@ +use crate::pylifecycle::request_vm_from_interpreter; +use core::ffi::c_int; +use core::ptr; +use rustpython_vm::VirtualMachine; +use rustpython_vm::vm::thread::ThreadedVirtualMachine; +use std::cell::RefCell; + +thread_local! { + static VM: RefCell> = const { RefCell::new(None) }; +} + +pub fn with_vm(f: impl FnOnce(&VirtualMachine) -> R) -> R { + VM.with(|vm_ref| { + let vm = vm_ref.borrow(); + let vm = vm + .as_ref() + .expect("Thread was not attached to an interpreter"); + vm.run(f) + }) +} + +#[allow(non_camel_case_types)] +type PyGILState_STATE = c_int; + +#[repr(C)] +pub struct PyThreadState { + _interp: *mut std::ffi::c_void, +} + +pub(crate) fn attach_vm_to_thread() { + VM.with(|vm| { + vm.borrow_mut() + .get_or_insert_with(|| request_vm_from_interpreter()); + }); +} + +#[unsafe(no_mangle)] +pub extern "C" fn PyGILState_Ensure() -> PyGILState_STATE { + attach_vm_to_thread(); + + 0 +} + +#[unsafe(no_mangle)] +pub extern "C" fn PyGILState_Release(_state: PyGILState_STATE) {} + +#[unsafe(no_mangle)] +pub extern "C" fn PyEval_SaveThread() -> *mut PyThreadState { + ptr::null_mut() +} + +#[unsafe(no_mangle)] +pub extern "C" fn PyEval_RestoreThread(_tstate: *mut PyThreadState) {} diff --git a/crates/capi/src/refcount.rs b/crates/capi/src/refcount.rs new file mode 100644 index 00000000000..5e6f55f0460 --- /dev/null +++ b/crates/capi/src/refcount.rs @@ -0,0 +1,29 @@ +use crate::PyObject; +use rustpython_vm::PyObjectRef; +use std::ptr::NonNull; + +#[unsafe(no_mangle)] +#[allow(clippy::not_unsafe_ptr_arg_deref)] +pub extern "C" fn _Py_DecRef(op: *mut PyObject) { + let Some(ptr) = NonNull::new(op) else { + return; + }; + + let owned = unsafe { PyObjectRef::from_raw(ptr) }; + + // Dropping so we decrement the refcount + drop(owned); +} + +#[unsafe(no_mangle)] +#[allow(clippy::not_unsafe_ptr_arg_deref)] +pub extern "C" fn _Py_IncRef(op: *mut PyObject) { + if op.is_null() { + return; + } + + // SAFETY: op is non-null and expected to be a valid pointer for this shim. + let owned = unsafe { (*op).to_owned() }; + + std::mem::forget(owned); +} diff --git a/crates/capi/src/traceback.rs b/crates/capi/src/traceback.rs new file mode 100644 index 00000000000..c73c952d5e7 --- /dev/null +++ b/crates/capi/src/traceback.rs @@ -0,0 +1,9 @@ +use core::ffi::c_int; + +use crate::PyObject; + +#[unsafe(no_mangle)] +pub extern "C" fn PyTraceBack_Print(_tb: *mut PyObject, _file: *mut PyObject) -> c_int { + crate::log_stub("PyTraceBack_Print"); + -1 +} diff --git a/crates/capi/src/tupleobject.rs b/crates/capi/src/tupleobject.rs new file mode 100644 index 00000000000..996a835bb78 --- /dev/null +++ b/crates/capi/src/tupleobject.rs @@ -0,0 +1,15 @@ +use core::ptr; + +use crate::PyObject; + +#[unsafe(no_mangle)] +pub extern "C" fn PyTuple_Size(_tuple: *mut PyObject) -> isize { + crate::log_stub("PyTuple_Size"); + 0 +} + +#[unsafe(no_mangle)] +pub extern "C" fn PyTuple_GetItem(_tuple: *mut PyObject, _pos: isize) -> *mut PyObject { + crate::log_stub("PyTuple_GetItem"); + ptr::null_mut() +} diff --git a/crates/capi/src/unicodeobject.rs b/crates/capi/src/unicodeobject.rs new file mode 100644 index 00000000000..27240521110 --- /dev/null +++ b/crates/capi/src/unicodeobject.rs @@ -0,0 +1,52 @@ +use core::ffi::c_char; +use core::ptr; +use core::slice; +use core::str; + +use crate::PyObject; +use crate::pystate::with_vm; +use rustpython_vm::PyObjectRef; + +#[unsafe(no_mangle)] +pub extern "C" fn PyUnicode_FromStringAndSize(s: *const c_char, len: isize) -> *mut PyObject { + let len = usize::try_from(len).expect("PyUnicode_FromStringAndSize called with negative len"); + let text = if s.is_null() { + if len != 0 { + panic!("PyUnicode_FromStringAndSize called with null data and non-zero len"); + } + "" + } else { + // SAFETY: caller passes a valid C buffer of length `len`. + let bytes = unsafe { slice::from_raw_parts(s.cast::(), len) }; + str::from_utf8(bytes).expect("PyUnicode_FromStringAndSize got non-UTF8 data") + }; + + with_vm(|vm| { + let obj: PyObjectRef = vm.ctx.new_str(text).into(); + obj.into_raw().as_ptr() + }) +} + +#[unsafe(no_mangle)] +pub extern "C" fn PyUnicode_AsUTF8AndSize( + _unicode: *mut PyObject, + _size: *mut isize, +) -> *const c_char { + crate::log_stub("PyUnicode_AsUTF8AndSize"); + ptr::null() +} + +#[unsafe(no_mangle)] +pub extern "C" fn PyUnicode_AsEncodedString( + _unicode: *mut PyObject, + _encoding: *const c_char, + _errors: *const c_char, +) -> *mut PyObject { + crate::log_stub("PyUnicode_AsEncodedString"); + ptr::null_mut() +} + +#[unsafe(no_mangle)] +pub extern "C" fn PyUnicode_InternInPlace(_string: *mut *mut PyObject) { + crate::log_stub("PyUnicode_InternInPlace"); +} diff --git a/example_projects/pyo3_embed/.cargo/config.toml b/example_projects/pyo3_embed/.cargo/config.toml new file mode 100644 index 00000000000..54a884e2458 --- /dev/null +++ b/example_projects/pyo3_embed/.cargo/config.toml @@ -0,0 +1,2 @@ +[env] +PYO3_CONFIG_FILE = { value = "pyo3-rustpython.config", relative = true } diff --git a/example_projects/pyo3_embed/Cargo.toml b/example_projects/pyo3_embed/Cargo.toml new file mode 100644 index 00000000000..d424a486184 --- /dev/null +++ b/example_projects/pyo3_embed/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "example_pyo3_embed" +version = "0.1.0" +edition = "2021" + +[dependencies] +pyo3 = { version = "0.28.3", features = ["abi3-py314"] } +rustpython-capi = { path = "../../crates/capi" } + +[workspace] + +[patch.crates-io] diff --git a/example_projects/pyo3_embed/README.md b/example_projects/pyo3_embed/README.md new file mode 100644 index 00000000000..7d9cc2cf98b --- /dev/null +++ b/example_projects/pyo3_embed/README.md @@ -0,0 +1,11 @@ +# PyO3 embed against RustPython C-API + +This example demonstrates linking `pyo3` against RustPython's minimal C-API shim (`rustpython-capi`) instead of a system CPython library. + +From this directory, run: + +```shell +cargo run +``` + +The local `.cargo/config.toml` sets `PYO3_CONFIG_FILE` automatically. diff --git a/example_projects/pyo3_embed/pyo3-rustpython.config b/example_projects/pyo3_embed/pyo3-rustpython.config new file mode 100644 index 00000000000..2b097508e4f --- /dev/null +++ b/example_projects/pyo3_embed/pyo3-rustpython.config @@ -0,0 +1,9 @@ +implementation=CPython +version=3.14 +shared=false +abi3=true +build_flags= +suppress_build_script_link_lines=true +extra_build_script_line=cargo:rustc-link-search=native=/Users/basschoenmaeckers/repo/RustPython/target/debug +extra_build_script_line=cargo:rustc-link-lib=static=rustpython_capi +extra_build_script_line=cargo:rustc-link-lib=framework=CoreFoundation diff --git a/example_projects/pyo3_embed/src/main.rs b/example_projects/pyo3_embed/src/main.rs new file mode 100644 index 00000000000..3ba2c549411 --- /dev/null +++ b/example_projects/pyo3_embed/src/main.rs @@ -0,0 +1,23 @@ +use pyo3::prelude::*; +use pyo3::types::{PyInt, PyString}; + +fn main() { + Python::initialize(); + + Python::attach(|py| { + let number = PyInt::new(py, 123); + assert!(number.is_instance_of::()); + assert_eq!(number.extract::().unwrap(), 123); + + let string = PyString::new(py, "Hello, World!"); + assert!(string.is_instance_of::()); + + let number = number.unbind(); + std::thread::spawn(move || { + Python::attach(|py| { + let number = number.bind(py); + assert!(number.is_instance_of::()); + }); + }).join().unwrap(); + }); +}