| 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 | use std::path::{Path, PathBuf}; |
| 6 | |
| 7 | use crate::bindgen::cargo::cargo_expand; |
| 8 | use crate::bindgen::cargo::cargo_lock::{self, Lock}; |
| 9 | pub(crate) use crate::bindgen::cargo::cargo_metadata::PackageRef; |
| 10 | use crate::bindgen::cargo::cargo_metadata::{self, Metadata}; |
| 11 | use crate::bindgen::cargo::cargo_toml; |
| 12 | use crate::bindgen::config::Profile; |
| 13 | use crate::bindgen::error::Error; |
| 14 | use crate::bindgen::ir::Cfg; |
| 15 | |
| 16 | /// Parse a dependency string used in Cargo.lock |
| 17 | fn parse_dep_string(dep_string: &str) -> (&str, Option<&str>) { |
| 18 | let split: Vec<&str> = dep_string.split_whitespace().collect(); |
| 19 | |
| 20 | (split[0], split.get(index:1).cloned()) |
| 21 | } |
| 22 | |
| 23 | /// A collection of metadata for a library from cargo. |
| 24 | #[derive (Clone, Debug)] |
| 25 | pub(crate) struct Cargo { |
| 26 | manifest_path: PathBuf, |
| 27 | binding_crate_name: String, |
| 28 | lock: Option<Lock>, |
| 29 | metadata: Metadata, |
| 30 | clean: bool, |
| 31 | } |
| 32 | |
| 33 | impl Cargo { |
| 34 | /// Gather metadata from cargo for a specific library and binding crate |
| 35 | /// name. If dependency finding isn't needed then Cargo.lock files don't |
| 36 | /// need to be parsed. |
| 37 | pub(crate) fn load( |
| 38 | crate_dir: &Path, |
| 39 | lock_file: Option<&str>, |
| 40 | binding_crate_name: Option<&str>, |
| 41 | use_cargo_lock: bool, |
| 42 | clean: bool, |
| 43 | only_target_dependencies: bool, |
| 44 | existing_metadata_file: Option<&Path>, |
| 45 | ) -> Result<Cargo, Error> { |
| 46 | let toml_path = crate_dir.join("Cargo.toml" ); |
| 47 | let metadata = |
| 48 | cargo_metadata::metadata(&toml_path, existing_metadata_file, only_target_dependencies) |
| 49 | .map_err(|x| Error::CargoMetadata(toml_path.to_str().unwrap().to_owned(), x))?; |
| 50 | let lock_path = lock_file |
| 51 | .map(PathBuf::from) |
| 52 | .unwrap_or_else(|| Path::new(&metadata.workspace_root).join("Cargo.lock" )); |
| 53 | |
| 54 | let lock = if use_cargo_lock { |
| 55 | match cargo_lock::lock(&lock_path) { |
| 56 | Ok(lock) => Some(lock), |
| 57 | Err(x) => { |
| 58 | warn!("Couldn't load lock file {:?}: {:?}" , lock_path, x); |
| 59 | None |
| 60 | } |
| 61 | } |
| 62 | } else { |
| 63 | None |
| 64 | }; |
| 65 | |
| 66 | // Use the specified binding crate name or infer it from the manifest |
| 67 | let binding_crate_name = match binding_crate_name { |
| 68 | Some(s) => s.to_owned(), |
| 69 | None => { |
| 70 | let manifest = cargo_toml::manifest(&toml_path) |
| 71 | .map_err(|x| Error::CargoToml(toml_path.to_str().unwrap().to_owned(), x))?; |
| 72 | manifest.package.name |
| 73 | } |
| 74 | }; |
| 75 | |
| 76 | Ok(Cargo { |
| 77 | manifest_path: toml_path, |
| 78 | binding_crate_name, |
| 79 | lock, |
| 80 | metadata, |
| 81 | clean, |
| 82 | }) |
| 83 | } |
| 84 | |
| 85 | pub(crate) fn binding_crate_name(&self) -> &str { |
| 86 | &self.binding_crate_name |
| 87 | } |
| 88 | |
| 89 | pub(crate) fn binding_crate_ref(&self) -> PackageRef { |
| 90 | match self.find_pkg_ref(&self.binding_crate_name) { |
| 91 | Some(pkg_ref) => pkg_ref, |
| 92 | None => panic!( |
| 93 | "Unable to find {} for {:?}" , |
| 94 | self.binding_crate_name, self.manifest_path |
| 95 | ), |
| 96 | } |
| 97 | } |
| 98 | |
| 99 | pub(crate) fn dependencies(&self, package: &PackageRef) -> Vec<(PackageRef, Option<Cfg>)> { |
| 100 | let lock = match self.lock { |
| 101 | Some(ref lock) => lock, |
| 102 | None => return vec![], |
| 103 | }; |
| 104 | |
| 105 | let mut dependencies = None; |
| 106 | |
| 107 | // Find the dependencies listing in the lockfile |
| 108 | if let Some(ref root) = lock.root { |
| 109 | // If the version is not on the lockfile then it shouldn't be |
| 110 | // ambiguous. |
| 111 | if root.name == package.name |
| 112 | && package |
| 113 | .version |
| 114 | .as_ref() |
| 115 | .map_or(true, |v| *v == root.version) |
| 116 | { |
| 117 | dependencies = root.dependencies.as_ref(); |
| 118 | } |
| 119 | } |
| 120 | if dependencies.is_none() { |
| 121 | if let Some(ref lock_packages) = lock.package { |
| 122 | for lock_package in lock_packages { |
| 123 | if lock_package.name == package.name |
| 124 | && package |
| 125 | .version |
| 126 | .as_ref() |
| 127 | .map_or(true, |v| *v == lock_package.version) |
| 128 | { |
| 129 | dependencies = lock_package.dependencies.as_ref(); |
| 130 | break; |
| 131 | } |
| 132 | } |
| 133 | } |
| 134 | } |
| 135 | if dependencies.is_none() { |
| 136 | return vec![]; |
| 137 | } |
| 138 | |
| 139 | dependencies |
| 140 | .unwrap() |
| 141 | .iter() |
| 142 | .map(|dep| { |
| 143 | let (dep_name, dep_version) = parse_dep_string(dep); |
| 144 | |
| 145 | // If a version was not specified find the only package with the name of the dependency |
| 146 | let dep_version = dep_version.or_else(|| { |
| 147 | let mut versions = self.metadata.packages.iter().filter_map(|package| { |
| 148 | if package.name_and_version.name != dep_name { |
| 149 | return None; |
| 150 | } |
| 151 | package.name_and_version.version.as_deref() |
| 152 | }); |
| 153 | |
| 154 | // If the iterator contains more items, meaning multiple versions of the same |
| 155 | // package are present, warn! amd abort. |
| 156 | let version = versions.next(); |
| 157 | if versions.next().is_none() { |
| 158 | version |
| 159 | } else { |
| 160 | warn!("when looking for a version for package {}, multiple versions where found" , dep_name); |
| 161 | None |
| 162 | } |
| 163 | }); |
| 164 | |
| 165 | // Try to find the cfgs in the Cargo.toml |
| 166 | let cfg = self |
| 167 | .metadata |
| 168 | .packages |
| 169 | .get(package) |
| 170 | .and_then(|meta_package| meta_package.dependencies.get(dep_name)) |
| 171 | .and_then(Cfg::load_metadata); |
| 172 | |
| 173 | let package_ref = PackageRef { |
| 174 | name: dep_name.to_owned(), |
| 175 | version: dep_version.map(|v| v.to_owned()), |
| 176 | }; |
| 177 | |
| 178 | (package_ref, cfg) |
| 179 | }) |
| 180 | .collect() |
| 181 | } |
| 182 | |
| 183 | /// Finds the package reference in `cargo metadata` that has `package_name` |
| 184 | /// ignoring the version. |
| 185 | fn find_pkg_ref(&self, package_name: &str) -> Option<PackageRef> { |
| 186 | for package in &self.metadata.packages { |
| 187 | if package.name_and_version.name == package_name { |
| 188 | return Some(package.name_and_version.clone()); |
| 189 | } |
| 190 | } |
| 191 | None |
| 192 | } |
| 193 | |
| 194 | /// Finds the directory for a specified package reference. |
| 195 | #[allow (unused)] |
| 196 | pub(crate) fn find_crate_dir(&self, package: &PackageRef) -> Option<PathBuf> { |
| 197 | self.metadata |
| 198 | .packages |
| 199 | .get(package) |
| 200 | .and_then(|meta_package| { |
| 201 | Path::new(&meta_package.manifest_path) |
| 202 | .parent() |
| 203 | .map(|x| x.to_owned()) |
| 204 | }) |
| 205 | } |
| 206 | |
| 207 | /// Finds `src/lib.rs` for a specified package reference. |
| 208 | pub(crate) fn find_crate_src(&self, package: &PackageRef) -> Option<PathBuf> { |
| 209 | let kind_lib = String::from("lib" ); |
| 210 | let kind_staticlib = String::from("staticlib" ); |
| 211 | let kind_rlib = String::from("rlib" ); |
| 212 | let kind_cdylib = String::from("cdylib" ); |
| 213 | let kind_dylib = String::from("dylib" ); |
| 214 | |
| 215 | self.metadata |
| 216 | .packages |
| 217 | .get(package) |
| 218 | .and_then(|meta_package| { |
| 219 | for target in &meta_package.targets { |
| 220 | if target.kind.contains(&kind_lib) |
| 221 | || target.kind.contains(&kind_staticlib) |
| 222 | || target.kind.contains(&kind_rlib) |
| 223 | || target.kind.contains(&kind_cdylib) |
| 224 | || target.kind.contains(&kind_dylib) |
| 225 | { |
| 226 | return Some(PathBuf::from(&target.src_path)); |
| 227 | } |
| 228 | } |
| 229 | None |
| 230 | }) |
| 231 | } |
| 232 | |
| 233 | pub(crate) fn expand_crate( |
| 234 | &self, |
| 235 | package: &PackageRef, |
| 236 | expand_all_features: bool, |
| 237 | expand_default_features: bool, |
| 238 | expand_features: &Option<Vec<String>>, |
| 239 | profile: Profile, |
| 240 | ) -> Result<String, cargo_expand::Error> { |
| 241 | cargo_expand::expand( |
| 242 | &self.manifest_path, |
| 243 | &package.name, |
| 244 | package.version.as_deref(), |
| 245 | self.clean, |
| 246 | expand_all_features, |
| 247 | expand_default_features, |
| 248 | expand_features, |
| 249 | profile, |
| 250 | ) |
| 251 | } |
| 252 | } |
| 253 | |