summaryrefslogtreecommitdiffstats
path: root/third_party/rust/cargo_metadata
diff options
context:
space:
mode:
Diffstat (limited to 'third_party/rust/cargo_metadata')
-rw-r--r--third_party/rust/cargo_metadata/.cargo-checksum.json1
-rw-r--r--third_party/rust/cargo_metadata/CHANGELOG.md38
-rw-r--r--third_party/rust/cargo_metadata/Cargo.toml55
-rw-r--r--third_party/rust/cargo_metadata/LICENSE-MIT23
-rw-r--r--third_party/rust/cargo_metadata/README.md12
-rw-r--r--third_party/rust/cargo_metadata/src/dependency.rs90
-rw-r--r--third_party/rust/cargo_metadata/src/diagnostic.rs160
-rw-r--r--third_party/rust/cargo_metadata/src/errors.rs52
-rw-r--r--third_party/rust/cargo_metadata/src/lib.rs792
-rw-r--r--third_party/rust/cargo_metadata/src/messages.rs175
-rw-r--r--third_party/rust/cargo_metadata/tests/selftest.rs163
-rw-r--r--third_party/rust/cargo_metadata/tests/test_samples.rs647
12 files changed, 2208 insertions, 0 deletions
diff --git a/third_party/rust/cargo_metadata/.cargo-checksum.json b/third_party/rust/cargo_metadata/.cargo-checksum.json
new file mode 100644
index 0000000000..7838a4a29d
--- /dev/null
+++ b/third_party/rust/cargo_metadata/.cargo-checksum.json
@@ -0,0 +1 @@
+{"files":{"CHANGELOG.md":"8b3e29799cdedf02f169bb519072ace2e2b6b9413f4ce8fa0666c2d1d964084e","Cargo.toml":"57d432cd172cc87ee4c31b0e4c21c52d06ba1a48da9decd34581b2671c47d71d","LICENSE-MIT":"23f18e03dc49df91622fe2a76176497404e46ced8a715d9d2b67a7446571cca3","README.md":"d51a5b3347bed2441b20986be81bfd4611ca2c5614f950116b273199a9bcf2de","src/dependency.rs":"c593ddc73d863c5712e2aba58b5f4d9bd915a5ac0bc17df71642aa79aa93bfdc","src/diagnostic.rs":"fee47d27390f1026ff99ffade5dfd2ab3e9b9839c3f33ce91a7dcde875551374","src/errors.rs":"797afd61efdd843ae570d9e972dd2425d33823d4a78c0c488028493dffb45c7a","src/lib.rs":"5ec701f3589c5d71c152b5abe7ad5f222aee4d4a5f9992bced1d357bad36e227","src/messages.rs":"a8e3ee31dc8cce5762b4b085be29fe4d7189a789f3a149ef2b6c17604d94528b","tests/selftest.rs":"73afd494c1bf7dd4e1a99971e9ff66a0e21fc7bf3e327663df15d2350dcdfc70","tests/test_samples.rs":"ee2b4737adfa1930c1610bb3ec0fc94b7f1a3691bb09545da69044eef2f5ba6b"},"package":"08a1ec454bc3eead8719cb56e15dbbfecdbc14e4b3a3ae4936cc6e31f5fc0d07"} \ No newline at end of file
diff --git a/third_party/rust/cargo_metadata/CHANGELOG.md b/third_party/rust/cargo_metadata/CHANGELOG.md
new file mode 100644
index 0000000000..fcc0b26426
--- /dev/null
+++ b/third_party/rust/cargo_metadata/CHANGELOG.md
@@ -0,0 +1,38 @@
+# Changelog
+
+## Unreleased
+
+### Added
+
+- Re-exported `semver` crate directly.
+
+### Changed
+
+- Made `parse_stream` more versatile by accepting anything that implements `Read`.
+
+### Removed
+
+- Removed re-exports for `BuildMetadata` and `Prerelease` from `semver` crate.
+
+### Fixed
+
+- Added missing `manifest_path` field to `Artifact`. Fixes #187.
+
+## [0.15.0] - 2022-06-22
+
+### Added
+
+- Re-exported `BuildMetadata` and `Prerelease` from `semver` crate.
+- Added `workspace_packages` function.
+- Added `Edition` enum to better parse edition field.
+- Added `rust-version` field to Cargo manifest.
+
+### Changed
+
+- Bumped msrv from `1.40.0` to `1.42.0`.
+
+### Internal Changes
+
+- Updated `derive_builder` to the latest version.
+- Made use of `matches!` macros where possible.
+- Fixed some tests
diff --git a/third_party/rust/cargo_metadata/Cargo.toml b/third_party/rust/cargo_metadata/Cargo.toml
new file mode 100644
index 0000000000..25a28ef4f6
--- /dev/null
+++ b/third_party/rust/cargo_metadata/Cargo.toml
@@ -0,0 +1,55 @@
+# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
+#
+# When uploading crates to the registry Cargo will automatically
+# "normalize" Cargo.toml files for maximal compatibility
+# with all versions of Cargo and also rewrite `path` dependencies
+# to registry (e.g., crates.io) dependencies.
+#
+# If you are reading this file be aware that the original Cargo.toml
+# will likely look very different (and much more reasonable).
+# See Cargo.toml.orig for the original contents.
+
+[package]
+edition = "2018"
+rust-version = "1.42.0"
+name = "cargo_metadata"
+version = "0.15.3"
+authors = ["Oliver Schneider <git-spam-no-reply9815368754983@oli-obk.de>"]
+description = "structured access to the output of `cargo metadata`"
+readme = "README.md"
+license = "MIT"
+repository = "https://github.com/oli-obk/cargo_metadata"
+
+[package.metadata.cargo_metadata_test]
+some_field = true
+other_field = "foo"
+
+[dependencies.camino]
+version = "1.0.7"
+features = ["serde1"]
+
+[dependencies.cargo-platform]
+version = "0.1.2"
+
+[dependencies.derive_builder]
+version = "0.11.1"
+optional = true
+
+[dependencies.semver]
+version = "1.0.7"
+features = ["serde"]
+
+[dependencies.serde]
+version = "1.0.136"
+features = ["derive"]
+
+[dependencies.serde_json]
+version = "1.0.79"
+features = ["unbounded_depth"]
+
+[dependencies.thiserror]
+version = "1.0.31"
+
+[features]
+builder = ["derive_builder"]
+default = []
diff --git a/third_party/rust/cargo_metadata/LICENSE-MIT b/third_party/rust/cargo_metadata/LICENSE-MIT
new file mode 100644
index 0000000000..31aa79387f
--- /dev/null
+++ b/third_party/rust/cargo_metadata/LICENSE-MIT
@@ -0,0 +1,23 @@
+Permission is hereby granted, free of charge, to any
+person obtaining a copy of this software and associated
+documentation files (the "Software"), to deal in the
+Software without restriction, including without
+limitation the rights to use, copy, modify, merge,
+publish, distribute, sublicense, and/or sell copies of
+the Software, and to permit persons to whom the Software
+is furnished to do so, subject to the following
+conditions:
+
+The above copyright notice and this permission notice
+shall be included in all copies or substantial portions
+of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
+ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
+TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
+PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
+IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+DEALINGS IN THE SOFTWARE.
diff --git a/third_party/rust/cargo_metadata/README.md b/third_party/rust/cargo_metadata/README.md
new file mode 100644
index 0000000000..03743f31bc
--- /dev/null
+++ b/third_party/rust/cargo_metadata/README.md
@@ -0,0 +1,12 @@
+# cargo_metadata
+
+Structured access to the output of `cargo metadata`. Usually used from within a `cargo-*` executable.
+
+Also supports serialization to aid in implementing `--message-format=json`-like
+output generation in `cargo-*` subcommands, since some of the types in what
+`cargo --message-format=json` emits are exactly the same as the ones from `cargo metadata`.
+
+[![Build Status](https://api.travis-ci.org/oli-obk/cargo_metadata.svg?branch=master)](https://travis-ci.org/oli-obk/cargo_metadata)
+[![crates.io](https://img.shields.io/crates/v/cargo_metadata.svg)](https://crates.io/crates/cargo_metadata)
+
+[Documentation](https://docs.rs/cargo_metadata/)
diff --git a/third_party/rust/cargo_metadata/src/dependency.rs b/third_party/rust/cargo_metadata/src/dependency.rs
new file mode 100644
index 0000000000..89bcf9f2f1
--- /dev/null
+++ b/third_party/rust/cargo_metadata/src/dependency.rs
@@ -0,0 +1,90 @@
+//! This module contains `Dependency` and the types/functions it uses for deserialization.
+
+use std::fmt;
+
+use camino::Utf8PathBuf;
+#[cfg(feature = "builder")]
+use derive_builder::Builder;
+use semver::VersionReq;
+use serde::{Deserialize, Deserializer, Serialize};
+
+#[derive(Eq, PartialEq, Clone, Debug, Copy, Hash, Serialize, Deserialize)]
+/// Dependencies can come in three kinds
+pub enum DependencyKind {
+ #[serde(rename = "normal")]
+ /// The 'normal' kind
+ Normal,
+ #[serde(rename = "dev")]
+ /// Those used in tests only
+ Development,
+ #[serde(rename = "build")]
+ /// Those used in build scripts only
+ Build,
+ #[doc(hidden)]
+ #[serde(other)]
+ Unknown,
+}
+
+impl Default for DependencyKind {
+ fn default() -> DependencyKind {
+ DependencyKind::Normal
+ }
+}
+
+impl fmt::Display for DependencyKind {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ let s = serde_json::to_string(self).unwrap();
+ // skip opening and closing quotes
+ f.write_str(&s[1..s.len() - 1])
+ }
+}
+
+/// The `kind` can be `null`, which is interpreted as the default - `Normal`.
+pub(super) fn parse_dependency_kind<'de, D>(d: D) -> Result<DependencyKind, D::Error>
+where
+ D: Deserializer<'de>,
+{
+ Deserialize::deserialize(d).map(|x: Option<_>| x.unwrap_or_default())
+}
+
+#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq)]
+#[cfg_attr(feature = "builder", derive(Builder))]
+#[non_exhaustive]
+#[cfg_attr(feature = "builder", builder(pattern = "owned", setter(into)))]
+/// A dependency of the main crate
+pub struct Dependency {
+ /// Name as given in the `Cargo.toml`
+ pub name: String,
+ /// The source of dependency
+ pub source: Option<String>,
+ /// The required version
+ pub req: VersionReq,
+ /// The kind of dependency this is
+ #[serde(deserialize_with = "parse_dependency_kind")]
+ pub kind: DependencyKind,
+ /// Whether this dependency is required or optional
+ pub optional: bool,
+ /// Whether the default features in this dependency are used.
+ pub uses_default_features: bool,
+ /// The list of features enabled for this dependency.
+ pub features: Vec<String>,
+ /// The target this dependency is specific to.
+ ///
+ /// Use the [`Display`] trait to access the contents.
+ ///
+ /// [`Display`]: std::fmt::Display
+ pub target: Option<Platform>,
+ /// If the dependency is renamed, this is the new name for the dependency
+ /// as a string. None if it is not renamed.
+ pub rename: Option<String>,
+ /// The URL of the index of the registry where this dependency is from.
+ ///
+ /// If None, the dependency is from crates.io.
+ pub registry: Option<String>,
+ /// The file system path for a local path dependency.
+ ///
+ /// Only produced on cargo 1.51+
+ pub path: Option<Utf8PathBuf>,
+}
+
+pub use cargo_platform::Platform;
diff --git a/third_party/rust/cargo_metadata/src/diagnostic.rs b/third_party/rust/cargo_metadata/src/diagnostic.rs
new file mode 100644
index 0000000000..dbe21128a0
--- /dev/null
+++ b/third_party/rust/cargo_metadata/src/diagnostic.rs
@@ -0,0 +1,160 @@
+//! This module contains `Diagnostic` and the types/functions it uses for deserialization.
+
+#[cfg(feature = "builder")]
+use derive_builder::Builder;
+use serde::{Deserialize, Serialize};
+use std::fmt;
+
+/// The error code associated to this diagnostic.
+#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
+#[cfg_attr(feature = "builder", derive(Builder))]
+#[non_exhaustive]
+#[cfg_attr(feature = "builder", builder(pattern = "owned", setter(into)))]
+pub struct DiagnosticCode {
+ /// The code itself.
+ pub code: String,
+ /// An explanation for the code
+ pub explanation: Option<String>,
+}
+
+/// A line of code associated with the Diagnostic
+#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
+#[cfg_attr(feature = "builder", derive(Builder))]
+#[non_exhaustive]
+#[cfg_attr(feature = "builder", builder(pattern = "owned", setter(into)))]
+pub struct DiagnosticSpanLine {
+ /// The line of code associated with the error
+ pub text: String,
+ /// Start of the section of the line to highlight. 1-based, character offset in self.text
+ pub highlight_start: usize,
+ /// End of the section of the line to highlight. 1-based, character offset in self.text
+ pub highlight_end: usize,
+}
+
+/// Macro expansion information associated with a diagnostic.
+#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
+#[cfg_attr(feature = "builder", derive(Builder))]
+#[non_exhaustive]
+#[cfg_attr(feature = "builder", builder(pattern = "owned", setter(into)))]
+pub struct DiagnosticSpanMacroExpansion {
+ /// span where macro was applied to generate this code; note that
+ /// this may itself derive from a macro (if
+ /// `span.expansion.is_some()`)
+ pub span: DiagnosticSpan,
+
+ /// name of macro that was applied (e.g., "foo!" or "#[derive(Eq)]")
+ pub macro_decl_name: String,
+
+ /// span where macro was defined (if known)
+ pub def_site_span: Option<DiagnosticSpan>,
+}
+
+/// A section of the source code associated with a Diagnostic
+#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
+#[cfg_attr(feature = "builder", derive(Builder))]
+#[non_exhaustive]
+#[cfg_attr(feature = "builder", builder(pattern = "owned", setter(into)))]
+pub struct DiagnosticSpan {
+ /// The file name or the macro name this diagnostic comes from.
+ pub file_name: String,
+ /// The byte offset in the file where this diagnostic starts from.
+ pub byte_start: u32,
+ /// The byte offset in the file where this diagnostic ends.
+ pub byte_end: u32,
+ /// 1-based. The line in the file.
+ pub line_start: usize,
+ /// 1-based. The line in the file.
+ pub line_end: usize,
+ /// 1-based, character offset.
+ pub column_start: usize,
+ /// 1-based, character offset.
+ pub column_end: usize,
+ /// Is this a "primary" span -- meaning the point, or one of the points,
+ /// where the error occurred?
+ ///
+ /// There are rare cases where multiple spans are marked as primary,
+ /// e.g. "immutable borrow occurs here" and "mutable borrow ends here" can
+ /// be two separate spans both "primary". Top (parent) messages should
+ /// always have at least one primary span, unless it has 0 spans. Child
+ /// messages may have 0 or more primary spans.
+ pub is_primary: bool,
+ /// Source text from the start of line_start to the end of line_end.
+ pub text: Vec<DiagnosticSpanLine>,
+ /// Label that should be placed at this location (if any)
+ pub label: Option<String>,
+ /// If we are suggesting a replacement, this will contain text
+ /// that should be sliced in atop this span.
+ pub suggested_replacement: Option<String>,
+ /// If the suggestion is approximate
+ pub suggestion_applicability: Option<Applicability>,
+ /// Macro invocations that created the code at this span, if any.
+ pub expansion: Option<Box<DiagnosticSpanMacroExpansion>>,
+}
+
+/// Whether a suggestion can be safely applied.
+#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
+#[non_exhaustive]
+pub enum Applicability {
+ /// The suggested replacement can be applied automatically safely
+ MachineApplicable,
+ /// The suggested replacement has placeholders that will need to be manually
+ /// replaced.
+ HasPlaceholders,
+ /// The suggested replacement may be incorrect in some circumstances. Needs
+ /// human review.
+ MaybeIncorrect,
+ /// The suggested replacement will probably not work.
+ Unspecified,
+}
+
+/// The diagnostic level
+#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Hash)]
+#[non_exhaustive]
+#[serde(rename_all = "lowercase")]
+pub enum DiagnosticLevel {
+ /// Internal compiler error
+ #[serde(rename = "error: internal compiler error")]
+ Ice,
+ /// Error
+ Error,
+ /// Warning
+ Warning,
+ /// Failure note
+ #[serde(rename = "failure-note")]
+ FailureNote,
+ /// Note
+ Note,
+ /// Help
+ Help,
+}
+
+/// A diagnostic message generated by rustc
+#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
+#[cfg_attr(feature = "builder", derive(Builder))]
+#[non_exhaustive]
+#[cfg_attr(feature = "builder", builder(pattern = "owned", setter(into)))]
+pub struct Diagnostic {
+ /// The error message of this diagnostic.
+ pub message: String,
+ /// The associated error code for this diagnostic
+ pub code: Option<DiagnosticCode>,
+ /// "error: internal compiler error", "error", "warning", "note", "help"
+ pub level: DiagnosticLevel,
+ /// A list of source code spans this diagnostic is associated with.
+ pub spans: Vec<DiagnosticSpan>,
+ /// Associated diagnostic messages.
+ pub children: Vec<Diagnostic>,
+ /// The message as rustc would render it
+ pub rendered: Option<String>,
+}
+
+impl fmt::Display for Diagnostic {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ if let Some(ref rendered) = self.rendered {
+ f.write_str(rendered)?;
+ } else {
+ f.write_str("cargo didn't render this message")?;
+ }
+ Ok(())
+ }
+}
diff --git a/third_party/rust/cargo_metadata/src/errors.rs b/third_party/rust/cargo_metadata/src/errors.rs
new file mode 100644
index 0000000000..4d08200c83
--- /dev/null
+++ b/third_party/rust/cargo_metadata/src/errors.rs
@@ -0,0 +1,52 @@
+use std::{io, str::Utf8Error, string::FromUtf8Error};
+
+/// Custom result type for `cargo_metadata::Error`
+pub type Result<T> = ::std::result::Result<T, Error>;
+
+/// Error returned when executing/parsing `cargo metadata` fails.
+///
+/// # Note about Backtraces
+///
+/// This error type does not contain backtraces, but each error variant
+/// comes from _one_ specific place, so it's not really needed for the
+/// inside of this crate. If you need a backtrace down to, but not inside
+/// of, a failed call of `cargo_metadata` you can do one of multiple thinks:
+///
+/// 1. Convert it to a `failure::Error` (possible using the `?` operator),
+/// which is similar to a `Box<::std::error::Error + 'static + Send + Sync>`.
+/// 2. Have appropriate variants in your own error type. E.g. you could wrap
+/// a `failure::Context<Error>` or add a `failure::Backtrace` field (which
+/// is empty if `RUST_BACKTRACE` is not set, so it's simple to use).
+/// 3. You still can place a failure based error into a `error_chain` if you
+/// really want to. (Either through foreign_links or by making it a field
+/// value of a `ErrorKind` variant).
+///
+#[derive(Debug, thiserror::Error)]
+pub enum Error {
+ /// Error during execution of `cargo metadata`
+ #[error("`cargo metadata` exited with an error: {stderr}")]
+ CargoMetadata {
+ /// stderr returned by the `cargo metadata` command
+ stderr: String,
+ },
+
+ /// IO Error during execution of `cargo metadata`
+ #[error("failed to start `cargo metadata`: {0}")]
+ Io(#[from] io::Error),
+
+ /// Output of `cargo metadata` was not valid utf8
+ #[error("cannot convert the stdout of `cargo metadata`: {0}")]
+ Utf8(#[from] Utf8Error),
+
+ /// Error output of `cargo metadata` was not valid utf8
+ #[error("cannot convert the stderr of `cargo metadata`: {0}")]
+ ErrUtf8(#[from] FromUtf8Error),
+
+ /// Deserialization error (structure of json did not match expected structure)
+ #[error("failed to interpret `cargo metadata`'s json: {0}")]
+ Json(#[from] ::serde_json::Error),
+
+ /// The output did not contain any json
+ #[error("could not find any json in the output of `cargo metadata`")]
+ NoJson,
+}
diff --git a/third_party/rust/cargo_metadata/src/lib.rs b/third_party/rust/cargo_metadata/src/lib.rs
new file mode 100644
index 0000000000..27c72da0d3
--- /dev/null
+++ b/third_party/rust/cargo_metadata/src/lib.rs
@@ -0,0 +1,792 @@
+#![deny(missing_docs)]
+//! Structured access to the output of `cargo metadata` and `cargo --message-format=json`.
+//! Usually used from within a `cargo-*` executable
+//!
+//! See the [cargo book](https://doc.rust-lang.org/cargo/index.html) for
+//! details on cargo itself.
+//!
+//! ## Examples
+//!
+//! ```rust
+//! # extern crate cargo_metadata;
+//! # use std::path::Path;
+//! let mut args = std::env::args().skip_while(|val| !val.starts_with("--manifest-path"));
+//!
+//! let mut cmd = cargo_metadata::MetadataCommand::new();
+//! let manifest_path = match args.next() {
+//! Some(ref p) if p == "--manifest-path" => {
+//! cmd.manifest_path(args.next().unwrap());
+//! }
+//! Some(p) => {
+//! cmd.manifest_path(p.trim_start_matches("--manifest-path="));
+//! }
+//! None => {}
+//! };
+//!
+//! let _metadata = cmd.exec().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;
+//! # fn main() {
+//! use cargo_metadata::{MetadataCommand, CargoOpt};
+//!
+//! let _metadata = MetadataCommand::new()
+//! .manifest_path("./Cargo.toml")
+//! .features(CargoOpt::AllFeatures)
+//! .exec()
+//! .unwrap();
+//! # }
+//! ```
+//!
+//! Parse message-format output:
+//!
+//! ```
+//! # extern crate cargo_metadata;
+//! use std::process::{Stdio, Command};
+//! use cargo_metadata::Message;
+//!
+//! let mut command = Command::new("cargo")
+//! .args(&["build", "--message-format=json-render-diagnostics"])
+//! .stdout(Stdio::piped())
+//! .spawn()
+//! .unwrap();
+//!
+//! let reader = std::io::BufReader::new(command.stdout.take().unwrap());
+//! for message in cargo_metadata::Message::parse_stream(reader) {
+//! match message.unwrap() {
+//! Message::CompilerMessage(msg) => {
+//! println!("{:?}", msg);
+//! },
+//! Message::CompilerArtifact(artifact) => {
+//! println!("{:?}", artifact);
+//! },
+//! Message::BuildScriptExecuted(script) => {
+//! println!("{:?}", script);
+//! },
+//! Message::BuildFinished(finished) => {
+//! println!("{:?}", finished);
+//! },
+//! _ => () // Unknown message
+//! }
+//! }
+//!
+//! let output = command.wait().expect("Couldn't get cargo's exit status");
+//! ```
+
+use camino::Utf8PathBuf;
+#[cfg(feature = "builder")]
+use derive_builder::Builder;
+use std::collections::HashMap;
+use std::env;
+use std::ffi::OsString;
+use std::fmt;
+use std::hash::Hash;
+use std::path::PathBuf;
+use std::process::{Command, Stdio};
+use std::str::from_utf8;
+
+pub use camino;
+pub use semver;
+use semver::{Version, VersionReq};
+
+pub use dependency::{Dependency, DependencyKind};
+use diagnostic::Diagnostic;
+pub use errors::{Error, Result};
+#[allow(deprecated)]
+pub use messages::parse_messages;
+pub use messages::{
+ Artifact, ArtifactProfile, BuildFinished, BuildScript, CompilerMessage, Message, MessageIter,
+};
+use serde::{Deserialize, Serialize};
+
+mod dependency;
+pub mod diagnostic;
+mod errors;
+mod messages;
+
+/// An "opaque" identifier for a package.
+/// It is possible to inspect the `repr` field, if the need arises, but its
+/// precise format is an implementation detail and is subject to change.
+///
+/// `Metadata` can be indexed by `PackageId`.
+#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
+#[serde(transparent)]
+pub struct PackageId {
+ /// The underlying string representation of id.
+ pub repr: String,
+}
+
+impl std::fmt::Display for PackageId {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ fmt::Display::fmt(&self.repr, f)
+ }
+}
+
+/// Helpers for default metadata fields
+fn is_null(value: &serde_json::Value) -> bool {
+ matches!(value, serde_json::Value::Null)
+}
+
+#[derive(Clone, Serialize, Deserialize, Debug)]
+#[cfg_attr(feature = "builder", derive(Builder))]
+#[non_exhaustive]
+#[cfg_attr(feature = "builder", builder(pattern = "owned", setter(into)))]
+/// 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<PackageId>,
+ /// Dependencies graph
+ pub resolve: Option<Resolve>,
+ /// Workspace root
+ pub workspace_root: Utf8PathBuf,
+ /// Build directory
+ pub target_directory: Utf8PathBuf,
+ /// The workspace-level metadata object. Null if non-existent.
+ #[serde(rename = "metadata", default, skip_serializing_if = "is_null")]
+ pub workspace_metadata: serde_json::Value,
+ /// The metadata format version
+ version: usize,
+}
+
+impl Metadata {
+ /// Get the workspace's root package of this metadata instance.
+ pub fn root_package(&self) -> Option<&Package> {
+ match &self.resolve {
+ Some(resolve) => {
+ // if dependencies are resolved, use Cargo's answer
+ let root = resolve.root.as_ref()?;
+ self.packages.iter().find(|pkg| &pkg.id == root)
+ }
+ None => {
+ // if dependencies aren't resolved, check for a root package manually
+ let root_manifest_path = self.workspace_root.join("Cargo.toml");
+ self.packages
+ .iter()
+ .find(|pkg| pkg.manifest_path == root_manifest_path)
+ }
+ }
+ }
+
+ /// Get the workspace packages.
+ pub fn workspace_packages(&self) -> Vec<&Package> {
+ self.packages
+ .iter()
+ .filter(|&p| self.workspace_members.contains(&p.id))
+ .collect()
+ }
+}
+
+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)]
+#[cfg_attr(feature = "builder", derive(Builder))]
+#[non_exhaustive]
+#[cfg_attr(feature = "builder", builder(pattern = "owned", setter(into)))]
+/// A dependency graph
+pub struct Resolve {
+ /// Nodes in a dependencies graph
+ pub nodes: Vec<Node>,
+
+ /// The crate for which the metadata was read.
+ pub root: Option<PackageId>,
+}
+
+#[derive(Clone, Serialize, Deserialize, Debug)]
+#[cfg_attr(feature = "builder", derive(Builder))]
+#[non_exhaustive]
+#[cfg_attr(feature = "builder", builder(pattern = "owned", setter(into)))]
+/// A node in a dependencies graph
+pub struct Node {
+ /// An opaque identifier for a package
+ pub id: PackageId,
+ /// Dependencies in a structured format.
+ ///
+ /// `deps` handles renamed dependencies whereas `dependencies` does not.
+ #[serde(default)]
+ pub deps: Vec<NodeDep>,
+
+ /// List of opaque identifiers for this node's dependencies.
+ /// It doesn't support renamed dependencies. See `deps`.
+ pub dependencies: Vec<PackageId>,
+
+ /// Features enabled on the crate
+ #[serde(default)]
+ pub features: Vec<String>,
+}
+
+#[derive(Clone, Serialize, Deserialize, Debug)]
+#[cfg_attr(feature = "builder", derive(Builder))]
+#[non_exhaustive]
+#[cfg_attr(feature = "builder", builder(pattern = "owned", setter(into)))]
+/// A dependency in a node
+pub struct NodeDep {
+ /// The name of the dependency's library target.
+ /// If the crate was renamed, it is the new name.
+ pub name: String,
+ /// Package ID (opaque unique identifier)
+ pub pkg: PackageId,
+ /// The kinds of dependencies.
+ ///
+ /// This field was added in Rust 1.41.
+ #[serde(default)]
+ pub dep_kinds: Vec<DepKindInfo>,
+}
+
+#[derive(Clone, Serialize, Deserialize, Debug)]
+#[cfg_attr(feature = "builder", derive(Builder))]
+#[non_exhaustive]
+#[cfg_attr(feature = "builder", builder(pattern = "owned", setter(into)))]
+/// Information about a dependency kind.
+pub struct DepKindInfo {
+ /// The kind of dependency.
+ #[serde(deserialize_with = "dependency::parse_dependency_kind")]
+ pub kind: DependencyKind,
+ /// The target platform for the dependency.
+ ///
+ /// This is `None` if it is not a target dependency.
+ ///
+ /// Use the [`Display`] trait to access the contents.
+ ///
+ /// By default all platform dependencies are included in the resolve
+ /// graph. Use Cargo's `--filter-platform` flag if you only want to
+ /// include dependencies for a specific platform.
+ ///
+ /// [`Display`]: std::fmt::Display
+ pub target: Option<dependency::Platform>,
+}
+
+#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq)]
+#[cfg_attr(feature = "builder", derive(Builder))]
+#[non_exhaustive]
+#[cfg_attr(feature = "builder", builder(pattern = "owned", setter(into)))]
+/// One or more crates described by a single `Cargo.toml`
+///
+/// Each [`target`][Package::targets] of a `Package` will be built as a crate.
+/// For more information, see <https://doc.rust-lang.org/book/ch07-01-packages-and-crates.html>.
+pub struct Package {
+ /// Name as given in the `Cargo.toml`
+ pub name: String,
+ /// Version given in the `Cargo.toml`
+ pub version: Version,
+ /// Authors given in the `Cargo.toml`
+ #[serde(default)]
+ pub authors: Vec<String>,
+ /// An opaque identifier for a package
+ pub id: PackageId,
+ /// The source of the package, e.g.
+ /// crates.io or `None` for local projects.
+ pub source: Option<Source>,
+ /// 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>,
+ /// If the package is using a nonstandard license, this key may be specified instead of
+ /// `license`, and must point to a file relative to the manifest.
+ pub license_file: Option<Utf8PathBuf>,
+ /// 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: Utf8PathBuf,
+ /// Categories as given in the `Cargo.toml`
+ #[serde(default)]
+ pub categories: Vec<String>,
+ /// Keywords as given in the `Cargo.toml`
+ #[serde(default)]
+ pub keywords: Vec<String>,
+ /// Readme as given in the `Cargo.toml`
+ pub readme: Option<Utf8PathBuf>,
+ /// Repository as given in the `Cargo.toml`
+ // can't use `url::Url` because that requires a more recent stable compiler
+ pub repository: Option<String>,
+ /// Homepage as given in the `Cargo.toml`
+ ///
+ /// On versions of cargo before 1.49, this will always be [`None`].
+ pub homepage: Option<String>,
+ /// Documentation URL as given in the `Cargo.toml`
+ ///
+ /// On versions of cargo before 1.49, this will always be [`None`].
+ pub documentation: Option<String>,
+ /// Default Rust edition for the package
+ ///
+ /// Beware that individual targets may specify their own edition in
+ /// [`Target::edition`].
+ #[serde(default)]
+ pub edition: Edition,
+ /// Contents of the free form package.metadata section
+ ///
+ /// This contents can be serialized to a struct using serde:
+ ///
+ /// ```rust
+ /// use serde::Deserialize;
+ /// use serde_json::json;
+ ///
+ /// #[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, skip_serializing_if = "is_null")]
+ pub metadata: serde_json::Value,
+ /// The name of a native library the package is linking to.
+ pub links: Option<String>,
+ /// List of registries to which this package may be published.
+ ///
+ /// Publishing is unrestricted if `None`, and forbidden if the `Vec` is empty.
+ ///
+ /// This is always `None` if running with a version of Cargo older than 1.39.
+ pub publish: Option<Vec<String>>,
+ /// The default binary to run by `cargo run`.
+ ///
+ /// This is always `None` if running with a version of Cargo older than 1.55.
+ pub default_run: Option<String>,
+ /// The minimum supported Rust version of this package.
+ ///
+ /// This is always `None` if running with a version of Cargo older than 1.58.
+ pub rust_version: Option<VersionReq>,
+}
+
+impl Package {
+ /// Full path to the license file if one is present in the manifest
+ pub fn license_file(&self) -> Option<Utf8PathBuf> {
+ self.license_file.as_ref().map(|file| {
+ self.manifest_path
+ .parent()
+ .unwrap_or(&self.manifest_path)
+ .join(file)
+ })
+ }
+
+ /// Full path to the readme file if one is present in the manifest
+ pub fn readme(&self) -> Option<Utf8PathBuf> {
+ self.readme.as_ref().map(|file| {
+ self.manifest_path
+ .parent()
+ .unwrap_or(&self.manifest_path)
+ .join(file)
+ })
+ }
+}
+
+/// The source of a package such as crates.io.
+#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq)]
+#[serde(transparent)]
+pub struct Source {
+ /// The underlying string representation of a source.
+ pub repr: String,
+}
+
+impl Source {
+ /// Returns true if the source is crates.io.
+ pub fn is_crates_io(&self) -> bool {
+ self.repr == "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.repr, f)
+ }
+}
+
+#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq, Hash)]
+#[cfg_attr(feature = "builder", derive(Builder))]
+#[cfg_attr(feature = "builder", builder(pattern = "owned", setter(into)))]
+#[non_exhaustive]
+/// 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", "custom-build")
+ pub kind: Vec<String>,
+ /// Almost the same as `kind`, except when an example is a library instead of an executable.
+ /// In that case `crate_types` contains things like `rlib` and `dylib` while `kind` is `example`
+ #[serde(default)]
+ #[cfg_attr(feature = "builder", builder(default))]
+ pub crate_types: Vec<String>,
+
+ #[serde(default)]
+ #[cfg_attr(feature = "builder", builder(default))]
+ #[serde(rename = "required-features")]
+ /// This target is built only if these features are enabled.
+ /// It doesn't apply to `lib` targets.
+ pub required_features: Vec<String>,
+ /// Path to the main source file of the target
+ pub src_path: Utf8PathBuf,
+ /// Rust edition for this target
+ #[serde(default)]
+ #[cfg_attr(feature = "builder", builder(default))]
+ pub edition: Edition,
+ /// Whether or not this target has doc tests enabled, and the target is
+ /// compatible with doc testing.
+ ///
+ /// This is always `true` if running with a version of Cargo older than 1.37.
+ #[serde(default = "default_true")]
+ #[cfg_attr(feature = "builder", builder(default = "true"))]
+ pub doctest: bool,
+ /// Whether or not this target is tested by default by `cargo test`.
+ ///
+ /// This is always `true` if running with a version of Cargo older than 1.47.
+ #[serde(default = "default_true")]
+ #[cfg_attr(feature = "builder", builder(default = "true"))]
+ pub test: bool,
+ /// Whether or not this target is documented by `cargo doc`.
+ ///
+ /// This is always `true` if running with a version of Cargo older than 1.50.
+ #[serde(default = "default_true")]
+ #[cfg_attr(feature = "builder", builder(default = "true"))]
+ pub doc: bool,
+}
+
+impl Target {
+ fn is_kind(&self, name: &str) -> bool {
+ self.kind.iter().any(|kind| kind == name)
+ }
+
+ /// Return true if this target is of kind "lib".
+ pub fn is_lib(&self) -> bool {
+ self.is_kind("lib")
+ }
+
+ /// Return true if this target is of kind "bin".
+ pub fn is_bin(&self) -> bool {
+ self.is_kind("bin")
+ }
+
+ /// Return true if this target is of kind "example".
+ pub fn is_example(&self) -> bool {
+ self.is_kind("example")
+ }
+
+ /// Return true if this target is of kind "test".
+ pub fn is_test(&self) -> bool {
+ self.is_kind("test")
+ }
+
+ /// Return true if this target is of kind "bench".
+ pub fn is_bench(&self) -> bool {
+ self.is_kind("bench")
+ }
+
+ /// Return true if this target is of kind "custom-build".
+ pub fn is_custom_build(&self) -> bool {
+ self.is_kind("custom-build")
+ }
+}
+
+/// The Rust edition
+///
+/// As of writing this comment rust editions 2024, 2027 and 2030 are not actually a thing yet but are parsed nonetheless for future proofing.
+#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord, Hash)]
+#[non_exhaustive]
+pub enum Edition {
+ /// Edition 2015
+ #[serde(rename = "2015")]
+ E2015,
+ /// Edition 2018
+ #[serde(rename = "2018")]
+ E2018,
+ /// Edition 2021
+ #[serde(rename = "2021")]
+ E2021,
+ #[doc(hidden)]
+ #[serde(rename = "2024")]
+ _E2024,
+ #[doc(hidden)]
+ #[serde(rename = "2027")]
+ _E2027,
+ #[doc(hidden)]
+ #[serde(rename = "2030")]
+ _E2030,
+}
+
+impl Edition {
+ /// Return the string representation of the edition
+ pub fn as_str(&self) -> &'static str {
+ use Edition::*;
+ match self {
+ E2015 => "2015",
+ E2018 => "2018",
+ E2021 => "2021",
+ _E2024 => "2024",
+ _E2027 => "2027",
+ _E2030 => "2030",
+ }
+ }
+}
+
+impl fmt::Display for Edition {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ f.write_str(self.as_str())
+ }
+}
+
+impl Default for Edition {
+ fn default() -> Self {
+ Self::E2015
+ }
+}
+
+fn default_true() -> bool {
+ true
+}
+
+/// Cargo features flags
+#[derive(Debug, Clone)]
+pub enum CargoOpt {
+ /// Run cargo with `--features-all`
+ AllFeatures,
+ /// Run cargo with `--no-default-features`
+ NoDefaultFeatures,
+ /// Run cargo with `--features <FEATURES>`
+ SomeFeatures(Vec<String>),
+}
+
+/// A builder for configurating `cargo metadata` invocation.
+#[derive(Debug, Clone, Default)]
+pub struct MetadataCommand {
+ /// Path to `cargo` executable. If not set, this will use the
+ /// the `$CARGO` environment variable, and if that is not set, will
+ /// simply be `cargo`.
+ cargo_path: Option<PathBuf>,
+ /// Path to `Cargo.toml`
+ manifest_path: Option<PathBuf>,
+ /// Current directory of the `cargo metadata` process.
+ current_dir: Option<PathBuf>,
+ /// Output information only about workspace members and don't fetch dependencies.
+ no_deps: bool,
+ /// Collections of `CargoOpt::SomeFeatures(..)`
+ features: Vec<String>,
+ /// Latched `CargoOpt::AllFeatures`
+ all_features: bool,
+ /// Latched `CargoOpt::NoDefaultFeatures`
+ no_default_features: bool,
+ /// Arbitrary command line flags to pass to `cargo`. These will be added
+ /// to the end of the command line invocation.
+ other_options: Vec<String>,
+ /// Arbitrary environment variables to set when running `cargo`. These will be merged into
+ /// the calling environment, overriding any which clash.
+ env: HashMap<OsString, OsString>,
+ /// Show stderr
+ verbose: bool,
+}
+
+impl MetadataCommand {
+ /// Creates a default `cargo metadata` command, which will look for
+ /// `Cargo.toml` in the ancestors of the current directory.
+ pub fn new() -> MetadataCommand {
+ MetadataCommand::default()
+ }
+ /// Path to `cargo` executable. If not set, this will use the
+ /// the `$CARGO` environment variable, and if that is not set, will
+ /// simply be `cargo`.
+ pub fn cargo_path(&mut self, path: impl Into<PathBuf>) -> &mut MetadataCommand {
+ self.cargo_path = Some(path.into());
+ self
+ }
+ /// Path to `Cargo.toml`
+ pub fn manifest_path(&mut self, path: impl Into<PathBuf>) -> &mut MetadataCommand {
+ self.manifest_path = Some(path.into());
+ self
+ }
+ /// Current directory of the `cargo metadata` process.
+ pub fn current_dir(&mut self, path: impl Into<PathBuf>) -> &mut MetadataCommand {
+ self.current_dir = Some(path.into());
+ self
+ }
+ /// Output information only about workspace members and don't fetch dependencies.
+ pub fn no_deps(&mut self) -> &mut MetadataCommand {
+ self.no_deps = true;
+ self
+ }
+ /// Which features to include.
+ ///
+ /// Call this multiple times to specify advanced feature configurations:
+ ///
+ /// ```no_run
+ /// # use cargo_metadata::{CargoOpt, MetadataCommand};
+ /// MetadataCommand::new()
+ /// .features(CargoOpt::NoDefaultFeatures)
+ /// .features(CargoOpt::SomeFeatures(vec!["feat1".into(), "feat2".into()]))
+ /// .features(CargoOpt::SomeFeatures(vec!["feat3".into()]))
+ /// // ...
+ /// # ;
+ /// ```
+ ///
+ /// # Panics
+ ///
+ /// `cargo metadata` rejects multiple `--no-default-features` flags. Similarly, the `features()`
+ /// method panics when specifying multiple `CargoOpt::NoDefaultFeatures`:
+ ///
+ /// ```should_panic
+ /// # use cargo_metadata::{CargoOpt, MetadataCommand};
+ /// MetadataCommand::new()
+ /// .features(CargoOpt::NoDefaultFeatures)
+ /// .features(CargoOpt::NoDefaultFeatures) // <-- panic!
+ /// // ...
+ /// # ;
+ /// ```
+ ///
+ /// The method also panics for multiple `CargoOpt::AllFeatures` arguments:
+ ///
+ /// ```should_panic
+ /// # use cargo_metadata::{CargoOpt, MetadataCommand};
+ /// MetadataCommand::new()
+ /// .features(CargoOpt::AllFeatures)
+ /// .features(CargoOpt::AllFeatures) // <-- panic!
+ /// // ...
+ /// # ;
+ /// ```
+ pub fn features(&mut self, features: CargoOpt) -> &mut MetadataCommand {
+ match features {
+ CargoOpt::SomeFeatures(features) => self.features.extend(features),
+ CargoOpt::NoDefaultFeatures => {
+ assert!(
+ !self.no_default_features,
+ "Do not supply CargoOpt::NoDefaultFeatures more than once!"
+ );
+ self.no_default_features = true;
+ }
+ CargoOpt::AllFeatures => {
+ assert!(
+ !self.all_features,
+ "Do not supply CargoOpt::AllFeatures more than once!"
+ );
+ self.all_features = true;
+ }
+ }
+ self
+ }
+ /// Arbitrary command line flags to pass to `cargo`. These will be added
+ /// to the end of the command line invocation.
+ pub fn other_options(&mut self, options: impl Into<Vec<String>>) -> &mut MetadataCommand {
+ self.other_options = options.into();
+ self
+ }
+
+ /// Arbitrary environment variables to set when running `cargo`. These will be merged into
+ /// the calling environment, overriding any which clash.
+ ///
+ /// Some examples of when you may want to use this:
+ /// 1. Setting cargo config values without needing a .cargo/config.toml file, e.g. to set
+ /// `CARGO_NET_GIT_FETCH_WITH_CLI=true`
+ /// 2. To specify a custom path to RUSTC if your rust toolchain components aren't laid out in
+ /// the way cargo expects by default.
+ ///
+ /// ```no_run
+ /// # use cargo_metadata::{CargoOpt, MetadataCommand};
+ /// MetadataCommand::new()
+ /// .env("CARGO_NET_GIT_FETCH_WITH_CLI", "true")
+ /// .env("RUSTC", "/path/to/rustc")
+ /// // ...
+ /// # ;
+ /// ```
+ pub fn env<K: Into<OsString>, V: Into<OsString>>(
+ &mut self,
+ key: K,
+ val: V,
+ ) -> &mut MetadataCommand {
+ self.env.insert(key.into(), val.into());
+ self
+ }
+
+ /// Set whether to show stderr
+ pub fn verbose(&mut self, verbose: bool) -> &mut MetadataCommand {
+ self.verbose = verbose;
+ self
+ }
+
+ /// Builds a command for `cargo metadata`. This is the first
+ /// part of the work of `exec`.
+ pub fn cargo_command(&self) -> Command {
+ let cargo = self
+ .cargo_path
+ .clone()
+ .or_else(|| env::var("CARGO").map(PathBuf::from).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 !self.features.is_empty() {
+ cmd.arg("--features").arg(self.features.join(","));
+ }
+ if self.all_features {
+ cmd.arg("--all-features");
+ }
+ if self.no_default_features {
+ cmd.arg("--no-default-features");
+ }
+
+ if let Some(manifest_path) = &self.manifest_path {
+ cmd.arg("--manifest-path").arg(manifest_path.as_os_str());
+ }
+ cmd.args(&self.other_options);
+
+ cmd.envs(&self.env);
+
+ cmd
+ }
+
+ /// Parses `cargo metadata` output. `data` must have been
+ /// produced by a command built with `cargo_command`.
+ pub fn parse<T: AsRef<str>>(data: T) -> Result<Metadata> {
+ let meta = serde_json::from_str(data.as_ref())?;
+ Ok(meta)
+ }
+
+ /// Runs configured `cargo metadata` and returns parsed `Metadata`.
+ pub fn exec(&self) -> Result<Metadata> {
+ let mut command = self.cargo_command();
+ if self.verbose {
+ command.stderr(Stdio::inherit());
+ }
+ let output = command.output()?;
+ if !output.status.success() {
+ return Err(Error::CargoMetadata {
+ stderr: String::from_utf8(output.stderr)?,
+ });
+ }
+ let stdout = from_utf8(&output.stdout)?
+ .lines()
+ .find(|line| line.starts_with('{'))
+ .ok_or(Error::NoJson)?;
+ Self::parse(stdout)
+ }
+}
diff --git a/third_party/rust/cargo_metadata/src/messages.rs b/third_party/rust/cargo_metadata/src/messages.rs
new file mode 100644
index 0000000000..ea2abd2508
--- /dev/null
+++ b/third_party/rust/cargo_metadata/src/messages.rs
@@ -0,0 +1,175 @@
+use super::{Diagnostic, PackageId, Target};
+use camino::Utf8PathBuf;
+#[cfg(feature = "builder")]
+use derive_builder::Builder;
+use serde::{Deserialize, Serialize};
+use std::fmt;
+use std::io::{self, BufRead, Read};
+
+/// Profile settings used to determine which compiler flags to use for a
+/// target.
+#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
+#[cfg_attr(feature = "builder", derive(Builder))]
+#[non_exhaustive]
+#[cfg_attr(feature = "builder", builder(pattern = "owned", setter(into)))]
+pub struct ArtifactProfile {
+ /// Optimization level. Possible values are 0-3, s or z.
+ pub opt_level: String,
+ /// The amount of debug info. 0 for none, 1 for limited, 2 for full
+ pub debuginfo: Option<u32>,
+ /// State of the `cfg(debug_assertions)` directive, enabling macros like
+ /// `debug_assert!`
+ pub debug_assertions: bool,
+ /// State of the overflow checks.
+ pub overflow_checks: bool,
+ /// Whether this profile is a test
+ pub test: bool,
+}
+
+/// A compiler-generated file.
+#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
+#[cfg_attr(feature = "builder", derive(Builder))]
+#[non_exhaustive]
+#[cfg_attr(feature = "builder", builder(pattern = "owned", setter(into)))]
+pub struct Artifact {
+ /// The package this artifact belongs to
+ pub package_id: PackageId,
+ /// Path to the `Cargo.toml` file
+ #[serde(default)]
+ pub manifest_path: Utf8PathBuf,
+ /// The target this artifact was compiled for
+ pub target: Target,
+ /// The profile this artifact was compiled with
+ pub profile: ArtifactProfile,
+ /// The enabled features for this artifact
+ pub features: Vec<String>,
+ /// The full paths to the generated artifacts
+ /// (e.g. binary file and separate debug info)
+ pub filenames: Vec<Utf8PathBuf>,
+ /// Path to the executable file
+ pub executable: Option<Utf8PathBuf>,
+ /// If true, then the files were already generated
+ pub fresh: bool,
+}
+
+/// Message left by the compiler
+// TODO: Better name. This one comes from machine_message.rs
+#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
+#[cfg_attr(feature = "builder", derive(Builder))]
+#[non_exhaustive]
+#[cfg_attr(feature = "builder", builder(pattern = "owned", setter(into)))]
+pub struct CompilerMessage {
+ /// The package this message belongs to
+ pub package_id: PackageId,
+ /// The target this message is aimed at
+ pub target: Target,
+ /// The message the compiler sent.
+ pub message: Diagnostic,
+}
+
+/// Output of a build script execution.
+#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
+#[cfg_attr(feature = "builder", derive(Builder))]
+#[non_exhaustive]
+#[cfg_attr(feature = "builder", builder(pattern = "owned", setter(into)))]
+pub struct BuildScript {
+ /// The package this build script execution belongs to
+ pub package_id: PackageId,
+ /// The libs to link
+ pub linked_libs: Vec<Utf8PathBuf>,
+ /// The paths to search when resolving libs
+ pub linked_paths: Vec<Utf8PathBuf>,
+ /// Various `--cfg` flags to pass to the compiler
+ pub cfgs: Vec<String>,
+ /// The environment variables to add to the compilation
+ pub env: Vec<(String, String)>,
+ /// The `OUT_DIR` environment variable where this script places its output
+ ///
+ /// Added in Rust 1.41.
+ #[serde(default)]
+ pub out_dir: Utf8PathBuf,
+}
+
+/// Final result of a build.
+#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
+#[cfg_attr(feature = "builder", derive(Builder))]
+#[non_exhaustive]
+#[cfg_attr(feature = "builder", builder(pattern = "owned", setter(into)))]
+pub struct BuildFinished {
+ /// Whether or not the build finished successfully.
+ pub success: bool,
+}
+
+/// A cargo message
+#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
+#[non_exhaustive]
+#[serde(tag = "reason", rename_all = "kebab-case")]
+pub enum Message {
+ /// The compiler generated an artifact
+ CompilerArtifact(Artifact),
+ /// The compiler wants to display a message
+ CompilerMessage(CompilerMessage),
+ /// A build script successfully executed.
+ BuildScriptExecuted(BuildScript),
+ /// The build has finished.
+ ///
+ /// This is emitted at the end of the build as the last message.
+ /// Added in Rust 1.44.
+ BuildFinished(BuildFinished),
+ /// A line of text which isn't a cargo or compiler message.
+ /// Line separator is not included
+ #[serde(skip)]
+ TextLine(String),
+}
+
+impl Message {
+ /// Creates an iterator of Message from a Read outputting a stream of JSON
+ /// messages. For usage information, look at the top-level documentation.
+ pub fn parse_stream<R: Read>(input: R) -> MessageIter<R> {
+ MessageIter { input }
+ }
+}
+
+impl fmt::Display for CompilerMessage {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ write!(f, "{}", self.message)
+ }
+}
+
+/// An iterator of Messages.
+pub struct MessageIter<R> {
+ input: R,
+}
+
+impl<R: BufRead> Iterator for MessageIter<R> {
+ type Item = io::Result<Message>;
+ fn next(&mut self) -> Option<Self::Item> {
+ let mut line = String::new();
+ self.input
+ .read_line(&mut line)
+ .map(|n| {
+ if n == 0 {
+ None
+ } else {
+ if line.ends_with('\n') {
+ line.truncate(line.len() - 1);
+ }
+ let mut deserializer = serde_json::Deserializer::from_str(&line);
+ deserializer.disable_recursion_limit();
+ Some(Message::deserialize(&mut deserializer).unwrap_or(Message::TextLine(line)))
+ }
+ })
+ .transpose()
+ }
+}
+
+/// An iterator of Message.
+type MessageIterator<R> =
+ serde_json::StreamDeserializer<'static, serde_json::de::IoRead<R>, Message>;
+
+/// Creates an iterator of Message from a Read outputting a stream of JSON
+/// messages. For usage information, look at the top-level documentation.
+#[deprecated(note = "Use Message::parse_stream instead")]
+pub fn parse_messages<R: Read>(input: R) -> MessageIterator<R> {
+ serde_json::Deserializer::from_reader(input).into_iter::<Message>()
+}
diff --git a/third_party/rust/cargo_metadata/tests/selftest.rs b/third_party/rust/cargo_metadata/tests/selftest.rs
new file mode 100644
index 0000000000..d6ab61832d
--- /dev/null
+++ b/third_party/rust/cargo_metadata/tests/selftest.rs
@@ -0,0 +1,163 @@
+use std::env::current_dir;
+use std::path::PathBuf;
+
+use semver::Version;
+
+use cargo_metadata::{CargoOpt, Error, MetadataCommand};
+use serde::Deserialize;
+
+#[derive(Debug, PartialEq, Eq, Deserialize)]
+struct TestPackageMetadata {
+ some_field: bool,
+ other_field: String,
+}
+
+#[test]
+fn metadata() {
+ let metadata = MetadataCommand::new().no_deps().exec().unwrap();
+
+ let this = &metadata.packages[0];
+ assert_eq!(this.name, "cargo_metadata");
+ assert_eq!(this.targets.len(), 3);
+
+ let lib = this
+ .targets
+ .iter()
+ .find(|t| t.name == "cargo_metadata")
+ .unwrap();
+ assert_eq!(lib.kind[0], "lib");
+ assert_eq!(lib.crate_types[0], "lib");
+
+ let selftest = this.targets.iter().find(|t| t.name == "selftest").unwrap();
+ assert_eq!(selftest.name, "selftest");
+ assert_eq!(selftest.kind[0], "test");
+ assert_eq!(selftest.crate_types[0], "bin");
+
+ let package_metadata = &metadata.packages[0]
+ .metadata
+ .as_object()
+ .expect("package.metadata must be a table.");
+ assert_eq!(package_metadata.len(), 1);
+
+ let value = package_metadata.get("cargo_metadata_test").unwrap();
+ let test_package_metadata: TestPackageMetadata = serde_json::from_value(value.clone()).unwrap();
+ assert_eq!(
+ test_package_metadata,
+ TestPackageMetadata {
+ some_field: true,
+ other_field: "foo".into(),
+ }
+ );
+}
+
+#[test]
+fn builder_interface() {
+ let _ = MetadataCommand::new()
+ .manifest_path("Cargo.toml")
+ .exec()
+ .unwrap();
+ let _ = MetadataCommand::new()
+ .manifest_path(String::from("Cargo.toml"))
+ .exec()
+ .unwrap();
+ let _ = MetadataCommand::new()
+ .manifest_path(PathBuf::from("Cargo.toml"))
+ .exec()
+ .unwrap();
+ let _ = MetadataCommand::new()
+ .manifest_path("Cargo.toml")
+ .no_deps()
+ .exec()
+ .unwrap();
+ let _ = MetadataCommand::new()
+ .manifest_path("Cargo.toml")
+ .features(CargoOpt::AllFeatures)
+ .exec()
+ .unwrap();
+ let _ = MetadataCommand::new()
+ .manifest_path("Cargo.toml")
+ .current_dir(current_dir().unwrap())
+ .exec()
+ .unwrap();
+}
+
+#[test]
+fn error1() {
+ match MetadataCommand::new().manifest_path("foo").exec() {
+ Err(Error::CargoMetadata { stderr }) => assert_eq!(
+ stderr.trim(),
+ "error: the manifest-path must be a path to a Cargo.toml file"
+ ),
+ _ => unreachable!(),
+ }
+}
+
+#[test]
+fn error2() {
+ match MetadataCommand::new()
+ .manifest_path("foo/Cargo.toml")
+ .exec()
+ {
+ Err(Error::CargoMetadata { stderr }) => assert_eq!(
+ stderr.trim(),
+ "error: manifest path `foo/Cargo.toml` does not exist"
+ ),
+ _ => unreachable!(),
+ }
+}
+
+#[test]
+fn cargo_path() {
+ match MetadataCommand::new()
+ .cargo_path("this does not exist")
+ .exec()
+ {
+ Err(Error::Io(e)) => assert_eq!(e.kind(), std::io::ErrorKind::NotFound),
+ _ => unreachable!(),
+ }
+}
+
+#[test]
+fn metadata_deps() {
+ std::env::set_var("CARGO_PROFILE", "3");
+ let metadata = MetadataCommand::new()
+ .manifest_path("Cargo.toml")
+ .exec()
+ .unwrap();
+ let this_id = metadata
+ .workspace_members
+ .first()
+ .expect("Did not find ourselves");
+ let this = &metadata[this_id];
+
+ assert_eq!(this.name, "cargo_metadata");
+
+ let workspace_packages = metadata.workspace_packages();
+ assert_eq!(workspace_packages.len(), 1);
+ assert_eq!(&workspace_packages[0].id, this_id);
+
+ let lib = this
+ .targets
+ .iter()
+ .find(|t| t.name == "cargo_metadata")
+ .unwrap();
+ assert_eq!(lib.kind[0], "lib");
+ assert_eq!(lib.crate_types[0], "lib");
+
+ let selftest = this.targets.iter().find(|t| t.name == "selftest").unwrap();
+ assert_eq!(selftest.name, "selftest");
+ assert_eq!(selftest.kind[0], "test");
+ assert_eq!(selftest.crate_types[0], "bin");
+
+ let dependencies = &this.dependencies;
+
+ let serde = dependencies
+ .iter()
+ .find(|dep| dep.name == "serde")
+ .expect("Did not find serde dependency");
+
+ assert_eq!(serde.kind, cargo_metadata::DependencyKind::Normal);
+ assert!(!serde.req.matches(&Version::parse("1.0.0").unwrap()));
+ assert!(serde.req.matches(&Version::parse("1.99.99").unwrap()));
+ assert!(!serde.req.matches(&Version::parse("2.0.0").unwrap()));
+}
diff --git a/third_party/rust/cargo_metadata/tests/test_samples.rs b/third_party/rust/cargo_metadata/tests/test_samples.rs
new file mode 100644
index 0000000000..3c747c595a
--- /dev/null
+++ b/third_party/rust/cargo_metadata/tests/test_samples.rs
@@ -0,0 +1,647 @@
+extern crate cargo_metadata;
+extern crate semver;
+#[macro_use]
+extern crate serde_json;
+
+use camino::Utf8PathBuf;
+use cargo_metadata::{CargoOpt, DependencyKind, Edition, Metadata, MetadataCommand};
+
+#[test]
+fn old_minimal() {
+ // Output from oldest supported version (1.24).
+ // This intentionally has as many null fields as possible.
+ // 1.8 is when metadata was introduced.
+ // Older versions not supported because the following are required:
+ // - `workspace_members` added in 1.13
+ // - `target_directory` added in 1.19
+ // - `workspace_root` added in 1.24
+ let json = r#"
+{
+ "packages": [
+ {
+ "name": "foo",
+ "version": "0.1.0",
+ "id": "foo 0.1.0 (path+file:///foo)",
+ "license": null,
+ "license_file": null,
+ "description": null,
+ "source": null,
+ "dependencies": [
+ {
+ "name": "somedep",
+ "source": null,
+ "req": "^1.0",
+ "kind": null,
+ "optional": false,
+ "uses_default_features": true,
+ "features": [],
+ "target": null
+ }
+ ],
+ "targets": [
+ {
+ "kind": [
+ "bin"
+ ],
+ "crate_types": [
+ "bin"
+ ],
+ "name": "foo",
+ "src_path": "/foo/src/main.rs"
+ }
+ ],
+ "features": {},
+ "manifest_path": "/foo/Cargo.toml"
+ }
+ ],
+ "workspace_members": [
+ "foo 0.1.0 (path+file:///foo)"
+ ],
+ "resolve": null,
+ "target_directory": "/foo/target",
+ "version": 1,
+ "workspace_root": "/foo"
+}
+"#;
+ let meta: Metadata = serde_json::from_str(json).unwrap();
+ assert_eq!(meta.packages.len(), 1);
+ let pkg = &meta.packages[0];
+ assert_eq!(pkg.name, "foo");
+ assert_eq!(pkg.version, semver::Version::parse("0.1.0").unwrap());
+ assert_eq!(pkg.authors.len(), 0);
+ assert_eq!(pkg.id.to_string(), "foo 0.1.0 (path+file:///foo)");
+ assert_eq!(pkg.description, None);
+ assert_eq!(pkg.license, None);
+ assert_eq!(pkg.license_file, None);
+ assert_eq!(pkg.default_run, None);
+ assert_eq!(pkg.rust_version, None);
+ assert_eq!(pkg.dependencies.len(), 1);
+ let dep = &pkg.dependencies[0];
+ assert_eq!(dep.name, "somedep");
+ assert_eq!(dep.source, None);
+ assert_eq!(dep.req, semver::VersionReq::parse("^1.0").unwrap());
+ assert_eq!(dep.kind, DependencyKind::Normal);
+ assert!(!dep.optional);
+ assert!(dep.uses_default_features);
+ assert_eq!(dep.features.len(), 0);
+ assert!(dep.target.is_none());
+ assert_eq!(dep.rename, None);
+ assert_eq!(dep.registry, None);
+ assert_eq!(pkg.targets.len(), 1);
+ let target = &pkg.targets[0];
+ assert_eq!(target.name, "foo");
+ assert_eq!(target.kind, vec!["bin"]);
+ assert_eq!(target.crate_types, vec!["bin"]);
+ assert_eq!(target.required_features.len(), 0);
+ assert_eq!(target.src_path, "/foo/src/main.rs");
+ assert_eq!(target.edition, Edition::E2015);
+ assert!(target.doctest);
+ assert!(target.test);
+ assert!(target.doc);
+ assert_eq!(pkg.features.len(), 0);
+ assert_eq!(pkg.manifest_path, "/foo/Cargo.toml");
+ assert_eq!(pkg.categories.len(), 0);
+ assert_eq!(pkg.keywords.len(), 0);
+ assert_eq!(pkg.readme, None);
+ assert_eq!(pkg.repository, None);
+ assert_eq!(pkg.homepage, None);
+ assert_eq!(pkg.documentation, None);
+ assert_eq!(pkg.edition, Edition::E2015);
+ assert_eq!(pkg.metadata, serde_json::Value::Null);
+ assert_eq!(pkg.links, None);
+ assert_eq!(pkg.publish, None);
+ assert_eq!(meta.workspace_members.len(), 1);
+ assert_eq!(
+ meta.workspace_members[0].to_string(),
+ "foo 0.1.0 (path+file:///foo)"
+ );
+ assert!(meta.resolve.is_none());
+ assert_eq!(meta.workspace_root, "/foo");
+ assert_eq!(meta.workspace_metadata, serde_json::Value::Null);
+ assert_eq!(meta.target_directory, "/foo/target");
+}
+
+macro_rules! sorted {
+ ($e:expr) => {{
+ let mut v = $e.clone();
+ v.sort();
+ v
+ }};
+}
+
+fn cargo_version() -> semver::Version {
+ let output = std::process::Command::new("cargo")
+ .arg("-V")
+ .output()
+ .expect("Failed to exec cargo.");
+ let out = std::str::from_utf8(&output.stdout)
+ .expect("invalid utf8")
+ .trim();
+ let split: Vec<&str> = out.split_whitespace().collect();
+ assert!(split.len() >= 2, "cargo -V output is unexpected: {}", out);
+ let mut ver = semver::Version::parse(split[1]).expect("cargo -V semver could not be parsed");
+ // Don't care about metadata, it is awkward to compare.
+ ver.pre = semver::Prerelease::EMPTY;
+ ver.build = semver::BuildMetadata::EMPTY;
+ ver
+}
+
+#[derive(serde::Deserialize, PartialEq, Eq, Debug)]
+struct WorkspaceMetadata {
+ testobject: TestObject,
+}
+
+#[derive(serde::Deserialize, PartialEq, Eq, Debug)]
+struct TestObject {
+ myvalue: String,
+}
+
+#[test]
+fn all_the_fields() {
+ // All the fields currently generated as of 1.60. This tries to exercise as
+ // much as possible.
+ let ver = cargo_version();
+ let minimum = semver::Version::parse("1.56.0").unwrap();
+ if ver < minimum {
+ // edition added in 1.30
+ // rename added in 1.31
+ // links added in 1.33
+ // doctest added in 1.37
+ // publish added in 1.39
+ // dep_kinds added in 1.41
+ // test added in 1.47
+ // homepage added in 1.49
+ // documentation added in 1.49
+ // doc added in 1.50
+ // path added in 1.51
+ // default_run added in 1.55
+ // rust_version added in 1.58
+ eprintln!("Skipping all_the_fields test, cargo {} is too old.", ver);
+ return;
+ }
+ let meta = MetadataCommand::new()
+ .manifest_path("tests/all/Cargo.toml")
+ .exec()
+ .unwrap();
+ assert_eq!(meta.workspace_root.file_name().unwrap(), "all");
+ assert_eq!(
+ serde_json::from_value::<WorkspaceMetadata>(meta.workspace_metadata).unwrap(),
+ WorkspaceMetadata {
+ testobject: TestObject {
+ myvalue: "abc".to_string()
+ }
+ }
+ );
+ assert_eq!(meta.workspace_members.len(), 1);
+ assert!(meta.workspace_members[0].to_string().starts_with("all"));
+
+ assert_eq!(meta.packages.len(), 9);
+ let all = meta.packages.iter().find(|p| p.name == "all").unwrap();
+ assert_eq!(all.version, semver::Version::parse("0.1.0").unwrap());
+ assert_eq!(all.authors, vec!["Jane Doe <user@example.com>"]);
+ assert!(all.id.to_string().starts_with("all"));
+ assert_eq!(all.description, Some("Package description.".to_string()));
+ assert_eq!(all.license, Some("MIT/Apache-2.0".to_string()));
+ assert_eq!(all.license_file, Some(Utf8PathBuf::from("LICENSE")));
+ assert!(all.license_file().unwrap().ends_with("tests/all/LICENSE"));
+ assert_eq!(all.publish, Some(vec![]));
+ assert_eq!(all.links, Some("foo".to_string()));
+ assert_eq!(all.default_run, Some("otherbin".to_string()));
+ if ver >= semver::Version::parse("1.58.0").unwrap() {
+ assert_eq!(
+ all.rust_version,
+ Some(semver::VersionReq::parse("1.56").unwrap())
+ );
+ }
+
+ assert_eq!(all.dependencies.len(), 8);
+ let bitflags = all
+ .dependencies
+ .iter()
+ .find(|d| d.name == "bitflags")
+ .unwrap();
+ assert_eq!(
+ bitflags.source,
+ Some("registry+https://github.com/rust-lang/crates.io-index".to_string())
+ );
+ assert!(bitflags.optional);
+ assert_eq!(bitflags.req, semver::VersionReq::parse("^1.0").unwrap());
+
+ let path_dep = all
+ .dependencies
+ .iter()
+ .find(|d| d.name == "path-dep")
+ .unwrap();
+ assert_eq!(path_dep.source, None);
+ assert_eq!(path_dep.kind, DependencyKind::Normal);
+ assert_eq!(path_dep.req, semver::VersionReq::parse("*").unwrap());
+ assert_eq!(
+ path_dep.path.as_ref().map(|p| p.ends_with("path-dep")),
+ Some(true),
+ );
+
+ all.dependencies
+ .iter()
+ .find(|d| d.name == "namedep")
+ .unwrap();
+
+ let featdep = all
+ .dependencies
+ .iter()
+ .find(|d| d.name == "featdep")
+ .unwrap();
+ assert_eq!(featdep.features, vec!["i128"]);
+ assert!(!featdep.uses_default_features);
+
+ let renamed = all
+ .dependencies
+ .iter()
+ .find(|d| d.name == "oldname")
+ .unwrap();
+ assert_eq!(renamed.rename, Some("newname".to_string()));
+
+ let devdep = all
+ .dependencies
+ .iter()
+ .find(|d| d.name == "devdep")
+ .unwrap();
+ assert_eq!(devdep.kind, DependencyKind::Development);
+
+ let bdep = all.dependencies.iter().find(|d| d.name == "bdep").unwrap();
+ assert_eq!(bdep.kind, DependencyKind::Build);
+
+ let windep = all
+ .dependencies
+ .iter()
+ .find(|d| d.name == "windep")
+ .unwrap();
+ assert_eq!(
+ windep.target.as_ref().map(|x| x.to_string()),
+ Some("cfg(windows)".to_string())
+ );
+
+ macro_rules! get_file_name {
+ ($v:expr) => {
+ all.targets
+ .iter()
+ .find(|t| t.src_path.file_name().unwrap() == $v)
+ .unwrap()
+ };
+ }
+ assert_eq!(all.targets.len(), 8);
+ let lib = get_file_name!("lib.rs");
+ assert_eq!(lib.name, "all");
+ assert_eq!(sorted!(lib.kind), vec!["cdylib", "rlib", "staticlib"]);
+ assert_eq!(
+ sorted!(lib.crate_types),
+ vec!["cdylib", "rlib", "staticlib"]
+ );
+ assert_eq!(lib.required_features.len(), 0);
+ assert_eq!(lib.edition, Edition::E2018);
+ assert!(lib.doctest);
+ assert!(lib.test);
+ assert!(lib.doc);
+
+ let main = get_file_name!("main.rs");
+ assert_eq!(main.crate_types, vec!["bin"]);
+ assert_eq!(main.kind, vec!["bin"]);
+ assert!(!main.doctest);
+ assert!(main.test);
+ assert!(main.doc);
+
+ let otherbin = get_file_name!("otherbin.rs");
+ assert_eq!(otherbin.edition, Edition::E2015);
+ assert!(!otherbin.doc);
+
+ let reqfeat = get_file_name!("reqfeat.rs");
+ assert_eq!(reqfeat.required_features, vec!["feat2"]);
+
+ let ex1 = get_file_name!("ex1.rs");
+ assert_eq!(ex1.kind, vec!["example"]);
+ assert!(!ex1.test);
+
+ let t1 = get_file_name!("t1.rs");
+ assert_eq!(t1.kind, vec!["test"]);
+
+ let b1 = get_file_name!("b1.rs");
+ assert_eq!(b1.kind, vec!["bench"]);
+
+ let build = get_file_name!("build.rs");
+ assert_eq!(build.kind, vec!["custom-build"]);
+
+ if ver >= semver::Version::parse("1.60.0").unwrap() {
+ // 1.60 now reports optional dependencies within the features table
+ assert_eq!(all.features.len(), 4);
+ assert_eq!(all.features["bitflags"], vec!["dep:bitflags"]);
+ } else {
+ assert_eq!(all.features.len(), 3);
+ }
+ assert_eq!(all.features["feat1"].len(), 0);
+ assert_eq!(all.features["feat2"].len(), 0);
+ assert_eq!(sorted!(all.features["default"]), vec!["bitflags", "feat1"]);
+
+ assert!(all.manifest_path.ends_with("all/Cargo.toml"));
+ assert_eq!(all.categories, vec!["command-line-utilities"]);
+ assert_eq!(all.keywords, vec!["cli"]);
+ assert_eq!(all.readme, Some(Utf8PathBuf::from("README.md")));
+ assert!(all.readme().unwrap().ends_with("tests/all/README.md"));
+ assert_eq!(
+ all.repository,
+ Some("https://github.com/oli-obk/cargo_metadata/".to_string())
+ );
+ assert_eq!(
+ all.homepage,
+ Some("https://github.com/oli-obk/cargo_metadata/".to_string())
+ );
+ assert_eq!(
+ all.documentation,
+ Some("https://docs.rs/cargo_metadata/".to_string())
+ );
+ assert_eq!(all.edition, Edition::E2018);
+ assert_eq!(
+ all.metadata,
+ json!({
+ "docs": {
+ "rs": {
+ "all-features": true,
+ "default-target": "x86_64-unknown-linux-gnu",
+ "rustc-args": ["--example-rustc-arg"]
+ }
+ }
+ })
+ );
+
+ let resolve = meta.resolve.as_ref().unwrap();
+ assert!(resolve
+ .root
+ .as_ref()
+ .unwrap()
+ .to_string()
+ .starts_with("all"));
+
+ assert_eq!(resolve.nodes.len(), 9);
+ let path_dep = resolve
+ .nodes
+ .iter()
+ .find(|n| n.id.to_string().starts_with("path-dep"))
+ .unwrap();
+ assert_eq!(path_dep.deps.len(), 0);
+ assert_eq!(path_dep.dependencies.len(), 0);
+ assert_eq!(path_dep.features.len(), 0);
+
+ let bitflags = resolve
+ .nodes
+ .iter()
+ .find(|n| n.id.to_string().starts_with("bitflags"))
+ .unwrap();
+ assert_eq!(bitflags.features, vec!["default"]);
+
+ let featdep = resolve
+ .nodes
+ .iter()
+ .find(|n| n.id.to_string().starts_with("featdep"))
+ .unwrap();
+ assert_eq!(featdep.features, vec!["i128"]);
+
+ let all = resolve
+ .nodes
+ .iter()
+ .find(|n| n.id.to_string().starts_with("all"))
+ .unwrap();
+ assert_eq!(all.dependencies.len(), 8);
+ assert_eq!(all.deps.len(), 8);
+ let newname = all.deps.iter().find(|d| d.name == "newname").unwrap();
+ assert!(newname.pkg.to_string().starts_with("oldname"));
+ // Note the underscore here.
+ let path_dep = all.deps.iter().find(|d| d.name == "path_dep").unwrap();
+ assert!(path_dep.pkg.to_string().starts_with("path-dep"));
+ assert_eq!(path_dep.dep_kinds.len(), 1);
+ let kind = &path_dep.dep_kinds[0];
+ assert_eq!(kind.kind, DependencyKind::Normal);
+ assert!(kind.target.is_none());
+
+ let namedep = all
+ .deps
+ .iter()
+ .find(|d| d.name == "different_name")
+ .unwrap();
+ assert!(namedep.pkg.to_string().starts_with("namedep"));
+ assert_eq!(sorted!(all.features), vec!["bitflags", "default", "feat1"]);
+
+ let bdep = all.deps.iter().find(|d| d.name == "bdep").unwrap();
+ assert_eq!(bdep.dep_kinds.len(), 1);
+ let kind = &bdep.dep_kinds[0];
+ assert_eq!(kind.kind, DependencyKind::Build);
+ assert!(kind.target.is_none());
+
+ let devdep = all.deps.iter().find(|d| d.name == "devdep").unwrap();
+ assert_eq!(devdep.dep_kinds.len(), 1);
+ let kind = &devdep.dep_kinds[0];
+ assert_eq!(kind.kind, DependencyKind::Development);
+ assert!(kind.target.is_none());
+
+ let windep = all.deps.iter().find(|d| d.name == "windep").unwrap();
+ assert_eq!(windep.dep_kinds.len(), 1);
+ let kind = &windep.dep_kinds[0];
+ assert_eq!(kind.kind, DependencyKind::Normal);
+ assert_eq!(
+ kind.target.as_ref().map(|x| x.to_string()),
+ Some("cfg(windows)".to_string())
+ );
+}
+
+#[test]
+fn alt_registry() {
+ // This is difficult to test (would need to set up a custom index).
+ // Just manually check the JSON is handled.
+ let json = r#"
+{
+ "packages": [
+ {
+ "name": "alt",
+ "version": "0.1.0",
+ "id": "alt 0.1.0 (path+file:///alt)",
+ "source": null,
+ "dependencies": [
+ {
+ "name": "alt2",
+ "source": "registry+https://example.com",
+ "req": "^0.1",
+ "kind": null,
+ "rename": null,
+ "optional": false,
+ "uses_default_features": true,
+ "features": [],
+ "target": null,
+ "registry": "https://example.com"
+ }
+ ],
+ "targets": [
+ {
+ "kind": [
+ "lib"
+ ],
+ "crate_types": [
+ "lib"
+ ],
+ "name": "alt",
+ "src_path": "/alt/src/lib.rs",
+ "edition": "2018"
+ }
+ ],
+ "features": {},
+ "manifest_path": "/alt/Cargo.toml",
+ "metadata": null,
+ "authors": [],
+ "categories": [],
+ "keywords": [],
+ "readme": null,
+ "repository": null,
+ "edition": "2018",
+ "links": null
+ }
+ ],
+ "workspace_members": [
+ "alt 0.1.0 (path+file:///alt)"
+ ],
+ "resolve": null,
+ "target_directory": "/alt/target",
+ "version": 1,
+ "workspace_root": "/alt"
+}
+"#;
+ let meta: Metadata = serde_json::from_str(json).unwrap();
+ assert_eq!(meta.packages.len(), 1);
+ let alt = &meta.packages[0];
+ let deps = &alt.dependencies;
+ assert_eq!(deps.len(), 1);
+ let dep = &deps[0];
+ assert_eq!(dep.registry, Some("https://example.com".to_string()));
+}
+
+#[test]
+fn current_dir() {
+ let meta = MetadataCommand::new()
+ .current_dir("tests/all/namedep")
+ .exec()
+ .unwrap();
+ let namedep = meta.packages.iter().find(|p| p.name == "namedep").unwrap();
+ assert!(namedep.name.starts_with("namedep"));
+}
+
+#[test]
+fn parse_stream_is_robust() {
+ // Proc macros can print stuff to stdout, which naturally breaks JSON messages.
+ // Let's check that we don't die horribly in this case, and report an error.
+ let json_output = r##"{"reason":"compiler-artifact","package_id":"chatty 0.1.0 (path+file:///chatty-macro/chatty)","manifest_path":"chatty-macro/Cargo.toml","target":{"kind":["proc-macro"],"crate_types":["proc-macro"],"name":"chatty","src_path":"/chatty-macro/chatty/src/lib.rs","edition":"2018","doctest":true},"profile":{"opt_level":"0","debuginfo":2,"debug_assertions":true,"overflow_checks":true,"test":false},"features":[],"filenames":["/chatty-macro/target/debug/deps/libchatty-f2adcff24cdf3bb2.so"],"executable":null,"fresh":false}
+Evil proc macro was here!
+{"reason":"compiler-artifact","package_id":"chatty-macro 0.1.0 (path+file:///chatty-macro)","manifest_path":"chatty-macro/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"chatty-macro","src_path":"/chatty-macro/src/lib.rs","edition":"2018","doctest":true},"profile":{"opt_level":"0","debuginfo":2,"debug_assertions":true,"overflow_checks":true,"test":false},"features":[],"filenames":["/chatty-macro/target/debug/libchatty_macro.rlib","/chatty-macro/target/debug/deps/libchatty_macro-cb5956ed52a11fb6.rmeta"],"executable":null,"fresh":false}
+"##;
+ let mut n_messages = 0;
+ let mut text = String::new();
+ for message in cargo_metadata::Message::parse_stream(json_output.as_bytes()) {
+ let message = message.unwrap();
+ match message {
+ cargo_metadata::Message::TextLine(line) => text = line,
+ _ => n_messages += 1,
+ }
+ }
+ assert_eq!(n_messages, 2);
+ assert_eq!(text, "Evil proc macro was here!");
+}
+
+#[test]
+fn advanced_feature_configuration() {
+ fn build_features<F: FnOnce(&mut MetadataCommand) -> &mut MetadataCommand>(
+ func: F,
+ ) -> Vec<String> {
+ let mut meta = MetadataCommand::new();
+ let meta = meta.manifest_path("tests/all/Cargo.toml");
+
+ let meta = func(meta);
+ let meta = meta.exec().unwrap();
+
+ let resolve = meta.resolve.as_ref().unwrap();
+
+ let all = resolve
+ .nodes
+ .iter()
+ .find(|n| n.id.to_string().starts_with("all"))
+ .unwrap();
+
+ all.features.clone()
+ }
+
+ // Default behavior; tested above
+ let default_features = build_features(|meta| meta);
+ assert_eq!(
+ sorted!(default_features),
+ vec!["bitflags", "default", "feat1"]
+ );
+
+ // Manually specify the same default features
+ let manual_features = build_features(|meta| {
+ meta.features(CargoOpt::NoDefaultFeatures)
+ .features(CargoOpt::SomeFeatures(vec![
+ "feat1".into(),
+ "bitflags".into(),
+ ]))
+ });
+ assert_eq!(sorted!(manual_features), vec!["bitflags", "feat1"]);
+
+ // Multiple SomeFeatures is same as one longer SomeFeatures
+ let manual_features = build_features(|meta| {
+ meta.features(CargoOpt::NoDefaultFeatures)
+ .features(CargoOpt::SomeFeatures(vec!["feat1".into()]))
+ .features(CargoOpt::SomeFeatures(vec!["feat2".into()]))
+ });
+ assert_eq!(sorted!(manual_features), vec!["feat1", "feat2"]);
+
+ // No features + All features == All features
+ let all_features = build_features(|meta| {
+ meta.features(CargoOpt::AllFeatures)
+ .features(CargoOpt::NoDefaultFeatures)
+ });
+ assert_eq!(
+ sorted!(all_features),
+ vec!["bitflags", "default", "feat1", "feat2"]
+ );
+
+ // The '--all-features' flag supersedes other feature flags
+ let all_flag_variants = build_features(|meta| {
+ meta.features(CargoOpt::SomeFeatures(vec!["feat2".into()]))
+ .features(CargoOpt::NoDefaultFeatures)
+ .features(CargoOpt::AllFeatures)
+ });
+ assert_eq!(sorted!(all_flag_variants), sorted!(all_features));
+}
+
+#[test]
+fn depkind_to_string() {
+ assert_eq!(DependencyKind::Normal.to_string(), "normal");
+ assert_eq!(DependencyKind::Development.to_string(), "dev");
+ assert_eq!(DependencyKind::Build.to_string(), "build");
+ assert_eq!(DependencyKind::Unknown.to_string(), "Unknown");
+}
+
+#[test]
+fn basic_workspace_root_package_exists() {
+ // First try with dependencies
+ let meta = MetadataCommand::new()
+ .manifest_path("tests/basic_workspace/Cargo.toml")
+ .exec()
+ .unwrap();
+ assert_eq!(meta.root_package().unwrap().name, "ex_bin");
+ // Now with no_deps, it should still work exactly the same
+ let meta = MetadataCommand::new()
+ .manifest_path("tests/basic_workspace/Cargo.toml")
+ .no_deps()
+ .exec()
+ .unwrap();
+ assert_eq!(
+ meta.root_package()
+ .expect("workspace root still exists when no_deps used")
+ .name,
+ "ex_bin"
+ );
+}