Skip to content

Commit 8e2fbbd

Browse files
author
gogo
committed
Create python tutorial.
1 parent 41b91c8 commit 8e2fbbd

File tree

13 files changed

+551
-0
lines changed

13 files changed

+551
-0
lines changed

docs/manual/src/SUMMARY.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,10 @@
3333
- [Building a Swift module](./swift/module.md)
3434
- [Integrating with Xcode](./swift/xcode.md)
3535

36+
# Python
37+
38+
- [setup project](./python/setup.md)
39+
3640
# Internals
3741
- [Design Principles](./internals/design_principles.md)
3842
- [Navigating the Code](./internals/crates.md)

docs/manual/src/python/setup.md

Lines changed: 236 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,236 @@
1+
# Intro
2+
3+
The main idea is to build the bindings with:
4+
5+
python .\setup.py bdist_wheel --verbose ;
6+
$wheelFile = Get-ChildItem -Path .\dist\ -Recurse -Include * ;
7+
pip3 install $wheelFile --force-reinstall ;
8+
9+
Then you must create a setup.py file. This file will include the command to generate python .py bindings, link .dll and then build to pipy. Requires python version greater than 3.6.
10+
11+
12+
The full example is available at this [address](https://github.com/mozilla/uniffi-rs/tree/main/examples/distributing/uniffi-rust-to-python-library/src/setup.py). In order to reproduce the issue, let's see some points about the setup.py file.
13+
14+
15+
# Create setup.py file.
16+
## rustc
17+
18+
get rustc version from your setup.py file:
19+
20+
```python
21+
def get_rustc_info():
22+
"""
23+
Get the rustc info from `rustc --version --verbose`, parsed into a
24+
dictionary.
25+
"""
26+
regex = re.compile(r"(?P<key>[^:]+)(: *(?P<value>\S+))")
27+
28+
output = subprocess.check_output(["rustc", "--version", "--verbose"])
29+
30+
data = {}
31+
for line in output.decode("utf-8").splitlines():
32+
match = regex.match(line)
33+
if match:
34+
d = match.groupdict()
35+
data[d["key"]] = d["value"]
36+
37+
return data
38+
```
39+
40+
## Macos compatibility
41+
42+
The macos compatibility depends of the files generated with the command.
43+
44+
```python
45+
def macos_compat(target):
46+
if target.startswith("aarch64-"):
47+
return "11.0"
48+
return "10.7"
49+
```
50+
51+
52+
53+
## target
54+
55+
The uniffy-bindgen command will generate different output that you will need to guess for future operations:
56+
57+
```python
58+
# The logic for specifying wheel tags in setuptools/wheel is very complex, hard
59+
# to override, and is really meant for extensions that are compiled against
60+
# libpython.so, not this case where we have a fairly portable Rust-compiled
61+
# binary that should work across a number of Python versions. Therefore, we
62+
# just skip all of its logic be overriding the `get_tag` method with something
63+
# simple that only handles the cases we need.
64+
class bdist_wheel(wheel.bdist_wheel.bdist_wheel):
65+
def get_tag(self):
66+
cpu, _, __ = target.partition("-")
67+
impl, abi_tag = "cp36", "abi3"
68+
if "-linux" in target:
69+
plat_name = f"linux_{cpu}"
70+
elif "-darwin" in target:
71+
compat = macos_compat(target).replace(".", "_")
72+
if cpu == "aarch64":
73+
cpu = "arm64"
74+
plat_name = f"macosx_{compat}_{cpu}"
75+
elif "-windows" in target:
76+
impl, abi_tag = "py3", "none"
77+
if cpu == "i686":
78+
plat_name = "win32"
79+
elif cpu == "x86_64":
80+
plat_name = "win_amd64"
81+
else:
82+
raise ValueError("Unsupported Windows platform")
83+
else:
84+
# Keep local wheel build on BSD/etc. working
85+
_, __, plat_name = super().get_tag()
86+
87+
return (impl, abi_tag, plat_name)
88+
```
89+
90+
91+
92+
## Get extension name
93+
94+
Guess extension from command target flag:
95+
96+
```python
97+
target = get_rustc_info()["host"]
98+
99+
extension = ""
100+
file_start = ""
101+
if "-darwin" in target:
102+
shared_object = "libmath.dylib"
103+
extension = ".dylib"
104+
file_start = "lib"
105+
elif "-windows" in target:
106+
shared_object = "mymath.dll"
107+
extension = ".dll"
108+
file_start = ""
109+
else:
110+
# Anything else must be an ELF platform - Linux, *BSD, Solaris/illumos
111+
shared_object = "libmath.so"
112+
extension = ".so"
113+
file_start = "lib"
114+
115+
new_shared_object_name = file_start + "uniffi_mymath" + extension
116+
```
117+
118+
119+
120+
```python
121+
class InstallPlatlib(install):
122+
def finalize_options(self):
123+
install.finalize_options(self)
124+
if self.distribution.has_ext_modules():
125+
self.install_lib = self.install_platlib
126+
```
127+
128+
# Build
129+
130+
```python
131+
class build(_build):
132+
def run(self):
133+
try:
134+
# Use `check_output` to suppress output
135+
subprocess.check_output(["cargo"])
136+
except subprocess.CalledProcessError:
137+
print("Install Rust and Cargo through Rustup: https://rustup.rs/.")
138+
sys.exit(1)
139+
140+
env = os.environ.copy()
141+
142+
# For `musl`-based targets (e.g. Alpine Linux), we need to set a flag
143+
# to produce a shared object Python extension.
144+
if "-musl" in target:
145+
env["RUSTFLAGS"] = (
146+
env.get("RUSTFLAGS", "") + " -C target-feature=-crt-static"
147+
)
148+
if target == "i686-pc-windows-gnu":
149+
env["RUSTFLAGS"] = env.get("RUSTFLAGS", "") + " -C panic=abort"
150+
151+
command = [
152+
"cargo",
153+
"build",
154+
#"--package",
155+
#"math",
156+
"--target",
157+
target,
158+
]
159+
160+
if buildvariant != "debug":
161+
command.append(f"--{buildvariant}")
162+
163+
if "-darwin" in target:
164+
env["MACOSX_DEPLOYMENT_TARGET"] = macos_compat(target)
165+
166+
subprocess.check_call(command, env=env)
167+
168+
shutil.copyfile(
169+
SRC_ROOT / "uniffi-rust-to-python-library" / "target" / target / buildvariant / "deps" / shared_object,
170+
SRC_ROOT / "uniffi-rust-to-python-library" / "out" / new_shared_object_name,
171+
)
172+
173+
command = [
174+
"cargo",
175+
"run",
176+
"--features=uniffi/cli",
177+
"--bin",
178+
"uniffi-bindgen",
179+
"generate",
180+
"src/math.udl",
181+
"--language",
182+
"python",
183+
"--out-dir",
184+
SRC_ROOT / "uniffi-rust-to-python-library" / "target",
185+
]
186+
187+
subprocess.check_call(command, env=env)
188+
189+
shutil.copyfile(
190+
SRC_ROOT / "uniffi-rust-to-python-library" / "target" / "mymath.py", SRC_ROOT / "uniffi-rust-to-python-library" / "out" / "mymath.py"
191+
)
192+
193+
return _build.run(self)
194+
```
195+
196+
197+
## setup()
198+
199+
```python
200+
setup(
201+
author="gogo2464",
202+
author_email="[email protected]",
203+
classifiers=[
204+
"Intended Audience :: Developers",
205+
"Natural Language :: English",
206+
"Programming Language :: Python :: 3"
207+
],
208+
description="Example project in order to complete a uniffi-rs tutorial.",
209+
long_description="example",
210+
install_requires=requirements,
211+
long_description_content_type="text/markdown",
212+
include_package_data=True,
213+
keywords="example",
214+
name="mymath",
215+
version="0.1.0",
216+
packages=[
217+
"mymath"
218+
],
219+
package_dir={
220+
"mymath": "out"
221+
},
222+
setup_requires=requirements,
223+
url="no_url",
224+
zip_safe=False,
225+
package_data={"mymath": [new_shared_object_name]},
226+
distclass=BinaryDistribution,
227+
cmdclass={"install": InstallPlatlib, "bdist_wheel": bdist_wheel, "build": build},
228+
)
229+
230+
```
231+
232+
233+
234+
## COngratulation.
235+
236+
It was not easy but you did it! In case of issue, fell free to consul the next chapter for real life example.
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
[package]
2+
name = "uniffi-rust-to-python-library"
3+
version = "0.1.0"
4+
edition = "2021"
5+
6+
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
7+
8+
[dependencies]
9+
uniffi = {version = "*", features = [ "cli" ]}
10+
uniffi_macros = "*"
11+
uniffi_bindgen = "*"
12+
13+
[build-dependencies]
14+
uniffi = { version = "*", features = [ "build", "cli" ] }
15+
16+
[[bin]]
17+
# This can be whatever name makes sense for your project, but the rest of this tutorial assumes uniffi-bindgen.
18+
name = "uniffi-bindgen"
19+
path = "uniffi-bindgen.rs"
20+
21+
[lib]
22+
crate-type = ["cdylib"]
23+
name = "mymath"
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
## Minimal example of uniffi-rs with setup.py
2+
3+
# Building
4+
5+
.\run.bat
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
fn main() {
2+
uniffi::generate_scaffolding("src/math.udl").unwrap();
3+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Empty file in order to keep this folder in github.
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
cargo run --features=uniffi/cli --bin uniffi-bindgen generate .\src\math.udl --language python --out-dir out
2+
python -m venv venv3
3+
call ".\venv3\Scripts\activate"
4+
pip install yapf
5+
python .\src\setup.py install
6+
python .\testing\testing.py
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
pub fn add(left: u32, right: u32) -> u32 {
2+
left + right
3+
}
4+
5+
uniffi::include_scaffolding!("math");
6+
7+
#[cfg(test)]
8+
mod tests {
9+
use super::*;
10+
11+
#[test]
12+
fn it_works() {
13+
let result = add(2, 2);
14+
assert_eq!(result, 4);
15+
}
16+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
/*pub fn add(left: u32, right: u32) -> u32 {
2+
left + right
3+
}
4+
5+
uniffi::include_scaffolding!("math");
6+
7+
#[cfg(test)]
8+
mod tests {
9+
use super::*;
10+
11+
#[test]
12+
fn it_works() {
13+
let result = add(2, 2);
14+
assert_eq!(result, 4);
15+
}
16+
}
17+
*/
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
namespace mymath {
2+
u32 add(u32 left, u32 right);
3+
};

0 commit comments

Comments
 (0)