interoptopus_backend_cpython/
lib.rs

1//! Generates CPython bindings for [Interoptopus](https://github.com/ralfbiedert/interoptopus).
2//!
3//! # Usage
4//!
5//! Assuming you have written a crate containing your FFI logic called `example_library_ffi` and
6//! want to generate **CPython bindings** for Python 3.7+, follow the instructions below.
7//!
8//! ### Inside Your Library
9//!
10//! Add [**Interoptopus**](https://crates.io/crates/interoptopus) attributes to the library you have
11//! written, and define an inventory function listing all symbols you wish to export. An overview of all
12//! supported constructs can be found in the
13//! [**reference project**](https://github.com/ralfbiedert/interoptopus/tree/master/reference_project/src).
14//!
15//! ```rust
16//! use interoptopus::{ffi_function, ffi_type, Inventory, InventoryBuilder, function};
17//!
18//! #[ffi_type]
19//! #[repr(C)]
20//! pub struct Vec2 {
21//!     pub x: f32,
22//!     pub y: f32,
23//! }
24//!
25//! #[ffi_function]
26//! #[no_mangle]
27//! pub extern "C" fn my_function(input: Vec2) -> Vec2 {
28//!     input
29//! }
30//!
31//! pub fn my_inventory() -> Inventory {
32//!     InventoryBuilder::new()
33//!         .register(function!(my_function))
34//!         .inventory()
35//! }
36//! ```
37//!
38//!
39//! Add these to your `Cargo.toml` so the attributes and the binding generator can be found
40//! (replace `...` with the latest version):
41//!
42//! ```toml
43//! [lib]
44//! crate-type = ["cdylib", "rlib"]
45//!
46//! [dependencies]
47//! interoptopus = "..."
48//! interoptopus_backend_cpython = "..."
49//! ```
50//!
51//! Create a unit test in `tests/bindings.rs` which will generate your bindings when run
52//! with `cargo test`. In real projects you might want to add this code to another crate instead:
53//!
54//! ```
55//! use interoptopus::util::NamespaceMappings;
56//! use interoptopus::{Error, Interop};
57//!
58//! #[test]
59//! fn bindings_cpython_cffi() -> Result<(), Error> {
60//!     use interoptopus_backend_cpython::{Config, Generator};
61//!
62//!     let library = example_library_ffi::my_inventory();
63//!
64//!     Generator::new(Config::default(), library)
65//!         .write_file("bindings/python/example_library.py")?;
66//!
67//!     Ok(())
68//! }
69//! ```
70//!
71//! Now run `cargo test`.
72//!
73//! If anything is unclear you can find a [**working sample on Github**](https://github.com/ralfbiedert/interoptopus/tree/master/examples/hello_world).
74//!
75//! ### Generated Output
76//!
77//! The output below is what this backend might generate. Have a look at the [`Config`] struct
78//! if you want to customize something. If you really don't like how something is generated it is
79//! easy to [**create your own**](https://github.com/ralfbiedert/interoptopus/blob/master/FAQ.md#new-backends).
80//!
81//! ```python
82//! from __future__ import annotations
83//! import ctypes
84//! import typing
85//!
86//! T = typing.TypeVar("T")
87//! c_lib = None
88//!
89//! def init_lib(path):
90//!     """Initializes the native library. Must be called at least once before anything else."""
91//!     global c_lib
92//!     c_lib = ctypes.cdll.LoadLibrary(path)
93//!     c_lib.my_function.argtypes = [Vec2]
94//!     c_lib.my_function.restype = Vec2
95//!
96//!
97//! def my_function(input: Vec2) -> Vec2:
98//!     return c_lib.my_function(input)
99//!
100//!
101//! TRUE = ctypes.c_uint8(1)
102//! FALSE = ctypes.c_uint8(0)
103//!
104//!
105//! class Vec2(ctypes.Structure):
106//!     # These fields represent the underlying C data layout
107//!     _fields_ = [
108//!         ("x", ctypes.c_float),
109//!         ("y", ctypes.c_float),
110//!     ]
111//!
112//!     def __init__(self, x: float = None, y: float = None):
113//!         if x is not None:
114//!             self.x = x
115//!         if y is not None:
116//!             self.y = y
117//!
118//!     @property
119//!     def x(self) -> float:
120//!         return ctypes.Structure.__get__(self, "x")
121//!
122//!     @x.setter
123//!     def x(self, value: float):
124//!         return ctypes.Structure.__set__(self, "x", value)
125//!
126//!     @property
127//!     def y(self) -> float:
128//!         return ctypes.Structure.__get__(self, "y")
129//!
130//!     @y.setter
131//!     def y(self, value: float):
132//!         return ctypes.Structure.__set__(self, "y", value)
133//!
134//! ```
135
136use interoptopus::writer::IndentWriter;
137use interoptopus::Interop;
138use interoptopus::{Error, Inventory};
139
140mod config;
141mod converter;
142mod docs;
143mod testing;
144mod writer;
145
146pub use config::{Config, DocConfig};
147pub use converter::Converter;
148pub use docs::DocGenerator;
149pub use testing::run_python_if_installed;
150pub use writer::PythonWriter;
151
152/// **Start here**, main converter implementing [`Interop`].
153pub struct Generator {
154    config: Config,
155    library: Inventory,
156    converter: Converter,
157}
158
159impl Generator {
160    pub fn new(config: Config, library: Inventory) -> Self {
161        Self {
162            config,
163            library,
164            converter: Converter {},
165        }
166    }
167}
168
169impl Interop for Generator {
170    fn write_to(&self, w: &mut IndentWriter) -> Result<(), Error> {
171        self.write_all(w)
172    }
173}
174
175impl PythonWriter for Generator {
176    fn config(&self) -> &Config {
177        &self.config
178    }
179
180    fn inventory(&self) -> &Inventory {
181        &self.library
182    }
183
184    fn converter(&self) -> &Converter {
185        &self.converter
186    }
187}