#![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, 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, } #[derive(Clone, Deserialize, Debug)] /// A crate pub struct Package { #[serde(flatten)] pub name_and_version: PackageRef, id: String, source: Option, /// List of dependencies of this particular package pub dependencies: HashSet, /// Targets provided by the crate (lib, bin, example, test, ...) pub targets: Vec, features: HashMap>, /// 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, /// Whether this is required or optional pub req: String, kind: Option, optional: bool, uses_default_features: bool, features: Vec, pub target: Option, } #[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, /// 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, /// 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 for Error { fn from(err: io::Error) -> Self { Error::Io(err) } } impl From for Error { fn from(err: Utf8Error) -> Self { Error::Utf8(err) } } impl From 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 for Package { fn borrow(&self) -> &PackageRef { &self.name_and_version } } impl Hash for Package { fn hash(&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 for Dependency { fn borrow(&self) -> &str { &self.name } } impl Hash for Dependency { fn hash(&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 { 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 { 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) }