diff options
Diffstat (limited to 'src/bindgen/cargo/cargo_metadata.rs')
-rw-r--r-- | src/bindgen/cargo/cargo_metadata.rs | 253 |
1 files changed, 253 insertions, 0 deletions
diff --git a/src/bindgen/cargo/cargo_metadata.rs b/src/bindgen/cargo/cargo_metadata.rs new file mode 100644 index 0000000..01ab80f --- /dev/null +++ b/src/bindgen/cargo/cargo_metadata.rs @@ -0,0 +1,253 @@ +#![deny(missing_docs)] +#![allow(dead_code)] +//! Structured access to the output of `cargo metadata` +//! Usually used from within a `cargo-*` executable + +// Forked from `https://github.com/oli-obk/cargo_metadata` +// Modifications: +// 1. Remove `resolve` from Metadata because it was causing parse failures +// 2. Fix the `manifest-path` argument +// 3. Add `--all-features` argument +// 4. Remove the `--no-deps` argument + +use std::borrow::{Borrow, Cow}; +use std::collections::{HashMap, HashSet}; +use std::env; +use std::error; +use std::fmt; +use std::hash::{Hash, Hasher}; +use std::io; +use std::path::Path; +use std::process::{Command, Output}; +use std::str::Utf8Error; + +#[derive(Clone, Deserialize, Debug)] +/// Starting point for metadata returned by `cargo metadata` +pub struct Metadata { + /// A list of all crates referenced by this crate (and the crate itself) + pub packages: HashSet<Package>, + version: usize, + /// path to the workspace containing the `Cargo.lock` + pub workspace_root: String, +} + +/// A reference to a package including it's name and the specific version. +#[derive(Clone, Debug, Hash, Eq, PartialEq, Serialize, Deserialize)] +pub struct PackageRef { + pub name: String, + pub version: Option<String>, +} + +#[derive(Clone, Deserialize, Debug)] +/// A crate +pub struct Package { + #[serde(flatten)] + pub name_and_version: PackageRef, + id: String, + source: Option<String>, + /// List of dependencies of this particular package + pub dependencies: HashSet<Dependency>, + /// Targets provided by the crate (lib, bin, example, test, ...) + pub targets: Vec<Target>, + features: HashMap<String, Vec<String>>, + /// path containing the `Cargo.toml` + pub manifest_path: String, +} + +#[derive(Clone, Deserialize, Debug)] +/// A dependency of the main crate +pub struct Dependency { + /// Name as given in the `Cargo.toml` + pub name: String, + source: Option<String>, + /// Whether this is required or optional + pub req: String, + kind: Option<String>, + optional: bool, + uses_default_features: bool, + features: Vec<String>, + pub target: Option<String>, +} + +#[derive(Clone, Deserialize, Debug)] +/// A single target (lib, bin, example, ...) provided by a crate +pub struct Target { + /// Name as given in the `Cargo.toml` or generated from the file name + pub name: String, + /// Kind of target ("bin", "example", "test", "bench", "lib") + pub kind: Vec<String>, + /// Almost the same as `kind`, except when an example is a library instad of an executable. + /// In that case `crate_types` contains things like `rlib` and `dylib` while `kind` is `example` + #[serde(default)] + pub crate_types: Vec<String>, + /// Path to the main source file of the target + pub src_path: String, +} + +#[derive(Debug)] +/// Possible errors that can occur during metadata parsing. +pub enum Error { + /// Error during execution of `cargo metadata` + Io(io::Error), + /// Metadata extraction failure + Metadata(Output), + /// Output of `cargo metadata` was not valid utf8 + Utf8(Utf8Error), + /// Deserialization error (structure of json did not match expected structure) + Json(serde_json::Error), +} + +impl From<io::Error> for Error { + fn from(err: io::Error) -> Self { + Error::Io(err) + } +} +impl From<Utf8Error> for Error { + fn from(err: Utf8Error) -> Self { + Error::Utf8(err) + } +} +impl From<serde_json::Error> for Error { + fn from(err: serde_json::Error) -> Self { + Error::Json(err) + } +} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + Error::Io(ref err) => err.fmt(f), + Error::Metadata(_) => write!(f, "Metadata error"), + Error::Utf8(ref err) => err.fmt(f), + Error::Json(ref err) => err.fmt(f), + } + } +} + +impl error::Error for Error { + fn source(&self) -> Option<&(dyn error::Error + 'static)> { + match self { + Error::Io(ref err) => Some(err), + Error::Metadata(_) => None, + Error::Utf8(ref err) => Some(err), + Error::Json(ref err) => Some(err), + } + } +} + +// Implementations that let us lookup Packages and Dependencies by name (string) + +impl Borrow<PackageRef> for Package { + fn borrow(&self) -> &PackageRef { + &self.name_and_version + } +} + +impl Hash for Package { + fn hash<H: Hasher>(&self, state: &mut H) { + self.name_and_version.hash(state); + } +} + +impl PartialEq for Package { + fn eq(&self, other: &Self) -> bool { + self.name_and_version == other.name_and_version + } +} + +impl Eq for Package {} + +impl Borrow<str> for Dependency { + fn borrow(&self) -> &str { + &self.name + } +} + +impl Hash for Dependency { + fn hash<H: Hasher>(&self, state: &mut H) { + self.name.hash(state); + } +} + +impl PartialEq for Dependency { + fn eq(&self, other: &Self) -> bool { + self.name == other.name + } +} + +impl Eq for Dependency {} + +fn discover_target(manifest_path: &Path) -> Option<String> { + if let Ok(target) = std::env::var("TARGET") { + return Some(target); + } + + // We must be running as a standalone script, not under cargo. + // Let's use the host platform instead. + // We figure out the host platform through rustc and use that. + // We unfortunatelly cannot go through cargo, since cargo rustc _also_ builds. + // If `rustc` fails to run, we just fall back to not passing --filter-platforms. + // + // NOTE: We set the current directory in case of rustup shenanigans. + let rustc = env::var("RUSTC").unwrap_or_else(|_| String::from("rustc")); + debug!("Discovering host platform by {:?}", rustc); + + let rustc_output = Command::new(rustc) + .current_dir(manifest_path.parent().unwrap()) + .arg("-vV") + .output(); + let rustc_output = match rustc_output { + Ok(ref out) => String::from_utf8_lossy(&out.stdout), + Err(..) => return None, + }; + + let field = "host: "; + rustc_output + .lines() + .find_map(|l| l.strip_prefix(field).map(|stripped| stripped.to_string())) +} + +/// The main entry point to obtaining metadata +pub fn metadata( + manifest_path: &Path, + existing_metadata_file: Option<&Path>, + only_target: bool, +) -> Result<Metadata, Error> { + let output; + let metadata = match existing_metadata_file { + Some(path) => Cow::Owned(std::fs::read_to_string(path)?), + None => { + let target = if only_target { + let target = discover_target(manifest_path); + if target.is_none() { + warn!( + "Failed to discover host platform for cargo metadata; \ + will fetch dependencies for all platforms." + ); + } + target + } else { + None + }; + + let cargo = env::var("CARGO").unwrap_or_else(|_| String::from("cargo")); + let mut cmd = Command::new(cargo); + cmd.arg("metadata"); + cmd.arg("--all-features"); + cmd.arg("--format-version").arg("1"); + if let Some(target) = target { + cmd.arg("--filter-platform").arg(target); + } + cmd.arg("--manifest-path"); + cmd.arg(manifest_path); + output = cmd.output()?; + if !output.status.success() { + return Err(Error::Metadata(output)); + } + Cow::Borrowed(std::str::from_utf8(&output.stdout)?) + } + }; + + let meta: Metadata = serde_json::from_str(&metadata)?; + Ok(meta) +} |