summaryrefslogtreecommitdiffstats
path: root/src/bindgen/cargo/cargo_metadata.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/bindgen/cargo/cargo_metadata.rs')
-rw-r--r--src/bindgen/cargo/cargo_metadata.rs253
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)
+}