/* This Source Code Form is subject to the terms of the Mozilla Public *License,v.2.0.IfacopyoftheMPLwasnotdistributedwiththis
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
//! # Uniffi: easily build cross-platform software components in Rust //! //! This is a highly-experimental crate for building cross-language software components //! in Rust, based on things we've learned and patterns we've developed in the //! [mozilla/application-services](https://github.com/mozilla/application-services) project. //! //! The idea is to let you write your code once, in Rust, and then re-use it from many //! other programming languages via Rust's C-compatible FFI layer and some automagically //! generated binding code. If you think of it as a kind of [wasm-bindgen](https://github.com/rustwasm/wasm-bindgen) //! wannabe, with a clunkier developer experience but support for more target languages, //! you'll be pretty close to the mark. //! //! Currently supported target languages include Kotlin, Swift and Python. //! //! ## Usage // //! To build a cross-language component using `uniffi`, follow these steps. //! //! ### 1) Specify your Component Interface //! //! Start by thinking about the interface you want to expose for use //! from other languages. Use the Interface Definition Language to specify your interface //! in a `.udl` file, where it can be processed by the tools from this crate. //! For example you might define an interface like this: //! //! ```text //! namespace example { //! u32 foo(u32 bar); //! } //! //! dictionary MyData { //! u32 num_foos; //! bool has_a_bar; //! } //! ``` //! //! ### 2) Implement the Component Interface as a Rust crate //! //! With the interface, defined, provide a corresponding implementation of that interface //! as a standard-looking Rust crate, using functions and structs and so-on. For example //! an implementation of the above Component Interface might look like this: //! //! ```text //! fn foo(bar: u32) -> u32 { //! // TODO: a better example! //! bar + 42 //! } //! //! struct MyData { //! num_foos: u32, //! has_a_bar: bool //! } //! ``` //! //! ### 3) Generate and include component scaffolding from the UDL file //! //! Add to your crate `uniffi_build` under `[build-dependencies]`, //! then add a `build.rs` script to your crate and have it call `uniffi_build::generate_scaffolding` //! to process your `.udl` file. This will generate some Rust code to be included in the top-level source //! code of your crate. If your UDL file is named `example.udl`, then your build script would call: //! //! ```text //! uniffi_build::generate_scaffolding("src/example.udl") //! ``` //! //! This would output a rust file named `example.uniffi.rs`, ready to be //! included into the code of your rust crate like this: //! //! ```text //! include_scaffolding!("example"); //! ``` //! //! ### 4) Generate foreign language bindings for the library //! //! You will need ensure a local `uniffi-bindgen` - see <https://mozilla.github.io/uniffi-rs/tutorial/foreign_language_bindings.html> //! This utility provides a command-line tool that can produce code to //! consume the Rust library in any of several supported languages. //! It is done by calling (in kotlin for example): //! //! ```text //! cargo run --bin -p uniffi-bindgen --language kotlin ./src/example.udl //! ``` //! //! This will produce a file `example.kt` in the same directory as the .udl file, containing kotlin bindings //! to load and use the compiled rust code via its C-compatible FFI. //!
use anyhow::{anyhow, bail, Context, Result}; use camino::{Utf8Path, Utf8PathBuf}; use fs_err::{selfas fs, File}; use serde::Deserialize; use std::io::prelude::*; use std::io::ErrorKind; use std::process::Command;
/// The options used when creating bindings. Named such /// it doesn't cause confusion that it's settings specific to /// the generator itself. // TODO: We should try and move the public interface of the module to // this struct. For now, only the BindingGenerator uses it. #[derive(Debug, Default)] pubstruct GenerationSettings { pub out_dir: Utf8PathBuf, pub try_format_code: bool, pub cdylib: Option<String>,
}
/// A trait representing a UniFFI Binding Generator /// /// External crates that implement binding generators, should implement this type /// and call the [`generate_external_bindings`] using a type that implements this trait. pubtrait BindingGenerator: Sized { /// Handles configuring the bindings type Config;
/// Creates a new config. fn new_config(&self, root_toml: &toml::Value) -> Result<Self::Config>;
/// Update the various config items in preparation to write one or more of them. /// /// # Arguments /// - `cdylib`: The name of the cdylib file, if known. /// - `library_path`: The name of library used to extract the symbols. /// - `components`: A mutable array of [`Component`]s to be updated. fn update_component_configs(
&self,
settings: &GenerationSettings,
components: &mut Vec<Component<Self::Config>>,
) -> Result<()>;
/// Writes the bindings to the output directory /// /// # Arguments /// - `components`: An array of [`Component`]s representing the items to be generated. /// - `out_dir`: The path to where the binding generator should write the output bindings fn write_bindings(
&self,
settings: &GenerationSettings,
components: &[Component<Self::Config>],
) -> Result<()>;
}
/// A trait to alter language specific type representations. /// /// It is meant to be implemented by each language oracle. It takes a /// ['ComponentInterface'] and uses its own specific language adjustment /// functions to be able to generate language specific templates. pubtrait VisitMut { /// Go through each `Record` of a [`ComponentInterface`] and /// adjust it to language specific naming conventions. fn visit_record(&self, record: &mut Record);
/// Change the name of an `Object` of a [`ComponentInterface` /// to language specific naming conventions. fn visit_object(&self, object: &mut Object);
/// Change the name of a `Field` of an `Enum` `Variant` /// to language specific naming conventions. fn visit_field(&self, field: &mut Field);
/// Change the name of a `FfiField` inside a `FfiStruct` /// to language specific naming conventions. fn visit_ffi_field(&self, ffi_field: &mut FfiField);
/// Change the `Arugment` of a `FfiFunction` in the [`ComponentInterface`] /// to language specific naming conventions. fn visit_ffi_argument(&self, ffi_argument: &mut FfiArgument);
/// Go through each `Enum` of a [`ComponentInterface`] and /// adjust it to language specific naming conventions. fn visit_enum(&self, is_error: bool, enum_: &mutEnum);
/// Change the naming of the key in the [`ComponentInterface`] /// `BTreeMap` where all `Enum`s are stored to reflect the changed /// name of an `Enum`. fn visit_enum_key(&self, key: &mut String) -> String;
/// Go through each `Variant` of an `Enum` and /// adjust it to language specific naming conventions. fn visit_variant(&self, is_error: bool, variant: &mut Variant);
/// Go through each `Type` in the `TypeUniverse` of /// a [`ComponentInterface`] and adjust it to language specific /// naming conventions. fn visit_type(&self, type_: &mutType);
/// Go through each error name in the interface and adjust it to language specific naming /// conventions. The new name must match the name of the Enum/Object definition after it's /// visited. fn visit_error_name(&self, name: &mut String);
/// Go through each `Method` of an `Object` and /// adjust it to language specific naming conventions. fn visit_method(&self, method: &mut Method);
/// Go through each `Argument` of a `Function` and /// adjust it to language specific naming conventions. fn visit_argument(&self, argument: &mutArgument);
/// Go through each `Constructor` of a [`ComponentInterface`] and /// adjust it to language specific naming conventions. fn visit_constructor(&self, constructor: &mut Constructor);
/// Go through each `Function` of a [`ComponentInterface`] and /// adjust it to language specific naming conventions. fn visit_function(&self, function: &mutFunction);
}
/// Everything needed to generate a ComponentInterface. #[derive(Debug)] pubstruct Component<Config> { pub ci: ComponentInterface, pub config: Config,
}
/// A trait used by the bindgen to obtain config information about a source crate /// which was found in the metadata for the library. /// /// This is an abstraction around needing the source directory for a crate. /// In most cases `cargo_metadata` can be used, but this should be able to work in /// more environments. pubtrait BindgenCrateConfigSupplier { /// Get the toml for the crate. Probably came from uniffi.toml in the root of the crate source. fn get_toml(&self, _crate_name: &str) -> Result<Option<toml::value::Table>> {
Ok(None)
} /// Obtains the contents of the named UDL file which was referenced by the type metadata. fn get_udl(&self, crate_name: &str, udl_name: &str) -> Result<String> {
bail!("Crate {crate_name} has no UDL {udl_name}")
}
}
pubstruct EmptyCrateConfigSupplier; impl BindgenCrateConfigSupplier for EmptyCrateConfigSupplier {}
/// A convenience function for the CLI to help avoid using static libs /// in places cdylibs are required. pubfn is_cdylib(library_file: impl AsRef<Utf8Path>) -> bool { crate::library_mode::calc_cdylib_name(library_file.as_ref()).is_some()
}
/// Generate bindings for an external binding generator /// Ideally, this should replace the [`generate_bindings`] function below /// /// Implements an entry point for external binding generators. /// The function does the following: /// - It parses the `udl` in a [`ComponentInterface`] /// - Creates an instance of [`BindingGenerator`], based on type argument `B`, and run [`BindingGenerator::write_bindings`] on it /// /// # Arguments /// - `binding_generator`: Type that implements BindingGenerator /// - `udl_file`: The path to the UDL file /// - `config_file_override`: The path to the configuration toml file, most likely called `uniffi.toml`. If [`None`], the function will try to guess based on the crate's root. /// - `out_dir_override`: The path to write the bindings to. If [`None`], it will be the path to the parent directory of the `udl_file` /// - `library_file`: The path to a dynamic library to attempt to extract the definitions from and extend the component interface with. No extensions to component interface occur if it's [`None`] /// - `crate_name`: Override the default crate name that is guessed from UDL file path. pubfn generate_external_bindings<T: BindingGenerator>(
binding_generator: &T,
udl_file: impl AsRef<Utf8Path>,
config_file_override: Option<impl AsRef<Utf8Path>>,
out_dir_override: Option<impl AsRef<Utf8Path>>,
library_file: Option<impl AsRef<Utf8Path>>,
crate_name: Option<&str>,
try_format_code: bool,
) -> Result<()> { let crate_name = crate_name
.map(|c| Ok(c.to_string()))
.unwrap_or_else(|| crate_name_from_cargo_toml(udl_file.as_ref()))?; letmut ci = parse_udl(udl_file.as_ref(), &crate_name)?; iflet Some(ref library_file) = library_file {
macro_metadata::add_to_ci_from_library(&mut ci, library_file.as_ref())?;
} let crate_root = &guess_crate_root(udl_file.as_ref()).context("Failed to guess crate root")?;
let config_file_override = config_file_override.as_ref().map(|p| p.as_ref());
let config = { let crate_config = load_toml_file(Some(&crate_root.join("uniffi.toml")))
.context("failed to load {crate_root}/uniffi.toml")?; let toml_value =
overridden_config_value(crate_config.unwrap_or_default(), config_file_override)?;
binding_generator.new_config(&toml_value)?
};
// Generate the infrastructural Rust code for implementing the UDL interface, // such as the `extern "C"` function definitions and record data types. // Locates and parses Cargo.toml to determine the name of the crate. pubfn generate_component_scaffolding(
udl_file: &Utf8Path,
out_dir_override: Option<&Utf8Path>,
format_code: bool,
) -> Result<()> { let component = parse_udl(udl_file, &crate_name_from_cargo_toml(udl_file)?)?;
generate_component_scaffolding_inner(component, udl_file, out_dir_override, format_code)
}
// Generate the infrastructural Rust code for implementing the UDL interface, // such as the `extern "C"` function definitions and record data types, using // the specified crate name. pubfn generate_component_scaffolding_for_crate(
udl_file: &Utf8Path,
crate_name: &str,
out_dir_override: Option<&Utf8Path>,
format_code: bool,
) -> Result<()> { let component = parse_udl(udl_file, crate_name)?;
generate_component_scaffolding_inner(component, udl_file, out_dir_override, format_code)
}
fn generate_component_scaffolding_inner(
component: ComponentInterface,
udl_file: &Utf8Path,
out_dir_override: Option<&Utf8Path>,
format_code: bool,
) -> Result<()> { let file_stem = udl_file.file_stem().context("not a file")?; let filename = format!("{file_stem}.uniffi.rs"); let out_path = get_out_dir(udl_file, out_dir_override)?.join(filename); letmut f = File::create(&out_path)?;
write!(f, "{}", RustScaffolding::new(&component, file_stem))
.context("Failed to write output file")?; if format_code {
format_code_with_rustfmt(&out_path)?;
}
Ok(())
}
// Generate the bindings in the target languages that call the scaffolding // Rust code. pubfn generate_bindings<T: BindingGenerator>(
udl_file: &Utf8Path,
config_file_override: Option<&Utf8Path>,
binding_generator: T,
out_dir_override: Option<&Utf8Path>,
library_file: Option<&Utf8Path>,
crate_name: Option<&str>,
try_format_code: bool,
) -> Result<()> {
generate_external_bindings(
&binding_generator,
udl_file,
config_file_override,
out_dir_override,
library_file,
crate_name,
try_format_code,
)
}
// Given the path to a UDL file, locate and parse the corresponding Cargo.toml to determine // the library crate name. // Note that this is largely a copy of code in uniffi_macros/src/util.rs, but sharing it // isn't trivial and it's not particularly complicated so we've just copied it. fn crate_name_from_cargo_toml(udl_file: &Utf8Path) -> Result<String> { #[derive(Deserialize)] struct CargoToml {
package: Package, #[serde(default)]
lib: Lib,
}
let file = guess_crate_root(udl_file)?.join("Cargo.toml"); let cargo_toml_bytes =
fs::read(file).context("Can't find Cargo.toml to determine the crate name")?;
let cargo_toml = toml::from_slice::<CargoToml>(&cargo_toml_bytes)?;
let lib_crate_name = cargo_toml
.lib
.name
.unwrap_or_else(|| cargo_toml.package.name.replace('-', "_"));
Ok(lib_crate_name)
}
/// Guess the root directory of the crate from the path of its UDL file. /// /// For now, we assume that the UDL file is in `./src/something.udl` relative /// to the crate root. We might consider something more sophisticated in /// future. pubfn guess_crate_root(udl_file: &Utf8Path) -> Result<&Utf8Path> { let path_guess = udl_file
.parent()
.context("UDL file has no parent folder!")?
.parent()
.context("UDL file has no grand-parent folder!")?; if !path_guess.join("Cargo.toml").is_file() {
bail!("UDL file does not appear to be inside a crate")
}
Ok(path_guess)
}
fn get_out_dir(udl_file: &Utf8Path, out_dir_override: Option<&Utf8Path>) -> Result<Utf8PathBuf> {
Ok(match out_dir_override {
Some(s) => { // Create the directory if it doesn't exist yet.
fs::create_dir_all(s)?;
s.canonicalize_utf8().context("Unable to find out-dir")?
}
None => udl_file
.parent()
.context("File has no parent directory")?
.to_owned(),
})
}
fn parse_udl(udl_file: &Utf8Path, crate_name: &str) -> Result<ComponentInterface> { let udl = fs::read_to_string(udl_file)
.with_context(|| format!("Failed to read UDL from {udl_file}"))?; let group = uniffi_udl::parse_udl(&udl, crate_name)?;
ComponentInterface::from_metadata(group)
}
fn format_code_with_rustfmt(path: &Utf8Path) -> Result<()> { let status = Command::new("rustfmt").arg(path).status().map_err(|e| { let ctx = match e.kind() {
ErrorKind::NotFound => "formatting was requested, but rustfmt was not found",
_ => "unknown error when calling rustfmt",
};
anyhow!(e).context(ctx)
})?; if !status.success() {
bail!("rustmt failed when formatting scaffolding. Note: --no-format can be used to skip formatting");
}
Ok(())
}
/// Load TOML from file if the file exists. fn load_toml_file(source: Option<&Utf8Path>) -> Result<Option<toml::value::Table>> { iflet Some(source) = source { if source.exists() { let contents =
fs::read_to_string(source).with_context(|| format!("read file: {:?}", source))?; return Ok(Some(
toml::de::from_str(&contents)
.with_context(|| format!("parse toml: {:?}", source))?,
));
}
}
Ok(None)
}
/// Load the default `uniffi.toml` config, merge TOML trees with `config_file_override` if specified. fn overridden_config_value( mut config: toml::value::Table,
config_file_override: Option<&Utf8Path>,
) -> Result<toml::Value> { let override_config = load_toml_file(config_file_override).context("override config")?; iflet Some(override_config) = override_config {
merge_toml(&mut config, override_config);
}
Ok(toml::Value::from(config))
}
// FIXME(HACK): // Include the askama config file into the build. // That way cargo tracks the file and other tools relying on file tracking see it as well. // See https://bugzilla.mozilla.org/show_bug.cgi?id=1774585 // In the future askama should handle that itself by using the `track_path::path` API, // see https://github.com/rust-lang/rust/pull/84029 #[allow(dead_code)] mod __unused { const _: &[u8] = include_bytes!("../askama.toml");
}
#[cfg(test)] mod test { usesuper::*;
#[test] fn test_guessing_of_crate_root_directory_from_udl_file() { // When running this test, this will be the ./uniffi_bindgen directory. let this_crate_root = Utf8PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").unwrap());
let example_crate_root = this_crate_root
.parent()
.expect("should have a parent directory")
.join("examples/arithmetic");
assert_eq!(
guess_crate_root(&example_crate_root.join("src/arthmetic.udl")).unwrap(),
example_crate_root
);
let not_a_crate_root = &this_crate_root.join("src/templates");
assert!(guess_crate_root(¬_a_crate_root.join("src/example.udl")).is_err());
}
#[test] fn test_merge_toml() { let default = r#"
foo = "foo"
bar = "bar"
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.