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
5use std::path::{Path, PathBuf};
6
7use crate::bindgen::cargo::cargo_expand;
8use crate::bindgen::cargo::cargo_lock::{self, Lock};
9pub(crate) use crate::bindgen::cargo::cargo_metadata::PackageRef;
10use crate::bindgen::cargo::cargo_metadata::{self, Metadata};
11use crate::bindgen::cargo::cargo_toml;
12use crate::bindgen::config::Profile;
13use crate::bindgen::error::Error;
14use crate::bindgen::ir::Cfg;
15
16/// Parse a dependency string used in Cargo.lock
17fn 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)]
25pub(crate) struct Cargo {
26 manifest_path: PathBuf,
27 binding_crate_name: String,
28 lock: Option<Lock>,
29 metadata: Metadata,
30 clean: bool,
31}
32
33impl 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