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