Skip to content

Commit 404e29c

Browse files
author
gogo
committed
Create python tutorial.
1 parent b7d7bbd commit 404e29c

File tree

11 files changed

+322
-0
lines changed

11 files changed

+322
-0
lines changed

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ members = [
1919
"examples/sprites",
2020
"examples/todolist",
2121
"examples/traits",
22+
"examples/distributing/uniffi-rust-to-python-library",
2223

2324
"fixtures/benchmarks",
2425
"fixtures/coverall",

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: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
# Intro
2+
3+
The main idea is to build the bindings with commands.
4+
5+
So 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.
6+
7+
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).
8+
9+
Once you reproducted the template on your project, fell free to run with:
10+
11+
## Windows Powershell
12+
13+
```powershell
14+
python .\src\setup.py bdist_wheel ;
15+
$wheelFile = Get-ChildItem -Path .\dist\ -Recurse -Include * ;
16+
pip install $wheelFile --force-reinstall ;
17+
```
18+
19+
## MacOs and Linux commands:
20+
21+
```bash
22+
python3 ./src/setup.py bdist_wheel --verbose ;
23+
pip3 install ./dist/* --force-reinstall ;
24+
```
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
[package]
2+
name = "python"
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: 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: 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+
};
Lines changed: 235 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,235 @@
1+
from setuptools import setup, Distribution, find_packages
2+
from setuptools.command.install import install
3+
4+
from distutils.command.build import build as _build
5+
import os
6+
import re
7+
import shutil
8+
import subprocess
9+
import sys
10+
from pathlib import Path
11+
import wheel.bdist_wheel
12+
13+
sys.dont_write_bytecode = True
14+
15+
if sys.version_info < (3, 6):
16+
print("the example project requires at least Python 3.6", file=sys.stderr)
17+
sys.exit(1)
18+
19+
from pathlib import Path # noqa
20+
21+
# Path to the directory containing this file
22+
PYTHON_ROOT = Path(__file__).parent.absolute()
23+
24+
# Relative path to this directory from cwd.
25+
FROM_TOP = PYTHON_ROOT.relative_to(Path.cwd())
26+
27+
# Path to the root of the git checkout
28+
SRC_ROOT = PYTHON_ROOT.parents[1]
29+
30+
requirements = [
31+
"wheel",
32+
"setuptools",
33+
]
34+
35+
buildvariant = "release"
36+
37+
38+
class BinaryDistribution(Distribution):
39+
def is_pure(self):
40+
return False
41+
42+
def has_ext_modules(self):
43+
return True
44+
45+
46+
def macos_compat(target):
47+
if target.startswith("aarch64-"):
48+
return "11.0"
49+
return "10.7"
50+
51+
52+
# The logic for specifying wheel tags in setuptools/wheel is very complex, hard
53+
# to override, and is really meant for extensions that are compiled against
54+
# libpython.so, not this case where we have a fairly portable Rust-compiled
55+
# binary that should work across a number of Python versions. Therefore, we
56+
# just skip all of its logic be overriding the `get_tag` method with something
57+
# simple that only handles the cases we need.
58+
class bdist_wheel(wheel.bdist_wheel.bdist_wheel):
59+
def get_tag(self):
60+
cpu, _, __ = target.partition("-")
61+
impl, abi_tag = "cp36", "abi3"
62+
if "-linux" in target:
63+
plat_name = f"linux_{cpu}"
64+
elif "-darwin" in target:
65+
compat = macos_compat(target).replace(".", "_")
66+
if cpu == "aarch64":
67+
cpu = "arm64"
68+
plat_name = f"macosx_{compat}_{cpu}"
69+
elif "-windows" in target:
70+
impl, abi_tag = "py3", "none"
71+
if cpu == "i686":
72+
plat_name = "win32"
73+
elif cpu == "x86_64":
74+
plat_name = "win_amd64"
75+
else:
76+
raise ValueError("Unsupported Windows platform")
77+
else:
78+
# Keep local wheel build on BSD/etc. working
79+
_, __, plat_name = super().get_tag()
80+
81+
return (impl, abi_tag, plat_name)
82+
83+
84+
class InstallPlatlib(install):
85+
def finalize_options(self):
86+
install.finalize_options(self)
87+
if self.distribution.has_ext_modules():
88+
self.install_lib = self.install_platlib
89+
90+
91+
def get_rustc_info():
92+
"""
93+
Get the rustc info from `rustc --version --verbose`, parsed into a
94+
dictionary.
95+
"""
96+
regex = re.compile(r"(?P<key>[^:]+)(: *(?P<value>\S+))")
97+
98+
output = subprocess.check_output(["rustc", "--version", "--verbose"])
99+
100+
data = {}
101+
for line in output.decode("utf-8").splitlines():
102+
match = regex.match(line)
103+
if match:
104+
d = match.groupdict()
105+
data[d["key"]] = d["value"]
106+
107+
return data
108+
109+
110+
target = get_rustc_info()["host"]
111+
112+
extension = ""
113+
file_start = ""
114+
if "-darwin" in target:
115+
shared_object = "libmath.dylib"
116+
extension = ".dylib"
117+
file_start = "lib"
118+
elif "-windows" in target:
119+
shared_object = "mymath.dll"
120+
extension = ".dll"
121+
file_start = ""
122+
else:
123+
# Anything else must be an ELF platform - Linux, *BSD, Solaris/illumos
124+
shared_object = "libmath.so"
125+
extension = ".so"
126+
file_start = "lib"
127+
128+
new_shared_object_name = file_start + "uniffi_mymath" + extension
129+
130+
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-dir",
157+
"out",
158+
"--target",
159+
target,
160+
]
161+
162+
if buildvariant != "debug":
163+
command.append(f"--{buildvariant}")
164+
165+
if "-darwin" in target:
166+
env["MACOSX_DEPLOYMENT_TARGET"] = macos_compat(target)
167+
168+
subprocess.check_call(command, env=env)
169+
170+
#os.makedirs(os.path.dirname(SRC_ROOT / "uniffi-rust-to-python-library" / "out"))#, exist_ok=True
171+
172+
#print("root: {0}".format(SRC_ROOT))
173+
174+
#print([name for name in os.listdir(".") if os.path.isdir(name) and "target" in os.path.isdir(name)][0])
175+
176+
177+
print("{0}".format(SRC_ROOT / "out" / target / buildvariant / "deps" / shared_object))
178+
179+
180+
shutil.copyfile(
181+
SRC_ROOT / "uniffi-rust-to-python-library" / "out" / target / buildvariant / "deps" / shared_object, #SRC_ROOT / "uniffi-rust-to-python-library" / "target" / target / buildvariant / "deps" / shared_object,
182+
SRC_ROOT / "uniffi-rust-to-python-library" / "out" / new_shared_object_name,
183+
)
184+
185+
command = [
186+
"cargo",
187+
"run",
188+
"--features=uniffi/cli",
189+
"--bin",
190+
"uniffi-bindgen",
191+
"generate",
192+
"src/math.udl",
193+
"--language",
194+
"python",
195+
"--out-dir",
196+
SRC_ROOT / "uniffi-rust-to-python-library" / "target",
197+
]
198+
199+
subprocess.check_call(command, env=env)
200+
201+
shutil.copyfile(
202+
SRC_ROOT / "uniffi-rust-to-python-library" / "target" / "mymath.py", SRC_ROOT / "uniffi-rust-to-python-library" / "out" / "mymath.py"
203+
)
204+
205+
return _build.run(self)
206+
207+
setup(
208+
author="gogo2464",
209+
author_email="[email protected]",
210+
classifiers=[
211+
"Intended Audience :: Developers",
212+
"Natural Language :: English",
213+
"Programming Language :: Python :: 3"
214+
],
215+
description="Example project in order to complete a uniffi-rs tutorial.",
216+
long_description="example",
217+
install_requires=requirements,
218+
long_description_content_type="text/markdown",
219+
include_package_data=True,
220+
keywords="example",
221+
name="mymath",
222+
version="0.1.0",
223+
packages=[
224+
"mymath"
225+
],
226+
package_dir={
227+
"mymath": "out"
228+
},
229+
setup_requires=requirements,
230+
url="no_url",
231+
zip_safe=False,
232+
package_data={"mymath": [new_shared_object_name]},
233+
distclass=BinaryDistribution,
234+
cmdclass={"install": InstallPlatlib, "bdist_wheel": bdist_wheel, "build": build},
235+
)
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
from mymath import mymath
2+
3+
value = mymath.add(1,2)
4+
assert value == 3, f"add not ok"
5+
print("1 + 2 = {0}. OK!".format(value))
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
fn main() {
2+
uniffi::uniffi_bindgen_main()
3+
}

0 commit comments

Comments
 (0)