Skip to content

Commit

Permalink
Add support for uniffi library mode
Browse files Browse the repository at this point in the history
  • Loading branch information
messense committed Aug 12, 2023
1 parent e02368b commit c18ffde
Show file tree
Hide file tree
Showing 12 changed files with 873 additions and 25 deletions.
8 changes: 0 additions & 8 deletions guide/src/bindings.md
Expand Up @@ -131,11 +131,3 @@ print_cli_args = "my_module:print_cli_args"

uniffi bindings use [uniffi-rs](https://mozilla.github.io/uniffi-rs/) to generate Python `ctypes` bindings
from an interface definition file. uniffi wheels are compatible with all python versions including pypy.

You need to install [uniffi-bindgen](https://mozilla.github.io/uniffi-rs/tutorial/Prerequisites.html#the-uniffi-bindgen-cli-tool) first to build wheels for `uniffi` bindings:

```bash
pip install uniffi-bindgen==0.24.1
```

Note that `uniffi-bindgen` version should be aligned with your Rust `uniffi` dependency version.
2 changes: 1 addition & 1 deletion src/build_options.rs
Expand Up @@ -744,7 +744,7 @@ fn filter_cargo_targets(
.iter()
.filter(|target| match bridge {
BridgeModel::Bin(_) => {
let is_bin = target.kind.contains(&"bin".to_string());
let is_bin = target.is_bin();
if target.required_features.is_empty() {
is_bin
} else {
Expand Down
75 changes: 59 additions & 16 deletions src/module_writer.rs
Expand Up @@ -875,28 +875,60 @@ struct UniFfiBindings {
path: PathBuf,
}

fn uniffi_bindgen_command(crate_dir: &Path) -> Result<Command> {
let manifest_path = crate_dir.join("Cargo.toml");
let cargo_metadata = cargo_metadata::MetadataCommand::new()
.manifest_path(&manifest_path)
.verbose(true)
.exec()?;
let root_pkg = cargo_metadata.root_package().unwrap();
let has_uniffi_bindgen_target = root_pkg
.targets
.iter()
.any(|target| target.name == "uniffi-bindgen" && target.is_bin());
let command = if has_uniffi_bindgen_target {
let mut command = Command::new("cargo");
command.args(["run", "--bin", "uniffi-bindgen", "--manifest-path"]);
command.arg(manifest_path);
command
} else {
Command::new("uniffi-bindgen")
};
Ok(command)
}

fn generate_uniffi_bindings(
crate_dir: &Path,
target_dir: &Path,
target_os: Os,
artifact: &Path,
) -> Result<UniFfiBindings> {
let binding_dir = target_dir.join("maturin").join("uniffi");
// `binding_dir` must use absolute path because we chdir to `crate_dir`
// when running uniffi-bindgen
let binding_dir = target_dir
.normalize()?
.join("maturin")
.join("uniffi")
.into_path_buf();
fs::create_dir_all(&binding_dir)?;

let pattern = crate_dir.join("src").join("*.udl");
let udls = glob::glob(pattern.to_str().unwrap())?
.map(|p| p.unwrap())
.collect::<Vec<_>>();
if udls.is_empty() {
bail!("No UDL files found in {}", crate_dir.join("src").display());
let is_library = if udls.is_empty() {
true
} else if udls.len() > 1 {
bail!(
"Multiple UDL files found in {}",
crate_dir.join("src").display()
);
}
} else {
false
};

let mut cmd = Command::new("uniffi-bindgen");
let mut cmd = uniffi_bindgen_command(crate_dir)?;
cmd.current_dir(crate_dir);
cmd.args([
"generate",
"--no-format",
Expand All @@ -906,7 +938,6 @@ fn generate_uniffi_bindings(
]);
cmd.arg(&binding_dir);

let udl = &udls[0];
let config_file = crate_dir.join("uniffi.toml");
let mut cdylib_name = None;
if config_file.is_file() {
Expand All @@ -915,10 +946,25 @@ fn generate_uniffi_bindings(
.bindings
.get("python")
.and_then(|py| py.cdylib_name.clone());
cmd.arg("--config");
cmd.arg(config_file);
if !is_library {
cmd.arg("--config");
cmd.arg(config_file);
}
}
cmd.arg(udl);

let py_binding_name = if is_library {
cmd.arg("--library");
cmd.arg(artifact);
let file_stem = artifact.file_stem().unwrap().to_str().unwrap();
file_stem
.strip_prefix("lib")
.unwrap_or(file_stem)
.to_string()
} else {
let udl = &udls[0];
cmd.arg(udl);
udl.file_stem().unwrap().to_str().unwrap().to_string()
};
debug!("Running {:?}", cmd);
let mut child = cmd.spawn().context(
"Failed to run uniffi-bindgen, did you install it? Try `pip install uniffi-bindgen`",
Expand All @@ -928,14 +974,11 @@ fn generate_uniffi_bindings(
bail!("Command {:?} failed", cmd);
}

let py_binding_name = udl.file_stem().unwrap();
let py_binding = binding_dir.join(py_binding_name).with_extension("py");
let name = py_binding_name.to_str().unwrap().to_string();

let py_binding = binding_dir.join(&py_binding_name).with_extension("py");
// uniffi bindings hardcoded the extension filenames
let cdylib_name = match cdylib_name {
Some(name) => name,
None => format!("uniffi_{name}"),
None => format!("uniffi_{py_binding_name}"),
};
let cdylib = match target_os {
Os::Macos => format!("lib{cdylib_name}.dylib"),
Expand All @@ -944,7 +987,7 @@ fn generate_uniffi_bindings(
};

Ok(UniFfiBindings {
name,
name: py_binding_name,
cdylib,
path: py_binding,
})
Expand All @@ -967,7 +1010,7 @@ pub fn write_uniffi_module(
name: binding_name,
cdylib,
path: uniffi_binding,
} = generate_uniffi_bindings(crate_dir, target_dir, target_os)?;
} = generate_uniffi_bindings(crate_dir, target_dir, target_os, artifact)?;
let py_init = format!("from .{binding_name} import * # NOQA\n");

if !editable {
Expand Down

0 comments on commit c18ffde

Please sign in to comment.