Backed out 5 changesets (bug 1890092, bug 1888683) for causing build bustages & crash...
[gecko.git] / third_party / rust / uniffi_bindgen / src / lib.rs
blob019b24022fd3df7756c02944485c9539a947f61e
1 /* This Source Code Form is subject to the terms of the Mozilla Public
2  * License, v. 2.0. If a copy of the MPL was not distributed with this
3  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 //! # Uniffi: easily build cross-platform software components in Rust
6 //!
7 //! This is a highly-experimental crate for building cross-language software components
8 //! in Rust, based on things we've learned and patterns we've developed in the
9 //! [mozilla/application-services](https://github.com/mozilla/application-services) project.
10 //!
11 //! The idea is to let you write your code once, in Rust, and then re-use it from many
12 //! other programming languages via Rust's C-compatible FFI layer and some automagically
13 //! generated binding code. If you think of it as a kind of [wasm-bindgen](https://github.com/rustwasm/wasm-bindgen)
14 //! wannabe, with a clunkier developer experience but support for more target languages,
15 //! you'll be pretty close to the mark.
16 //!
17 //! Currently supported target languages include Kotlin, Swift and Python.
18 //!
19 //! ## Usage
21 //! To build a cross-language component using `uniffi`, follow these steps.
22 //!
23 //! ### 1) Specify your Component Interface
24 //!
25 //! Start by thinking about the interface you want to expose for use
26 //! from other languages. Use the Interface Definition Language to specify your interface
27 //! in a `.udl` file, where it can be processed by the tools from this crate.
28 //! For example you might define an interface like this:
29 //!
30 //! ```text
31 //! namespace example {
32 //!   u32 foo(u32 bar);
33 //! }
34 //!
35 //! dictionary MyData {
36 //!   u32 num_foos;
37 //!   bool has_a_bar;
38 //! }
39 //! ```
40 //!
41 //! ### 2) Implement the Component Interface as a Rust crate
42 //!
43 //! With the interface, defined, provide a corresponding implementation of that interface
44 //! as a standard-looking Rust crate, using functions and structs and so-on. For example
45 //! an implementation of the above Component Interface might look like this:
46 //!
47 //! ```text
48 //! fn foo(bar: u32) -> u32 {
49 //!     // TODO: a better example!
50 //!     bar + 42
51 //! }
52 //!
53 //! struct MyData {
54 //!   num_foos: u32,
55 //!   has_a_bar: bool
56 //! }
57 //! ```
58 //!
59 //! ### 3) Generate and include component scaffolding from the UDL file
60 //!
61 //! First you will need to install `uniffi-bindgen` on your system using `cargo install uniffi_bindgen`.
62 //! Then add to your crate `uniffi_build` under `[build-dependencies]`.
63 //! Finally, add a `build.rs` script to your crate and have it call `uniffi_build::generate_scaffolding`
64 //! to process your `.udl` file. This will generate some Rust code to be included in the top-level source
65 //! code of your crate. If your UDL file is named `example.udl`, then your build script would call:
66 //!
67 //! ```text
68 //! uniffi_build::generate_scaffolding("src/example.udl")
69 //! ```
70 //!
71 //! This would output a rust file named `example.uniffi.rs`, ready to be
72 //! included into the code of your rust crate like this:
73 //!
74 //! ```text
75 //! include_scaffolding!("example");
76 //! ```
77 //!
78 //! ### 4) Generate foreign language bindings for the library
79 //!
80 //! The `uniffi-bindgen` utility provides a command-line tool that can produce code to
81 //! consume the Rust library in any of several supported languages.
82 //! It is done by calling (in kotlin for example):
83 //!
84 //! ```text
85 //! uniffi-bindgen --language kotlin ./src/example.udl
86 //! ```
87 //!
88 //! This will produce a file `example.kt` in the same directory as the .udl file, containing kotlin bindings
89 //! to load and use the compiled rust code via its C-compatible FFI.
90 //!
92 #![warn(rust_2018_idioms, unused_qualifications)]
93 #![allow(unknown_lints)]
95 use anyhow::{anyhow, bail, Context, Result};
96 use camino::{Utf8Path, Utf8PathBuf};
97 use fs_err::{self as fs, File};
98 use serde::{de::DeserializeOwned, Deserialize, Serialize};
99 use std::io::prelude::*;
100 use std::io::ErrorKind;
101 use std::{collections::HashMap, process::Command};
103 pub mod backend;
104 pub mod bindings;
105 pub mod interface;
106 pub mod library_mode;
107 pub mod macro_metadata;
108 pub mod scaffolding;
110 use bindings::TargetLanguage;
111 pub use interface::ComponentInterface;
112 use scaffolding::RustScaffolding;
114 /// Trait for bindings configuration.  Each bindings language defines one of these.
116 /// BindingsConfigs are initially loaded from `uniffi.toml` file.  Then the trait methods are used
117 /// to fill in missing values.
118 pub trait BindingsConfig: DeserializeOwned {
119     /// Update missing values using the `ComponentInterface`
120     fn update_from_ci(&mut self, ci: &ComponentInterface);
122     /// Update missing values using the dylib file for the main crate, when in library mode.
123     ///
124     /// cdylib_name will be the library filename without the leading `lib` and trailing extension
125     fn update_from_cdylib_name(&mut self, cdylib_name: &str);
127     /// Update missing values from config instances from dependent crates
128     ///
129     /// config_map maps crate names to config instances. This is mostly used to set up external
130     /// types.
131     fn update_from_dependency_configs(&mut self, config_map: HashMap<&str, &Self>);
134 /// Binding generator config with no members
135 #[derive(Clone, Debug, Deserialize, Hash, PartialEq, PartialOrd, Ord, Eq)]
136 pub struct EmptyBindingsConfig;
138 impl BindingsConfig for EmptyBindingsConfig {
139     fn update_from_ci(&mut self, _ci: &ComponentInterface) {}
140     fn update_from_cdylib_name(&mut self, _cdylib_name: &str) {}
141     fn update_from_dependency_configs(&mut self, _config_map: HashMap<&str, &Self>) {}
144 /// A trait representing a UniFFI Binding Generator
146 /// External crates that implement binding generators, should implement this type
147 /// and call the [`generate_external_bindings`] using a type that implements this trait.
148 pub trait BindingGenerator: Sized {
149     /// Handles configuring the bindings
150     type Config: BindingsConfig;
152     /// Writes the bindings to the output directory
153     ///
154     /// # Arguments
155     /// - `ci`: A [`ComponentInterface`] representing the interface
156     /// - `config`: An instance of the [`BindingsConfig`] associated with this type
157     /// - `out_dir`: The path to where the binding generator should write the output bindings
158     fn write_bindings(
159         &self,
160         ci: &ComponentInterface,
161         config: &Self::Config,
162         out_dir: &Utf8Path,
163     ) -> Result<()>;
165     /// Check if `library_path` used by library mode is valid for this generator
166     fn check_library_path(&self, library_path: &Utf8Path, cdylib_name: Option<&str>) -> Result<()>;
169 struct BindingGeneratorDefault {
170     target_languages: Vec<TargetLanguage>,
171     try_format_code: bool,
174 impl BindingGenerator for BindingGeneratorDefault {
175     type Config = Config;
177     fn write_bindings(
178         &self,
179         ci: &ComponentInterface,
180         config: &Self::Config,
181         out_dir: &Utf8Path,
182     ) -> Result<()> {
183         for &language in &self.target_languages {
184             bindings::write_bindings(
185                 &config.bindings,
186                 ci,
187                 out_dir,
188                 language,
189                 self.try_format_code,
190             )?;
191         }
192         Ok(())
193     }
195     fn check_library_path(&self, library_path: &Utf8Path, cdylib_name: Option<&str>) -> Result<()> {
196         for &language in &self.target_languages {
197             if cdylib_name.is_none() && language != TargetLanguage::Swift {
198                 bail!("Generate bindings for {language} requires a cdylib, but {library_path} was given");
199             }
200         }
201         Ok(())
202     }
205 /// Generate bindings for an external binding generator
206 /// Ideally, this should replace the [`generate_bindings`] function below
208 /// Implements an entry point for external binding generators.
209 /// The function does the following:
210 /// - It parses the `udl` in a [`ComponentInterface`]
211 /// - Parses the `uniffi.toml` and loads it into the type that implements [`BindingsConfig`]
212 /// - Creates an instance of [`BindingGenerator`], based on type argument `B`, and run [`BindingGenerator::write_bindings`] on it
214 /// # Arguments
215 /// - `binding_generator`: Type that implements BindingGenerator
216 /// - `udl_file`: The path to the UDL file
217 /// - `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.
218 /// - `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`
219 /// - `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`]
220 /// - `crate_name`: Override the default crate name that is guessed from UDL file path.
221 pub fn generate_external_bindings<T: BindingGenerator>(
222     binding_generator: T,
223     udl_file: impl AsRef<Utf8Path>,
224     config_file_override: Option<impl AsRef<Utf8Path>>,
225     out_dir_override: Option<impl AsRef<Utf8Path>>,
226     library_file: Option<impl AsRef<Utf8Path>>,
227     crate_name: Option<&str>,
228 ) -> Result<()> {
229     let crate_name = crate_name
230         .map(|c| Ok(c.to_string()))
231         .unwrap_or_else(|| crate_name_from_cargo_toml(udl_file.as_ref()))?;
232     let mut component = parse_udl(udl_file.as_ref(), &crate_name)?;
233     if let Some(ref library_file) = library_file {
234         macro_metadata::add_to_ci_from_library(&mut component, library_file.as_ref())?;
235     }
236     let crate_root = &guess_crate_root(udl_file.as_ref()).context("Failed to guess crate root")?;
238     let config_file_override = config_file_override.as_ref().map(|p| p.as_ref());
240     let config = {
241         let mut config = load_initial_config::<T::Config>(crate_root, config_file_override)?;
242         config.update_from_ci(&component);
243         if let Some(ref library_file) = library_file {
244             if let Some(cdylib_name) = crate::library_mode::calc_cdylib_name(library_file.as_ref())
245             {
246                 config.update_from_cdylib_name(cdylib_name)
247             }
248         };
249         config
250     };
252     let out_dir = get_out_dir(
253         udl_file.as_ref(),
254         out_dir_override.as_ref().map(|p| p.as_ref()),
255     )?;
256     binding_generator.write_bindings(&component, &config, &out_dir)
259 // Generate the infrastructural Rust code for implementing the UDL interface,
260 // such as the `extern "C"` function definitions and record data types.
261 // Locates and parses Cargo.toml to determine the name of the crate.
262 pub fn generate_component_scaffolding(
263     udl_file: &Utf8Path,
264     out_dir_override: Option<&Utf8Path>,
265     format_code: bool,
266 ) -> Result<()> {
267     let component = parse_udl(udl_file, &crate_name_from_cargo_toml(udl_file)?)?;
268     generate_component_scaffolding_inner(component, udl_file, out_dir_override, format_code)
271 // Generate the infrastructural Rust code for implementing the UDL interface,
272 // such as the `extern "C"` function definitions and record data types, using
273 // the specified crate name.
274 pub fn generate_component_scaffolding_for_crate(
275     udl_file: &Utf8Path,
276     crate_name: &str,
277     out_dir_override: Option<&Utf8Path>,
278     format_code: bool,
279 ) -> Result<()> {
280     let component = parse_udl(udl_file, crate_name)?;
281     generate_component_scaffolding_inner(component, udl_file, out_dir_override, format_code)
284 fn generate_component_scaffolding_inner(
285     component: ComponentInterface,
286     udl_file: &Utf8Path,
287     out_dir_override: Option<&Utf8Path>,
288     format_code: bool,
289 ) -> Result<()> {
290     let file_stem = udl_file.file_stem().context("not a file")?;
291     let filename = format!("{file_stem}.uniffi.rs");
292     let out_path = get_out_dir(udl_file, out_dir_override)?.join(filename);
293     let mut f = File::create(&out_path)?;
294     write!(f, "{}", RustScaffolding::new(&component, file_stem))
295         .context("Failed to write output file")?;
296     if format_code {
297         format_code_with_rustfmt(&out_path)?;
298     }
299     Ok(())
302 // Generate the bindings in the target languages that call the scaffolding
303 // Rust code.
304 pub fn generate_bindings(
305     udl_file: &Utf8Path,
306     config_file_override: Option<&Utf8Path>,
307     target_languages: Vec<TargetLanguage>,
308     out_dir_override: Option<&Utf8Path>,
309     library_file: Option<&Utf8Path>,
310     crate_name: Option<&str>,
311     try_format_code: bool,
312 ) -> Result<()> {
313     generate_external_bindings(
314         BindingGeneratorDefault {
315             target_languages,
316             try_format_code,
317         },
318         udl_file,
319         config_file_override,
320         out_dir_override,
321         library_file,
322         crate_name,
323     )
326 pub fn print_repr(library_path: &Utf8Path) -> Result<()> {
327     let metadata = macro_metadata::extract_from_library(library_path)?;
328     println!("{metadata:#?}");
329     Ok(())
332 // Given the path to a UDL file, locate and parse the corresponding Cargo.toml to determine
333 // the library crate name.
334 // Note that this is largely a copy of code in uniffi_macros/src/util.rs, but sharing it
335 // isn't trivial and it's not particularly complicated so we've just copied it.
336 fn crate_name_from_cargo_toml(udl_file: &Utf8Path) -> Result<String> {
337     #[derive(Deserialize)]
338     struct CargoToml {
339         package: Package,
340         #[serde(default)]
341         lib: Lib,
342     }
344     #[derive(Deserialize)]
345     struct Package {
346         name: String,
347     }
349     #[derive(Default, Deserialize)]
350     struct Lib {
351         name: Option<String>,
352     }
354     let file = guess_crate_root(udl_file)?.join("Cargo.toml");
355     let cargo_toml_bytes =
356         fs::read(file).context("Can't find Cargo.toml to determine the crate name")?;
358     let cargo_toml = toml::from_slice::<CargoToml>(&cargo_toml_bytes)?;
360     let lib_crate_name = cargo_toml
361         .lib
362         .name
363         .unwrap_or_else(|| cargo_toml.package.name.replace('-', "_"));
365     Ok(lib_crate_name)
368 /// Guess the root directory of the crate from the path of its UDL file.
370 /// For now, we assume that the UDL file is in `./src/something.udl` relative
371 /// to the crate root. We might consider something more sophisticated in
372 /// future.
373 pub fn guess_crate_root(udl_file: &Utf8Path) -> Result<&Utf8Path> {
374     let path_guess = udl_file
375         .parent()
376         .context("UDL file has no parent folder!")?
377         .parent()
378         .context("UDL file has no grand-parent folder!")?;
379     if !path_guess.join("Cargo.toml").is_file() {
380         bail!("UDL file does not appear to be inside a crate")
381     }
382     Ok(path_guess)
385 fn get_out_dir(udl_file: &Utf8Path, out_dir_override: Option<&Utf8Path>) -> Result<Utf8PathBuf> {
386     Ok(match out_dir_override {
387         Some(s) => {
388             // Create the directory if it doesn't exist yet.
389             fs::create_dir_all(s)?;
390             s.canonicalize_utf8().context("Unable to find out-dir")?
391         }
392         None => udl_file
393             .parent()
394             .context("File has no parent directory")?
395             .to_owned(),
396     })
399 fn parse_udl(udl_file: &Utf8Path, crate_name: &str) -> Result<ComponentInterface> {
400     let udl = fs::read_to_string(udl_file)
401         .with_context(|| format!("Failed to read UDL from {udl_file}"))?;
402     let group = uniffi_udl::parse_udl(&udl, crate_name)?;
403     ComponentInterface::from_metadata(group)
406 fn format_code_with_rustfmt(path: &Utf8Path) -> Result<()> {
407     let status = Command::new("rustfmt").arg(path).status().map_err(|e| {
408         let ctx = match e.kind() {
409             ErrorKind::NotFound => "formatting was requested, but rustfmt was not found",
410             _ => "unknown error when calling rustfmt",
411         };
412         anyhow!(e).context(ctx)
413     })?;
414     if !status.success() {
415         bail!("rustmt failed when formatting scaffolding. Note: --no-format can be used to skip formatting");
416     }
417     Ok(())
420 fn load_initial_config<Config: DeserializeOwned>(
421     crate_root: &Utf8Path,
422     config_file_override: Option<&Utf8Path>,
423 ) -> Result<Config> {
424     let path = match config_file_override {
425         Some(cfg) => Some(cfg.to_owned()),
426         None => crate_root.join("uniffi.toml").canonicalize_utf8().ok(),
427     };
428     let toml_config = match path {
429         Some(path) => {
430             let contents = fs::read_to_string(path).context("Failed to read config file")?;
431             toml::de::from_str(&contents)?
432         }
433         None => toml::Value::from(toml::value::Table::default()),
434     };
435     Ok(toml_config.try_into()?)
438 #[derive(Debug, Clone, Default, Serialize, Deserialize)]
439 pub struct Config {
440     #[serde(default)]
441     bindings: bindings::Config,
444 impl BindingsConfig for Config {
445     fn update_from_ci(&mut self, ci: &ComponentInterface) {
446         self.bindings.kotlin.update_from_ci(ci);
447         self.bindings.swift.update_from_ci(ci);
448         self.bindings.python.update_from_ci(ci);
449         self.bindings.ruby.update_from_ci(ci);
450     }
452     fn update_from_cdylib_name(&mut self, cdylib_name: &str) {
453         self.bindings.kotlin.update_from_cdylib_name(cdylib_name);
454         self.bindings.swift.update_from_cdylib_name(cdylib_name);
455         self.bindings.python.update_from_cdylib_name(cdylib_name);
456         self.bindings.ruby.update_from_cdylib_name(cdylib_name);
457     }
459     fn update_from_dependency_configs(&mut self, config_map: HashMap<&str, &Self>) {
460         self.bindings.kotlin.update_from_dependency_configs(
461             config_map
462                 .iter()
463                 .map(|(key, config)| (*key, &config.bindings.kotlin))
464                 .collect(),
465         );
466         self.bindings.swift.update_from_dependency_configs(
467             config_map
468                 .iter()
469                 .map(|(key, config)| (*key, &config.bindings.swift))
470                 .collect(),
471         );
472         self.bindings.python.update_from_dependency_configs(
473             config_map
474                 .iter()
475                 .map(|(key, config)| (*key, &config.bindings.python))
476                 .collect(),
477         );
478         self.bindings.ruby.update_from_dependency_configs(
479             config_map
480                 .iter()
481                 .map(|(key, config)| (*key, &config.bindings.ruby))
482                 .collect(),
483         );
484     }
487 // FIXME(HACK):
488 // Include the askama config file into the build.
489 // That way cargo tracks the file and other tools relying on file tracking see it as well.
490 // See https://bugzilla.mozilla.org/show_bug.cgi?id=1774585
491 // In the future askama should handle that itself by using the `track_path::path` API,
492 // see https://github.com/rust-lang/rust/pull/84029
493 #[allow(dead_code)]
494 mod __unused {
495     const _: &[u8] = include_bytes!("../askama.toml");
498 #[cfg(test)]
499 mod test {
500     use super::*;
502     #[test]
503     fn test_guessing_of_crate_root_directory_from_udl_file() {
504         // When running this test, this will be the ./uniffi_bindgen directory.
505         let this_crate_root = Utf8PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").unwrap());
507         let example_crate_root = this_crate_root
508             .parent()
509             .expect("should have a parent directory")
510             .join("examples/arithmetic");
511         assert_eq!(
512             guess_crate_root(&example_crate_root.join("src/arthmetic.udl")).unwrap(),
513             example_crate_root
514         );
516         let not_a_crate_root = &this_crate_root.join("src/templates");
517         assert!(guess_crate_root(&not_a_crate_root.join("src/example.udl")).is_err());
518     }