diff options
Diffstat (limited to 'src/bindgen/cargo/cargo.rs')
-rw-r--r-- | src/bindgen/cargo/cargo.rs | 252 |
1 files changed, 252 insertions, 0 deletions
diff --git a/src/bindgen/cargo/cargo.rs b/src/bindgen/cargo/cargo.rs new file mode 100644 index 0000000..69cf938 --- /dev/null +++ b/src/bindgen/cargo/cargo.rs @@ -0,0 +1,252 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +use std::path::{Path, PathBuf}; + +use crate::bindgen::cargo::cargo_expand; +use crate::bindgen::cargo::cargo_lock::{self, Lock}; +pub(crate) use crate::bindgen::cargo::cargo_metadata::PackageRef; +use crate::bindgen::cargo::cargo_metadata::{self, Metadata}; +use crate::bindgen::cargo::cargo_toml; +use crate::bindgen::config::Profile; +use crate::bindgen::error::Error; +use crate::bindgen::ir::Cfg; + +/// Parse a dependency string used in Cargo.lock +fn parse_dep_string(dep_string: &str) -> (&str, Option<&str>) { + let split: Vec<&str> = dep_string.split_whitespace().collect(); + + (split[0], split.get(1).cloned()) +} + +/// A collection of metadata for a library from cargo. +#[derive(Clone, Debug)] +pub(crate) struct Cargo { + manifest_path: PathBuf, + binding_crate_name: String, + lock: Option<Lock>, + metadata: Metadata, + clean: bool, +} + +impl Cargo { + /// Gather metadata from cargo for a specific library and binding crate + /// name. If dependency finding isn't needed then Cargo.lock files don't + /// need to be parsed. + pub(crate) fn load( + crate_dir: &Path, + lock_file: Option<&str>, + binding_crate_name: Option<&str>, + use_cargo_lock: bool, + clean: bool, + only_target_dependencies: bool, + existing_metadata_file: Option<&Path>, + ) -> Result<Cargo, Error> { + let toml_path = crate_dir.join("Cargo.toml"); + let metadata = + cargo_metadata::metadata(&toml_path, existing_metadata_file, only_target_dependencies) + .map_err(|x| Error::CargoMetadata(toml_path.to_str().unwrap().to_owned(), x))?; + let lock_path = lock_file + .map(PathBuf::from) + .unwrap_or_else(|| Path::new(&metadata.workspace_root).join("Cargo.lock")); + + let lock = if use_cargo_lock { + match cargo_lock::lock(&lock_path) { + Ok(lock) => Some(lock), + Err(x) => { + warn!("Couldn't load lock file {:?}: {:?}", lock_path, x); + None + } + } + } else { + None + }; + + // Use the specified binding crate name or infer it from the manifest + let binding_crate_name = match binding_crate_name { + Some(s) => s.to_owned(), + None => { + let manifest = cargo_toml::manifest(&toml_path) + .map_err(|x| Error::CargoToml(toml_path.to_str().unwrap().to_owned(), x))?; + manifest.package.name + } + }; + + Ok(Cargo { + manifest_path: toml_path, + binding_crate_name, + lock, + metadata, + clean, + }) + } + + pub(crate) fn binding_crate_name(&self) -> &str { + &self.binding_crate_name + } + + pub(crate) fn binding_crate_ref(&self) -> PackageRef { + match self.find_pkg_ref(&self.binding_crate_name) { + Some(pkg_ref) => pkg_ref, + None => panic!( + "Unable to find {} for {:?}", + self.binding_crate_name, self.manifest_path + ), + } + } + + pub(crate) fn dependencies(&self, package: &PackageRef) -> Vec<(PackageRef, Option<Cfg>)> { + let lock = match self.lock { + Some(ref lock) => lock, + None => return vec![], + }; + + let mut dependencies = None; + + // Find the dependencies listing in the lockfile + if let Some(ref root) = lock.root { + // If the version is not on the lockfile then it shouldn't be + // ambiguous. + if root.name == package.name + && package + .version + .as_ref() + .map_or(true, |v| *v == root.version) + { + dependencies = root.dependencies.as_ref(); + } + } + if dependencies.is_none() { + if let Some(ref lock_packages) = lock.package { + for lock_package in lock_packages { + if lock_package.name == package.name + && package + .version + .as_ref() + .map_or(true, |v| *v == lock_package.version) + { + dependencies = lock_package.dependencies.as_ref(); + break; + } + } + } + } + if dependencies.is_none() { + return vec![]; + } + + dependencies + .unwrap() + .iter() + .map(|dep| { + let (dep_name, dep_version) = parse_dep_string(dep); + + // If a version was not specified find the only package with the name of the dependency + let dep_version = dep_version.or_else(|| { + let mut versions = self.metadata.packages.iter().filter_map(|package| { + if package.name_and_version.name != dep_name { + return None; + } + package.name_and_version.version.as_deref() + }); + + // If the iterator contains more items, meaning multiple versions of the same + // package are present, warn! amd abort. + let version = versions.next(); + if versions.next().is_none() { + version + } else { + warn!("when looking for a version for package {}, multiple versions where found", dep_name); + None + } + }); + + // Try to find the cfgs in the Cargo.toml + let cfg = self + .metadata + .packages + .get(package) + .and_then(|meta_package| meta_package.dependencies.get(dep_name)) + .and_then(Cfg::load_metadata); + + let package_ref = PackageRef { + name: dep_name.to_owned(), + version: dep_version.map(|v| v.to_owned()), + }; + + (package_ref, cfg) + }) + .collect() + } + + /// Finds the package reference in `cargo metadata` that has `package_name` + /// ignoring the version. + fn find_pkg_ref(&self, package_name: &str) -> Option<PackageRef> { + for package in &self.metadata.packages { + if package.name_and_version.name == package_name { + return Some(package.name_and_version.clone()); + } + } + None + } + + /// Finds the directory for a specified package reference. + #[allow(unused)] + pub(crate) fn find_crate_dir(&self, package: &PackageRef) -> Option<PathBuf> { + self.metadata + .packages + .get(package) + .and_then(|meta_package| { + Path::new(&meta_package.manifest_path) + .parent() + .map(|x| x.to_owned()) + }) + } + + /// Finds `src/lib.rs` for a specified package reference. + pub(crate) fn find_crate_src(&self, package: &PackageRef) -> Option<PathBuf> { + let kind_lib = String::from("lib"); + let kind_staticlib = String::from("staticlib"); + let kind_rlib = String::from("rlib"); + let kind_cdylib = String::from("cdylib"); + let kind_dylib = String::from("dylib"); + + self.metadata + .packages + .get(package) + .and_then(|meta_package| { + for target in &meta_package.targets { + if target.kind.contains(&kind_lib) + || target.kind.contains(&kind_staticlib) + || target.kind.contains(&kind_rlib) + || target.kind.contains(&kind_cdylib) + || target.kind.contains(&kind_dylib) + { + return Some(PathBuf::from(&target.src_path)); + } + } + None + }) + } + + pub(crate) fn expand_crate( + &self, + package: &PackageRef, + expand_all_features: bool, + expand_default_features: bool, + expand_features: &Option<Vec<String>>, + profile: Profile, + ) -> Result<String, cargo_expand::Error> { + cargo_expand::expand( + &self.manifest_path, + &package.name, + package.version.as_deref(), + self.clean, + expand_all_features, + expand_default_features, + expand_features, + profile, + ) + } +} |