Skip to content
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
9 changes: 8 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 8 additions & 1 deletion tools/embedded-uniffi-bindgen/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,11 @@ license = "MPL-2.0"
name = "embedded-uniffi-bindgen"

[dependencies]
uniffi = { version = "0.29.0", features = ["cli"] }
anyhow = "1"
camino = "1"
glob = "0.3"
toml = "0.5"
clap = {version = "4.2", default-features = false, features = ["std", "derive"]}
serde = { version = "1", features = ["derive"] }
uniffi_bindgen = { version = "0.29.3" }
uniffi_pipeline = { version = "0.29.3" }
186 changes: 186 additions & 0 deletions tools/embedded-uniffi-bindgen/src/config_supplier.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */

// This is the only significant difference from vanilla UniFFI,
//
// We define our own config supplier than parses `Cargo.toml` files directly to avoid a dependency
// on `cargo`. This code tries to parse as little as possible to make this work.
//
// We could move this code into the main uniffi repo and add a flag to use it instead of the
// cargo-based config supplier.

use std::{
collections::{HashMap, HashSet},
env, fs,
sync::LazyLock,
};

use anyhow::{anyhow, Context, Result};
use camino::{Utf8Path, Utf8PathBuf};
use serde::Deserialize;
use uniffi_bindgen::BindgenCrateConfigSupplier;

pub struct NoCargoConfigSupplier;

impl BindgenCrateConfigSupplier for NoCargoConfigSupplier {
fn get_toml(&self, crate_name: &str) -> Result<Option<toml::value::Table>> {
match self.get_toml_path(crate_name) {
None => Ok(None),
Some(path) => Ok(Some(toml::from_str(&fs::read_to_string(path)?)?)),
}
}

fn get_toml_path(&self, crate_name: &str) -> Option<Utf8PathBuf> {
let crate_map = CRATE_MAP.as_ref().expect("Error parsing Cargo.toml files");
let crate_root = crate_map.get(crate_name)?;
let toml_path = crate_root.join("uniffi.toml");
toml_path.exists().then_some(toml_path)
}

/// 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> {
let crate_map = CRATE_MAP.as_ref().expect("Error parsing Cargo.toml files");
let crate_root = crate_map
.get(crate_name)
.ok_or_else(|| anyhow!("Unknown crate: {crate_name}"))?;
let udl_path = crate_root.join(format!("src/{udl_name}.udl"));
fs::read_to_string(&udl_path).context(format!("Error reading {udl_path}"))
}
}

static CRATE_MAP: LazyLock<Result<HashMap<String, Utf8PathBuf>>> =
LazyLock::new(find_workspace_crates);

/// Find all crates in this workspace and return a map of crate_name => crate_root_path
fn find_workspace_crates() -> Result<HashMap<String, Utf8PathBuf>> {
let workspace_toml = find_workspace_toml()?;

let mut toml_paths_to_process = vec![];
for path in workspace_toml.data.workspace.unwrap().members {
toml_paths_to_process.extend(join_and_glob(&workspace_toml.dir, path)?)
}
let mut toml_paths_processed = HashSet::new();
let mut map = HashMap::new();

loop {
let Some(crate_dir) = toml_paths_to_process.pop() else {
break;
};
let toml_path = join(&crate_dir, "Cargo.toml")?;
if !toml_paths_processed.insert(toml_path.clone()) {
continue;
}

let toml = CargoToml::from_path(&toml_path)?;
let new_paths = find_other_cargo_toml_paths(&crate_dir, &toml)?;
toml_paths_to_process.extend(new_paths);

// Add both the package name and library name to the map
if let Some(package) = toml.package {
map.insert(package.name.replace("-", "_"), crate_dir.clone());
}

if let Some(CargoLibrary { name: Some(name) }) = toml.lib {
map.insert(name.replace("-", "_"), crate_dir);
}
}
Ok(map)
}

/// Find the workspace Cargo.toml file.
///
/// Returns the parsed TOML data plus the directory of the file
fn find_workspace_toml() -> Result<CargoTomlFile> {
let current_dir = camino::Utf8PathBuf::from_path_buf(env::current_dir()?)
.map_err(|_| anyhow!("path is not UTF8"))?;
let mut dir = current_dir.as_path();
loop {
let path = dir.join("Cargo.toml");
if path.exists() {
let toml = CargoToml::from_path(&path)?;
if toml.workspace.is_some() {
return Ok(CargoTomlFile {
data: toml,
dir: dir.to_path_buf(),
});
}
}
dir = dir
.parent()
.ok_or_else(|| anyhow!("Couldn't find workspace Cargo.toml"))?;
}
}

/// Process Cargo.toml data and return all crate paths referenced in it
fn find_other_cargo_toml_paths(crate_dir: &Utf8Path, toml: &CargoToml) -> Result<Vec<Utf8PathBuf>> {
toml.dependencies
.iter()
.flat_map(|d| d.values())
.filter_map(|dep| match dep {
CargoDependency::Detailed { path: Some(path) } => Some(join(crate_dir, path)),
_ => None,
})
.collect()
}

fn join(dir: &Utf8Path, child: impl AsRef<str>) -> Result<Utf8PathBuf> {
let child = child.as_ref();
dir.join(child)
.canonicalize_utf8()
.map_err(|p| anyhow!("Invalid path: {p} {dir}, {child}"))
}

fn join_and_glob(dir: &Utf8Path, child: impl AsRef<str>) -> Result<Vec<Utf8PathBuf>> {
let child = child.as_ref();
glob::glob(dir.join(child).as_str())?
.map(|entry| anyhow::Ok(Utf8PathBuf::try_from(entry?)?))
.map(|path| Ok(path?.canonicalize_utf8()?))
.collect()
}

#[derive(Debug)]
struct CargoTomlFile {
data: CargoToml,
dir: Utf8PathBuf,
}

#[derive(Debug, Deserialize)]
struct CargoToml {
package: Option<CargoPackage>,
lib: Option<CargoLibrary>,
workspace: Option<CargoWorkspace>,
dependencies: Option<HashMap<String, CargoDependency>>,
}

impl CargoToml {
fn from_path(path: &Utf8Path) -> Result<Self> {
let contents = fs::read_to_string(path).context(format!("reading {path}"))?;
toml::from_str(&contents).context(format!("parsing {path}"))
}
}

#[derive(Debug, Deserialize)]
struct CargoPackage {
name: String,
}

#[derive(Debug, Deserialize)]
struct CargoLibrary {
name: Option<String>,
}

#[derive(Debug, Deserialize)]
struct CargoWorkspace {
members: Vec<Utf8PathBuf>,
}

#[derive(Debug, Deserialize)]
#[serde(untagged)]
enum CargoDependency {
#[allow(dead_code)]
Simple(String),
Detailed {
path: Option<Utf8PathBuf>,
},
}
7 changes: 5 additions & 2 deletions tools/embedded-uniffi-bindgen/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */

fn main() {
uniffi::uniffi_bindgen_main()
pub mod config_supplier;
mod uniffi_bindgen;

fn main() -> anyhow::Result<()> {
uniffi_bindgen::run_main()
}
Loading