Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Don't require uniffi-bindgen to be installed for uniffi bindings #1762

Merged
merged 1 commit into from
Sep 2, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 0 additions & 8 deletions guide/src/bindings.md
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
82 changes: 67 additions & 15 deletions src/module_writer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -875,28 +875,69 @@ 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)
// 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<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
};

// 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",
Expand All @@ -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() {
Expand All @@ -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`",
Expand All @@ -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"),
Expand All @@ -944,7 +996,7 @@ fn generate_uniffi_bindings(
};

Ok(UniFfiBindings {
name,
name: py_binding_name,
cdylib,
path: py_binding,
})
Expand All @@ -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 {
Expand Down