cargo_metadata-0.15.4/.cargo_vcs_info.json0000644000000001360000000000100140560ustar { "git": { "sha1": "184e836e3e197a07f5ea7eb30b5662163f0bc4ea" }, "path_in_vcs": "" }cargo_metadata-0.15.4/.github/workflows/main.yml000064400000000000000000000017641046102023000177220ustar 00000000000000name: CI on: [push, pull_request] jobs: rustfmt: name: rustfmt runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - name: Install rust run: rustup update --no-self-update stable && rustup default stable - name: Check formatting run: cargo fmt -- --check test: name: Test runs-on: ubuntu-latest strategy: matrix: include: - rust: stable - rust: beta - rust: nightly - rust: 1.56.0 steps: - uses: actions/checkout@v2 - name: Install rust run: rustup update --no-self-update ${{ matrix.rust }} && rustup default ${{ matrix.rust }} - name: Run tests run: | cargo build --verbose cargo build --verbose --no-default-features cargo test --verbose cargo test --verbose --no-default-features cargo test --verbose --all-features # doesn't work right now #- name: Check semver # uses: obi1kenobi/cargo-semver-checks-action@v1 cargo_metadata-0.15.4/.github/workflows/release.yml000064400000000000000000000047111046102023000204110ustar 00000000000000name: Release new version on: workflow_dispatch: secrets: CARGO_REGISTRY_TOKEN: required: true env: RUST_BACKTRACE: 1 CARGO_TERM_COLOR: always jobs: create-release: name: Create release runs-on: ubuntu-latest if: github.ref == 'refs/heads/main' steps: - name: Checkout uses: actions/checkout@v3 with: persist-credentials: true - name: Install rust uses: actions-rs/toolchain@v1 with: toolchain: stable profile: minimal override: true - uses: Swatinem/rust-cache@v2 # Determine which version we're about to publish, so we can tag it appropriately. # If the tag already exists, then we've already published this version. - name: Determine current version id: version-check run: | # Fail on first error, on undefined variables, and on errors in pipes. set -euo pipefail export VERSION="$(cargo metadata --format-version 1 | \ jq --arg crate_name cargo_metadata --exit-status -r \ '.packages[] | select(.name == $crate_name) | .version')" echo "version=$VERSION" >> $GITHUB_OUTPUT if [[ "$(git tag -l "$VERSION")" != '' ]]; then echo "Aborting: Version $VERSION is already published, we found its tag in the repo." exit 1 fi # TODO: Replace this with the cargo-semver-checks v2 GitHub Action when it's stabilized: # https://github.com/obi1kenobi/cargo-semver-checks-action/pull/21 - name: Semver-check run: | # Fail on first error, on undefined variables, and on errors in pipes. set -euo pipefail cargo install --locked cargo-semver-checks cargo semver-checks check-release - name: Publish run: cargo publish env: CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }} - name: Tag the version run: | # Fail on first error, on undefined variables, and on errors in pipes. set -euo pipefail git tag "${{ steps.version-check.outputs.version }}" git push origin "${{ steps.version-check.outputs.version }}" - uses: taiki-e/create-gh-release-action@v1 name: Create GitHub release with: branch: main ref: refs/tags/${{ steps.version-check.outputs.version }} env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} cargo_metadata-0.15.4/.gitignore000064400000000000000000000000441046102023000146340ustar 00000000000000Cargo.lock .idea/ target temp/ tmp/ cargo_metadata-0.15.4/CHANGELOG.md000064400000000000000000000014751046102023000144660ustar 00000000000000# Changelog ## Unreleased - Bumped MSRV from `1.42.0` to `1.56.0`. ### 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 cargo_metadata-0.15.4/Cargo.toml0000644000000025140000000000100120560ustar # 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.4" authors = ["Oliver Schneider "] 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 = [] cargo_metadata-0.15.4/Cargo.toml.orig000064400000000000000000000014541046102023000155410ustar 00000000000000[package] name = "cargo_metadata" version = "0.15.4" authors = ["Oliver Schneider "] repository = "https://github.com/oli-obk/cargo_metadata" description = "structured access to the output of `cargo metadata`" license = "MIT" readme = "README.md" edition = "2018" rust-version = "1.42.0" [dependencies] camino = { version = "1.0.7", features = ["serde1"] } cargo-platform = "0.1.2" derive_builder = { version = "0.11.1", optional = true } semver = { version = "1.0.7", features = ["serde"] } serde = { version = "1.0.136", features = ["derive"] } serde_json = { version = "1.0.79", features = ["unbounded_depth"] } thiserror = "1.0.31" [features] default = [] builder = ["derive_builder"] [package.metadata.cargo_metadata_test] some_field = true other_field = "foo" cargo_metadata-0.15.4/LICENSE-MIT000064400000000000000000000017771046102023000143160ustar 00000000000000Permission 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. cargo_metadata-0.15.4/README.md000064400000000000000000000013121046102023000141220ustar 00000000000000# 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://github.com/oli-obk/cargo_metadata/workflows/CI/badge.svg?branch=main)](https://github.com/oli-obk/cargo_metadata/actions/workflows/main.yml?query=branch%3Amain) [![crates.io](https://img.shields.io/crates/v/cargo_metadata.svg)](https://crates.io/crates/cargo_metadata) [Documentation](https://docs.rs/cargo_metadata/) cargo_metadata-0.15.4/src/dependency.rs000064400000000000000000000054731046102023000161320ustar 00000000000000//! 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 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, /// 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, /// The target this dependency is specific to. /// /// Use the [`Display`] trait to access the contents. /// /// [`Display`]: std::fmt::Display pub target: Option, /// 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, /// The URL of the index of the registry where this dependency is from. /// /// If None, the dependency is from crates.io. pub registry: Option, /// The file system path for a local path dependency. /// /// Only produced on cargo 1.51+ pub path: Option, } pub use cargo_platform::Platform; cargo_metadata-0.15.4/src/diagnostic.rs000064400000000000000000000135761046102023000161430ustar 00000000000000//! 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, } /// 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, } /// 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, /// Label that should be placed at this location (if any) pub label: Option, /// If we are suggesting a replacement, this will contain text /// that should be sliced in atop this span. pub suggested_replacement: Option, /// If the suggestion is approximate pub suggestion_applicability: Option, /// Macro invocations that created the code at this span, if any. pub expansion: Option>, } /// 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, /// "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, /// Associated diagnostic messages. pub children: Vec, /// The message as rustc would render it pub rendered: Option, } 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(()) } } cargo_metadata-0.15.4/src/errors.rs000064400000000000000000000042211046102023000153160ustar 00000000000000use std::{io, str::Utf8Error, string::FromUtf8Error}; /// Custom result type for `cargo_metadata::Error` pub type Result = ::std::result::Result; /// 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` 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, } cargo_metadata-0.15.4/src/lib.rs000064400000000000000000000650631046102023000145630ustar 00000000000000#![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::{BTreeMap, 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}; #[cfg(feature = "builder")] pub use dependency::DependencyBuilder; 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, }; #[cfg(feature = "builder")] pub use messages::{ ArtifactBuilder, ArtifactProfileBuilder, BuildFinishedBuilder, BuildScriptBuilder, CompilerMessageBuilder, }; use serde::{Deserialize, Serialize, Serializer}; 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) } /// Helper to ensure that hashmaps serialize in sorted order, to make /// serialization deterministic. fn sorted_map( value: &HashMap, serializer: S, ) -> std::result::Result { value .iter() .collect::>() .serialize(serializer) } #[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, /// A list of all workspace members pub workspace_members: Vec, /// Dependencies graph pub resolve: Option, /// 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, /// The crate for which the metadata was read. pub root: Option, } #[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, /// List of opaque identifiers for this node's dependencies. /// It doesn't support renamed dependencies. See `deps`. pub dependencies: Vec, /// Features enabled on the crate #[serde(default)] pub features: Vec, } #[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, } #[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, } #[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 . 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, /// 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, /// Description as given in the `Cargo.toml` pub description: Option, /// List of dependencies of this particular package pub dependencies: Vec, /// License as given in the `Cargo.toml` pub license: Option, /// 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, /// Targets provided by the crate (lib, bin, example, test, ...) pub targets: Vec, /// Features provided by the crate, mapped to the features required by that feature. #[serde(serialize_with = "sorted_map")] pub features: HashMap>, /// Path containing the `Cargo.toml` pub manifest_path: Utf8PathBuf, /// Categories as given in the `Cargo.toml` #[serde(default)] pub categories: Vec, /// Keywords as given in the `Cargo.toml` #[serde(default)] pub keywords: Vec, /// Readme as given in the `Cargo.toml` pub readme: Option, /// Repository as given in the `Cargo.toml` // can't use `url::Url` because that requires a more recent stable compiler pub repository: Option, /// Homepage as given in the `Cargo.toml` /// /// On versions of cargo before 1.49, this will always be [`None`]. pub homepage: Option, /// Documentation URL as given in the `Cargo.toml` /// /// On versions of cargo before 1.49, this will always be [`None`]. pub documentation: Option, /// 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, /// 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>, /// 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, /// 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, } impl Package { /// Full path to the license file if one is present in the manifest pub fn license_file(&self) -> Option { 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 { 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, /// 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, #[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, /// 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, Copy, 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 ` SomeFeatures(Vec), } /// 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, /// Path to `Cargo.toml` manifest_path: Option, /// Current directory of the `cargo metadata` process. current_dir: Option, /// Output information only about workspace members and don't fetch dependencies. no_deps: bool, /// Collections of `CargoOpt::SomeFeatures(..)` features: Vec, /// 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, /// Arbitrary environment variables to set when running `cargo`. These will be merged into /// the calling environment, overriding any which clash. env: HashMap, /// 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) -> &mut MetadataCommand { self.cargo_path = Some(path.into()); self } /// Path to `Cargo.toml` pub fn manifest_path(&mut self, path: impl Into) -> &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) -> &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>) -> &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, V: Into>( &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>(data: T) -> Result { let meta = serde_json::from_str(data.as_ref())?; Ok(meta) } /// Runs configured `cargo metadata` and returns parsed `Metadata`. pub fn exec(&self) -> Result { 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) } } cargo_metadata-0.15.4/src/messages.rs000064400000000000000000000140771046102023000156230ustar 00000000000000use 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, /// 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, /// The full paths to the generated artifacts /// (e.g. binary file and separate debug info) pub filenames: Vec, /// Path to the executable file pub executable: Option, /// 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, /// The paths to search when resolving libs pub linked_paths: Vec, /// Various `--cfg` flags to pass to the compiler pub cfgs: Vec, /// 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(input: R) -> MessageIter { 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 { input: R, } impl Iterator for MessageIter { type Item = io::Result; fn next(&mut self) -> Option { 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 = serde_json::StreamDeserializer<'static, serde_json::de::IoRead, 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(input: R) -> MessageIterator { serde_json::Deserializer::from_reader(input).into_iter::() } cargo_metadata-0.15.4/tests/selftest.rs000064400000000000000000000106371046102023000162160ustar 00000000000000use 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())); } cargo_metadata-0.15.4/tests/test_samples.rs000064400000000000000000000514361046102023000170720ustar 00000000000000extern 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::(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 "]); 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 &mut MetadataCommand>( func: F, ) -> Vec { 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" ); }