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