cbindgen/bindgen/cargo/
cargo.rs

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(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<&Path>,
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_to_generate_bindings_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 {dep_name}, multiple versions where found");
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 for which we want to generate bindings in `cargo metadata`
184    /// matching on `package_name` and verifying the manifest path matches so that we don't get a
185    /// a dependency with the same name (fix for https://github.com/mozilla/cbindgen/issues/900)
186    fn find_pkg_to_generate_bindings_ref(&self, package_name: &str) -> Option<PackageRef> {
187        // Keep a list of candidates in case the manifest check fails, so that the old behavior
188        // still applies, returning the first package that was found
189        let mut candidates = vec![];
190        for package in &self.metadata.packages {
191            if package.name_and_version.name == package_name {
192                // If we are sure it is the right package return it
193                if Path::new(package.manifest_path.as_str()) == self.manifest_path {
194                    return Some(package.name_and_version.clone());
195                }
196                // Otherwise note that a package was found
197                candidates.push(package.name_and_version.clone());
198            }
199        }
200
201        // If we could not verify the manifest path but we found candidates return the first one if
202        // any, this is the old behavior which did not check for manifest path, kept for backwards
203        // compatibility
204        candidates.into_iter().next()
205    }
206
207    /// Finds the directory for a specified package reference.
208    #[allow(unused)]
209    pub(crate) fn find_crate_dir(&self, package: &PackageRef) -> Option<PathBuf> {
210        self.metadata
211            .packages
212            .get(package)
213            .and_then(|meta_package| {
214                Path::new(&meta_package.manifest_path)
215                    .parent()
216                    .map(|x| x.to_owned())
217            })
218    }
219
220    /// Finds `src/lib.rs` for a specified package reference.
221    pub(crate) fn find_crate_src(&self, package: &PackageRef) -> Option<PathBuf> {
222        let kind_lib = String::from("lib");
223        let kind_staticlib = String::from("staticlib");
224        let kind_rlib = String::from("rlib");
225        let kind_cdylib = String::from("cdylib");
226        let kind_dylib = String::from("dylib");
227
228        self.metadata
229            .packages
230            .get(package)
231            .and_then(|meta_package| {
232                for target in &meta_package.targets {
233                    if target.kind.contains(&kind_lib)
234                        || target.kind.contains(&kind_staticlib)
235                        || target.kind.contains(&kind_rlib)
236                        || target.kind.contains(&kind_cdylib)
237                        || target.kind.contains(&kind_dylib)
238                    {
239                        return Some(PathBuf::from(&target.src_path));
240                    }
241                }
242                None
243            })
244    }
245
246    pub(crate) fn expand_crate(
247        &self,
248        package: &PackageRef,
249        expand_all_features: bool,
250        expand_default_features: bool,
251        expand_features: &Option<Vec<String>>,
252        profile: Profile,
253    ) -> Result<String, cargo_expand::Error> {
254        cargo_expand::expand(
255            &self.manifest_path,
256            &package.name,
257            package.version.as_deref(),
258            self.clean,
259            expand_all_features,
260            expand_default_features,
261            expand_features,
262            profile,
263        )
264    }
265}