blob: 8b441456a0186434e78596b3c221e774e92a4bf3 [file] [log] [blame]
#![deny(missing_docs)]
//! Structured access to the output of `cargo metadata`
//! Usually used from within a `cargo-*` executable
//!
//! ## Examples
//!
//! With [`std::env::args()`](https://doc.rust-lang.org/std/env/fn.args.html):
//!
//! ```rust
//! # // This should be kept in sync with the equivalent example in the readme.
//! # extern crate cargo_metadata;
//! # use std::path::Path;
//! let mut args = std::env::args().skip_while(|val| !val.starts_with("--manifest-path"));
//!
//! let manifest_path = match args.next() {
//! Some(ref p) if p == "--manifest-path" => args.next(),
//! Some(p) => Some(p.trim_left_matches("--manifest-path=").to_string()),
//! None => None,
//! };
//!
//! let _metadata = cargo_metadata::metadata(manifest_path.as_ref().map(Path::new)).unwrap();
//! ```
//!
//! With [`docopt`](https://docs.rs/docopt):
//!
//! ```rust
//! # // This should be kept in sync with the equivalent example in the readme.
//! # extern crate cargo_metadata;
//! # extern crate docopt;
//! # #[macro_use] extern crate serde_derive;
//! # use std::path::Path;
//! # use docopt::Docopt;
//! # fn main() {
//! const USAGE: &str = "
//! Cargo metadata test function
//!
//! Usage:
//! cargo_metadata [--manifest-path PATH]
//! ";
//!
//! #[derive(Debug, Deserialize)]
//! struct Args {
//! arg_manifest_path: Option<String>,
//! }
//!
//! let args: Args = Docopt::new(USAGE)
//! .and_then(|d| d.deserialize())
//! .unwrap_or_else(|e| e.exit());
//!
//! let _metadata =
//! cargo_metadata::metadata(args.arg_manifest_path.as_ref().map(Path::new)).unwrap();
//! # }
//! ```
//!
//! With [`clap`](https://docs.rs/clap):
//!
//! ```rust
//! # // This should be kept in sync with the equivalent example in the readme.
//! # extern crate cargo_metadata;
//! # extern crate clap;
//! # use std::path::Path;
//!
//! let matches = clap::App::new("myapp")
//! .arg(
//! clap::Arg::with_name("manifest-path")
//! .long("manifest-path")
//! .value_name("PATH")
//! .takes_value(true),
//! )
//! .get_matches();
//!
//! let _metadata =
//! cargo_metadata::metadata(matches.value_of("manifest-path").map(Path::new)).unwrap();
//! ```
//!
//! Pass features flags
//!
//! ```rust
//! # // This should be kept in sync with the equivalent example in the readme.
//! # extern crate cargo_metadata;
//! # use std::path::Path;
//!
//! let manifest_path = Path::new("./Cargo.toml");
//! let features = cargo_metadata::CargoOpt::AllFeatures;
//! let _metadata =
//! cargo_metadata::metadata_run(Some(manifest_path), false, Some(features)).unwrap();
//!
//! ```
#[macro_use]
extern crate error_chain;
extern crate semver;
extern crate serde;
#[macro_use]
extern crate serde_derive;
extern crate serde_json;
use std::collections::HashMap;
use std::env;
use std::path::Path;
use std::process::Command;
use std::str::from_utf8;
pub use errors::{Error, ErrorKind, Result};
pub use dependency::{Dependency, DependencyKind};
mod errors;
mod dependency;
#[derive(Clone, Serialize, 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: Vec<Package>,
/// A list of all workspace members
pub workspace_members: Vec<WorkspaceMember>,
/// Dependencies graph
pub resolve: Option<Resolve>,
/// Workspace root
#[serde(default)]
pub workspace_root: String,
/// Build directory
pub target_directory: String,
version: usize,
#[doc(hidden)]
#[serde(skip)]
__do_not_match_exhaustively: (),
}
#[derive(Clone, Serialize, Deserialize, Debug)]
/// A dependency graph
pub struct Resolve {
/// Nodes in a dependencies graph
pub nodes: Vec<Node>,
#[doc(hidden)]
#[serde(skip)]
__do_not_match_exhaustively: (),
}
#[derive(Clone, Serialize, Deserialize, Debug)]
/// A node in a dependencies graph
pub struct Node {
/// An opaque identifier for a package
pub id: String,
/// List of opaque identifiers for this node's dependencies
pub dependencies: Vec<String>,
#[doc(hidden)]
#[serde(skip)]
__do_not_match_exhaustively: (),
}
#[derive(Clone, Serialize, Deserialize, Debug)]
/// A crate
pub struct Package {
/// Name as given in the `Cargo.toml`
pub name: String,
/// Version given in the `Cargo.toml`
pub version: String,
/// Authors given in the `Cargo.toml`
#[serde(default)]
pub authors: Vec<String>,
/// An opaque identifier for a package
pub id: String,
source: Option<String>,
/// Description as given in the `Cargo.toml`
pub description: Option<String>,
/// List of dependencies of this particular package
pub dependencies: Vec<Dependency>,
/// License as given in the `Cargo.toml`
pub license: Option<String>,
/// Targets provided by the crate (lib, bin, example, test, ...)
pub targets: Vec<Target>,
/// Features provided by the crate, mapped to the features required by that feature.
pub features: HashMap<String, Vec<String>>,
/// Path containing the `Cargo.toml`
pub manifest_path: String,
/// Categories as given in the `Cargo.toml`
pub categories: Vec<String>,
/// Keywords as given in the `Cargo.toml`
pub keywords: Vec<String>,
/// Readme as given in the `Cargo.toml`
pub readme: Option<String>,
/// Repository as given in the `Cargo.toml`
pub repository: Option<String>,
/// Default Rust edition for the package
///
/// Beware that individual targets may specify their own edition in
/// [`Target::edition`](struct.Target.html#structfield.edition).
#[serde(default = "edition_default")]
pub edition: String,
/// Contents of the free form package.metadata section
///
/// This contents can be serialized to a struct using serde:
///
/// ```rust
/// #[macro_use]
/// extern crate serde_json;
/// #[macro_use]
/// extern crate serde_derive;
///
/// #[derive(Debug, Deserialize)]
/// struct SomePackageMetadata {
/// some_value: i32,
/// }
///
/// fn main() {
/// let value = json!({
/// "some_value": 42,
/// });
///
/// let package_metadata: SomePackageMetadata = serde_json::from_value(value).unwrap();
/// assert_eq!(package_metadata.some_value, 42);
/// }
///
/// ```
#[serde(default)]
pub metadata: serde_json::Value,
#[doc(hidden)]
#[serde(skip)]
__do_not_match_exhaustively: (),
}
#[derive(Clone, Serialize, 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,
/// Rust edition for this target
#[serde(default = "edition_default")]
pub edition: String,
#[doc(hidden)]
#[serde(skip)]
__do_not_match_exhaustively: (),
}
#[derive(Clone, Debug, Serialize, Deserialize)]
/// A workspace member. This is basically identical to `cargo::core::package_id::PackageId`, except
/// that this does not use `Arc` internally.
#[serde(transparent)]
pub struct WorkspaceMember {
/// The raw package id as given by cargo
pub raw: String,
}
impl WorkspaceMember {
fn part(&self, n: usize) -> &str {
self.raw.splitn(3, ' ').nth(n).unwrap()
}
/// The name of the crate
pub fn name(&self) -> &str {
self.part(0)
}
/// The version of the crate
pub fn version(&self) -> semver::Version {
semver::Version::parse(self.part(1)).expect("bad version in cargo metadata")
}
/// The path to the crate in url format
pub fn url(&self) -> &str {
let url = self.part(2);
&url[1..url.len() - 1]
}
}
fn edition_default() -> String {
"2015".to_string()
}
/// Cargo features flags
pub enum CargoOpt {
/// Run cargo with `--features-all`
AllFeatures,
/// Run cargo with `--no-default-features`
NoDefaultFeatures,
/// Run cargo with `--features <FEATURES>`
SomeFeatures(Vec<String>),
}
/// Obtain metadata only about the root package and don't fetch dependencies
///
/// # Parameters
///
/// - `manifest_path`: Path to the manifest.
pub fn metadata(manifest_path: Option<&Path>) -> Result<Metadata> {
metadata_run(manifest_path, false, None)
}
/// Obtain metadata only about the root package and dependencies
///
/// # Parameters
///
/// - `manifest_path`: Path to the manifest.
/// - `deps`: Whether to include dependencies.
pub fn metadata_deps(manifest_path: Option<&Path>, deps: bool) -> Result<Metadata> {
metadata_run(manifest_path, deps, None)
}
/// The main entry point to obtaining metadata
///
/// # Parameters
///
/// - `manifest_path`: Path to the manifest.
/// - `deps`: Whether to include dependencies.
/// - `feat`: Which features to include, `None` for defaults.
pub fn metadata_run(manifest_path: Option<&Path>, deps: bool, features: Option<CargoOpt>) -> Result<Metadata> {
let cargo = env::var("CARGO").unwrap_or_else(|_| String::from("cargo"));
let mut cmd = Command::new(cargo);
cmd.arg("metadata");
if !deps {
cmd.arg("--no-deps");
}
if let Some(features) = features {
match features {
CargoOpt::AllFeatures => cmd.arg("--all-features"),
CargoOpt::NoDefaultFeatures => cmd.arg("--no-default-features"),
CargoOpt::SomeFeatures(ftrs) => cmd.arg(format!("--fatures {:?}", ftrs)),
};
}
cmd.args(&["--format-version", "1"]);
if let Some(manifest_path) = manifest_path {
cmd.arg("--manifest-path").arg(manifest_path.as_os_str());
}
let output = cmd.output()?;
if !output.status.success() {
return Err(ErrorKind::CargoMetadata(String::from_utf8(output.stderr)?).into());
}
let stdout = from_utf8(&output.stdout)?;
let meta = serde_json::from_str(stdout)?;
Ok(meta)
}