|
| 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 | +} |
0 commit comments