From 3c5442f870e7aa4361e8cbce318adcdd5e1dfd4d Mon Sep 17 00:00:00 2001 From: gogo Date: Thu, 6 Jul 2023 22:43:18 +0200 Subject: [PATCH] Create python tutorial. --- Cargo.toml | 1 + docs/manual/src/SUMMARY.md | 4 + docs/manual/src/python/setup.md | 31 +++ .../uniffi-rust-to-python-library/Cargo.toml | 23 ++ .../uniffi-rust-to-python-library/README.md | 5 + .../uniffi-rust-to-python-library/build.rs | 3 + .../uniffi-rust-to-python-library/src/lib.rs | 16 ++ .../src/math.udl | 3 + .../src/setup.py | 235 ++++++++++++++++++ .../testing/testing.py | 5 + .../uniffi-bindgen.rs | 3 + 11 files changed, 329 insertions(+) create mode 100644 docs/manual/src/python/setup.md create mode 100644 examples/distributing/uniffi-rust-to-python-library/Cargo.toml create mode 100644 examples/distributing/uniffi-rust-to-python-library/README.md create mode 100644 examples/distributing/uniffi-rust-to-python-library/build.rs create mode 100644 examples/distributing/uniffi-rust-to-python-library/src/lib.rs create mode 100644 examples/distributing/uniffi-rust-to-python-library/src/math.udl create mode 100644 examples/distributing/uniffi-rust-to-python-library/src/setup.py create mode 100644 examples/distributing/uniffi-rust-to-python-library/testing/testing.py create mode 100644 examples/distributing/uniffi-rust-to-python-library/uniffi-bindgen.rs diff --git a/Cargo.toml b/Cargo.toml index 7cec1a37e7..8c46c17514 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,6 +19,7 @@ members = [ "examples/sprites", "examples/todolist", "examples/traits", + "examples/distributing/uniffi-rust-to-python-library", "fixtures/benchmarks", "fixtures/coverall", diff --git a/docs/manual/src/SUMMARY.md b/docs/manual/src/SUMMARY.md index b734431964..704e7094b6 100644 --- a/docs/manual/src/SUMMARY.md +++ b/docs/manual/src/SUMMARY.md @@ -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) diff --git a/docs/manual/src/python/setup.md b/docs/manual/src/python/setup.md new file mode 100644 index 0000000000..9a213f290a --- /dev/null +++ b/docs/manual/src/python/setup.md @@ -0,0 +1,31 @@ +# Intro + +The main idea is to build the bindings with commands. + +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. + +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). + +Once you reproducted the template on your project, fell free to run with: + +## Windows Powershell + +```powershell +python .\setup.py bdist_wheel --verbose ; +$wheelFile = Get-ChildItem -Path .\dist\ -Recurse -Include * ; +pip3 install $wheelFile --force-reinstall ; +``` + +## Linux Bash + +```bash +python .\setup.py bdist_wheel --verbose ; +pip3 install ./dist/* --force-reinstall ; +``` + +## MacOs Commands + +```bash +python3 ./cryptatools-core/setup.py bdist_wheel --verbose ; +pip3 install ./dist/* --force-reinstall ; +``` \ No newline at end of file diff --git a/examples/distributing/uniffi-rust-to-python-library/Cargo.toml b/examples/distributing/uniffi-rust-to-python-library/Cargo.toml new file mode 100644 index 0000000000..369cbe5864 --- /dev/null +++ b/examples/distributing/uniffi-rust-to-python-library/Cargo.toml @@ -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" \ No newline at end of file diff --git a/examples/distributing/uniffi-rust-to-python-library/README.md b/examples/distributing/uniffi-rust-to-python-library/README.md new file mode 100644 index 0000000000..d6176ec3f6 --- /dev/null +++ b/examples/distributing/uniffi-rust-to-python-library/README.md @@ -0,0 +1,5 @@ +## Minimal example of uniffi-rs with setup.py + +# Building + +.\run.bat \ No newline at end of file diff --git a/examples/distributing/uniffi-rust-to-python-library/build.rs b/examples/distributing/uniffi-rust-to-python-library/build.rs new file mode 100644 index 0000000000..9df738afab --- /dev/null +++ b/examples/distributing/uniffi-rust-to-python-library/build.rs @@ -0,0 +1,3 @@ +fn main() { + uniffi::generate_scaffolding("src/math.udl").unwrap(); +} \ No newline at end of file diff --git a/examples/distributing/uniffi-rust-to-python-library/src/lib.rs b/examples/distributing/uniffi-rust-to-python-library/src/lib.rs new file mode 100644 index 0000000000..8e967a20e2 --- /dev/null +++ b/examples/distributing/uniffi-rust-to-python-library/src/lib.rs @@ -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); + } +} diff --git a/examples/distributing/uniffi-rust-to-python-library/src/math.udl b/examples/distributing/uniffi-rust-to-python-library/src/math.udl new file mode 100644 index 0000000000..dc929e9305 --- /dev/null +++ b/examples/distributing/uniffi-rust-to-python-library/src/math.udl @@ -0,0 +1,3 @@ +namespace mymath { + u32 add(u32 left, u32 right); +}; \ No newline at end of file diff --git a/examples/distributing/uniffi-rust-to-python-library/src/setup.py b/examples/distributing/uniffi-rust-to-python-library/src/setup.py new file mode 100644 index 0000000000..622c915d8c --- /dev/null +++ b/examples/distributing/uniffi-rust-to-python-library/src/setup.py @@ -0,0 +1,235 @@ +from setuptools import setup, Distribution, find_packages +from setuptools.command.install import install + +from distutils.command.build import build as _build +import os +import re +import shutil +import subprocess +import sys +from pathlib import Path +import wheel.bdist_wheel + +sys.dont_write_bytecode = True + +if sys.version_info < (3, 6): + print("the example project requires at least Python 3.6", file=sys.stderr) + sys.exit(1) + +from pathlib import Path # noqa + +# Path to the directory containing this file +PYTHON_ROOT = Path(__file__).parent.absolute() + +# Relative path to this directory from cwd. +FROM_TOP = PYTHON_ROOT.relative_to(Path.cwd()) + +# Path to the root of the git checkout +SRC_ROOT = PYTHON_ROOT.parents[1] + +requirements = [ + "wheel", + "setuptools", +] + +buildvariant = "release" + + +class BinaryDistribution(Distribution): + def is_pure(self): + return False + + def has_ext_modules(self): + return True + + +def macos_compat(target): + if target.startswith("aarch64-"): + return "11.0" + return "10.7" + + +# 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) + + +class InstallPlatlib(install): + def finalize_options(self): + install.finalize_options(self) + if self.distribution.has_ext_modules(): + self.install_lib = self.install_platlib + + +def get_rustc_info(): + """ + Get the rustc info from `rustc --version --verbose`, parsed into a + dictionary. + """ + regex = re.compile(r"(?P[^:]+)(: *(?P\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 + + +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 + + +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-dir", + "out", + "--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) + + #os.makedirs(os.path.dirname(SRC_ROOT / "uniffi-rust-to-python-library" / "out"))#, exist_ok=True + + #print("root: {0}".format(SRC_ROOT)) + + #print([name for name in os.listdir(".") if os.path.isdir(name) and "target" in os.path.isdir(name)][0]) + + + print("{0}".format(SRC_ROOT / "out" / target / buildvariant / "deps" / shared_object)) + + + shutil.copyfile( + 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, + 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( + author="gogo2464", + author_email="gogo246475@gmail.com", + 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}, +) diff --git a/examples/distributing/uniffi-rust-to-python-library/testing/testing.py b/examples/distributing/uniffi-rust-to-python-library/testing/testing.py new file mode 100644 index 0000000000..82d6b62725 --- /dev/null +++ b/examples/distributing/uniffi-rust-to-python-library/testing/testing.py @@ -0,0 +1,5 @@ +from mymath import mymath + +value = mymath.add(1,2) +assert value == 3, f"add not ok" +print("1 + 2 = {0}. OK!".format(value)) \ No newline at end of file diff --git a/examples/distributing/uniffi-rust-to-python-library/uniffi-bindgen.rs b/examples/distributing/uniffi-rust-to-python-library/uniffi-bindgen.rs new file mode 100644 index 0000000000..d96eac70f9 --- /dev/null +++ b/examples/distributing/uniffi-rust-to-python-library/uniffi-bindgen.rs @@ -0,0 +1,3 @@ +fn main() { + uniffi::uniffi_bindgen_main() +} \ No newline at end of file