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 | |