From cbe6e0b59f74ce00d5bfe3112c6a7acdddf26a7c Mon Sep 17 00:00:00 2001 From: messense Date: Sat, 2 Sep 2023 11:58:22 +0800 Subject: [PATCH] Don't require `uniffi-bindgen` to be installed for uniffi bindings --- guide/src/bindings.md | 8 ----- src/build_options.rs | 2 +- src/module_writer.rs | 82 +++++++++++++++++++++++++++++++++++-------- 3 files changed, 68 insertions(+), 24 deletions(-) diff --git a/guide/src/bindings.md b/guide/src/bindings.md index 28e75355e..ae9f6b39d 100644 --- a/guide/src/bindings.md +++ b/guide/src/bindings.md @@ -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. diff --git a/src/build_options.rs b/src/build_options.rs index 1624e4262..94b55350b 100644 --- a/src/build_options.rs +++ b/src/build_options.rs @@ -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 { diff --git a/src/module_writer.rs b/src/module_writer.rs index 9319def8c..2a9e9ebc7 100644 --- a/src/module_writer.rs +++ b/src/module_writer.rs @@ -875,28 +875,69 @@ struct UniFfiBindings { path: PathBuf, } +fn uniffi_bindgen_command(crate_dir: &Path) -> Result { + let manifest_path = crate_dir.join("Cargo.toml"); + let cargo_metadata = cargo_metadata::MetadataCommand::new() + .manifest_path(&manifest_path) + // We don't need to resolve the dependency graph + .no_deps() + .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 { - 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::>(); - 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 + }; + + // Disallow library mode without UDL files for now + // Should be removed in https://github.com/PyO3/maturin/pull/1729 + // once uniffi release a new version + if is_library { + bail!("No UDL files found in {}", crate_dir.join("src").display()); } - 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", @@ -906,7 +947,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() { @@ -915,10 +955,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`", @@ -928,14 +983,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"), @@ -944,7 +996,7 @@ fn generate_uniffi_bindings( }; Ok(UniFfiBindings { - name, + name: py_binding_name, cdylib, path: py_binding, }) @@ -967,7 +1019,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 {