#![deny(missing_docs)]
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::fmt;
use std::path::{Path, PathBuf};
use std::process::Command;
use std::str::from_utf8;
use semver::Version;
pub use dependency::{Dependency, DependencyKind};
use diagnostic::Diagnostic;
pub use errors::{Error, Result};
pub use messages::{
parse_messages, Artifact, ArtifactProfile, BuildScript, CompilerMessage, Message,
};
mod dependency;
mod diagnostic;
mod errors;
mod messages;
#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[serde(transparent)]
pub struct PackageId {
pub repr: String,
}
impl std::fmt::Display for PackageId {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
fmt::Display::fmt(&self.repr, f)
}
}
#[derive(Clone, Serialize, Deserialize, Debug)]
pub struct Metadata {
pub packages: Vec<Package>,
pub workspace_members: Vec<PackageId>,
pub resolve: Option<Resolve>,
pub workspace_root: PathBuf,
pub target_directory: PathBuf,
version: usize,
#[doc(hidden)]
#[serde(skip)]
__do_not_match_exhaustively: (),
}
impl<'a> std::ops::Index<&'a PackageId> for Metadata {
type Output = Package;
fn index(&self, idx: &'a PackageId) -> &Package {
self.packages
.iter()
.find(|p| p.id == *idx)
.unwrap_or_else(|| panic!("no package with this id: {:?}", idx))
}
}
#[derive(Clone, Serialize, Deserialize, Debug)]
pub struct Resolve {
pub nodes: Vec<Node>,
pub root: Option<PackageId>,
#[doc(hidden)]
#[serde(skip)]
__do_not_match_exhaustively: (),
}
#[derive(Clone, Serialize, Deserialize, Debug)]
pub struct Node {
pub id: PackageId,
#[serde(default)]
pub deps: Vec<NodeDep>,
pub dependencies: Vec<PackageId>,
#[serde(default)]
pub features: Vec<String>,
#[doc(hidden)]
#[serde(skip)]
__do_not_match_exhaustively: (),
}
#[derive(Clone, Serialize, Deserialize, Debug)]
pub struct NodeDep {
pub name: String,
pub pkg: PackageId,
#[doc(hidden)]
#[serde(skip)]
__do_not_match_exhaustively: (),
}
#[derive(Clone, Serialize, Deserialize, Debug)]
pub struct Package {
pub name: String,
pub version: Version,
#[serde(default)]
pub authors: Vec<String>,
pub id: PackageId,
pub source: Option<Source>,
pub description: Option<String>,
pub dependencies: Vec<Dependency>,
pub license: Option<String>,
pub license_file: Option<PathBuf>,
pub targets: Vec<Target>,
pub features: HashMap<String, Vec<String>>,
pub manifest_path: PathBuf,
#[serde(default)]
pub categories: Vec<String>,
#[serde(default)]
pub keywords: Vec<String>,
pub readme: Option<String>,
pub repository: Option<String>,
#[serde(default = "edition_default")]
pub edition: String,
#[serde(default)]
pub metadata: serde_json::Value,
pub links: Option<String>,
#[doc(hidden)]
#[serde(skip)]
__do_not_match_exhaustively: (),
}
#[derive(Clone, Serialize, Deserialize, Debug)]
#[serde(transparent)]
pub struct Source(String);
impl Source {
pub fn is_crates_io(&self) -> bool {
self.0 == "registry+https://github.com/rust-lang/crates.io-index"
}
}
impl std::fmt::Display for Source {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
fmt::Display::fmt(&self.0, f)
}
}
#[derive(Clone, Serialize, Deserialize, Debug)]
pub struct Target {
pub name: String,
pub kind: Vec<String>,
#[serde(default)]
pub crate_types: Vec<String>,
#[serde(default)]
#[serde(rename = "required-features")]
pub required_features: Vec<String>,
pub src_path: PathBuf,
#[serde(default = "edition_default")]
pub edition: String,
#[doc(hidden)]
#[serde(skip)]
__do_not_match_exhaustively: (),
}
fn edition_default() -> String {
"2015".to_string()
}
#[derive(Debug, Clone)]
pub enum CargoOpt {
AllFeatures,
NoDefaultFeatures,
SomeFeatures(Vec<String>),
}
#[derive(Debug, Clone, Default)]
pub struct MetadataCommand {
cargo_path: Option<PathBuf>,
manifest_path: Option<PathBuf>,
current_dir: Option<PathBuf>,
no_deps: bool,
features: Option<CargoOpt>,
other_options: Vec<String>,
}
impl MetadataCommand {
pub fn new() -> MetadataCommand {
MetadataCommand::default()
}
pub fn cargo_path(&mut self, path: impl AsRef<Path>) -> &mut MetadataCommand {
self.cargo_path = Some(path.as_ref().to_path_buf());
self
}
pub fn manifest_path(&mut self, path: impl AsRef<Path>) -> &mut MetadataCommand {
self.manifest_path = Some(path.as_ref().to_path_buf());
self
}
pub fn current_dir(&mut self, path: impl AsRef<Path>) -> &mut MetadataCommand {
self.current_dir = Some(path.as_ref().to_path_buf());
self
}
pub fn no_deps(&mut self) -> &mut MetadataCommand {
self.no_deps = true;
self
}
pub fn features(&mut self, features: CargoOpt) -> &mut MetadataCommand {
self.features = Some(features);
self
}
pub fn other_options(&mut self, options: impl AsRef<[String]>) -> &mut MetadataCommand {
self.other_options = options.as_ref().to_vec();
self
}
pub fn exec(&mut self) -> Result<Metadata> {
let cargo = self.cargo_path.clone()
.or_else(|| env::var("CARGO")
.map(|s| PathBuf::from(s))
.ok())
.unwrap_or_else(|| PathBuf::from("cargo"));
let mut cmd = Command::new(cargo);
cmd.args(&["metadata", "--format-version", "1"]);
if self.no_deps {
cmd.arg("--no-deps");
}
if let Some(path) = self.current_dir.as_ref() {
cmd.current_dir(path);
}
if let Some(features) = &self.features {
match features {
CargoOpt::AllFeatures => cmd.arg("--all-features"),
CargoOpt::NoDefaultFeatures => cmd.arg("--no-default-features"),
CargoOpt::SomeFeatures(ftrs) => cmd.arg("--features").arg(ftrs.join(",")),
};
}
if let Some(manifest_path) = &self.manifest_path {
cmd.arg("--manifest-path").arg(manifest_path.as_os_str());
}
cmd.args(&self.other_options);
let output = cmd.output()?;
if !output.status.success() {
return Err(Error::CargoMetadata { stderr: String::from_utf8(output.stderr)? });
}
let stdout = from_utf8(&output.stdout)?;
let meta = serde_json::from_str(stdout)?;
Ok(meta)
}
}