Skip to content

Commit

Permalink
Create python tutorial.
Browse files Browse the repository at this point in the history
  • Loading branch information
gogo2464 committed Jul 10, 2023
1 parent 41b91c8 commit 122a1e1
Show file tree
Hide file tree
Showing 13 changed files with 552 additions and 0 deletions.
4 changes: 4 additions & 0 deletions docs/manual/src/SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,10 @@
- [Building a Swift module](./swift/module.md)
- [Integrating with Xcode](./swift/xcode.md)

# Python

- [setup project](./python/setup.md)

# Internals
- [Design Principles](./internals/design_principles.md)
- [Navigating the Code](./internals/crates.md)
Expand Down
236 changes: 236 additions & 0 deletions docs/manual/src/python/setup.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,236 @@
# Intro

The main idea is to build the bindings with:

python .\setup.py bdist_wheel --verbose ;
$wheelFile = Get-ChildItem -Path .\dist\ -Recurse -Include * ;
pip3 install $wheelFile --force-reinstall ;

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.


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.


# Create setup.py file.
## rustc

get rustc version from your setup.py file:

```python
def get_rustc_info():
"""
Get the rustc info from `rustc --version --verbose`, parsed into a
dictionary.
"""
regex = re.compile(r"(?P<key>[^:]+)(: *(?P<value>\S+))")

output = subprocess.check_output(["rustc", "--version", "--verbose"])

data = {}
for line in output.decode("utf-8").splitlines():
match = regex.match(line)
if match:
d = match.groupdict()
data[d["key"]] = d["value"]

return data
```

## Macos compatibility

The macos compatibility depends of the files generated with the command.

```python
def macos_compat(target):
if target.startswith("aarch64-"):
return "11.0"
return "10.7"
```



## target

The uniffy-bindgen command will generate different output that you will need to guess for future operations:

```python
# The logic for specifying wheel tags in setuptools/wheel is very complex, hard
# to override, and is really meant for extensions that are compiled against
# libpython.so, not this case where we have a fairly portable Rust-compiled
# binary that should work across a number of Python versions. Therefore, we
# just skip all of its logic be overriding the `get_tag` method with something
# simple that only handles the cases we need.
class bdist_wheel(wheel.bdist_wheel.bdist_wheel):
def get_tag(self):
cpu, _, __ = target.partition("-")
impl, abi_tag = "cp36", "abi3"
if "-linux" in target:
plat_name = f"linux_{cpu}"
elif "-darwin" in target:
compat = macos_compat(target).replace(".", "_")
if cpu == "aarch64":
cpu = "arm64"
plat_name = f"macosx_{compat}_{cpu}"
elif "-windows" in target:
impl, abi_tag = "py3", "none"
if cpu == "i686":
plat_name = "win32"
elif cpu == "x86_64":
plat_name = "win_amd64"
else:
raise ValueError("Unsupported Windows platform")
else:
# Keep local wheel build on BSD/etc. working
_, __, plat_name = super().get_tag()

return (impl, abi_tag, plat_name)
```



## Get extension name

Guess extension from command target flag:

```python
target = get_rustc_info()["host"]

extension = ""
file_start = ""
if "-darwin" in target:
shared_object = "libmath.dylib"
extension = ".dylib"
file_start = "lib"
elif "-windows" in target:
shared_object = "mymath.dll"
extension = ".dll"
file_start = ""
else:
# Anything else must be an ELF platform - Linux, *BSD, Solaris/illumos
shared_object = "libmath.so"
extension = ".so"
file_start = "lib"

new_shared_object_name = file_start + "uniffi_mymath" + extension
```



```python
class InstallPlatlib(install):
def finalize_options(self):
install.finalize_options(self)
if self.distribution.has_ext_modules():
self.install_lib = self.install_platlib
```

# Build

```python
class build(_build):
def run(self):
try:
# Use `check_output` to suppress output
subprocess.check_output(["cargo"])
except subprocess.CalledProcessError:
print("Install Rust and Cargo through Rustup: https://rustup.rs/.")
sys.exit(1)

env = os.environ.copy()

# For `musl`-based targets (e.g. Alpine Linux), we need to set a flag
# to produce a shared object Python extension.
if "-musl" in target:
env["RUSTFLAGS"] = (
env.get("RUSTFLAGS", "") + " -C target-feature=-crt-static"
)
if target == "i686-pc-windows-gnu":
env["RUSTFLAGS"] = env.get("RUSTFLAGS", "") + " -C panic=abort"

command = [
"cargo",
"build",
#"--package",
#"math",
"--target",
target,
]

if buildvariant != "debug":
command.append(f"--{buildvariant}")

if "-darwin" in target:
env["MACOSX_DEPLOYMENT_TARGET"] = macos_compat(target)

subprocess.check_call(command, env=env)

shutil.copyfile(
SRC_ROOT / "uniffi-rust-to-python-library" / "target" / target / buildvariant / "deps" / shared_object,
SRC_ROOT / "uniffi-rust-to-python-library" / "out" / new_shared_object_name,
)

command = [
"cargo",
"run",
"--features=uniffi/cli",
"--bin",
"uniffi-bindgen",
"generate",
"src/math.udl",
"--language",
"python",
"--out-dir",
SRC_ROOT / "uniffi-rust-to-python-library" / "target",
]

subprocess.check_call(command, env=env)

shutil.copyfile(
SRC_ROOT / "uniffi-rust-to-python-library" / "target" / "mymath.py", SRC_ROOT / "uniffi-rust-to-python-library" / "out" / "mymath.py"
)

return _build.run(self)
```


## setup()

```python
setup(
author="gogo2464",
author_email="[email protected]",
classifiers=[
"Intended Audience :: Developers",
"Natural Language :: English",
"Programming Language :: Python :: 3"
],
description="Example project in order to complete a uniffi-rs tutorial.",
long_description="example",
install_requires=requirements,
long_description_content_type="text/markdown",
include_package_data=True,
keywords="example",
name="mymath",
version="0.1.0",
packages=[
"mymath"
],
package_dir={
"mymath": "out"
},
setup_requires=requirements,
url="no_url",
zip_safe=False,
package_data={"mymath": [new_shared_object_name]},
distclass=BinaryDistribution,
cmdclass={"install": InstallPlatlib, "bdist_wheel": bdist_wheel, "build": build},
)

```



## COngratulation.

It was not easy but you did it! In case of issue, fell free to consul the next chapter for real life example.
23 changes: 23 additions & 0 deletions examples/distributing/uniffi-rust-to-python-library/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
[package]
name = "uniffi-rust-to-python-library"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
uniffi = {version = "*", features = [ "cli" ]}
uniffi_macros = "*"
uniffi_bindgen = "*"

[build-dependencies]
uniffi = { version = "*", features = [ "build", "cli" ] }

[[bin]]
# This can be whatever name makes sense for your project, but the rest of this tutorial assumes uniffi-bindgen.
name = "uniffi-bindgen"
path = "uniffi-bindgen.rs"

[lib]
crate-type = ["cdylib"]
name = "mymath"
5 changes: 5 additions & 0 deletions examples/distributing/uniffi-rust-to-python-library/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
## Minimal example of uniffi-rs with setup.py

# Building

.\run.bat
3 changes: 3 additions & 0 deletions examples/distributing/uniffi-rust-to-python-library/build.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
fn main() {
uniffi::generate_scaffolding("src/math.udl").unwrap();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Empty file in order to keep this folder in github.
6 changes: 6 additions & 0 deletions examples/distributing/uniffi-rust-to-python-library/run.bat
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
cargo run --features=uniffi/cli --bin uniffi-bindgen generate .\src\math.udl --language python --out-dir out
python -m venv venv3
call ".\venv3\Scripts\activate"
pip install yapf
python .\src\setup.py install
python .\testing\testing.py
16 changes: 16 additions & 0 deletions examples/distributing/uniffi-rust-to-python-library/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
pub fn add(left: u32, right: u32) -> u32 {
left + right
}

uniffi::include_scaffolding!("math");

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn it_works() {
let result = add(2, 2);
assert_eq!(result, 4);
}
}
17 changes: 17 additions & 0 deletions examples/distributing/uniffi-rust-to-python-library/src/math.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
/*pub fn add(left: u32, right: u32) -> u32 {
left + right
}
uniffi::include_scaffolding!("math");
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn it_works() {
let result = add(2, 2);
assert_eq!(result, 4);
}
}
*/
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
namespace mymath {
u32 add(u32 left, u32 right);
};
Loading

0 comments on commit 122a1e1

Please sign in to comment.