Skip to content

Commit 4673a6b

Browse files
authored
Bug 2004947 - Remove cargo dependency from embedded-uniffi-bindgen (#7124)
Instead we do some parsing of the `Cargo.toml` files ourselves. I ran `cargo uniffi-bindgen generate -l kotlin --library ~/application-services/target/debug/libmegazord.so` with both the old and new version and the contents were identical.
1 parent caf03ed commit 4673a6b

File tree

5 files changed

+571
-4
lines changed

5 files changed

+571
-4
lines changed

Cargo.lock

Lines changed: 8 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

tools/embedded-uniffi-bindgen/Cargo.toml

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,11 @@ license = "MPL-2.0"
1010
name = "embedded-uniffi-bindgen"
1111

1212
[dependencies]
13-
uniffi = { version = "0.29.0", features = ["cli"] }
13+
anyhow = "1"
14+
camino = "1"
15+
glob = "0.3"
16+
toml = "0.5"
17+
clap = {version = "4.2", default-features = false, features = ["std", "derive"]}
18+
serde = { version = "1", features = ["derive"] }
19+
uniffi_bindgen = { version = "0.29.3" }
20+
uniffi_pipeline = { version = "0.29.3" }
Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,186 @@
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/. */
4+
5+
// This is the only significant difference from vanilla UniFFI,
6+
//
7+
// We define our own config supplier than parses `Cargo.toml` files directly to avoid a dependency
8+
// on `cargo`. This code tries to parse as little as possible to make this work.
9+
//
10+
// We could move this code into the main uniffi repo and add a flag to use it instead of the
11+
// cargo-based config supplier.
12+
13+
use std::{
14+
collections::{HashMap, HashSet},
15+
env, fs,
16+
sync::LazyLock,
17+
};
18+
19+
use anyhow::{anyhow, Context, Result};
20+
use camino::{Utf8Path, Utf8PathBuf};
21+
use serde::Deserialize;
22+
use uniffi_bindgen::BindgenCrateConfigSupplier;
23+
24+
pub struct NoCargoConfigSupplier;
25+
26+
impl BindgenCrateConfigSupplier for NoCargoConfigSupplier {
27+
fn get_toml(&self, crate_name: &str) -> Result<Option<toml::value::Table>> {
28+
match self.get_toml_path(crate_name) {
29+
None => Ok(None),
30+
Some(path) => Ok(Some(toml::from_str(&fs::read_to_string(path)?)?)),
31+
}
32+
}
33+
34+
fn get_toml_path(&self, crate_name: &str) -> Option<Utf8PathBuf> {
35+
let crate_map = CRATE_MAP.as_ref().expect("Error parsing Cargo.toml files");
36+
let crate_root = crate_map.get(crate_name)?;
37+
let toml_path = crate_root.join("uniffi.toml");
38+
toml_path.exists().then_some(toml_path)
39+
}
40+
41+
/// Obtains the contents of the named UDL file which was referenced by the type metadata.
42+
fn get_udl(&self, crate_name: &str, udl_name: &str) -> Result<String> {
43+
let crate_map = CRATE_MAP.as_ref().expect("Error parsing Cargo.toml files");
44+
let crate_root = crate_map
45+
.get(crate_name)
46+
.ok_or_else(|| anyhow!("Unknown crate: {crate_name}"))?;
47+
let udl_path = crate_root.join(format!("src/{udl_name}.udl"));
48+
fs::read_to_string(&udl_path).context(format!("Error reading {udl_path}"))
49+
}
50+
}
51+
52+
static CRATE_MAP: LazyLock<Result<HashMap<String, Utf8PathBuf>>> =
53+
LazyLock::new(find_workspace_crates);
54+
55+
/// Find all crates in this workspace and return a map of crate_name => crate_root_path
56+
fn find_workspace_crates() -> Result<HashMap<String, Utf8PathBuf>> {
57+
let workspace_toml = find_workspace_toml()?;
58+
59+
let mut toml_paths_to_process = vec![];
60+
for path in workspace_toml.data.workspace.unwrap().members {
61+
toml_paths_to_process.extend(join_and_glob(&workspace_toml.dir, path)?)
62+
}
63+
let mut toml_paths_processed = HashSet::new();
64+
let mut map = HashMap::new();
65+
66+
loop {
67+
let Some(crate_dir) = toml_paths_to_process.pop() else {
68+
break;
69+
};
70+
let toml_path = join(&crate_dir, "Cargo.toml")?;
71+
if !toml_paths_processed.insert(toml_path.clone()) {
72+
continue;
73+
}
74+
75+
let toml = CargoToml::from_path(&toml_path)?;
76+
let new_paths = find_other_cargo_toml_paths(&crate_dir, &toml)?;
77+
toml_paths_to_process.extend(new_paths);
78+
79+
// Add both the package name and library name to the map
80+
if let Some(package) = toml.package {
81+
map.insert(package.name.replace("-", "_"), crate_dir.clone());
82+
}
83+
84+
if let Some(CargoLibrary { name: Some(name) }) = toml.lib {
85+
map.insert(name.replace("-", "_"), crate_dir);
86+
}
87+
}
88+
Ok(map)
89+
}
90+
91+
/// Find the workspace Cargo.toml file.
92+
///
93+
/// Returns the parsed TOML data plus the directory of the file
94+
fn find_workspace_toml() -> Result<CargoTomlFile> {
95+
let current_dir = camino::Utf8PathBuf::from_path_buf(env::current_dir()?)
96+
.map_err(|_| anyhow!("path is not UTF8"))?;
97+
let mut dir = current_dir.as_path();
98+
loop {
99+
let path = dir.join("Cargo.toml");
100+
if path.exists() {
101+
let toml = CargoToml::from_path(&path)?;
102+
if toml.workspace.is_some() {
103+
return Ok(CargoTomlFile {
104+
data: toml,
105+
dir: dir.to_path_buf(),
106+
});
107+
}
108+
}
109+
dir = dir
110+
.parent()
111+
.ok_or_else(|| anyhow!("Couldn't find workspace Cargo.toml"))?;
112+
}
113+
}
114+
115+
/// Process Cargo.toml data and return all crate paths referenced in it
116+
fn find_other_cargo_toml_paths(crate_dir: &Utf8Path, toml: &CargoToml) -> Result<Vec<Utf8PathBuf>> {
117+
toml.dependencies
118+
.iter()
119+
.flat_map(|d| d.values())
120+
.filter_map(|dep| match dep {
121+
CargoDependency::Detailed { path: Some(path) } => Some(join(crate_dir, path)),
122+
_ => None,
123+
})
124+
.collect()
125+
}
126+
127+
fn join(dir: &Utf8Path, child: impl AsRef<str>) -> Result<Utf8PathBuf> {
128+
let child = child.as_ref();
129+
dir.join(child)
130+
.canonicalize_utf8()
131+
.map_err(|p| anyhow!("Invalid path: {p} {dir}, {child}"))
132+
}
133+
134+
fn join_and_glob(dir: &Utf8Path, child: impl AsRef<str>) -> Result<Vec<Utf8PathBuf>> {
135+
let child = child.as_ref();
136+
glob::glob(dir.join(child).as_str())?
137+
.map(|entry| anyhow::Ok(Utf8PathBuf::try_from(entry?)?))
138+
.map(|path| Ok(path?.canonicalize_utf8()?))
139+
.collect()
140+
}
141+
142+
#[derive(Debug)]
143+
struct CargoTomlFile {
144+
data: CargoToml,
145+
dir: Utf8PathBuf,
146+
}
147+
148+
#[derive(Debug, Deserialize)]
149+
struct CargoToml {
150+
package: Option<CargoPackage>,
151+
lib: Option<CargoLibrary>,
152+
workspace: Option<CargoWorkspace>,
153+
dependencies: Option<HashMap<String, CargoDependency>>,
154+
}
155+
156+
impl CargoToml {
157+
fn from_path(path: &Utf8Path) -> Result<Self> {
158+
let contents = fs::read_to_string(path).context(format!("reading {path}"))?;
159+
toml::from_str(&contents).context(format!("parsing {path}"))
160+
}
161+
}
162+
163+
#[derive(Debug, Deserialize)]
164+
struct CargoPackage {
165+
name: String,
166+
}
167+
168+
#[derive(Debug, Deserialize)]
169+
struct CargoLibrary {
170+
name: Option<String>,
171+
}
172+
173+
#[derive(Debug, Deserialize)]
174+
struct CargoWorkspace {
175+
members: Vec<Utf8PathBuf>,
176+
}
177+
178+
#[derive(Debug, Deserialize)]
179+
#[serde(untagged)]
180+
enum CargoDependency {
181+
#[allow(dead_code)]
182+
Simple(String),
183+
Detailed {
184+
path: Option<Utf8PathBuf>,
185+
},
186+
}

tools/embedded-uniffi-bindgen/src/main.rs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22
* License, v. 2.0. If a copy of the MPL was not distributed with this
33
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
44

5-
fn main() {
6-
uniffi::uniffi_bindgen_main()
5+
pub mod config_supplier;
6+
mod uniffi_bindgen;
7+
8+
fn main() -> anyhow::Result<()> {
9+
uniffi_bindgen::run_main()
710
}

0 commit comments

Comments
 (0)