cargo_metadata-0.19.2/.cargo_vcs_info.json0000644000000001360000000000100140600ustar { "git": { "sha1": "0b4be024c57d7855a2dfbdf2ec2a48f3e16e9f78" }, "path_in_vcs": "" }cargo_metadata-0.19.2/.github/workflows/main.yml000064400000000000000000000022441046102023000177160ustar 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 clippy: name: clippy runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - name: Install rust run: rustup update --no-self-update stable && rustup default stable - name: Clippy check run: cargo clippy --all-features -- -Dwarnings test: name: Test runs-on: ubuntu-latest strategy: matrix: include: - rust: stable - rust: beta - rust: nightly - rust: 1.78.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 cargo_metadata-0.19.2/.github/workflows/release.yml000064400000000000000000000043351046102023000204150ustar 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 - name: Semver-check uses: obi1kenobi/cargo-semver-checks-action@v2 with: rust-toolchain: manual # we've already installed Rust, don't install a new one - 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.19.2/.gitignore000064400000000000000000000000441046102023000146360ustar 00000000000000Cargo.lock .idea/ target temp/ tmp/ cargo_metadata-0.19.2/CHANGELOG.md000064400000000000000000000034741046102023000144710ustar 00000000000000# Changelog ## Unreleased - n/a ### Added - n/a ### Changed - Updated dependencies: - `thiserror` from `1.0.31` to `2.0.3` - `derive_builder` from `0.12` to `0.20` ### Removed - n/a ### Fixed - n/a ## [0.19.0] - 2024-11-20 ### Added - Re-exported `semver` crate directly. - Added implementation of `std::ops::Index<&PackageId>` for `Resolve`. - Added `pub fn is_kind(&self, name: TargetKind) -> bool` to `Target`. - Added derived implementations of `PartialEq`, `Eq` and `Hash` for `Metadata` and its members' types. - Added default fields to `PackageBuilder`. - Added `pub fn new(name:version:id:path:) -> Self` to `PackageBuilder` for providing all required fields upfront. ### Changed - Bumped MSRV from `1.42.0` to `1.56.0`. - Made `parse_stream` more versatile by accepting anything that implements `Read`. - Converted `TargetKind` and `CrateType` to an enum representation. ### Removed - Removed re-exports for `BuildMetadata` and `Prerelease` from `semver` crate. - Removed `.is_lib(…)`, `.is_bin(…)`, `.is_example(…)`, `.is_test(…)`, `.is_bench(…)`, `.is_custom_build(…)`, and `.is_proc_macro(…)` from `Target` (in favor of adding `.is_kind(…)`). ### 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 ## [0.15.1] - 2022-10-13 ### Added - Added `TestMessage`, `TestEvent`, `SuiteEvent` for parsing the `cargo test -- --format json` output. cargo_metadata-0.19.2/Cargo.lock0000644000000131750000000000100120420ustar # This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 3 [[package]] name = "camino" version = "1.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b96ec4966b5813e2c0507c1f86115c8c5abaadc3980879c3424042a02fd1ad3" dependencies = [ "serde", ] [[package]] name = "cargo-platform" version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e35af189006b9c0f00a064685c727031e3ed2d8020f7ba284d78cc2671bd36ea" dependencies = [ "serde", ] [[package]] name = "cargo_metadata" version = "0.19.2" dependencies = [ "camino", "cargo-platform", "derive_builder", "semver", "serde", "serde_json", "thiserror", ] [[package]] name = "darling" version = "0.20.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6f63b86c8a8826a49b8c21f08a2d07338eec8d900540f8630dc76284be802989" dependencies = [ "darling_core", "darling_macro", ] [[package]] name = "darling_core" version = "0.20.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95133861a8032aaea082871032f5815eb9e98cef03fa916ab4500513994df9e5" dependencies = [ "fnv", "ident_case", "proc-macro2", "quote", "strsim", "syn", ] [[package]] name = "darling_macro" version = "0.20.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" dependencies = [ "darling_core", "quote", "syn", ] [[package]] name = "derive_builder" version = "0.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "507dfb09ea8b7fa618fcf76e953f4f5e192547945816d5358edffe39f6f94947" dependencies = [ "derive_builder_macro", ] [[package]] name = "derive_builder_core" version = "0.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2d5bcf7b024d6835cfb3d473887cd966994907effbe9227e8c8219824d06c4e8" dependencies = [ "darling", "proc-macro2", "quote", "syn", ] [[package]] name = "derive_builder_macro" version = "0.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ab63b0e2bf4d5928aff72e83a7dace85d7bba5fe12dcc3c5a572d78caffd3f3c" dependencies = [ "derive_builder_core", "syn", ] [[package]] name = "fnv" version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] name = "ident_case" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" [[package]] name = "itoa" version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" [[package]] name = "memchr" version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" [[package]] name = "proc-macro2" version = "1.0.93" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99" dependencies = [ "unicode-ident", ] [[package]] name = "quote" version = "1.0.38" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc" dependencies = [ "proc-macro2", ] [[package]] name = "ryu" version = "1.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ea1a2d0a644769cc99faa24c3ad26b379b786fe7c36fd3c546254801650e6dd" [[package]] name = "semver" version = "1.0.25" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f79dfe2d285b0488816f30e700a7438c5a73d816b5b7d3ac72fbc48b0d185e03" dependencies = [ "serde", ] [[package]] name = "serde" version = "1.0.218" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e8dfc9d19bdbf6d17e22319da49161d5d0108e4188e8b680aef6299eed22df60" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" version = "1.0.218" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f09503e191f4e797cb8aac08e9a4a4695c5edf6a2e70e376d961ddd5c969f82b" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "serde_json" version = "1.0.139" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "44f86c3acccc9c65b153fe1b85a3be07fe5515274ec9f0653b4a0875731c72a6" dependencies = [ "itoa", "memchr", "ryu", "serde", ] [[package]] name = "strsim" version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "syn" version = "2.0.98" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "36147f1a48ae0ec2b5b3bc5b537d267457555a10dc06f3dbc8cb11ba3006d3b1" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] [[package]] name = "thiserror" version = "2.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d452f284b73e6d76dd36758a0c8684b1d5be31f92b89d07fd5822175732206fc" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" version = "2.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26afc1baea8a989337eeb52b6e72a039780ce45c3edfcc9c5b9d112feeb173c2" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "unicode-ident" version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "00e2473a93778eb0bad35909dff6a10d28e63f792f16ed15e404fca9d5eeedbe" cargo_metadata-0.19.2/Cargo.toml0000644000000031530000000000100120600ustar # 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 = "2021" rust-version = "1.78.0" name = "cargo_metadata" version = "0.19.2" authors = ["Oliver Schneider "] build = false autolib = false autobins = false autoexamples = false autotests = false autobenches = false 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] other_field = "foo" some_field = true [features] builder = ["derive_builder"] default = [] unstable = [] [lib] name = "cargo_metadata" path = "src/lib.rs" [[test]] name = "selftest" path = "tests/selftest.rs" [[test]] name = "test_samples" path = "tests/test_samples.rs" [dependencies.camino] version = "1.0.7" features = ["serde1"] [dependencies.cargo-platform] version = "0.1.2" [dependencies.derive_builder] version = "0.20" optional = true [dependencies.semver] version = "1.0.7" features = ["serde"] [dependencies.serde] version = "1.0.136" features = ["derive"] [dependencies.serde_json] version = "1.0.118" features = ["unbounded_depth"] [dependencies.thiserror] version = "2.0.3" cargo_metadata-0.19.2/Cargo.toml.orig000064400000000000000000000014701046102023000155410ustar 00000000000000[package] name = "cargo_metadata" version = "0.19.2" 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 = "2021" rust-version = "1.78.0" [dependencies] camino = { version = "1.0.7", features = ["serde1"] } cargo-platform = "0.1.2" derive_builder = { version = "0.20", optional = true } semver = { version = "1.0.7", features = ["serde"] } serde = { version = "1.0.136", features = ["derive"] } serde_json = { version = "1.0.118", features = ["unbounded_depth"] } thiserror = "2.0.3" [features] default = [] builder = ["derive_builder"] unstable = [] [package.metadata.cargo_metadata_test] some_field = true other_field = "foo" cargo_metadata-0.19.2/LICENSE-MIT000064400000000000000000000017771046102023000143200ustar 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.19.2/README.md000064400000000000000000000013121046102023000141240ustar 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.19.2/clippy.toml000064400000000000000000000003071046102023000150450ustar 00000000000000disallowed-types = [ { path = "std::collections::HashMap", reason = "HashMap's key order is unspecified and thus serializing such a type will result in a random output, use BTreeMap instead." } ]cargo_metadata-0.19.2/src/dependency.rs000064400000000000000000000053521046102023000161300ustar 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, Default)] /// Dependencies can come in three kinds pub enum DependencyKind { #[serde(rename = "normal")] #[default] /// 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 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, Hash)] #[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.19.2/src/diagnostic.rs000064400000000000000000000135761046102023000161450ustar 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.19.2/src/errors.rs000064400000000000000000000042211046102023000153200ustar 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.19.2/src/lib.rs000064400000000000000000001214231046102023000145560ustar 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; 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, FromStr}; pub use camino; pub use semver; use semver::Version; #[cfg(feature = "builder")] pub use dependency::DependencyBuilder; pub use dependency::{Dependency, DependencyKind}; use diagnostic::Diagnostic; pub use errors::{Error, Result}; #[cfg(feature = "unstable")] pub use libtest::TestMessage; #[allow(deprecated)] pub use messages::parse_messages; pub use messages::{ Artifact, ArtifactDebuginfo, ArtifactProfile, BuildFinished, BuildScript, CompilerMessage, Message, MessageIter, }; #[cfg(feature = "builder")] pub use messages::{ ArtifactBuilder, ArtifactProfileBuilder, BuildFinishedBuilder, BuildScriptBuilder, CompilerMessageBuilder, }; use serde::{Deserialize, Deserializer, Serialize}; mod dependency; pub mod diagnostic; mod errors; #[cfg(feature = "unstable")] pub mod libtest; 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, Hash, PartialOrd, Ord)] #[serde(transparent)] pub struct PackageId { /// The underlying string representation of id. pub repr: String, } impl 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, PartialEq, Eq, Hash)] #[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, /// The list of default workspace members /// /// This is not available if running with a version of Cargo older than 1.71. /// /// You can check whether it is available or missing using respectively /// [`WorkspaceDefaultMembers::is_available`] and [`WorkspaceDefaultMembers::is_missing`]. #[serde(default, skip_serializing_if = "WorkspaceDefaultMembers::is_missing")] pub workspace_default_members: WorkspaceDefaultMembers, /// 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() } /// Get the workspace default packages. /// /// # Panics /// /// This will panic if running with a version of Cargo older than 1.71. pub fn workspace_default_packages(&self) -> Vec<&Package> { self.packages .iter() .filter(|&p| self.workspace_default_members.contains(&p.id)) .collect() } } impl<'a> std::ops::Index<&'a PackageId> for Metadata { type Output = Package; fn index(&self, idx: &'a PackageId) -> &Self::Output { self.packages .iter() .find(|p| p.id == *idx) .unwrap_or_else(|| panic!("no package with this id: {:?}", idx)) } } #[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq, Hash, Default)] #[serde(transparent)] /// A list of default workspace members. /// /// See [`Metadata::workspace_default_members`]. /// /// It is only available if running a version of Cargo of 1.71 or newer. /// /// # Panics /// /// Dereferencing when running an older version of Cargo will panic. pub struct WorkspaceDefaultMembers(Option>); impl WorkspaceDefaultMembers { /// Return `true` if the list of workspace default members is supported by /// the called cargo-metadata version and `false` otherwise. /// /// In particular useful when parsing the output of `cargo-metadata` for /// versions of Cargo < 1.71, as dereferencing [`WorkspaceDefaultMembers`] /// for these versions will panic. /// /// Opposite of [`WorkspaceDefaultMembers::is_missing`]. pub fn is_available(&self) -> bool { self.0.is_some() } /// Return `false` if the list of workspace default members is supported by /// the called cargo-metadata version and `true` otherwise. /// /// In particular useful when parsing the output of `cargo-metadata` for /// versions of Cargo < 1.71, as dereferencing [`WorkspaceDefaultMembers`] /// for these versions will panic. /// /// Opposite of [`WorkspaceDefaultMembers::is_available`]. pub fn is_missing(&self) -> bool { self.0.is_none() } } impl core::ops::Deref for WorkspaceDefaultMembers { type Target = [PackageId]; fn deref(&self) -> &Self::Target { self.0 .as_ref() .expect("WorkspaceDefaultMembers should only be dereferenced on Cargo versions >= 1.71") } } #[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq, Hash)] #[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, } impl<'a> std::ops::Index<&'a PackageId> for Resolve { type Output = Node; fn index(&self, idx: &'a PackageId) -> &Self::Output { self.nodes .iter() .find(|p| p.id == *idx) .unwrap_or_else(|| panic!("no Node with this id: {:?}", idx)) } } #[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq, Hash)] #[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, PartialEq, Eq, Hash)] #[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, PartialEq, Eq, Hash)] #[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, Hash)] #[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 { /// The [`name` field](https://doc.rust-lang.org/cargo/reference/manifest.html#the-name-field) as given in the `Cargo.toml` // (We say "given in" instead of "specified in" since the `name` key cannot be inherited from the workspace.) pub name: String, /// The [`version` field](https://doc.rust-lang.org/cargo/reference/manifest.html#the-version-field) as specified in the `Cargo.toml` pub version: Version, /// The [`authors` field](https://doc.rust-lang.org/cargo/reference/manifest.html#the-authors-field) as specified in the `Cargo.toml` #[serde(default)] #[cfg_attr(feature = "builder", builder(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. #[cfg_attr(feature = "builder", builder(default))] pub source: Option, /// The [`description` field](https://doc.rust-lang.org/cargo/reference/manifest.html#the-description-field) as specified in the `Cargo.toml` #[cfg_attr(feature = "builder", builder(default))] pub description: Option, /// List of dependencies of this particular package #[cfg_attr(feature = "builder", builder(default))] pub dependencies: Vec, /// The [`license` field](https://doc.rust-lang.org/cargo/reference/manifest.html#the-license-and-license-file-fields) as specified in the `Cargo.toml` #[cfg_attr(feature = "builder", builder(default))] pub license: Option, /// The [`license-file` field](https://doc.rust-lang.org/cargo/reference/manifest.html#the-license-and-license-file-fields) as specified in the `Cargo.toml`. /// 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. #[cfg_attr(feature = "builder", builder(default))] pub license_file: Option, /// Targets provided by the crate (lib, bin, example, test, ...) #[cfg_attr(feature = "builder", builder(default))] pub targets: Vec, /// Features provided by the crate, mapped to the features required by that feature. #[cfg_attr(feature = "builder", builder(default))] pub features: BTreeMap>, /// Path containing the `Cargo.toml` pub manifest_path: Utf8PathBuf, /// The [`categories` field](https://doc.rust-lang.org/cargo/reference/manifest.html#the-categories-field) as specified in the `Cargo.toml` #[serde(default)] #[cfg_attr(feature = "builder", builder(default))] pub categories: Vec, /// The [`keywords` field](https://doc.rust-lang.org/cargo/reference/manifest.html#the-keywords-field) as specified in the `Cargo.toml` #[serde(default)] #[cfg_attr(feature = "builder", builder(default))] pub keywords: Vec, /// The [`readme` field](https://doc.rust-lang.org/cargo/reference/manifest.html#the-readme-field) as specified in the `Cargo.toml` #[cfg_attr(feature = "builder", builder(default))] pub readme: Option, /// The [`repository` URL](https://doc.rust-lang.org/cargo/reference/manifest.html#the-repository-field) as specified in the `Cargo.toml` // can't use `url::Url` because that requires a more recent stable compiler #[cfg_attr(feature = "builder", builder(default))] pub repository: Option, /// The [`homepage` URL](https://doc.rust-lang.org/cargo/reference/manifest.html#the-homepage-field) as specified in the `Cargo.toml`. /// /// On versions of cargo before 1.49, this will always be [`None`]. #[cfg_attr(feature = "builder", builder(default))] pub homepage: Option, /// The [`documentation` URL](https://doc.rust-lang.org/cargo/reference/manifest.html#the-documentation-field) as specified in the `Cargo.toml`. /// /// On versions of cargo before 1.49, this will always be [`None`]. #[cfg_attr(feature = "builder", builder(default))] pub documentation: Option, /// The default Rust edition for the package (either what's specified in the [`edition` field](https://doc.rust-lang.org/cargo/reference/manifest.html#the-edition-field) /// or defaulting to [`Edition::E2015`]). /// /// Beware that individual targets may specify their own edition in /// [`Target::edition`]. #[serde(default)] #[cfg_attr(feature = "builder", builder(default))] pub edition: Edition, /// Contents of the free form [`package.metadata` section](https://doc.rust-lang.org/cargo/reference/manifest.html#the-metadata-table). /// /// 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, /// } /// /// 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")] #[cfg_attr(feature = "builder", builder(default))] pub metadata: serde_json::Value, /// The name of a native library the package is linking to. #[cfg_attr(feature = "builder", builder(default))] pub links: Option, /// List of registries to which this package may be published (derived from the [`publish` field](https://doc.rust-lang.org/cargo/reference/manifest.html#the-publish-field)). /// /// 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. #[cfg_attr(feature = "builder", builder(default))] pub publish: Option>, /// The [`default-run` field](https://doc.rust-lang.org/cargo/reference/manifest.html#the-default-run-field) as given in the `Cargo.toml` // (We say "given in" instead of "specified in" since the `default-run` key cannot be inherited from the workspace.) /// The default binary to run by `cargo run`. /// /// This is always `None` if running with a version of Cargo older than 1.55. #[cfg_attr(feature = "builder", builder(default))] pub default_run: Option, /// The [`rust-version` field](https://doc.rust-lang.org/cargo/reference/manifest.html#the-rust-version-field) as specified in the `Cargo.toml`. /// The minimum supported Rust version of this package. /// /// This is always `None` if running with a version of Cargo older than 1.58. #[serde(default)] #[serde(deserialize_with = "deserialize_rust_version")] #[cfg_attr(feature = "builder", builder(default))] pub rust_version: Option, } #[cfg(feature = "builder")] impl PackageBuilder { /// Construct a new `PackageBuilder` with all required fields. pub fn new( name: impl Into, version: impl Into, id: impl Into, path: impl Into, ) -> Self { Self::default() .name(name) .version(version) .id(id) .manifest_path(path) } } 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. /// /// 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. #[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq, Hash)] #[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 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. /// /// The possible values are `example`, `test`, `bench`, `custom-build` and /// [Cargo crate types](https://doc.rust-lang.org/cargo/reference/cargo-targets.html#the-crate-type-field): /// `bin`, `lib`, `rlib`, `dylib`, `cdylib`, `staticlib`, `proc-macro`. /// /// Other possible values may be added in the future. pub kind: Vec, /// Similar to `kind`, but only reports the /// [Cargo crate types](https://doc.rust-lang.org/cargo/reference/cargo-targets.html#the-crate-type-field): /// `bin`, `lib`, `rlib`, `dylib`, `cdylib`, `staticlib`, `proc-macro`. /// Everything that's not a proc macro or a library of some kind is reported as "bin". /// /// Other possible values may be added in the future. #[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, } macro_rules! methods_target_is_kind { ($($name:ident => $kind:expr),*) => { $( /// Return true if this target is of kind `$kind`. pub fn $name(&self) -> bool { self.is_kind($kind) } )* } } impl Target { /// Return true if this target is of the given kind. pub fn is_kind(&self, name: TargetKind) -> bool { self.kind.iter().any(|kind| kind == &name) } // Generate `is_*` methods for each `TargetKind` methods_target_is_kind! { is_lib => TargetKind::Lib, is_bin => TargetKind::Bin, is_example => TargetKind::Example, is_test => TargetKind::Test, is_bench => TargetKind::Bench, is_custom_build => TargetKind::CustomBuild, is_proc_macro => TargetKind::ProcMacro, is_cdylib => TargetKind::CDyLib, is_dylib => TargetKind::DyLib, is_rlib => TargetKind::RLib, is_staticlib => TargetKind::StaticLib } } /// Kind of target. /// /// The possible values are `example`, `test`, `bench`, `custom-build` and /// [Cargo crate types](https://doc.rust-lang.org/cargo/reference/cargo-targets.html#the-crate-type-field): /// `bin`, `lib`, `rlib`, `dylib`, `cdylib`, `staticlib`, `proc-macro`. /// /// Other possible values may be added in the future. #[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)] #[non_exhaustive] pub enum TargetKind { /// `cargo bench` target #[serde(rename = "bench")] Bench, /// Binary executable target #[serde(rename = "bin")] Bin, /// Custom build target #[serde(rename = "custom-build")] CustomBuild, /// Dynamic system library target #[serde(rename = "cdylib")] CDyLib, /// Dynamic Rust library target #[serde(rename = "dylib")] DyLib, /// Example target #[serde(rename = "example")] Example, /// Rust library #[serde(rename = "lib")] Lib, /// Procedural Macro #[serde(rename = "proc-macro")] ProcMacro, /// Rust library for use as an intermediate artifact #[serde(rename = "rlib")] RLib, /// Static system library #[serde(rename = "staticlib")] StaticLib, /// Test target #[serde(rename = "test")] Test, /// Unknown type #[serde(untagged)] Unknown(String), } impl From<&str> for TargetKind { fn from(value: &str) -> Self { match value { "example" => TargetKind::Example, "test" => TargetKind::Test, "bench" => TargetKind::Bench, "custom-build" => TargetKind::CustomBuild, "bin" => TargetKind::Bin, "lib" => TargetKind::Lib, "rlib" => TargetKind::RLib, "dylib" => TargetKind::DyLib, "cdylib" => TargetKind::CDyLib, "staticlib" => TargetKind::StaticLib, "proc-macro" => TargetKind::ProcMacro, x => TargetKind::Unknown(x.to_string()), } } } impl FromStr for TargetKind { type Err = std::convert::Infallible; fn from_str(s: &str) -> std::result::Result { Ok(TargetKind::from(s)) } } impl fmt::Display for TargetKind { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self::Bench => "bench".fmt(f), Self::Bin => "bin".fmt(f), Self::CustomBuild => "custom-build".fmt(f), Self::CDyLib => "cdylib".fmt(f), Self::DyLib => "dylib".fmt(f), Self::Example => "example".fmt(f), Self::Lib => "lib".fmt(f), Self::ProcMacro => "proc-macro".fmt(f), Self::RLib => "rlib".fmt(f), Self::StaticLib => "staticlib".fmt(f), Self::Test => "test".fmt(f), Self::Unknown(x) => x.fmt(f), } } } /// Similar to `kind`, but only reports the /// [Cargo crate types](https://doc.rust-lang.org/cargo/reference/cargo-targets.html#the-crate-type-field): /// /// `bin`, `lib`, `rlib`, `dylib`, `cdylib`, `staticlib`, `proc-macro`. /// Everything that's not a proc macro or a library of some kind is reported as "bin". /// /// Other possible values may be added in the future. #[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)] #[non_exhaustive] pub enum CrateType { /// Binary executable target #[serde(rename = "bin")] Bin, /// Dynamic system library target #[serde(rename = "cdylib")] CDyLib, /// Dynamic Rust library target #[serde(rename = "dylib")] DyLib, /// Rust library #[serde(rename = "lib")] Lib, /// Procedural Macro #[serde(rename = "proc-macro")] ProcMacro, /// Rust library for use as an intermediate artifact #[serde(rename = "rlib")] RLib, /// Static system library #[serde(rename = "staticlib")] StaticLib, /// Unkown type #[serde(untagged)] Unknown(String), } impl From<&str> for CrateType { fn from(value: &str) -> Self { match value { "bin" => CrateType::Bin, "lib" => CrateType::Lib, "rlib" => CrateType::RLib, "dylib" => CrateType::DyLib, "cdylib" => CrateType::CDyLib, "staticlib" => CrateType::StaticLib, "proc-macro" => CrateType::ProcMacro, x => CrateType::Unknown(x.to_string()), } } } impl FromStr for CrateType { type Err = std::convert::Infallible; fn from_str(s: &str) -> std::result::Result { Ok(CrateType::from(s)) } } impl fmt::Display for CrateType { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self::Bin => "bin".fmt(f), Self::CDyLib => "cdylib".fmt(f), Self::DyLib => "dylib".fmt(f), Self::Lib => "lib".fmt(f), Self::ProcMacro => "proc-macro".fmt(f), Self::RLib => "rlib".fmt(f), Self::StaticLib => "staticlib".fmt(f), Self::Unknown(x) => x.fmt(f), } } } /// 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, Hash, PartialOrd, Ord)] #[non_exhaustive] pub enum Edition { /// Edition 2015 #[serde(rename = "2015")] E2015, /// Edition 2018 #[serde(rename = "2018")] E2018, /// Edition 2021 #[serde(rename = "2021")] E2021, /// Edition 2024 #[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 configuring `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: BTreeMap, /// 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) } } /// As per the Cargo Book the [`rust-version` field](https://doc.rust-lang.org/cargo/reference/manifest.html#the-rust-version-field) must: /// /// > be a bare version number with two or three components; /// > it cannot include semver operators or pre-release identifiers. /// /// [`semver::Version`] however requires three components. This function takes /// care of appending `.0` if the provided version number only has two components /// and ensuring that it does not contain a pre-release version or build metadata. fn deserialize_rust_version<'de, D>( deserializer: D, ) -> std::result::Result, D::Error> where D: Deserializer<'de>, { let mut buf = match Option::::deserialize(deserializer)? { None => return Ok(None), Some(buf) => buf, }; for char in buf.chars() { if char == '-' { return Err(serde::de::Error::custom( "pre-release identifiers are not supported in rust-version", )); } else if char == '+' { return Err(serde::de::Error::custom( "build metadata is not supported in rust-version", )); } } if buf.matches('.').count() == 1 { // e.g. 1.0 -> 1.0.0 buf.push_str(".0"); } Ok(Some( Version::parse(&buf).map_err(serde::de::Error::custom)?, )) } #[cfg(test)] mod test { use semver::Version; #[derive(Debug, serde::Deserialize)] struct BareVersion( #[serde(deserialize_with = "super::deserialize_rust_version")] Option, ); fn bare_version(str: &str) -> Version { serde_json::from_str::(&format!(r#""{}""#, str)) .unwrap() .0 .unwrap() } fn bare_version_err(str: &str) -> String { serde_json::from_str::(&format!(r#""{}""#, str)) .unwrap_err() .to_string() } #[test] fn test_deserialize_rust_version() { assert_eq!(bare_version("1.2"), Version::new(1, 2, 0)); assert_eq!(bare_version("1.2.0"), Version::new(1, 2, 0)); assert_eq!( bare_version_err("1.2.0-alpha"), "pre-release identifiers are not supported in rust-version" ); assert_eq!( bare_version_err("1.2.0+123"), "build metadata is not supported in rust-version" ); } } cargo_metadata-0.19.2/src/libtest.rs000064400000000000000000000145341046102023000154620ustar 00000000000000//! Parses output of [libtest](https://github.com/rust-lang/rust/blob/master/library/test/src/formatters/json.rs). //! //! Since this module parses output in an unstable format, all structs in this module may change at any time, and are exempt from semver guarantees. use serde::{Deserialize, Serialize}; /// Suite related event #[derive(Debug, PartialEq, Deserialize, Serialize)] #[serde(tag = "event")] #[serde(rename_all = "lowercase")] /// Suite event pub enum SuiteEvent { /// emitted on the start of a test run, and the start of the doctests Started { /// number of tests in this suite test_count: usize, }, /// the suite has finished Ok { /// the number of tests that passed passed: usize, /// the number of tests that failed failed: usize, /// number of tests that were ignored ignored: usize, /// number of benchmarks run measured: usize, /// i think this is based on what you specify in the cargo test argument filtered_out: usize, /// how long the suite took to run exec_time: f32, }, /// the suite has at least one failing test Failed { /// the number of tests that passed passed: usize, /// the number of tests that failed failed: usize, /// number of tests that were ignored ignored: usize, /// i think its something to do with benchmarks? measured: usize, /// i think this is based on what you specify in the cargo test argument filtered_out: usize, /// how long the suite took to run exec_time: f32, }, } #[derive(Debug, PartialEq, Deserialize, Serialize)] #[serde(tag = "event")] #[serde(rename_all = "lowercase")] /// Test event pub enum TestEvent { /// a new test starts Started { /// the name of this test name: String, }, /// the test has finished Ok { /// which one name: String, /// in how long exec_time: f32, /// what did it say? stdout: Option, }, /// the test has failed Failed { /// which one name: String, /// in how long exec_time: f32, /// why? stdout: Option, /// it timed out? reason: Option, /// what message message: Option, }, /// the test has been ignored Ignored { /// which one name: String, }, /// the test has timed out Timeout { /// which one name: String, }, } impl TestEvent { /// Get the name of this test pub fn name(&self) -> &str { let (Self::Started { name } | Self::Ok { name, .. } | Self::Ignored { name } | Self::Failed { name, .. } | Self::Timeout { name }) = self; name } /// Get the stdout of this test, if available. pub fn stdout(&self) -> Option<&str> { match self { Self::Ok { stdout, .. } | Self::Failed { stdout, .. } => stdout.as_deref(), _ => None, } } } #[derive(Debug, PartialEq, Deserialize, Serialize)] /// Represents the output of `cargo test -- -Zunstable-options --report-time --show-output --format json`. /// /// requires --report-time /// /// # Stability /// /// As this struct is for interfacing with the unstable libtest json output, this struct may change at any time, without semver guarantees. #[serde(tag = "type")] #[serde(rename_all = "lowercase")] pub enum TestMessage { /// suite related message Suite(SuiteEvent), /// test related message Test(TestEvent), /// bench related message Bench { /// name of benchmark name: String, /// distribution median: f32, /// deviation deviation: f32, /// thruput in MiB per second mib_per_second: Option, }, } #[test] fn deser() { macro_rules! run { ($($input:literal parses to $output:expr),+) => { $(assert_eq!(dbg!(serde_json::from_str::($input)).unwrap(), $output);)+ }; } run![ r#"{ "type": "suite", "event": "started", "test_count": 2 }"# parses to TestMessage::Suite(SuiteEvent::Started { test_count: 2 }), r#"{ "type": "test", "event": "started", "name": "fail" }"# parses to TestMessage::Test(TestEvent::Started { name: "fail".into() }), r#"{ "type": "test", "name": "fail", "event": "ok", "exec_time": 0.000003428, "stdout": "hello world" }"# parses to TestMessage::Test(TestEvent::Ok { name: "fail".into(), exec_time: 0.000003428, stdout: Some("hello world".into()) }), r#"{ "type": "test", "event": "started", "name": "nope" }"# parses to TestMessage::Test(TestEvent::Started { name: "nope".into() }), r#"{ "type": "test", "name": "nope", "event": "ignored" }"# parses to TestMessage::Test(TestEvent::Ignored { name: "nope".into() }), r#"{ "type": "suite", "event": "ok", "passed": 1, "failed": 0, "ignored": 1, "measured": 0, "filtered_out": 0, "exec_time": 0.000684028 }"# parses to TestMessage::Suite(SuiteEvent::Ok { passed: 1, failed: 0, ignored: 1, measured: 0, filtered_out: 0, exec_time: 0.000684028 }) ]; run![ r#"{ "type": "suite", "event": "started", "test_count": 2 }"# parses to TestMessage::Suite(SuiteEvent::Started { test_count: 2 }), r#"{ "type": "test", "event": "started", "name": "fail" }"# parses to TestMessage::Test(TestEvent::Started { name: "fail".into() }), r#"{ "type": "test", "event": "started", "name": "benc" }"# parses to TestMessage::Test(TestEvent::Started { name: "benc".into() }), r#"{ "type": "bench", "name": "benc", "median": 0, "deviation": 0 }"# parses to TestMessage::Bench { name: "benc".into(), median: 0., deviation: 0., mib_per_second: None }, r#"{ "type": "test", "name": "fail", "event": "failed", "exec_time": 0.000081092, "stdout": "thread 'fail' panicked" }"# parses to TestMessage::Test(TestEvent::Failed { name: "fail".into(), exec_time: 0.000081092, stdout: Some("thread 'fail' panicked".into()), reason: None, message: None} ), r#"{ "type": "suite", "event": "failed", "passed": 0, "failed": 1, "ignored": 0, "measured": 1, "filtered_out": 0, "exec_time": 0.000731068 }"# parses to TestMessage::Suite(SuiteEvent::Failed { passed: 0, failed: 1, ignored: 0, measured: 1, filtered_out: 0, exec_time: 0.000731068 }) ]; } cargo_metadata-0.19.2/src/messages.rs000064400000000000000000000243271046102023000156240ustar 00000000000000use super::{Diagnostic, PackageId, Target}; use camino::Utf8PathBuf; #[cfg(feature = "builder")] use derive_builder::Builder; use serde::{de, ser, Deserialize, Serialize}; use std::fmt::{self, Write}; 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 kind of debug information. #[serde(default)] pub debuginfo: ArtifactDebuginfo, /// 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, } /// The kind of debug information included in the artifact. #[derive(Debug, Clone, PartialEq, Eq, Hash, Default)] #[non_exhaustive] pub enum ArtifactDebuginfo { /// No debug information. #[default] None, /// Line directives only. LineDirectivesOnly, /// Line tables only. LineTablesOnly, /// Debug information without type or variable-level information. Limited, /// Full debug information. Full, /// An unknown integer level. /// /// This may be produced by a version of rustc in the future that has /// additional levels represented by an integer that are not known by this /// version of `cargo_metadata`. UnknownInt(i64), /// An unknown string level. /// /// This may be produced by a version of rustc in the future that has /// additional levels represented by a string that are not known by this /// version of `cargo_metadata`. UnknownString(String), } impl ser::Serialize for ArtifactDebuginfo { fn serialize(&self, serializer: S) -> Result where S: ser::Serializer, { match self { Self::None => 0.serialize(serializer), Self::LineDirectivesOnly => "line-directives-only".serialize(serializer), Self::LineTablesOnly => "line-tables-only".serialize(serializer), Self::Limited => 1.serialize(serializer), Self::Full => 2.serialize(serializer), Self::UnknownInt(n) => n.serialize(serializer), Self::UnknownString(s) => s.serialize(serializer), } } } impl<'de> de::Deserialize<'de> for ArtifactDebuginfo { fn deserialize(d: D) -> Result where D: de::Deserializer<'de>, { struct Visitor; impl de::Visitor<'_> for Visitor { type Value = ArtifactDebuginfo; fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { formatter.write_str("an integer or string") } fn visit_i64(self, value: i64) -> Result where E: de::Error, { let debuginfo = match value { 0 => ArtifactDebuginfo::None, 1 => ArtifactDebuginfo::Limited, 2 => ArtifactDebuginfo::Full, n => ArtifactDebuginfo::UnknownInt(n), }; Ok(debuginfo) } fn visit_u64(self, value: u64) -> Result where E: de::Error, { self.visit_i64(value as i64) } fn visit_str(self, value: &str) -> Result where E: de::Error, { let debuginfo = match value { "none" => ArtifactDebuginfo::None, "limited" => ArtifactDebuginfo::Limited, "full" => ArtifactDebuginfo::Full, "line-directives-only" => ArtifactDebuginfo::LineDirectivesOnly, "line-tables-only" => ArtifactDebuginfo::LineTablesOnly, s => ArtifactDebuginfo::UnknownString(s.to_string()), }; Ok(debuginfo) } fn visit_unit(self) -> Result where E: de::Error, { Ok(ArtifactDebuginfo::None) } } d.deserialize_any(Visitor) } } impl fmt::Display for ArtifactDebuginfo { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { ArtifactDebuginfo::None => f.write_char('0'), ArtifactDebuginfo::Limited => f.write_char('1'), ArtifactDebuginfo::Full => f.write_char('2'), ArtifactDebuginfo::LineDirectivesOnly => f.write_str("line-directives-only"), ArtifactDebuginfo::LineTablesOnly => f.write_str("line-tables-only"), ArtifactDebuginfo::UnknownInt(n) => write!(f, "{}", n), ArtifactDebuginfo::UnknownString(s) => f.write_str(s), } } } /// 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.19.2/tests/selftest.rs000064400000000000000000000117511046102023000162160ustar 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".into()); assert_eq!(lib.crate_types[0], "lib".into()); let selftest = this.targets.iter().find(|t| t.name == "selftest").unwrap(); assert_eq!(selftest.name, "selftest"); assert_eq!(selftest.kind[0], "test".into()); assert_eq!(selftest.crate_types[0], "bin".into()); 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".into()); assert_eq!(lib.crate_types[0], "lib".into()); let selftest = this.targets.iter().find(|t| t.name == "selftest").unwrap(); assert_eq!(selftest.name, "selftest"); assert_eq!(selftest.kind[0], "test".into()); assert_eq!(selftest.crate_types[0], "bin".into()); 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())); } #[test] fn workspace_default_packages() { let metadata = MetadataCommand::new() .manifest_path("Cargo.toml") .exec() .unwrap(); let workspace_packages = metadata.workspace_packages(); // this will only trigger on cargo versions that expose // workspace_default_members (that is, cargo >= 1.71) if metadata.workspace_default_members.is_available() { let default_packages = metadata.workspace_default_packages(); assert_eq!(default_packages, workspace_packages); } } cargo_metadata-0.19.2/tests/test_samples.rs000064400000000000000000000714451046102023000170760ustar 00000000000000extern crate cargo_metadata; extern crate semver; #[macro_use] extern crate serde_json; use camino::Utf8PathBuf; use cargo_metadata::{ ArtifactDebuginfo, CargoOpt, DependencyKind, Edition, Message, Metadata, MetadataCommand, }; /// Output from oldest version ever supported (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 const JSON_OLD_MINIMAL: &str = 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" } "#; #[test] fn old_minimal() { let meta: Metadata = serde_json::from_str(JSON_OLD_MINIMAL).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".into()]); assert_eq!(target.crate_types, vec!["bin".into()]); 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"); assert!(!meta.workspace_default_members.is_available()); assert!(meta.workspace_default_members.is_missing()); let serialized = serde_json::to_value(meta).unwrap(); assert!(!serialized .as_object() .unwrap() .contains_key("workspace_default_members")); } 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 // workspace_default_members added in 1.71 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.clone()).unwrap(), WorkspaceMetadata { testobject: TestObject { myvalue: "abc".to_string() } } ); assert_eq!(meta.workspace_members.len(), 1); assert!(meta.workspace_members[0].to_string().contains("all")); if ver >= semver::Version::parse("1.71.0").unwrap() { assert_eq!(&*meta.workspace_default_members, &meta.workspace_members); } 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().contains("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::Version::parse("1.56.0").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".into(), "rlib".into(), "staticlib".into()] ); assert_eq!( sorted!(lib.crate_types), vec!["cdylib".into(), "rlib".into(), "staticlib".into()] ); 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".into()]); assert_eq!(main.kind, vec!["bin".into()]); 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".into()]); assert!(!ex1.test); let t1 = get_file_name!("t1.rs"); assert_eq!(t1.kind, vec!["test".into()]); let b1 = get_file_name!("b1.rs"); assert_eq!(b1.kind, vec!["bench".into()]); let build = get_file_name!("build.rs"); assert_eq!(build.kind, vec!["custom-build".into()]); 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().contains("all")); assert_eq!(resolve.nodes.len(), 9); let path_dep = resolve .nodes .iter() .find(|n| n.id.to_string().contains("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().contains("bitflags")) .unwrap(); assert_eq!(bitflags.features, vec!["default"]); let featdep = resolve .nodes .iter() .find(|n| n.id.to_string().contains("featdep")) .unwrap(); assert_eq!(featdep.features, vec!["i128"]); let all = resolve .nodes .iter() .find(|n| n.id.to_string().contains("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().contains("oldname")); // Note the underscore here. let path_dep = all.deps.iter().find(|d| d.name == "path_dep").unwrap(); assert!(path_dep.pkg.to_string().contains("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().contains("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()) ); let serialized = serde_json::to_value(meta).unwrap(); if ver >= semver::Version::parse("1.71.0").unwrap() { assert!(serialized.as_object().unwrap()["workspace_default_members"] .as_array() .is_some()); } else { assert!(!serialized .as_object() .unwrap() .contains_key("workspace_default_members")); } } #[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.features.is_empty()) .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" ); } #[test] fn debuginfo_variants() { // Checks behavior for the different debuginfo variants. let variants = [ ("0", ArtifactDebuginfo::None), ("1", ArtifactDebuginfo::Limited), ("2", ArtifactDebuginfo::Full), ( "\"line-directives-only\"", ArtifactDebuginfo::LineDirectivesOnly, ), ("\"line-tables-only\"", ArtifactDebuginfo::LineTablesOnly), ("3", ArtifactDebuginfo::UnknownInt(3)), ( "\"abc\"", ArtifactDebuginfo::UnknownString("abc".to_string()), ), ("null", ArtifactDebuginfo::None), ]; for (value, expected) in variants { let s = r#"{"reason":"compiler-artifact","package_id":"cargo_metadata 0.16.0 (path+file:////cargo_metadata)","manifest_path":"/cargo_metadata/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"cargo_metadata","src_path":"/cargo_metadata/src/lib.rs","edition":"2018","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":DEBUGINFO,"debug_assertions":true,"overflow_checks":true,"test":false},"features":["default"],"filenames":["/cargo_metadata/target/debug/deps/libcargo_metadata-27f582f7187b9a2c.rmeta"],"executable":null,"fresh":false}"#; let message: Message = serde_json::from_str(&s.replace("DEBUGINFO", value)).unwrap(); match message { Message::CompilerArtifact(artifact) => { assert_eq!(artifact.profile.debuginfo, expected); let de_s = serde_json::to_string(&artifact.profile.debuginfo).unwrap(); // Note: Roundtrip does not retain null value. if value == "null" { assert_eq!(artifact.profile.debuginfo.to_string(), "0"); assert_eq!(de_s, "0"); } else { assert_eq!( artifact.profile.debuginfo.to_string(), value.trim_matches('"') ); assert_eq!(de_s, value); } } _ => panic!("unexpected {:?}", message), } } } #[test] #[should_panic = "WorkspaceDefaultMembers should only be dereferenced on Cargo versions >= 1.71"] fn missing_workspace_default_members() { let meta: Metadata = serde_json::from_str(JSON_OLD_MINIMAL).unwrap(); let _ = &*meta.workspace_default_members; } #[test] fn workspace_default_members_is_available() { // generated with cargo +1.71.0 metadata --format-version 1 let json = r#" { "packages": [ { "name": "basic", "version": "0.1.0", "id": "basic 0.1.0 (path+file:///example)", "license": null, "license_file": null, "description": null, "source": null, "dependencies": [], "targets": [ { "kind": [ "lib" ], "crate_types": [ "lib" ], "name": "basic", "src_path": "/example/src/lib.rs", "edition": "2021", "doc": true, "doctest": true, "test": true } ], "features": {}, "manifest_path": "/example/Cargo.toml", "metadata": null, "publish": null, "authors": [], "categories": [], "keywords": [], "readme": null, "repository": null, "homepage": null, "documentation": null, "edition": "2021", "links": null, "default_run": null, "rust_version": null } ], "workspace_members": [ "basic 0.1.0 (path+file:///example)" ], "workspace_default_members": [ "basic 0.1.0 (path+file:///example)" ], "resolve": { "nodes": [ { "id": "basic 0.1.0 (path+file:///example)", "dependencies": [], "deps": [], "features": [] } ], "root": "basic 0.1.0 (path+file:///example)" }, "target_directory": "/example/target", "version": 1, "workspace_root": "/example", "metadata": null } "#; let meta: Metadata = serde_json::from_str(json).unwrap(); assert!(meta.workspace_default_members.is_available()); assert!(!meta.workspace_default_members.is_missing()); } #[test] fn workspace_default_members_is_missing() { // generated with cargo +1.70.0 metadata --format-version 1 let json = r#" { "packages": [ { "name": "basic", "version": "0.1.0", "id": "basic 0.1.0 (path+file:///example)", "license": null, "license_file": null, "description": null, "source": null, "dependencies": [], "targets": [ { "kind": [ "lib" ], "crate_types": [ "lib" ], "name": "basic", "src_path": "/example/src/lib.rs", "edition": "2021", "doc": true, "doctest": true, "test": true } ], "features": {}, "manifest_path": "/example/Cargo.toml", "metadata": null, "publish": null, "authors": [], "categories": [], "keywords": [], "readme": null, "repository": null, "homepage": null, "documentation": null, "edition": "2021", "links": null, "default_run": null, "rust_version": null } ], "workspace_members": [ "basic 0.1.0 (path+file:///example)" ], "resolve": { "nodes": [ { "id": "basic 0.1.0 (path+file:///example)", "dependencies": [], "deps": [], "features": [] } ], "root": "basic 0.1.0 (path+file:///example)" }, "target_directory": "/example/target", "version": 1, "workspace_root": "/example", "metadata": null } "#; let meta: Metadata = serde_json::from_str(json).unwrap(); assert!(!meta.workspace_default_members.is_available()); assert!(meta.workspace_default_members.is_missing()); } #[test] fn test_unknown_target_kind_and_crate_type() { // Both kind and crate_type set to a type not yet known let json = r#" { "packages": [ { "name": "alt", "version": "0.1.0", "id": "alt 0.1.0 (path+file:///alt)", "source": null, "dependencies": [], "targets": [ { "kind": [ "future-kind" ], "crate_types": [ "future-type" ], "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); assert_eq!(meta.packages[0].targets.len(), 1); let target = &meta.packages[0].targets[0]; assert_eq!(target.kind[0], "future-kind".into()); assert_eq!(target.crate_types[0], "future-type".into()); }