├── .github └── workflows │ ├── main.yml │ └── release.yml ├── .gitignore ├── CHANGELOG.md ├── Cargo.toml ├── LICENSE-MIT ├── README.md ├── clippy.toml ├── src ├── dependency.rs ├── diagnostic.rs ├── errors.rs ├── lib.rs ├── libtest.rs └── messages.rs └── tests ├── all ├── Cargo.lock ├── Cargo.toml ├── bare-rust-version │ ├── Cargo.toml │ └── src │ │ └── lib.rs ├── bdep │ ├── Cargo.toml │ └── src │ │ └── lib.rs ├── benches │ └── b1.rs ├── build.rs ├── devdep │ ├── Cargo.toml │ └── src │ │ └── lib.rs ├── examples │ └── ex1.rs ├── featdep │ ├── Cargo.toml │ └── src │ │ └── lib.rs ├── namedep │ ├── Cargo.toml │ └── src │ │ └── lib.rs ├── oldname │ ├── Cargo.toml │ └── src │ │ └── lib.rs ├── path-dep │ ├── Cargo.toml │ └── src │ │ └── lib.rs ├── src │ ├── bin │ │ ├── otherbin.rs │ │ ├── reqfeat.rs │ │ └── reqfeat_slash.rs │ ├── lib.rs │ └── main.rs ├── tests │ └── t1.rs └── windep │ ├── Cargo.toml │ └── src │ └── lib.rs ├── basic_workspace ├── Cargo.toml ├── ex_lib │ ├── Cargo.toml │ └── src │ │ └── lib.rs └── src │ └── main.rs ├── selftest.rs └── test_samples.rs /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: [push, pull_request] 3 | 4 | jobs: 5 | rustfmt: 6 | name: rustfmt 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: actions/checkout@v2 10 | - name: Install rust 11 | run: rustup update --no-self-update stable && rustup default stable 12 | - name: Check formatting 13 | run: cargo fmt -- --check 14 | 15 | clippy: 16 | name: clippy 17 | runs-on: ubuntu-latest 18 | steps: 19 | - uses: actions/checkout@v2 20 | - name: Install rust 21 | run: rustup update --no-self-update stable && rustup default stable 22 | - name: Clippy check 23 | run: cargo clippy --all-features -- -Dwarnings 24 | 25 | test: 26 | name: Test 27 | runs-on: ubuntu-latest 28 | strategy: 29 | matrix: 30 | include: 31 | - rust: stable 32 | - rust: beta 33 | - rust: nightly 34 | - rust: 1.82.0 35 | steps: 36 | - uses: actions/checkout@v2 37 | - name: Install rust 38 | run: rustup update --no-self-update ${{ matrix.rust }} && rustup default ${{ matrix.rust }} 39 | - name: Run tests 40 | run: | 41 | cargo build --verbose 42 | cargo build --verbose --no-default-features 43 | cargo test --verbose 44 | cargo test --verbose --no-default-features 45 | cargo test --verbose --all-features 46 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release new version 2 | 3 | on: 4 | workflow_dispatch: 5 | secrets: 6 | CARGO_REGISTRY_TOKEN: 7 | required: true 8 | 9 | env: 10 | RUST_BACKTRACE: 1 11 | CARGO_TERM_COLOR: always 12 | 13 | jobs: 14 | create-release: 15 | name: Create release 16 | runs-on: ubuntu-latest 17 | if: github.ref == 'refs/heads/main' 18 | steps: 19 | - name: Checkout 20 | uses: actions/checkout@v3 21 | with: 22 | persist-credentials: true 23 | 24 | - name: Install rust 25 | uses: actions-rs/toolchain@v1 26 | with: 27 | toolchain: stable 28 | profile: minimal 29 | override: true 30 | 31 | - uses: Swatinem/rust-cache@v2 32 | 33 | # Determine which version we're about to publish, so we can tag it appropriately. 34 | # If the tag already exists, then we've already published this version. 35 | - name: Determine current version 36 | id: version-check 37 | run: | 38 | # Fail on first error, on undefined variables, and on errors in pipes. 39 | set -euo pipefail 40 | export VERSION="$(cargo metadata --format-version 1 | \ 41 | jq --arg crate_name cargo_metadata --exit-status -r \ 42 | '.packages[] | select(.name == $crate_name) | .version')" 43 | echo "version=$VERSION" >> $GITHUB_OUTPUT 44 | if [[ "$(git tag -l "$VERSION")" != '' ]]; then 45 | echo "Aborting: Version $VERSION is already published, we found its tag in the repo." 46 | exit 1 47 | fi 48 | 49 | - name: Semver-check 50 | uses: obi1kenobi/cargo-semver-checks-action@v2 51 | with: 52 | rust-toolchain: manual # we've already installed Rust, don't install a new one 53 | 54 | - name: Publish 55 | run: cargo publish 56 | env: 57 | CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }} 58 | 59 | - name: Tag the version 60 | run: | 61 | # Fail on first error, on undefined variables, and on errors in pipes. 62 | set -euo pipefail 63 | git tag "${{ steps.version-check.outputs.version }}" 64 | git push origin "${{ steps.version-check.outputs.version }}" 65 | 66 | - uses: taiki-e/create-gh-release-action@v1 67 | name: Create GitHub release 68 | with: 69 | branch: main 70 | ref: refs/tags/${{ steps.version-check.outputs.version }} 71 | env: 72 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 73 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | Cargo.lock 2 | .idea/ 3 | target 4 | temp/ 5 | tmp/ 6 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## Unreleased 4 | 5 | - n/a 6 | 7 | ### Added 8 | 9 | - Added `pub fn env_remove>(&mut self, key: K) -> &mut MetadataCommand` to `MetadataCommand`. 10 | 11 | ### Changed 12 | 13 | - Updated dependencies: 14 | - `thiserror` from `1.0.31` to `2.0.3` 15 | - `derive_builder` from `0.12` to `0.20` 16 | - Made `Dependency`'s `source` member the same type as `Package`'s `source` member: `Option`. 17 | 18 | ### Removed 19 | 20 | - n/a 21 | 22 | ### Fixed 23 | 24 | - n/a 25 | 26 | ## [0.19.0] - 2024-11-20 27 | 28 | ### Added 29 | 30 | - Re-exported `semver` crate directly. 31 | - Added implementation of `std::ops::Index<&PackageId>` for `Resolve`. 32 | - Added `pub fn is_kind(&self, name: TargetKind) -> bool` to `Target`. 33 | - Added derived implementations of `PartialEq`, `Eq` and `Hash` for `Metadata` and its members' types. 34 | - Added default fields to `PackageBuilder`. 35 | - Added `pub fn new(name:version:id:path:) -> Self` to `PackageBuilder` for providing all required fields upfront. 36 | 37 | ### Changed 38 | 39 | - Bumped MSRV from `1.42.0` to `1.56.0`. 40 | - Made `parse_stream` more versatile by accepting anything that implements `Read`. 41 | - Converted `TargetKind` and `CrateType` to an enum representation. 42 | 43 | ### Removed 44 | 45 | - Removed re-exports for `BuildMetadata` and `Prerelease` from `semver` crate. 46 | - 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(…)`). 47 | 48 | ### Fixed 49 | 50 | - Added missing `manifest_path` field to `Artifact`. Fixes #187. 51 | 52 | ## [0.15.0] - 2022-06-22 53 | 54 | ### Added 55 | 56 | - Re-exported `BuildMetadata` and `Prerelease` from `semver` crate. 57 | - Added `workspace_packages` function. 58 | - Added `Edition` enum to better parse edition field. 59 | - Added `rust-version` field to Cargo manifest. 60 | 61 | ### Changed 62 | 63 | - Bumped msrv from `1.40.0` to `1.42.0`. 64 | 65 | ### Internal Changes 66 | 67 | - Updated `derive_builder` to the latest version. 68 | - Made use of `matches!` macros where possible. 69 | - Fixed some tests 70 | 71 | ## [0.15.1] - 2022-10-13 72 | 73 | ### Added 74 | 75 | - Added `TestMessage`, `TestEvent`, `SuiteEvent` for parsing the `cargo test -- --format json` output. 76 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "cargo_metadata" 3 | version = "0.20.0" 4 | authors = ["Oliver Schneider "] 5 | repository = "https://github.com/oli-obk/cargo_metadata" 6 | description = "structured access to the output of `cargo metadata`" 7 | license = "MIT" 8 | readme = "README.md" 9 | edition = "2021" 10 | rust-version = "1.82.0" 11 | 12 | [dependencies] 13 | camino = { version = "1.0.7", features = ["serde1"] } 14 | cargo-platform = "0.2.0" 15 | cargo-util-schemas = "0.2.0" 16 | derive_builder = { version = "0.20", optional = true } 17 | semver = { version = "1.0.7", features = ["serde"] } 18 | serde = { version = "1.0.136", features = ["derive"] } 19 | serde_json = { version = "1.0.118", features = ["unbounded_depth"] } 20 | thiserror = "2.0.3" 21 | 22 | [features] 23 | default = [] 24 | builder = ["derive_builder"] 25 | unstable = [] 26 | 27 | [package.metadata.docs.rs] 28 | all-features = true 29 | 30 | [package.metadata.cargo_metadata_test] 31 | some_field = true 32 | other_field = "foo" 33 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Permission is hereby granted, free of charge, to any 2 | person obtaining a copy of this software and associated 3 | documentation files (the "Software"), to deal in the 4 | Software without restriction, including without 5 | limitation the rights to use, copy, modify, merge, 6 | publish, distribute, sublicense, and/or sell copies of 7 | the Software, and to permit persons to whom the Software 8 | is furnished to do so, subject to the following 9 | conditions: 10 | 11 | The above copyright notice and this permission notice 12 | shall be included in all copies or substantial portions 13 | of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 16 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 17 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 18 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 19 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 22 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 23 | DEALINGS IN THE SOFTWARE. 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # cargo_metadata 2 | 3 | Structured access to the output of `cargo metadata`. Usually used from within a `cargo-*` executable. 4 | 5 | Also supports serialization to aid in implementing `--message-format=json`-like 6 | output generation in `cargo-*` subcommands, since some of the types in what 7 | `cargo --message-format=json` emits are exactly the same as the ones from `cargo metadata`. 8 | 9 | [![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) 10 | [![crates.io](https://img.shields.io/crates/v/cargo_metadata.svg)](https://crates.io/crates/cargo_metadata) 11 | 12 | [Documentation](https://docs.rs/cargo_metadata/) 13 | -------------------------------------------------------------------------------- /clippy.toml: -------------------------------------------------------------------------------- 1 | disallowed-types = [ 2 | { 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." } 3 | ] -------------------------------------------------------------------------------- /src/dependency.rs: -------------------------------------------------------------------------------- 1 | //! This module contains `Dependency` and the types/functions it uses for deserialization. 2 | 3 | use std::fmt; 4 | 5 | use camino::Utf8PathBuf; 6 | #[cfg(feature = "builder")] 7 | use derive_builder::Builder; 8 | use semver::VersionReq; 9 | use serde::{Deserialize, Deserializer, Serialize}; 10 | 11 | use crate::Source; 12 | 13 | #[derive(Eq, PartialEq, Clone, Debug, Copy, Hash, Serialize, Deserialize, Default)] 14 | /// Dependencies can come in three kinds 15 | pub enum DependencyKind { 16 | #[serde(rename = "normal")] 17 | #[default] 18 | /// The 'normal' kind 19 | Normal, 20 | #[serde(rename = "dev")] 21 | /// Those used in tests only 22 | Development, 23 | #[serde(rename = "build")] 24 | /// Those used in build scripts only 25 | Build, 26 | #[doc(hidden)] 27 | #[serde(other)] 28 | Unknown, 29 | } 30 | 31 | impl fmt::Display for DependencyKind { 32 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 33 | let s = serde_json::to_string(self).unwrap(); 34 | // skip opening and closing quotes 35 | f.write_str(&s[1..s.len() - 1]) 36 | } 37 | } 38 | 39 | /// The `kind` can be `null`, which is interpreted as the default - `Normal`. 40 | pub(super) fn parse_dependency_kind<'de, D>(d: D) -> Result 41 | where 42 | D: Deserializer<'de>, 43 | { 44 | Deserialize::deserialize(d).map(|x: Option<_>| x.unwrap_or_default()) 45 | } 46 | 47 | #[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq, Hash)] 48 | #[cfg_attr(feature = "builder", derive(Builder))] 49 | #[non_exhaustive] 50 | #[cfg_attr(feature = "builder", builder(pattern = "owned", setter(into)))] 51 | /// A dependency of the main crate 52 | pub struct Dependency { 53 | /// Name as given in the `Cargo.toml` 54 | pub name: String, 55 | /// The source of dependency 56 | pub source: Option, 57 | /// The required version 58 | pub req: VersionReq, 59 | /// The kind of dependency this is 60 | #[serde(deserialize_with = "parse_dependency_kind")] 61 | pub kind: DependencyKind, 62 | /// Whether this dependency is required or optional 63 | pub optional: bool, 64 | /// Whether the default features in this dependency are used. 65 | pub uses_default_features: bool, 66 | /// The list of features enabled for this dependency. 67 | pub features: Vec, 68 | /// The target this dependency is specific to. 69 | /// 70 | /// Use the [`Display`] trait to access the contents. 71 | /// 72 | /// [`Display`]: std::fmt::Display 73 | pub target: Option, 74 | /// If the dependency is renamed, this is the new name for the dependency 75 | /// as a string. None if it is not renamed. 76 | pub rename: Option, 77 | /// The URL of the index of the registry where this dependency is from. 78 | /// 79 | /// If None, the dependency is from crates.io. 80 | pub registry: Option, 81 | /// The file system path for a local path dependency. 82 | /// 83 | /// Only produced on cargo 1.51+ 84 | pub path: Option, 85 | } 86 | 87 | pub use cargo_platform::Platform; 88 | -------------------------------------------------------------------------------- /src/diagnostic.rs: -------------------------------------------------------------------------------- 1 | //! This module contains `Diagnostic` and the types/functions it uses for deserialization. 2 | 3 | #[cfg(feature = "builder")] 4 | use derive_builder::Builder; 5 | use serde::{Deserialize, Serialize}; 6 | use std::fmt; 7 | 8 | /// The error code associated to this diagnostic. 9 | #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)] 10 | #[cfg_attr(feature = "builder", derive(Builder))] 11 | #[non_exhaustive] 12 | #[cfg_attr(feature = "builder", builder(pattern = "owned", setter(into)))] 13 | pub struct DiagnosticCode { 14 | /// The code itself. 15 | pub code: String, 16 | /// An explanation for the code 17 | pub explanation: Option, 18 | } 19 | 20 | /// A line of code associated with the Diagnostic 21 | #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)] 22 | #[cfg_attr(feature = "builder", derive(Builder))] 23 | #[non_exhaustive] 24 | #[cfg_attr(feature = "builder", builder(pattern = "owned", setter(into)))] 25 | pub struct DiagnosticSpanLine { 26 | /// The line of code associated with the error 27 | pub text: String, 28 | /// Start of the section of the line to highlight. 1-based, character offset in self.text 29 | pub highlight_start: usize, 30 | /// End of the section of the line to highlight. 1-based, character offset in self.text 31 | pub highlight_end: usize, 32 | } 33 | 34 | /// Macro expansion information associated with a diagnostic. 35 | #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)] 36 | #[cfg_attr(feature = "builder", derive(Builder))] 37 | #[non_exhaustive] 38 | #[cfg_attr(feature = "builder", builder(pattern = "owned", setter(into)))] 39 | pub struct DiagnosticSpanMacroExpansion { 40 | /// span where macro was applied to generate this code; note that 41 | /// this may itself derive from a macro (if 42 | /// `span.expansion.is_some()`) 43 | pub span: DiagnosticSpan, 44 | 45 | /// name of macro that was applied (e.g., "foo!" or "#[derive(Eq)]") 46 | pub macro_decl_name: String, 47 | 48 | /// span where macro was defined (if known) 49 | pub def_site_span: Option, 50 | } 51 | 52 | /// A section of the source code associated with a Diagnostic 53 | #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)] 54 | #[cfg_attr(feature = "builder", derive(Builder))] 55 | #[non_exhaustive] 56 | #[cfg_attr(feature = "builder", builder(pattern = "owned", setter(into)))] 57 | pub struct DiagnosticSpan { 58 | /// The file name or the macro name this diagnostic comes from. 59 | pub file_name: String, 60 | /// The byte offset in the file where this diagnostic starts from. 61 | pub byte_start: u32, 62 | /// The byte offset in the file where this diagnostic ends. 63 | pub byte_end: u32, 64 | /// 1-based. The line in the file. 65 | pub line_start: usize, 66 | /// 1-based. The line in the file. 67 | pub line_end: usize, 68 | /// 1-based, character offset. 69 | pub column_start: usize, 70 | /// 1-based, character offset. 71 | pub column_end: usize, 72 | /// Is this a "primary" span -- meaning the point, or one of the points, 73 | /// where the error occurred? 74 | /// 75 | /// There are rare cases where multiple spans are marked as primary, 76 | /// e.g. "immutable borrow occurs here" and "mutable borrow ends here" can 77 | /// be two separate spans both "primary". Top (parent) messages should 78 | /// always have at least one primary span, unless it has 0 spans. Child 79 | /// messages may have 0 or more primary spans. 80 | pub is_primary: bool, 81 | /// Source text from the start of line_start to the end of line_end. 82 | pub text: Vec, 83 | /// Label that should be placed at this location (if any) 84 | pub label: Option, 85 | /// If we are suggesting a replacement, this will contain text 86 | /// that should be sliced in atop this span. 87 | pub suggested_replacement: Option, 88 | /// If the suggestion is approximate 89 | pub suggestion_applicability: Option, 90 | /// Macro invocations that created the code at this span, if any. 91 | pub expansion: Option>, 92 | } 93 | 94 | /// Whether a suggestion can be safely applied. 95 | #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)] 96 | #[non_exhaustive] 97 | pub enum Applicability { 98 | /// The suggested replacement can be applied automatically safely 99 | MachineApplicable, 100 | /// The suggested replacement has placeholders that will need to be manually 101 | /// replaced. 102 | HasPlaceholders, 103 | /// The suggested replacement may be incorrect in some circumstances. Needs 104 | /// human review. 105 | MaybeIncorrect, 106 | /// The suggested replacement will probably not work. 107 | Unspecified, 108 | } 109 | 110 | /// The diagnostic level 111 | #[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Hash)] 112 | #[non_exhaustive] 113 | #[serde(rename_all = "lowercase")] 114 | pub enum DiagnosticLevel { 115 | /// Internal compiler error 116 | #[serde(rename = "error: internal compiler error")] 117 | Ice, 118 | /// Error 119 | Error, 120 | /// Warning 121 | Warning, 122 | /// Failure note 123 | #[serde(rename = "failure-note")] 124 | FailureNote, 125 | /// Note 126 | Note, 127 | /// Help 128 | Help, 129 | } 130 | 131 | /// A diagnostic message generated by rustc 132 | #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)] 133 | #[cfg_attr(feature = "builder", derive(Builder))] 134 | #[non_exhaustive] 135 | #[cfg_attr(feature = "builder", builder(pattern = "owned", setter(into)))] 136 | pub struct Diagnostic { 137 | /// The error message of this diagnostic. 138 | pub message: String, 139 | /// The associated error code for this diagnostic 140 | pub code: Option, 141 | /// "error: internal compiler error", "error", "warning", "note", "help" 142 | pub level: DiagnosticLevel, 143 | /// A list of source code spans this diagnostic is associated with. 144 | pub spans: Vec, 145 | /// Associated diagnostic messages. 146 | pub children: Vec, 147 | /// The message as rustc would render it 148 | pub rendered: Option, 149 | } 150 | 151 | impl fmt::Display for Diagnostic { 152 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 153 | if let Some(ref rendered) = self.rendered { 154 | f.write_str(rendered)?; 155 | } else { 156 | f.write_str("cargo didn't render this message")?; 157 | } 158 | Ok(()) 159 | } 160 | } 161 | -------------------------------------------------------------------------------- /src/errors.rs: -------------------------------------------------------------------------------- 1 | use std::{io, str::Utf8Error, string::FromUtf8Error}; 2 | 3 | /// Custom result type for `cargo_metadata::Error` 4 | pub type Result = ::std::result::Result; 5 | 6 | /// Error returned when executing/parsing `cargo metadata` fails. 7 | /// 8 | /// # Note about Backtraces 9 | /// 10 | /// This error type does not contain backtraces, but each error variant 11 | /// comes from _one_ specific place, so it's not really needed for the 12 | /// inside of this crate. If you need a backtrace down to, but not inside 13 | /// of, a failed call of `cargo_metadata` you can do one of multiple thinks: 14 | /// 15 | /// 1. Convert it to a `failure::Error` (possible using the `?` operator), 16 | /// which is similar to a `Box<::std::error::Error + 'static + Send + Sync>`. 17 | /// 2. Have appropriate variants in your own error type. E.g. you could wrap 18 | /// a `failure::Context` or add a `failure::Backtrace` field (which 19 | /// is empty if `RUST_BACKTRACE` is not set, so it's simple to use). 20 | /// 3. You still can place a failure based error into a `error_chain` if you 21 | /// really want to. (Either through foreign_links or by making it a field 22 | /// value of a `ErrorKind` variant). 23 | /// 24 | #[derive(Debug, thiserror::Error)] 25 | pub enum Error { 26 | /// Error during execution of `cargo metadata` 27 | #[error("`cargo metadata` exited with an error: {stderr}")] 28 | CargoMetadata { 29 | /// stderr returned by the `cargo metadata` command 30 | stderr: String, 31 | }, 32 | 33 | /// IO Error during execution of `cargo metadata` 34 | #[error("failed to start `cargo metadata`: {0}")] 35 | Io(#[from] io::Error), 36 | 37 | /// Output of `cargo metadata` was not valid utf8 38 | #[error("cannot convert the stdout of `cargo metadata`: {0}")] 39 | Utf8(#[from] Utf8Error), 40 | 41 | /// Error output of `cargo metadata` was not valid utf8 42 | #[error("cannot convert the stderr of `cargo metadata`: {0}")] 43 | ErrUtf8(#[from] FromUtf8Error), 44 | 45 | /// Deserialization error (structure of json did not match expected structure) 46 | #[error("failed to interpret `cargo metadata`'s json: {0}")] 47 | Json(#[from] ::serde_json::Error), 48 | 49 | /// The output did not contain any json 50 | #[error("could not find any json in the output of `cargo metadata`")] 51 | NoJson, 52 | } 53 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #![cfg_attr(docsrs, feature(doc_auto_cfg))] 2 | #![deny(missing_docs)] 3 | //! Structured access to the output of `cargo metadata` and `cargo --message-format=json`. 4 | //! Usually used from within a `cargo-*` executable 5 | //! 6 | //! See the [cargo book](https://doc.rust-lang.org/cargo/index.html) for 7 | //! details on cargo itself. 8 | //! 9 | //! ## Examples 10 | //! 11 | //! Get the current crate's metadata without default features but with all dependency information. 12 | //! 13 | //! ```rust 14 | //! # use std::path::Path; 15 | //! # use cargo_metadata::{MetadataCommand, CargoOpt}; 16 | //! let _metadata = MetadataCommand::new().exec().unwrap(); 17 | //! ``` 18 | //! 19 | //! 20 | //! If you have a program that takes `--manifest-path` as an argument, you can forward that 21 | //! to [MetadataCommand]: 22 | //! 23 | //! ```rust 24 | //! # use cargo_metadata::MetadataCommand; 25 | //! # use std::path::Path; 26 | //! let mut args = std::env::args().skip_while(|val| !val.starts_with("--manifest-path")); 27 | //! let mut cmd = MetadataCommand::new(); 28 | //! let manifest_path = match args.next() { 29 | //! Some(ref p) if p == "--manifest-path" => { 30 | //! cmd.manifest_path(args.next().unwrap()); 31 | //! } 32 | //! Some(p) => { 33 | //! cmd.manifest_path(p.trim_start_matches("--manifest-path=")); 34 | //! } 35 | //! None => {} 36 | //! }; 37 | //! 38 | //! let _metadata = cmd.exec().unwrap(); 39 | //! ``` 40 | //! 41 | //! Pass features flags, e.g. `--all-features`. 42 | //! 43 | //! ```rust 44 | //! # use std::path::Path; 45 | //! # use cargo_metadata::{MetadataCommand, CargoOpt}; 46 | //! let _metadata = MetadataCommand::new() 47 | //! .manifest_path("./Cargo.toml") 48 | //! .features(CargoOpt::AllFeatures) 49 | //! .exec() 50 | //! .unwrap(); 51 | //! ``` 52 | //! 53 | //! Parse message-format output produced by other cargo commands. 54 | //! It is recommended to use crates like `escargot` to produce the [Command]. 55 | //! 56 | //! ``` 57 | //! # use std::process::{Stdio, Command}; 58 | //! # use cargo_metadata::Message; 59 | //! let mut command = Command::new("cargo") 60 | //! .args(&["build", "--message-format=json-render-diagnostics"]) 61 | //! .stdout(Stdio::piped()) 62 | //! .spawn() 63 | //! .unwrap(); 64 | //! 65 | //! let reader = std::io::BufReader::new(command.stdout.take().unwrap()); 66 | //! for message in cargo_metadata::Message::parse_stream(reader) { 67 | //! match message.unwrap() { 68 | //! Message::CompilerMessage(msg) => { 69 | //! println!("{:?}", msg); 70 | //! }, 71 | //! Message::CompilerArtifact(artifact) => { 72 | //! println!("{:?}", artifact); 73 | //! }, 74 | //! Message::BuildScriptExecuted(script) => { 75 | //! println!("{:?}", script); 76 | //! }, 77 | //! Message::BuildFinished(finished) => { 78 | //! println!("{:?}", finished); 79 | //! }, 80 | //! _ => () // Unknown message 81 | //! } 82 | //! } 83 | //! 84 | //! let output = command.wait().expect("Couldn't get cargo's exit status"); 85 | //! ``` 86 | 87 | use camino::Utf8PathBuf; 88 | #[cfg(feature = "builder")] 89 | use derive_builder::Builder; 90 | use std::collections::BTreeMap; 91 | use std::env; 92 | use std::ffi::OsString; 93 | use std::fmt; 94 | use std::hash::Hash; 95 | use std::path::PathBuf; 96 | use std::process::{Command, Stdio}; 97 | use std::str::{from_utf8, FromStr}; 98 | 99 | pub use camino; 100 | pub use semver; 101 | use semver::Version; 102 | 103 | use cargo_util_schemas::manifest::{FeatureName, PackageName}; 104 | #[cfg(feature = "builder")] 105 | pub use dependency::DependencyBuilder; 106 | pub use dependency::{Dependency, DependencyKind}; 107 | use diagnostic::Diagnostic; 108 | pub use errors::{Error, Result}; 109 | #[cfg(feature = "unstable")] 110 | pub use libtest::TestMessage; 111 | #[allow(deprecated)] 112 | pub use messages::parse_messages; 113 | pub use messages::{ 114 | Artifact, ArtifactDebuginfo, ArtifactProfile, BuildFinished, BuildScript, CompilerMessage, 115 | Message, MessageIter, 116 | }; 117 | #[cfg(feature = "builder")] 118 | pub use messages::{ 119 | ArtifactBuilder, ArtifactProfileBuilder, BuildFinishedBuilder, BuildScriptBuilder, 120 | CompilerMessageBuilder, 121 | }; 122 | use serde::{Deserialize, Deserializer, Serialize}; 123 | 124 | mod dependency; 125 | pub mod diagnostic; 126 | mod errors; 127 | #[cfg(feature = "unstable")] 128 | pub mod libtest; 129 | mod messages; 130 | 131 | /// An "opaque" identifier for a package. 132 | /// 133 | /// It is possible to inspect the `repr` field, if the need arises, but its 134 | /// precise format is an implementation detail and is subject to change. 135 | /// 136 | /// `Metadata` can be indexed by `PackageId`. 137 | #[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)] 138 | #[serde(transparent)] 139 | pub struct PackageId { 140 | /// The underlying string representation of id. 141 | pub repr: String, 142 | } 143 | 144 | impl fmt::Display for PackageId { 145 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 146 | fmt::Display::fmt(&self.repr, f) 147 | } 148 | } 149 | 150 | /// Helpers for default metadata fields 151 | fn is_null(value: &serde_json::Value) -> bool { 152 | matches!(value, serde_json::Value::Null) 153 | } 154 | 155 | #[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq, Hash)] 156 | #[cfg_attr(feature = "builder", derive(Builder))] 157 | #[non_exhaustive] 158 | #[cfg_attr(feature = "builder", builder(pattern = "owned", setter(into)))] 159 | /// Starting point for metadata returned by `cargo metadata` 160 | pub struct Metadata { 161 | /// A list of all crates referenced by this crate (and the crate itself) 162 | pub packages: Vec, 163 | /// A list of all workspace members 164 | pub workspace_members: Vec, 165 | /// The list of default workspace members 166 | /// 167 | /// This is not available if running with a version of Cargo older than 1.71. 168 | /// 169 | /// You can check whether it is available or missing using respectively 170 | /// [`WorkspaceDefaultMembers::is_available`] and [`WorkspaceDefaultMembers::is_missing`]. 171 | #[serde(default, skip_serializing_if = "WorkspaceDefaultMembers::is_missing")] 172 | pub workspace_default_members: WorkspaceDefaultMembers, 173 | /// Dependencies graph 174 | pub resolve: Option, 175 | /// Workspace root 176 | pub workspace_root: Utf8PathBuf, 177 | /// Build directory 178 | pub target_directory: Utf8PathBuf, 179 | /// The workspace-level metadata object. Null if non-existent. 180 | #[serde(rename = "metadata", default, skip_serializing_if = "is_null")] 181 | pub workspace_metadata: serde_json::Value, 182 | /// The metadata format version 183 | version: usize, 184 | } 185 | 186 | impl Metadata { 187 | /// Get the workspace's root package of this metadata instance. 188 | pub fn root_package(&self) -> Option<&Package> { 189 | match &self.resolve { 190 | Some(resolve) => { 191 | // if dependencies are resolved, use Cargo's answer 192 | let root = resolve.root.as_ref()?; 193 | self.packages.iter().find(|pkg| &pkg.id == root) 194 | } 195 | None => { 196 | // if dependencies aren't resolved, check for a root package manually 197 | let root_manifest_path = self.workspace_root.join("Cargo.toml"); 198 | self.packages 199 | .iter() 200 | .find(|pkg| pkg.manifest_path == root_manifest_path) 201 | } 202 | } 203 | } 204 | 205 | /// Get the workspace packages. 206 | pub fn workspace_packages(&self) -> Vec<&Package> { 207 | self.packages 208 | .iter() 209 | .filter(|&p| self.workspace_members.contains(&p.id)) 210 | .collect() 211 | } 212 | 213 | /// Get the workspace default packages. 214 | /// 215 | /// # Panics 216 | /// 217 | /// This will panic if running with a version of Cargo older than 1.71. 218 | pub fn workspace_default_packages(&self) -> Vec<&Package> { 219 | self.packages 220 | .iter() 221 | .filter(|&p| self.workspace_default_members.contains(&p.id)) 222 | .collect() 223 | } 224 | } 225 | 226 | impl<'a> std::ops::Index<&'a PackageId> for Metadata { 227 | type Output = Package; 228 | 229 | fn index(&self, idx: &'a PackageId) -> &Self::Output { 230 | self.packages 231 | .iter() 232 | .find(|p| p.id == *idx) 233 | .unwrap_or_else(|| panic!("no package with this id: {:?}", idx)) 234 | } 235 | } 236 | 237 | #[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq, Hash, Default)] 238 | #[serde(transparent)] 239 | /// A list of default workspace members. 240 | /// 241 | /// See [`Metadata::workspace_default_members`]. 242 | /// 243 | /// It is only available if running a version of Cargo of 1.71 or newer. 244 | /// 245 | /// # Panics 246 | /// 247 | /// Dereferencing when running an older version of Cargo will panic. 248 | pub struct WorkspaceDefaultMembers(Option>); 249 | 250 | impl WorkspaceDefaultMembers { 251 | /// Return `true` if the list of workspace default members is supported by 252 | /// the called cargo-metadata version and `false` otherwise. 253 | /// 254 | /// In particular useful when parsing the output of `cargo-metadata` for 255 | /// versions of Cargo < 1.71, as dereferencing [`WorkspaceDefaultMembers`] 256 | /// for these versions will panic. 257 | /// 258 | /// Opposite of [`WorkspaceDefaultMembers::is_missing`]. 259 | pub fn is_available(&self) -> bool { 260 | self.0.is_some() 261 | } 262 | 263 | /// Return `false` if the list of workspace default members is supported by 264 | /// the called cargo-metadata version and `true` otherwise. 265 | /// 266 | /// In particular useful when parsing the output of `cargo-metadata` for 267 | /// versions of Cargo < 1.71, as dereferencing [`WorkspaceDefaultMembers`] 268 | /// for these versions will panic. 269 | /// 270 | /// Opposite of [`WorkspaceDefaultMembers::is_available`]. 271 | pub fn is_missing(&self) -> bool { 272 | self.0.is_none() 273 | } 274 | } 275 | 276 | impl core::ops::Deref for WorkspaceDefaultMembers { 277 | type Target = [PackageId]; 278 | 279 | fn deref(&self) -> &Self::Target { 280 | self.0 281 | .as_ref() 282 | .expect("WorkspaceDefaultMembers should only be dereferenced on Cargo versions >= 1.71") 283 | } 284 | } 285 | 286 | #[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq, Hash)] 287 | #[cfg_attr(feature = "builder", derive(Builder))] 288 | #[non_exhaustive] 289 | #[cfg_attr(feature = "builder", builder(pattern = "owned", setter(into)))] 290 | /// A dependency graph 291 | pub struct Resolve { 292 | /// Nodes in a dependencies graph 293 | pub nodes: Vec, 294 | 295 | /// The crate for which the metadata was read. 296 | pub root: Option, 297 | } 298 | 299 | impl<'a> std::ops::Index<&'a PackageId> for Resolve { 300 | type Output = Node; 301 | 302 | fn index(&self, idx: &'a PackageId) -> &Self::Output { 303 | self.nodes 304 | .iter() 305 | .find(|p| p.id == *idx) 306 | .unwrap_or_else(|| panic!("no Node with this id: {:?}", idx)) 307 | } 308 | } 309 | 310 | #[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq, Hash)] 311 | #[cfg_attr(feature = "builder", derive(Builder))] 312 | #[non_exhaustive] 313 | #[cfg_attr(feature = "builder", builder(pattern = "owned", setter(into)))] 314 | /// A node in a dependencies graph 315 | pub struct Node { 316 | /// An opaque identifier for a package 317 | pub id: PackageId, 318 | /// Dependencies in a structured format. 319 | /// 320 | /// `deps` handles renamed dependencies whereas `dependencies` does not. 321 | #[serde(default)] 322 | pub deps: Vec, 323 | 324 | /// List of opaque identifiers for this node's dependencies. 325 | /// It doesn't support renamed dependencies. See `deps`. 326 | pub dependencies: Vec, 327 | 328 | /// Features enabled on the crate 329 | #[serde(default)] 330 | pub features: Vec, 331 | } 332 | 333 | #[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq, Hash)] 334 | #[cfg_attr(feature = "builder", derive(Builder))] 335 | #[non_exhaustive] 336 | #[cfg_attr(feature = "builder", builder(pattern = "owned", setter(into)))] 337 | /// A dependency in a node 338 | pub struct NodeDep { 339 | /// The name of the dependency's library target. 340 | /// If the crate was renamed, it is the new name. 341 | pub name: PackageName, 342 | /// Package ID (opaque unique identifier) 343 | pub pkg: PackageId, 344 | /// The kinds of dependencies. 345 | /// 346 | /// This field was added in Rust 1.41. 347 | #[serde(default)] 348 | pub dep_kinds: Vec, 349 | } 350 | 351 | #[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq, Hash)] 352 | #[cfg_attr(feature = "builder", derive(Builder))] 353 | #[non_exhaustive] 354 | #[cfg_attr(feature = "builder", builder(pattern = "owned", setter(into)))] 355 | /// Information about a dependency kind. 356 | pub struct DepKindInfo { 357 | /// The kind of dependency. 358 | #[serde(deserialize_with = "dependency::parse_dependency_kind")] 359 | pub kind: DependencyKind, 360 | /// The target platform for the dependency. 361 | /// 362 | /// This is `None` if it is not a target dependency. 363 | /// 364 | /// Use the [`Display`] trait to access the contents. 365 | /// 366 | /// By default all platform dependencies are included in the resolve 367 | /// graph. Use Cargo's `--filter-platform` flag if you only want to 368 | /// include dependencies for a specific platform. 369 | /// 370 | /// [`Display`]: std::fmt::Display 371 | pub target: Option, 372 | } 373 | 374 | #[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq, Hash)] 375 | #[cfg_attr(feature = "builder", derive(Builder))] 376 | #[non_exhaustive] 377 | #[cfg_attr(feature = "builder", builder(pattern = "owned", setter(into)))] 378 | /// One or more crates described by a single `Cargo.toml` 379 | /// 380 | /// Each [`target`][Package::targets] of a `Package` will be built as a crate. 381 | /// For more information, see . 382 | pub struct Package { 383 | /// The [`name` field](https://doc.rust-lang.org/cargo/reference/manifest.html#the-name-field) as given in the `Cargo.toml` 384 | // (We say "given in" instead of "specified in" since the `name` key cannot be inherited from the workspace.) 385 | pub name: PackageName, 386 | /// The [`version` field](https://doc.rust-lang.org/cargo/reference/manifest.html#the-version-field) as specified in the `Cargo.toml` 387 | pub version: Version, 388 | /// The [`authors` field](https://doc.rust-lang.org/cargo/reference/manifest.html#the-authors-field) as specified in the `Cargo.toml` 389 | #[serde(default)] 390 | #[cfg_attr(feature = "builder", builder(default))] 391 | pub authors: Vec, 392 | /// An opaque identifier for a package 393 | pub id: PackageId, 394 | /// The source of the package, e.g. 395 | /// crates.io or `None` for local projects. 396 | // Note that this is NOT the same as cargo_util_schemas::RegistryName 397 | #[cfg_attr(feature = "builder", builder(default))] 398 | pub source: Option, 399 | /// The [`description` field](https://doc.rust-lang.org/cargo/reference/manifest.html#the-description-field) as specified in the `Cargo.toml` 400 | #[cfg_attr(feature = "builder", builder(default))] 401 | pub description: Option, 402 | /// List of dependencies of this particular package 403 | #[cfg_attr(feature = "builder", builder(default))] 404 | pub dependencies: Vec, 405 | /// The [`license` field](https://doc.rust-lang.org/cargo/reference/manifest.html#the-license-and-license-file-fields) as specified in the `Cargo.toml` 406 | #[cfg_attr(feature = "builder", builder(default))] 407 | pub license: Option, 408 | /// 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`. 409 | /// If the package is using a nonstandard license, this key may be specified instead of 410 | /// `license`, and must point to a file relative to the manifest. 411 | #[cfg_attr(feature = "builder", builder(default))] 412 | pub license_file: Option, 413 | /// Targets provided by the crate (lib, bin, example, test, ...) 414 | #[cfg_attr(feature = "builder", builder(default))] 415 | pub targets: Vec, 416 | /// Features provided by the crate, mapped to the features required by that feature. 417 | #[cfg_attr(feature = "builder", builder(default))] 418 | pub features: BTreeMap>, 419 | /// Path containing the `Cargo.toml` 420 | pub manifest_path: Utf8PathBuf, 421 | /// The [`categories` field](https://doc.rust-lang.org/cargo/reference/manifest.html#the-categories-field) as specified in the `Cargo.toml` 422 | #[serde(default)] 423 | #[cfg_attr(feature = "builder", builder(default))] 424 | pub categories: Vec, 425 | /// The [`keywords` field](https://doc.rust-lang.org/cargo/reference/manifest.html#the-keywords-field) as specified in the `Cargo.toml` 426 | #[serde(default)] 427 | #[cfg_attr(feature = "builder", builder(default))] 428 | pub keywords: Vec, 429 | /// The [`readme` field](https://doc.rust-lang.org/cargo/reference/manifest.html#the-readme-field) as specified in the `Cargo.toml` 430 | #[cfg_attr(feature = "builder", builder(default))] 431 | pub readme: Option, 432 | /// The [`repository` URL](https://doc.rust-lang.org/cargo/reference/manifest.html#the-repository-field) as specified in the `Cargo.toml` 433 | // can't use `url::Url` because that requires a more recent stable compiler 434 | #[cfg_attr(feature = "builder", builder(default))] 435 | pub repository: Option, 436 | /// The [`homepage` URL](https://doc.rust-lang.org/cargo/reference/manifest.html#the-homepage-field) as specified in the `Cargo.toml`. 437 | /// 438 | /// On versions of cargo before 1.49, this will always be [`None`]. 439 | #[cfg_attr(feature = "builder", builder(default))] 440 | pub homepage: Option, 441 | /// The [`documentation` URL](https://doc.rust-lang.org/cargo/reference/manifest.html#the-documentation-field) as specified in the `Cargo.toml`. 442 | /// 443 | /// On versions of cargo before 1.49, this will always be [`None`]. 444 | #[cfg_attr(feature = "builder", builder(default))] 445 | pub documentation: Option, 446 | /// 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) 447 | /// or defaulting to [`Edition::E2015`]). 448 | /// 449 | /// Beware that individual targets may specify their own edition in 450 | /// [`Target::edition`]. 451 | #[serde(default)] 452 | #[cfg_attr(feature = "builder", builder(default))] 453 | pub edition: Edition, 454 | /// Contents of the free form [`package.metadata` section](https://doc.rust-lang.org/cargo/reference/manifest.html#the-metadata-table). 455 | /// 456 | /// This contents can be serialized to a struct using serde: 457 | /// 458 | /// ```rust 459 | /// use serde::Deserialize; 460 | /// use serde_json::json; 461 | /// 462 | /// #[derive(Debug, Deserialize)] 463 | /// struct SomePackageMetadata { 464 | /// some_value: i32, 465 | /// } 466 | /// 467 | /// let value = json!({ 468 | /// "some_value": 42, 469 | /// }); 470 | /// 471 | /// let package_metadata: SomePackageMetadata = serde_json::from_value(value).unwrap(); 472 | /// assert_eq!(package_metadata.some_value, 42); 473 | /// 474 | /// ``` 475 | #[serde(default, skip_serializing_if = "is_null")] 476 | #[cfg_attr(feature = "builder", builder(default))] 477 | pub metadata: serde_json::Value, 478 | /// The name of a native library the package is linking to. 479 | #[cfg_attr(feature = "builder", builder(default))] 480 | pub links: Option, 481 | /// 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)). 482 | /// 483 | /// Publishing is unrestricted if `None`, and forbidden if the `Vec` is empty. 484 | /// 485 | /// This is always `None` if running with a version of Cargo older than 1.39. 486 | #[cfg_attr(feature = "builder", builder(default))] 487 | pub publish: Option>, 488 | /// The [`default-run` field](https://doc.rust-lang.org/cargo/reference/manifest.html#the-default-run-field) as given in the `Cargo.toml` 489 | // (We say "given in" instead of "specified in" since the `default-run` key cannot be inherited from the workspace.) 490 | /// The default binary to run by `cargo run`. 491 | /// 492 | /// This is always `None` if running with a version of Cargo older than 1.55. 493 | #[cfg_attr(feature = "builder", builder(default))] 494 | pub default_run: Option, 495 | /// The [`rust-version` field](https://doc.rust-lang.org/cargo/reference/manifest.html#the-rust-version-field) as specified in the `Cargo.toml`. 496 | /// The minimum supported Rust version of this package. 497 | /// 498 | /// This is always `None` if running with a version of Cargo older than 1.58. 499 | #[serde(default)] 500 | #[serde(deserialize_with = "deserialize_rust_version")] 501 | #[cfg_attr(feature = "builder", builder(default))] 502 | pub rust_version: Option, 503 | } 504 | 505 | #[cfg(feature = "builder")] 506 | impl PackageBuilder { 507 | /// Construct a new `PackageBuilder` with all required fields. 508 | pub fn new( 509 | name: impl Into, 510 | version: impl Into, 511 | id: impl Into, 512 | path: impl Into, 513 | ) -> Self { 514 | Self::default() 515 | .name(name) 516 | .version(version) 517 | .id(id) 518 | .manifest_path(path) 519 | } 520 | } 521 | 522 | impl Package { 523 | /// Full path to the license file if one is present in the manifest 524 | pub fn license_file(&self) -> Option { 525 | self.license_file.as_ref().map(|file| { 526 | self.manifest_path 527 | .parent() 528 | .unwrap_or(&self.manifest_path) 529 | .join(file) 530 | }) 531 | } 532 | 533 | /// Full path to the readme file if one is present in the manifest 534 | pub fn readme(&self) -> Option { 535 | self.readme.as_ref().map(|file| { 536 | self.manifest_path 537 | .parent() 538 | .unwrap_or(&self.manifest_path) 539 | .join(file) 540 | }) 541 | } 542 | } 543 | 544 | /// The source of a package such as crates.io. 545 | /// 546 | /// It is possible to inspect the `repr` field, if the need arises, but its 547 | /// precise format is an implementation detail and is subject to change. 548 | #[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq, Hash)] 549 | #[serde(transparent)] 550 | pub struct Source { 551 | /// The underlying string representation of a source. 552 | pub repr: String, 553 | } 554 | 555 | impl Source { 556 | /// Returns true if the source is crates.io. 557 | pub fn is_crates_io(&self) -> bool { 558 | self.repr == "registry+https://github.com/rust-lang/crates.io-index" 559 | } 560 | } 561 | 562 | impl fmt::Display for Source { 563 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 564 | fmt::Display::fmt(&self.repr, f) 565 | } 566 | } 567 | 568 | #[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq, Hash)] 569 | #[cfg_attr(feature = "builder", derive(Builder))] 570 | #[cfg_attr(feature = "builder", builder(pattern = "owned", setter(into)))] 571 | #[non_exhaustive] 572 | /// A single target (lib, bin, example, ...) provided by a crate 573 | pub struct Target { 574 | /// Name as given in the `Cargo.toml` or generated from the file name 575 | pub name: String, 576 | /// Kind of target. 577 | /// 578 | /// The possible values are `example`, `test`, `bench`, `custom-build` and 579 | /// [Cargo crate types](https://doc.rust-lang.org/cargo/reference/cargo-targets.html#the-crate-type-field): 580 | /// `bin`, `lib`, `rlib`, `dylib`, `cdylib`, `staticlib`, `proc-macro`. 581 | /// 582 | /// Other possible values may be added in the future. 583 | pub kind: Vec, 584 | /// Similar to `kind`, but only reports the 585 | /// [Cargo crate types](https://doc.rust-lang.org/cargo/reference/cargo-targets.html#the-crate-type-field): 586 | /// `bin`, `lib`, `rlib`, `dylib`, `cdylib`, `staticlib`, `proc-macro`. 587 | /// Everything that's not a proc macro or a library of some kind is reported as "bin". 588 | /// 589 | /// Other possible values may be added in the future. 590 | #[serde(default)] 591 | #[cfg_attr(feature = "builder", builder(default))] 592 | pub crate_types: Vec, 593 | 594 | #[serde(default)] 595 | #[cfg_attr(feature = "builder", builder(default))] 596 | #[serde(rename = "required-features")] 597 | /// This target is built only if these features are enabled. 598 | /// It doesn't apply to `lib` targets. 599 | pub required_features: Vec, 600 | /// Path to the main source file of the target 601 | pub src_path: Utf8PathBuf, 602 | /// Rust edition for this target 603 | #[serde(default)] 604 | #[cfg_attr(feature = "builder", builder(default))] 605 | pub edition: Edition, 606 | /// Whether or not this target has doc tests enabled, and the target is 607 | /// compatible with doc testing. 608 | /// 609 | /// This is always `true` if running with a version of Cargo older than 1.37. 610 | #[serde(default = "default_true")] 611 | #[cfg_attr(feature = "builder", builder(default = "true"))] 612 | pub doctest: bool, 613 | /// Whether or not this target is tested by default by `cargo test`. 614 | /// 615 | /// This is always `true` if running with a version of Cargo older than 1.47. 616 | #[serde(default = "default_true")] 617 | #[cfg_attr(feature = "builder", builder(default = "true"))] 618 | pub test: bool, 619 | /// Whether or not this target is documented by `cargo doc`. 620 | /// 621 | /// This is always `true` if running with a version of Cargo older than 1.50. 622 | #[serde(default = "default_true")] 623 | #[cfg_attr(feature = "builder", builder(default = "true"))] 624 | pub doc: bool, 625 | } 626 | 627 | macro_rules! methods_target_is_kind { 628 | ($($name:ident => $kind:expr),*) => { 629 | $( 630 | /// Return true if this target is of kind `$kind`. 631 | pub fn $name(&self) -> bool { 632 | self.is_kind($kind) 633 | } 634 | )* 635 | } 636 | } 637 | 638 | impl Target { 639 | /// Return true if this target is of the given kind. 640 | pub fn is_kind(&self, name: TargetKind) -> bool { 641 | self.kind.iter().any(|kind| kind == &name) 642 | } 643 | 644 | // Generate `is_*` methods for each `TargetKind` 645 | methods_target_is_kind! { 646 | is_lib => TargetKind::Lib, 647 | is_bin => TargetKind::Bin, 648 | is_example => TargetKind::Example, 649 | is_test => TargetKind::Test, 650 | is_bench => TargetKind::Bench, 651 | is_custom_build => TargetKind::CustomBuild, 652 | is_proc_macro => TargetKind::ProcMacro, 653 | is_cdylib => TargetKind::CDyLib, 654 | is_dylib => TargetKind::DyLib, 655 | is_rlib => TargetKind::RLib, 656 | is_staticlib => TargetKind::StaticLib 657 | } 658 | } 659 | 660 | /// Kind of target. 661 | /// 662 | /// The possible values are `example`, `test`, `bench`, `custom-build` and 663 | /// [Cargo crate types](https://doc.rust-lang.org/cargo/reference/cargo-targets.html#the-crate-type-field): 664 | /// `bin`, `lib`, `rlib`, `dylib`, `cdylib`, `staticlib`, `proc-macro`. 665 | /// 666 | /// Other possible values may be added in the future. 667 | #[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)] 668 | #[non_exhaustive] 669 | pub enum TargetKind { 670 | /// `cargo bench` target 671 | #[serde(rename = "bench")] 672 | Bench, 673 | /// Binary executable target 674 | #[serde(rename = "bin")] 675 | Bin, 676 | /// Custom build target 677 | #[serde(rename = "custom-build")] 678 | CustomBuild, 679 | /// Dynamic system library target 680 | #[serde(rename = "cdylib")] 681 | CDyLib, 682 | /// Dynamic Rust library target 683 | #[serde(rename = "dylib")] 684 | DyLib, 685 | /// Example target 686 | #[serde(rename = "example")] 687 | Example, 688 | /// Rust library 689 | #[serde(rename = "lib")] 690 | Lib, 691 | /// Procedural Macro 692 | #[serde(rename = "proc-macro")] 693 | ProcMacro, 694 | /// Rust library for use as an intermediate artifact 695 | #[serde(rename = "rlib")] 696 | RLib, 697 | /// Static system library 698 | #[serde(rename = "staticlib")] 699 | StaticLib, 700 | /// Test target 701 | #[serde(rename = "test")] 702 | Test, 703 | /// Unknown type 704 | #[serde(untagged)] 705 | Unknown(String), 706 | } 707 | 708 | impl From<&str> for TargetKind { 709 | fn from(value: &str) -> Self { 710 | match value { 711 | "example" => TargetKind::Example, 712 | "test" => TargetKind::Test, 713 | "bench" => TargetKind::Bench, 714 | "custom-build" => TargetKind::CustomBuild, 715 | "bin" => TargetKind::Bin, 716 | "lib" => TargetKind::Lib, 717 | "rlib" => TargetKind::RLib, 718 | "dylib" => TargetKind::DyLib, 719 | "cdylib" => TargetKind::CDyLib, 720 | "staticlib" => TargetKind::StaticLib, 721 | "proc-macro" => TargetKind::ProcMacro, 722 | x => TargetKind::Unknown(x.to_string()), 723 | } 724 | } 725 | } 726 | 727 | impl FromStr for TargetKind { 728 | type Err = std::convert::Infallible; 729 | 730 | fn from_str(s: &str) -> std::result::Result { 731 | Ok(TargetKind::from(s)) 732 | } 733 | } 734 | 735 | impl fmt::Display for TargetKind { 736 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 737 | match self { 738 | Self::Bench => "bench".fmt(f), 739 | Self::Bin => "bin".fmt(f), 740 | Self::CustomBuild => "custom-build".fmt(f), 741 | Self::CDyLib => "cdylib".fmt(f), 742 | Self::DyLib => "dylib".fmt(f), 743 | Self::Example => "example".fmt(f), 744 | Self::Lib => "lib".fmt(f), 745 | Self::ProcMacro => "proc-macro".fmt(f), 746 | Self::RLib => "rlib".fmt(f), 747 | Self::StaticLib => "staticlib".fmt(f), 748 | Self::Test => "test".fmt(f), 749 | Self::Unknown(x) => x.fmt(f), 750 | } 751 | } 752 | } 753 | 754 | /// Similar to `kind`, but only reports the 755 | /// [Cargo crate types](https://doc.rust-lang.org/cargo/reference/cargo-targets.html#the-crate-type-field): 756 | /// 757 | /// `bin`, `lib`, `rlib`, `dylib`, `cdylib`, `staticlib`, `proc-macro`. 758 | /// Everything that's not a proc macro or a library of some kind is reported as "bin". 759 | /// 760 | /// Other possible values may be added in the future. 761 | #[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)] 762 | #[non_exhaustive] 763 | pub enum CrateType { 764 | /// Binary executable target 765 | #[serde(rename = "bin")] 766 | Bin, 767 | /// Dynamic system library target 768 | #[serde(rename = "cdylib")] 769 | CDyLib, 770 | /// Dynamic Rust library target 771 | #[serde(rename = "dylib")] 772 | DyLib, 773 | /// Rust library 774 | #[serde(rename = "lib")] 775 | Lib, 776 | /// Procedural Macro 777 | #[serde(rename = "proc-macro")] 778 | ProcMacro, 779 | /// Rust library for use as an intermediate artifact 780 | #[serde(rename = "rlib")] 781 | RLib, 782 | /// Static system library 783 | #[serde(rename = "staticlib")] 784 | StaticLib, 785 | /// Unkown type 786 | #[serde(untagged)] 787 | Unknown(String), 788 | } 789 | 790 | impl From<&str> for CrateType { 791 | fn from(value: &str) -> Self { 792 | match value { 793 | "bin" => CrateType::Bin, 794 | "lib" => CrateType::Lib, 795 | "rlib" => CrateType::RLib, 796 | "dylib" => CrateType::DyLib, 797 | "cdylib" => CrateType::CDyLib, 798 | "staticlib" => CrateType::StaticLib, 799 | "proc-macro" => CrateType::ProcMacro, 800 | x => CrateType::Unknown(x.to_string()), 801 | } 802 | } 803 | } 804 | 805 | impl FromStr for CrateType { 806 | type Err = std::convert::Infallible; 807 | 808 | fn from_str(s: &str) -> std::result::Result { 809 | Ok(CrateType::from(s)) 810 | } 811 | } 812 | 813 | impl fmt::Display for CrateType { 814 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 815 | match self { 816 | Self::Bin => "bin".fmt(f), 817 | Self::CDyLib => "cdylib".fmt(f), 818 | Self::DyLib => "dylib".fmt(f), 819 | Self::Lib => "lib".fmt(f), 820 | Self::ProcMacro => "proc-macro".fmt(f), 821 | Self::RLib => "rlib".fmt(f), 822 | Self::StaticLib => "staticlib".fmt(f), 823 | Self::Unknown(x) => x.fmt(f), 824 | } 825 | } 826 | } 827 | 828 | /// The Rust edition 829 | /// 830 | /// As of writing this comment rust editions 2027 and 2030 are not actually a thing yet but are parsed nonetheless for future proofing. 831 | #[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Hash, PartialOrd, Ord)] 832 | #[non_exhaustive] 833 | pub enum Edition { 834 | /// Edition 2015 835 | #[serde(rename = "2015")] 836 | E2015, 837 | /// Edition 2018 838 | #[serde(rename = "2018")] 839 | E2018, 840 | /// Edition 2021 841 | #[serde(rename = "2021")] 842 | E2021, 843 | /// Edition 2024 844 | #[serde(rename = "2024")] 845 | E2024, 846 | #[doc(hidden)] 847 | #[serde(rename = "2027")] 848 | _E2027, 849 | #[doc(hidden)] 850 | #[serde(rename = "2030")] 851 | _E2030, 852 | } 853 | 854 | impl Edition { 855 | /// Return the string representation of the edition 856 | pub fn as_str(&self) -> &'static str { 857 | use Edition::*; 858 | match self { 859 | E2015 => "2015", 860 | E2018 => "2018", 861 | E2021 => "2021", 862 | E2024 => "2024", 863 | _E2027 => "2027", 864 | _E2030 => "2030", 865 | } 866 | } 867 | } 868 | 869 | impl fmt::Display for Edition { 870 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 871 | f.write_str(self.as_str()) 872 | } 873 | } 874 | 875 | impl Default for Edition { 876 | fn default() -> Self { 877 | Self::E2015 878 | } 879 | } 880 | 881 | fn default_true() -> bool { 882 | true 883 | } 884 | 885 | /// Cargo features flags 886 | #[derive(Debug, Clone)] 887 | pub enum CargoOpt { 888 | /// Run cargo with `--features-all` 889 | AllFeatures, 890 | /// Run cargo with `--no-default-features` 891 | NoDefaultFeatures, 892 | /// Run cargo with `--features ` 893 | SomeFeatures(Vec), 894 | } 895 | 896 | /// A builder for configuring `cargo metadata` invocation. 897 | #[derive(Debug, Clone, Default)] 898 | pub struct MetadataCommand { 899 | /// Path to `cargo` executable. If not set, this will use the 900 | /// the `$CARGO` environment variable, and if that is not set, will 901 | /// simply be `cargo`. 902 | cargo_path: Option, 903 | /// Path to `Cargo.toml` 904 | manifest_path: Option, 905 | /// Current directory of the `cargo metadata` process. 906 | current_dir: Option, 907 | /// Output information only about workspace members and don't fetch dependencies. 908 | no_deps: bool, 909 | /// Collections of `CargoOpt::SomeFeatures(..)` 910 | features: Vec, 911 | /// Latched `CargoOpt::AllFeatures` 912 | all_features: bool, 913 | /// Latched `CargoOpt::NoDefaultFeatures` 914 | no_default_features: bool, 915 | /// Arbitrary command line flags to pass to `cargo`. These will be added 916 | /// to the end of the command line invocation. 917 | other_options: Vec, 918 | /// Arbitrary environment variables to set or remove (depending on 919 | /// [`Option`] value) when running `cargo`. These will be merged into the 920 | /// calling environment, overriding any which clash. 921 | env: BTreeMap>, 922 | /// Show stderr 923 | verbose: bool, 924 | } 925 | 926 | impl MetadataCommand { 927 | /// Creates a default `cargo metadata` command, which will look for 928 | /// `Cargo.toml` in the ancestors of the current directory. 929 | pub fn new() -> MetadataCommand { 930 | MetadataCommand::default() 931 | } 932 | /// Path to `cargo` executable. If not set, this will use the 933 | /// the `$CARGO` environment variable, and if that is not set, will 934 | /// simply be `cargo`. 935 | pub fn cargo_path(&mut self, path: impl Into) -> &mut MetadataCommand { 936 | self.cargo_path = Some(path.into()); 937 | self 938 | } 939 | /// Path to `Cargo.toml` 940 | pub fn manifest_path(&mut self, path: impl Into) -> &mut MetadataCommand { 941 | self.manifest_path = Some(path.into()); 942 | self 943 | } 944 | /// Current directory of the `cargo metadata` process. 945 | pub fn current_dir(&mut self, path: impl Into) -> &mut MetadataCommand { 946 | self.current_dir = Some(path.into()); 947 | self 948 | } 949 | /// Output information only about workspace members and don't fetch dependencies. 950 | pub fn no_deps(&mut self) -> &mut MetadataCommand { 951 | self.no_deps = true; 952 | self 953 | } 954 | /// Which features to include. 955 | /// 956 | /// Call this multiple times to specify advanced feature configurations: 957 | /// 958 | /// ```no_run 959 | /// # use cargo_metadata::{CargoOpt, MetadataCommand}; 960 | /// MetadataCommand::new() 961 | /// .features(CargoOpt::NoDefaultFeatures) 962 | /// .features(CargoOpt::SomeFeatures(vec!["feat1".into(), "feat2".into()])) 963 | /// .features(CargoOpt::SomeFeatures(vec!["feat3".into()])) 964 | /// // ... 965 | /// # ; 966 | /// ``` 967 | /// 968 | /// # Panics 969 | /// 970 | /// `cargo metadata` rejects multiple `--no-default-features` flags. Similarly, the `features()` 971 | /// method panics when specifying multiple `CargoOpt::NoDefaultFeatures`: 972 | /// 973 | /// ```should_panic 974 | /// # use cargo_metadata::{CargoOpt, MetadataCommand}; 975 | /// MetadataCommand::new() 976 | /// .features(CargoOpt::NoDefaultFeatures) 977 | /// .features(CargoOpt::NoDefaultFeatures) // <-- panic! 978 | /// // ... 979 | /// # ; 980 | /// ``` 981 | /// 982 | /// The method also panics for multiple `CargoOpt::AllFeatures` arguments: 983 | /// 984 | /// ```should_panic 985 | /// # use cargo_metadata::{CargoOpt, MetadataCommand}; 986 | /// MetadataCommand::new() 987 | /// .features(CargoOpt::AllFeatures) 988 | /// .features(CargoOpt::AllFeatures) // <-- panic! 989 | /// // ... 990 | /// # ; 991 | /// ``` 992 | pub fn features(&mut self, features: CargoOpt) -> &mut MetadataCommand { 993 | match features { 994 | CargoOpt::SomeFeatures(features) => self.features.extend(features), 995 | CargoOpt::NoDefaultFeatures => { 996 | assert!( 997 | !self.no_default_features, 998 | "Do not supply CargoOpt::NoDefaultFeatures more than once!" 999 | ); 1000 | self.no_default_features = true; 1001 | } 1002 | CargoOpt::AllFeatures => { 1003 | assert!( 1004 | !self.all_features, 1005 | "Do not supply CargoOpt::AllFeatures more than once!" 1006 | ); 1007 | self.all_features = true; 1008 | } 1009 | } 1010 | self 1011 | } 1012 | /// Arbitrary command line flags to pass to `cargo`. These will be added 1013 | /// to the end of the command line invocation. 1014 | pub fn other_options(&mut self, options: impl Into>) -> &mut MetadataCommand { 1015 | self.other_options = options.into(); 1016 | self 1017 | } 1018 | 1019 | /// Arbitrary environment variables to set when running `cargo`. These will be merged into 1020 | /// the calling environment, overriding any which clash. 1021 | /// 1022 | /// Some examples of when you may want to use this: 1023 | /// 1. Setting cargo config values without needing a .cargo/config.toml file, e.g. to set 1024 | /// `CARGO_NET_GIT_FETCH_WITH_CLI=true` 1025 | /// 2. To specify a custom path to RUSTC if your rust toolchain components aren't laid out in 1026 | /// the way cargo expects by default. 1027 | /// 1028 | /// ```no_run 1029 | /// # use cargo_metadata::{CargoOpt, MetadataCommand}; 1030 | /// MetadataCommand::new() 1031 | /// .env("CARGO_NET_GIT_FETCH_WITH_CLI", "true") 1032 | /// .env("RUSTC", "/path/to/rustc") 1033 | /// // ... 1034 | /// # ; 1035 | /// ``` 1036 | pub fn env, V: Into>( 1037 | &mut self, 1038 | key: K, 1039 | val: V, 1040 | ) -> &mut MetadataCommand { 1041 | self.env.insert(key.into(), Some(val.into())); 1042 | self 1043 | } 1044 | 1045 | /// Arbitrary environment variables to remove when running `cargo`. These will be merged into 1046 | /// the calling environment, overriding any which clash. 1047 | /// 1048 | /// Some examples of when you may want to use this: 1049 | /// - Removing inherited environment variables in build scripts that can cause an error 1050 | /// when calling `cargo metadata` (for example, when cross-compiling). 1051 | /// 1052 | /// ```no_run 1053 | /// # use cargo_metadata::{CargoOpt, MetadataCommand}; 1054 | /// MetadataCommand::new() 1055 | /// .env_remove("CARGO_ENCODED_RUSTFLAGS") 1056 | /// // ... 1057 | /// # ; 1058 | /// ``` 1059 | pub fn env_remove>(&mut self, key: K) -> &mut MetadataCommand { 1060 | self.env.insert(key.into(), None); 1061 | self 1062 | } 1063 | 1064 | /// Set whether to show stderr 1065 | pub fn verbose(&mut self, verbose: bool) -> &mut MetadataCommand { 1066 | self.verbose = verbose; 1067 | self 1068 | } 1069 | 1070 | /// Builds a command for `cargo metadata`. This is the first 1071 | /// part of the work of `exec`. 1072 | pub fn cargo_command(&self) -> Command { 1073 | let cargo = self 1074 | .cargo_path 1075 | .clone() 1076 | .or_else(|| env::var("CARGO").map(PathBuf::from).ok()) 1077 | .unwrap_or_else(|| PathBuf::from("cargo")); 1078 | let mut cmd = Command::new(cargo); 1079 | cmd.args(["metadata", "--format-version", "1"]); 1080 | 1081 | if self.no_deps { 1082 | cmd.arg("--no-deps"); 1083 | } 1084 | 1085 | if let Some(path) = self.current_dir.as_ref() { 1086 | cmd.current_dir(path); 1087 | } 1088 | 1089 | if !self.features.is_empty() { 1090 | cmd.arg("--features").arg(self.features.join(",")); 1091 | } 1092 | if self.all_features { 1093 | cmd.arg("--all-features"); 1094 | } 1095 | if self.no_default_features { 1096 | cmd.arg("--no-default-features"); 1097 | } 1098 | 1099 | if let Some(manifest_path) = &self.manifest_path { 1100 | cmd.arg("--manifest-path").arg(manifest_path.as_os_str()); 1101 | } 1102 | cmd.args(&self.other_options); 1103 | 1104 | for (key, val) in &self.env { 1105 | match val { 1106 | Some(val) => cmd.env(key, val), 1107 | None => cmd.env_remove(key), 1108 | }; 1109 | } 1110 | 1111 | cmd 1112 | } 1113 | 1114 | /// Parses `cargo metadata` output. `data` must have been 1115 | /// produced by a command built with `cargo_command`. 1116 | pub fn parse>(data: T) -> Result { 1117 | let meta = serde_json::from_str(data.as_ref())?; 1118 | Ok(meta) 1119 | } 1120 | 1121 | /// Runs configured `cargo metadata` and returns parsed `Metadata`. 1122 | pub fn exec(&self) -> Result { 1123 | let mut command = self.cargo_command(); 1124 | if self.verbose { 1125 | command.stderr(Stdio::inherit()); 1126 | } 1127 | let output = command.output()?; 1128 | if !output.status.success() { 1129 | return Err(Error::CargoMetadata { 1130 | stderr: String::from_utf8(output.stderr)?, 1131 | }); 1132 | } 1133 | let stdout = from_utf8(&output.stdout)? 1134 | .lines() 1135 | .find(|line| line.starts_with('{')) 1136 | .ok_or(Error::NoJson)?; 1137 | Self::parse(stdout) 1138 | } 1139 | } 1140 | 1141 | /// As per the Cargo Book the [`rust-version` field](https://doc.rust-lang.org/cargo/reference/manifest.html#the-rust-version-field) must: 1142 | /// 1143 | /// > be a bare version number with two or three components; 1144 | /// > it cannot include semver operators or pre-release identifiers. 1145 | /// 1146 | /// [`semver::Version`] however requires three components. This function takes 1147 | /// care of appending `.0` if the provided version number only has two components 1148 | /// and ensuring that it does not contain a pre-release version or build metadata. 1149 | fn deserialize_rust_version<'de, D>( 1150 | deserializer: D, 1151 | ) -> std::result::Result, D::Error> 1152 | where 1153 | D: Deserializer<'de>, 1154 | { 1155 | let mut buf = match Option::::deserialize(deserializer)? { 1156 | None => return Ok(None), 1157 | Some(buf) => buf, 1158 | }; 1159 | 1160 | for char in buf.chars() { 1161 | if char == '-' { 1162 | return Err(serde::de::Error::custom( 1163 | "pre-release identifiers are not supported in rust-version", 1164 | )); 1165 | } else if char == '+' { 1166 | return Err(serde::de::Error::custom( 1167 | "build metadata is not supported in rust-version", 1168 | )); 1169 | } 1170 | } 1171 | 1172 | if buf.matches('.').count() == 1 { 1173 | // e.g. 1.0 -> 1.0.0 1174 | buf.push_str(".0"); 1175 | } 1176 | 1177 | Ok(Some( 1178 | Version::parse(&buf).map_err(serde::de::Error::custom)?, 1179 | )) 1180 | } 1181 | 1182 | #[cfg(test)] 1183 | mod test { 1184 | use semver::Version; 1185 | 1186 | #[derive(Debug, serde::Deserialize)] 1187 | struct BareVersion( 1188 | #[serde(deserialize_with = "super::deserialize_rust_version")] Option, 1189 | ); 1190 | 1191 | fn bare_version(str: &str) -> Version { 1192 | serde_json::from_str::(&format!(r#""{}""#, str)) 1193 | .unwrap() 1194 | .0 1195 | .unwrap() 1196 | } 1197 | 1198 | fn bare_version_err(str: &str) -> String { 1199 | serde_json::from_str::(&format!(r#""{}""#, str)) 1200 | .unwrap_err() 1201 | .to_string() 1202 | } 1203 | 1204 | #[test] 1205 | fn test_deserialize_rust_version() { 1206 | assert_eq!(bare_version("1.2"), Version::new(1, 2, 0)); 1207 | assert_eq!(bare_version("1.2.0"), Version::new(1, 2, 0)); 1208 | assert_eq!( 1209 | bare_version_err("1.2.0-alpha"), 1210 | "pre-release identifiers are not supported in rust-version" 1211 | ); 1212 | assert_eq!( 1213 | bare_version_err("1.2.0+123"), 1214 | "build metadata is not supported in rust-version" 1215 | ); 1216 | } 1217 | } 1218 | -------------------------------------------------------------------------------- /src/libtest.rs: -------------------------------------------------------------------------------- 1 | //! Parses output of [libtest](https://github.com/rust-lang/rust/blob/master/library/test/src/formatters/json.rs). 2 | //! 3 | //! 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. 4 | use serde::{Deserialize, Serialize}; 5 | 6 | /// Suite related event 7 | #[derive(Debug, PartialEq, Deserialize, Serialize)] 8 | #[serde(tag = "event")] 9 | #[serde(rename_all = "lowercase")] 10 | /// Suite event 11 | pub enum SuiteEvent { 12 | /// emitted on the start of a test run, and the start of the doctests 13 | Started { 14 | /// number of tests in this suite 15 | test_count: usize, 16 | }, 17 | /// the suite has finished 18 | Ok { 19 | /// the number of tests that passed 20 | passed: usize, 21 | /// the number of tests that failed 22 | failed: usize, 23 | /// number of tests that were ignored 24 | ignored: usize, 25 | /// number of benchmarks run 26 | measured: usize, 27 | /// i think this is based on what you specify in the cargo test argument 28 | filtered_out: usize, 29 | /// how long the suite took to run 30 | exec_time: f32, 31 | }, 32 | /// the suite has at least one failing test 33 | Failed { 34 | /// the number of tests that passed 35 | passed: usize, 36 | /// the number of tests that failed 37 | failed: usize, 38 | /// number of tests that were ignored 39 | ignored: usize, 40 | /// i think its something to do with benchmarks? 41 | measured: usize, 42 | /// i think this is based on what you specify in the cargo test argument 43 | filtered_out: usize, 44 | /// how long the suite took to run 45 | exec_time: f32, 46 | }, 47 | } 48 | 49 | #[derive(Debug, PartialEq, Deserialize, Serialize)] 50 | #[serde(tag = "event")] 51 | #[serde(rename_all = "lowercase")] 52 | /// Test event 53 | pub enum TestEvent { 54 | /// a new test starts 55 | Started { 56 | /// the name of this test 57 | name: String, 58 | }, 59 | /// the test has finished 60 | Ok { 61 | /// which one 62 | name: String, 63 | /// in how long 64 | exec_time: f32, 65 | /// what did it say? 66 | stdout: Option, 67 | }, 68 | /// the test has failed 69 | Failed { 70 | /// which one 71 | name: String, 72 | /// in how long 73 | exec_time: f32, 74 | /// why? 75 | stdout: Option, 76 | /// it timed out? 77 | reason: Option, 78 | /// what message 79 | message: Option, 80 | }, 81 | /// the test has been ignored 82 | Ignored { 83 | /// which one 84 | name: String, 85 | }, 86 | /// the test has timed out 87 | Timeout { 88 | /// which one 89 | name: String, 90 | }, 91 | } 92 | 93 | impl TestEvent { 94 | /// Get the name of this test 95 | pub fn name(&self) -> &str { 96 | let (Self::Started { name } 97 | | Self::Ok { name, .. } 98 | | Self::Ignored { name } 99 | | Self::Failed { name, .. } 100 | | Self::Timeout { name }) = self; 101 | name 102 | } 103 | 104 | /// Get the stdout of this test, if available. 105 | pub fn stdout(&self) -> Option<&str> { 106 | match self { 107 | Self::Ok { stdout, .. } | Self::Failed { stdout, .. } => stdout.as_deref(), 108 | _ => None, 109 | } 110 | } 111 | } 112 | 113 | #[derive(Debug, PartialEq, Deserialize, Serialize)] 114 | /// Represents the output of `cargo test -- -Zunstable-options --report-time --show-output --format json`. 115 | /// 116 | /// requires --report-time 117 | /// 118 | /// # Stability 119 | /// 120 | /// As this struct is for interfacing with the unstable libtest json output, this struct may change at any time, without semver guarantees. 121 | #[serde(tag = "type")] 122 | #[serde(rename_all = "lowercase")] 123 | pub enum TestMessage { 124 | /// suite related message 125 | Suite(SuiteEvent), 126 | /// test related message 127 | Test(TestEvent), 128 | /// bench related message 129 | Bench { 130 | /// name of benchmark 131 | name: String, 132 | /// distribution 133 | median: f32, 134 | /// deviation 135 | deviation: f32, 136 | /// thruput in MiB per second 137 | mib_per_second: Option, 138 | }, 139 | } 140 | 141 | #[test] 142 | fn deser() { 143 | macro_rules! run { 144 | ($($input:literal parses to $output:expr),+) => { 145 | $(assert_eq!(dbg!(serde_json::from_str::($input)).unwrap(), $output);)+ 146 | }; 147 | } 148 | run![ 149 | r#"{ "type": "suite", "event": "started", "test_count": 2 }"# parses to TestMessage::Suite(SuiteEvent::Started { test_count: 2 }), 150 | r#"{ "type": "test", "event": "started", "name": "fail" }"# parses to TestMessage::Test(TestEvent::Started { name: "fail".into() }), 151 | 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()) }), 152 | r#"{ "type": "test", "event": "started", "name": "nope" }"# parses to TestMessage::Test(TestEvent::Started { name: "nope".into() }), 153 | r#"{ "type": "test", "name": "nope", "event": "ignored" }"# parses to TestMessage::Test(TestEvent::Ignored { name: "nope".into() }), 154 | 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 }) 155 | ]; 156 | 157 | run![ 158 | r#"{ "type": "suite", "event": "started", "test_count": 2 }"# parses to TestMessage::Suite(SuiteEvent::Started { test_count: 2 }), 159 | r#"{ "type": "test", "event": "started", "name": "fail" }"# parses to TestMessage::Test(TestEvent::Started { name: "fail".into() }), 160 | r#"{ "type": "test", "event": "started", "name": "benc" }"# parses to TestMessage::Test(TestEvent::Started { name: "benc".into() }), 161 | r#"{ "type": "bench", "name": "benc", "median": 0, "deviation": 0 }"# parses to TestMessage::Bench { name: "benc".into(), median: 0., deviation: 0., mib_per_second: None }, 162 | 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} ), 163 | 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 }) 164 | ]; 165 | } 166 | -------------------------------------------------------------------------------- /src/messages.rs: -------------------------------------------------------------------------------- 1 | use super::{Diagnostic, PackageId, Target}; 2 | use camino::Utf8PathBuf; 3 | #[cfg(feature = "builder")] 4 | use derive_builder::Builder; 5 | use serde::{de, ser, Deserialize, Serialize}; 6 | use std::fmt::{self, Write}; 7 | use std::io::{self, BufRead, Read}; 8 | 9 | /// Profile settings used to determine which compiler flags to use for a 10 | /// target. 11 | #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)] 12 | #[cfg_attr(feature = "builder", derive(Builder))] 13 | #[non_exhaustive] 14 | #[cfg_attr(feature = "builder", builder(pattern = "owned", setter(into)))] 15 | pub struct ArtifactProfile { 16 | /// Optimization level. Possible values are 0-3, s or z. 17 | pub opt_level: String, 18 | /// The kind of debug information. 19 | #[serde(default)] 20 | pub debuginfo: ArtifactDebuginfo, 21 | /// State of the `cfg(debug_assertions)` directive, enabling macros like 22 | /// `debug_assert!` 23 | pub debug_assertions: bool, 24 | /// State of the overflow checks. 25 | pub overflow_checks: bool, 26 | /// Whether this profile is a test 27 | pub test: bool, 28 | } 29 | 30 | /// The kind of debug information included in the artifact. 31 | #[derive(Debug, Clone, PartialEq, Eq, Hash, Default)] 32 | #[non_exhaustive] 33 | pub enum ArtifactDebuginfo { 34 | /// No debug information. 35 | #[default] 36 | None, 37 | /// Line directives only. 38 | LineDirectivesOnly, 39 | /// Line tables only. 40 | LineTablesOnly, 41 | /// Debug information without type or variable-level information. 42 | Limited, 43 | /// Full debug information. 44 | Full, 45 | /// An unknown integer level. 46 | /// 47 | /// This may be produced by a version of rustc in the future that has 48 | /// additional levels represented by an integer that are not known by this 49 | /// version of `cargo_metadata`. 50 | UnknownInt(i64), 51 | /// An unknown string level. 52 | /// 53 | /// This may be produced by a version of rustc in the future that has 54 | /// additional levels represented by a string that are not known by this 55 | /// version of `cargo_metadata`. 56 | UnknownString(String), 57 | } 58 | 59 | impl ser::Serialize for ArtifactDebuginfo { 60 | fn serialize(&self, serializer: S) -> Result 61 | where 62 | S: ser::Serializer, 63 | { 64 | match self { 65 | Self::None => 0.serialize(serializer), 66 | Self::LineDirectivesOnly => "line-directives-only".serialize(serializer), 67 | Self::LineTablesOnly => "line-tables-only".serialize(serializer), 68 | Self::Limited => 1.serialize(serializer), 69 | Self::Full => 2.serialize(serializer), 70 | Self::UnknownInt(n) => n.serialize(serializer), 71 | Self::UnknownString(s) => s.serialize(serializer), 72 | } 73 | } 74 | } 75 | 76 | impl<'de> de::Deserialize<'de> for ArtifactDebuginfo { 77 | fn deserialize(d: D) -> Result 78 | where 79 | D: de::Deserializer<'de>, 80 | { 81 | struct Visitor; 82 | 83 | impl de::Visitor<'_> for Visitor { 84 | type Value = ArtifactDebuginfo; 85 | 86 | fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { 87 | formatter.write_str("an integer or string") 88 | } 89 | 90 | fn visit_i64(self, value: i64) -> Result 91 | where 92 | E: de::Error, 93 | { 94 | let debuginfo = match value { 95 | 0 => ArtifactDebuginfo::None, 96 | 1 => ArtifactDebuginfo::Limited, 97 | 2 => ArtifactDebuginfo::Full, 98 | n => ArtifactDebuginfo::UnknownInt(n), 99 | }; 100 | Ok(debuginfo) 101 | } 102 | 103 | fn visit_u64(self, value: u64) -> Result 104 | where 105 | E: de::Error, 106 | { 107 | self.visit_i64(value as i64) 108 | } 109 | 110 | fn visit_str(self, value: &str) -> Result 111 | where 112 | E: de::Error, 113 | { 114 | let debuginfo = match value { 115 | "none" => ArtifactDebuginfo::None, 116 | "limited" => ArtifactDebuginfo::Limited, 117 | "full" => ArtifactDebuginfo::Full, 118 | "line-directives-only" => ArtifactDebuginfo::LineDirectivesOnly, 119 | "line-tables-only" => ArtifactDebuginfo::LineTablesOnly, 120 | s => ArtifactDebuginfo::UnknownString(s.to_string()), 121 | }; 122 | Ok(debuginfo) 123 | } 124 | 125 | fn visit_unit(self) -> Result 126 | where 127 | E: de::Error, 128 | { 129 | Ok(ArtifactDebuginfo::None) 130 | } 131 | } 132 | 133 | d.deserialize_any(Visitor) 134 | } 135 | } 136 | 137 | impl fmt::Display for ArtifactDebuginfo { 138 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 139 | match self { 140 | ArtifactDebuginfo::None => f.write_char('0'), 141 | ArtifactDebuginfo::Limited => f.write_char('1'), 142 | ArtifactDebuginfo::Full => f.write_char('2'), 143 | ArtifactDebuginfo::LineDirectivesOnly => f.write_str("line-directives-only"), 144 | ArtifactDebuginfo::LineTablesOnly => f.write_str("line-tables-only"), 145 | ArtifactDebuginfo::UnknownInt(n) => write!(f, "{}", n), 146 | ArtifactDebuginfo::UnknownString(s) => f.write_str(s), 147 | } 148 | } 149 | } 150 | 151 | /// A compiler-generated file. 152 | #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)] 153 | #[cfg_attr(feature = "builder", derive(Builder))] 154 | #[non_exhaustive] 155 | #[cfg_attr(feature = "builder", builder(pattern = "owned", setter(into)))] 156 | pub struct Artifact { 157 | /// The package this artifact belongs to 158 | pub package_id: PackageId, 159 | /// Path to the `Cargo.toml` file 160 | #[serde(default)] 161 | pub manifest_path: Utf8PathBuf, 162 | /// The target this artifact was compiled for 163 | pub target: Target, 164 | /// The profile this artifact was compiled with 165 | pub profile: ArtifactProfile, 166 | /// The enabled features for this artifact 167 | pub features: Vec, 168 | /// The full paths to the generated artifacts 169 | /// (e.g. binary file and separate debug info) 170 | pub filenames: Vec, 171 | /// Path to the executable file 172 | pub executable: Option, 173 | /// If true, then the files were already generated 174 | pub fresh: bool, 175 | } 176 | 177 | /// Message left by the compiler 178 | // TODO: Better name. This one comes from machine_message.rs 179 | #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)] 180 | #[cfg_attr(feature = "builder", derive(Builder))] 181 | #[non_exhaustive] 182 | #[cfg_attr(feature = "builder", builder(pattern = "owned", setter(into)))] 183 | pub struct CompilerMessage { 184 | /// The package this message belongs to 185 | pub package_id: PackageId, 186 | /// The target this message is aimed at 187 | pub target: Target, 188 | /// The message the compiler sent. 189 | pub message: Diagnostic, 190 | } 191 | 192 | /// Output of a build script execution. 193 | #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)] 194 | #[cfg_attr(feature = "builder", derive(Builder))] 195 | #[non_exhaustive] 196 | #[cfg_attr(feature = "builder", builder(pattern = "owned", setter(into)))] 197 | pub struct BuildScript { 198 | /// The package this build script execution belongs to 199 | pub package_id: PackageId, 200 | /// The libs to link 201 | pub linked_libs: Vec, 202 | /// The paths to search when resolving libs 203 | pub linked_paths: Vec, 204 | /// Various `--cfg` flags to pass to the compiler 205 | pub cfgs: Vec, 206 | /// The environment variables to add to the compilation 207 | pub env: Vec<(String, String)>, 208 | /// The `OUT_DIR` environment variable where this script places its output 209 | /// 210 | /// Added in Rust 1.41. 211 | #[serde(default)] 212 | pub out_dir: Utf8PathBuf, 213 | } 214 | 215 | /// Final result of a build. 216 | #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)] 217 | #[cfg_attr(feature = "builder", derive(Builder))] 218 | #[non_exhaustive] 219 | #[cfg_attr(feature = "builder", builder(pattern = "owned", setter(into)))] 220 | pub struct BuildFinished { 221 | /// Whether or not the build finished successfully. 222 | pub success: bool, 223 | } 224 | 225 | /// A cargo message 226 | #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)] 227 | #[non_exhaustive] 228 | #[serde(tag = "reason", rename_all = "kebab-case")] 229 | pub enum Message { 230 | /// The compiler generated an artifact 231 | CompilerArtifact(Artifact), 232 | /// The compiler wants to display a message 233 | CompilerMessage(CompilerMessage), 234 | /// A build script successfully executed. 235 | BuildScriptExecuted(BuildScript), 236 | /// The build has finished. 237 | /// 238 | /// This is emitted at the end of the build as the last message. 239 | /// Added in Rust 1.44. 240 | BuildFinished(BuildFinished), 241 | /// A line of text which isn't a cargo or compiler message. 242 | /// Line separator is not included 243 | #[serde(skip)] 244 | TextLine(String), 245 | } 246 | 247 | impl Message { 248 | /// Creates an iterator of Message from a Read outputting a stream of JSON 249 | /// messages. For usage information, look at the top-level documentation. 250 | pub fn parse_stream(input: R) -> MessageIter { 251 | MessageIter { input } 252 | } 253 | } 254 | 255 | impl fmt::Display for CompilerMessage { 256 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 257 | write!(f, "{}", self.message) 258 | } 259 | } 260 | 261 | /// An iterator of Messages. 262 | pub struct MessageIter { 263 | input: R, 264 | } 265 | 266 | impl Iterator for MessageIter { 267 | type Item = io::Result; 268 | fn next(&mut self) -> Option { 269 | let mut line = String::new(); 270 | self.input 271 | .read_line(&mut line) 272 | .map(|n| { 273 | if n == 0 { 274 | None 275 | } else { 276 | if line.ends_with('\n') { 277 | line.truncate(line.len() - 1); 278 | } 279 | let mut deserializer = serde_json::Deserializer::from_str(&line); 280 | deserializer.disable_recursion_limit(); 281 | Some(Message::deserialize(&mut deserializer).unwrap_or(Message::TextLine(line))) 282 | } 283 | }) 284 | .transpose() 285 | } 286 | } 287 | 288 | /// An iterator of Message. 289 | type MessageIterator = 290 | serde_json::StreamDeserializer<'static, serde_json::de::IoRead, Message>; 291 | 292 | /// Creates an iterator of Message from a Read outputting a stream of JSON 293 | /// messages. For usage information, look at the top-level documentation. 294 | #[deprecated(note = "Use Message::parse_stream instead")] 295 | pub fn parse_messages(input: R) -> MessageIterator { 296 | serde_json::Deserializer::from_reader(input).into_iter::() 297 | } 298 | -------------------------------------------------------------------------------- /tests/all/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "all" 7 | version = "0.1.0" 8 | dependencies = [ 9 | "bdep", 10 | "bitflags", 11 | "devdep", 12 | "featdep", 13 | "namedep", 14 | "oldname", 15 | "path-dep", 16 | "windep", 17 | ] 18 | 19 | [[package]] 20 | name = "bdep" 21 | version = "0.1.0" 22 | 23 | [[package]] 24 | name = "bitflags" 25 | version = "1.0.4" 26 | source = "registry+https://github.com/rust-lang/crates.io-index" 27 | checksum = "228047a76f468627ca71776ecdebd732a3423081fcf5125585bcd7c49886ce12" 28 | 29 | [[package]] 30 | name = "devdep" 31 | version = "0.1.0" 32 | 33 | [[package]] 34 | name = "featdep" 35 | version = "0.1.0" 36 | 37 | [[package]] 38 | name = "namedep" 39 | version = "0.1.0" 40 | 41 | [[package]] 42 | name = "oldname" 43 | version = "0.1.0" 44 | 45 | [[package]] 46 | name = "path-dep" 47 | version = "0.1.0" 48 | 49 | [[package]] 50 | name = "windep" 51 | version = "0.1.0" 52 | -------------------------------------------------------------------------------- /tests/all/Cargo.toml: -------------------------------------------------------------------------------- 1 | # Used in tests/test_samples.rs 2 | [package] 3 | name = "all" 4 | version = "0.1.0" 5 | authors = ["Jane Doe "] 6 | edition = "2018" 7 | license = "MIT/Apache-2.0" 8 | license-file = "LICENSE" 9 | description = "Package description." 10 | categories = ["command-line-utilities"] 11 | keywords = ["cli"] 12 | readme = "README.md" 13 | repository = "https://github.com/oli-obk/cargo_metadata/" 14 | homepage = "https://github.com/oli-obk/cargo_metadata/" 15 | documentation = "https://docs.rs/cargo_metadata/" 16 | links = "foo" 17 | publish = false 18 | default-run = "otherbin" 19 | rust-version = "1.56" 20 | 21 | [package.metadata.docs.rs] 22 | all-features = true 23 | default-target = "x86_64-unknown-linux-gnu" 24 | rustc-args = [ "--example-rustc-arg" ] 25 | 26 | [dependencies] 27 | path-dep = { path = "path-dep" } 28 | namedep = { path = "namedep" } 29 | bitflags = { version = "1.0", optional = true } 30 | featdep = { path = "featdep", features = ["i128"], default-features = false } 31 | newname = { path = "oldname", package = "oldname" } 32 | 33 | [dev-dependencies] 34 | devdep = { path = "devdep" } 35 | 36 | [build-dependencies] 37 | bdep = { path = "bdep" } 38 | 39 | [target.'cfg(windows)'.dependencies] 40 | windep = { path = "windep" } 41 | 42 | [features] 43 | default = ["feat1", "bitflags"] 44 | feat1 = [] 45 | feat2 = [] 46 | 47 | [lib] 48 | crate-type = ["rlib", "cdylib", "staticlib"] 49 | 50 | [[bin]] 51 | name = "otherbin" 52 | edition = '2015' 53 | doc = false 54 | 55 | [[bin]] 56 | name = "reqfeat" 57 | required-features = ["feat2"] 58 | 59 | [[bin]] 60 | name = "reqfeat_slash" 61 | required-features = ["featdep/i128"] 62 | 63 | [workspace] 64 | exclude = ["bare-rust-version", "bdep", "benches", "devdep", "examples", "featdep", "namedep", "oldname", "path-dep", "windep"] 65 | 66 | [workspace.metadata.testobject] 67 | myvalue = "abc" 68 | -------------------------------------------------------------------------------- /tests/all/bare-rust-version/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "bare-rust-version" 3 | version = "0.1.0" 4 | edition = "2021" 5 | rust-version = "1.60" 6 | 7 | [dependencies] 8 | -------------------------------------------------------------------------------- /tests/all/bare-rust-version/src/lib.rs: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /tests/all/bdep/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "bdep" 3 | version = "0.1.0" 4 | edition = "2018" 5 | 6 | [dependencies] 7 | -------------------------------------------------------------------------------- /tests/all/bdep/src/lib.rs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oli-obk/cargo_metadata/fa60b98bc8016ee4702b2512dbb1b0d68669cdb0/tests/all/bdep/src/lib.rs -------------------------------------------------------------------------------- /tests/all/benches/b1.rs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oli-obk/cargo_metadata/fa60b98bc8016ee4702b2512dbb1b0d68669cdb0/tests/all/benches/b1.rs -------------------------------------------------------------------------------- /tests/all/build.rs: -------------------------------------------------------------------------------- 1 | fn main() {} 2 | -------------------------------------------------------------------------------- /tests/all/devdep/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "devdep" 3 | version = "0.1.0" 4 | edition = "2018" 5 | 6 | [dependencies] 7 | -------------------------------------------------------------------------------- /tests/all/devdep/src/lib.rs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oli-obk/cargo_metadata/fa60b98bc8016ee4702b2512dbb1b0d68669cdb0/tests/all/devdep/src/lib.rs -------------------------------------------------------------------------------- /tests/all/examples/ex1.rs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oli-obk/cargo_metadata/fa60b98bc8016ee4702b2512dbb1b0d68669cdb0/tests/all/examples/ex1.rs -------------------------------------------------------------------------------- /tests/all/featdep/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "featdep" 3 | version = "0.1.0" 4 | edition = "2018" 5 | 6 | [dependencies] 7 | 8 | [features] 9 | i128 = [] 10 | -------------------------------------------------------------------------------- /tests/all/featdep/src/lib.rs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oli-obk/cargo_metadata/fa60b98bc8016ee4702b2512dbb1b0d68669cdb0/tests/all/featdep/src/lib.rs -------------------------------------------------------------------------------- /tests/all/namedep/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "namedep" 3 | version = "0.1.0" 4 | edition = "2018" 5 | 6 | [dependencies] 7 | 8 | [lib] 9 | name = "different_name" 10 | -------------------------------------------------------------------------------- /tests/all/namedep/src/lib.rs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oli-obk/cargo_metadata/fa60b98bc8016ee4702b2512dbb1b0d68669cdb0/tests/all/namedep/src/lib.rs -------------------------------------------------------------------------------- /tests/all/oldname/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "oldname" 3 | version = "0.1.0" 4 | edition = "2018" 5 | 6 | [dependencies] 7 | -------------------------------------------------------------------------------- /tests/all/oldname/src/lib.rs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oli-obk/cargo_metadata/fa60b98bc8016ee4702b2512dbb1b0d68669cdb0/tests/all/oldname/src/lib.rs -------------------------------------------------------------------------------- /tests/all/path-dep/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "path-dep" 3 | version = "0.1.0" 4 | edition = "2018" 5 | 6 | [dependencies] 7 | -------------------------------------------------------------------------------- /tests/all/path-dep/src/lib.rs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oli-obk/cargo_metadata/fa60b98bc8016ee4702b2512dbb1b0d68669cdb0/tests/all/path-dep/src/lib.rs -------------------------------------------------------------------------------- /tests/all/src/bin/otherbin.rs: -------------------------------------------------------------------------------- 1 | fn main() {} 2 | -------------------------------------------------------------------------------- /tests/all/src/bin/reqfeat.rs: -------------------------------------------------------------------------------- 1 | fn main() {} 2 | -------------------------------------------------------------------------------- /tests/all/src/bin/reqfeat_slash.rs: -------------------------------------------------------------------------------- 1 | fn main() {} 2 | -------------------------------------------------------------------------------- /tests/all/src/lib.rs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oli-obk/cargo_metadata/fa60b98bc8016ee4702b2512dbb1b0d68669cdb0/tests/all/src/lib.rs -------------------------------------------------------------------------------- /tests/all/src/main.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | println!("Hello, world!"); 3 | } 4 | -------------------------------------------------------------------------------- /tests/all/tests/t1.rs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oli-obk/cargo_metadata/fa60b98bc8016ee4702b2512dbb1b0d68669cdb0/tests/all/tests/t1.rs -------------------------------------------------------------------------------- /tests/all/windep/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "windep" 3 | version = "0.1.0" 4 | edition = "2018" 5 | 6 | [dependencies] 7 | -------------------------------------------------------------------------------- /tests/all/windep/src/lib.rs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oli-obk/cargo_metadata/fa60b98bc8016ee4702b2512dbb1b0d68669cdb0/tests/all/windep/src/lib.rs -------------------------------------------------------------------------------- /tests/basic_workspace/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ex_bin" 3 | version = "0.1.0" 4 | 5 | [dependencies] 6 | ex_lib = { version = "0.1.0", path = "./ex_lib" } 7 | 8 | [workspace] 9 | -------------------------------------------------------------------------------- /tests/basic_workspace/ex_lib/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ex_lib" 3 | version = "0.1.0" 4 | 5 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 6 | 7 | [dependencies] 8 | -------------------------------------------------------------------------------- /tests/basic_workspace/ex_lib/src/lib.rs: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /tests/basic_workspace/src/main.rs: -------------------------------------------------------------------------------- 1 | fn main() {} 2 | -------------------------------------------------------------------------------- /tests/selftest.rs: -------------------------------------------------------------------------------- 1 | use std::env::current_dir; 2 | use std::path::PathBuf; 3 | 4 | use semver::Version; 5 | 6 | use cargo_metadata::{CargoOpt, Error, MetadataCommand}; 7 | use serde::Deserialize; 8 | 9 | #[derive(Debug, PartialEq, Eq, Deserialize)] 10 | struct TestPackageMetadata { 11 | some_field: bool, 12 | other_field: String, 13 | } 14 | 15 | #[test] 16 | fn metadata() { 17 | let metadata = MetadataCommand::new().no_deps().exec().unwrap(); 18 | 19 | let this = &metadata.packages[0]; 20 | assert_eq!(this.name.as_str(), "cargo_metadata"); 21 | assert_eq!(this.targets.len(), 3); 22 | 23 | let lib = this 24 | .targets 25 | .iter() 26 | .find(|t| t.name == "cargo_metadata") 27 | .unwrap(); 28 | assert_eq!(lib.kind[0], "lib".into()); 29 | assert_eq!(lib.crate_types[0], "lib".into()); 30 | 31 | let selftest = this.targets.iter().find(|t| t.name == "selftest").unwrap(); 32 | assert_eq!(selftest.name, "selftest"); 33 | assert_eq!(selftest.kind[0], "test".into()); 34 | assert_eq!(selftest.crate_types[0], "bin".into()); 35 | 36 | let package_metadata = &metadata.packages[0] 37 | .metadata 38 | .as_object() 39 | .expect("package.metadata must be a table."); 40 | // The second field is docs.rs metadata, ignore it 41 | assert_eq!(package_metadata.len(), 2); 42 | 43 | let value = package_metadata.get("cargo_metadata_test").unwrap(); 44 | let test_package_metadata: TestPackageMetadata = serde_json::from_value(value.clone()).unwrap(); 45 | assert_eq!( 46 | test_package_metadata, 47 | TestPackageMetadata { 48 | some_field: true, 49 | other_field: "foo".into(), 50 | } 51 | ); 52 | } 53 | 54 | #[test] 55 | fn builder_interface() { 56 | let _ = MetadataCommand::new() 57 | .manifest_path("Cargo.toml") 58 | .exec() 59 | .unwrap(); 60 | let _ = MetadataCommand::new() 61 | .manifest_path(String::from("Cargo.toml")) 62 | .exec() 63 | .unwrap(); 64 | let _ = MetadataCommand::new() 65 | .manifest_path(PathBuf::from("Cargo.toml")) 66 | .exec() 67 | .unwrap(); 68 | let _ = MetadataCommand::new() 69 | .manifest_path("Cargo.toml") 70 | .no_deps() 71 | .exec() 72 | .unwrap(); 73 | let _ = MetadataCommand::new() 74 | .manifest_path("Cargo.toml") 75 | .features(CargoOpt::AllFeatures) 76 | .exec() 77 | .unwrap(); 78 | let _ = MetadataCommand::new() 79 | .manifest_path("Cargo.toml") 80 | .current_dir(current_dir().unwrap()) 81 | .exec() 82 | .unwrap(); 83 | } 84 | 85 | #[test] 86 | fn error1() { 87 | match MetadataCommand::new().manifest_path("foo").exec() { 88 | Err(Error::CargoMetadata { stderr }) => assert_eq!( 89 | stderr.trim(), 90 | "error: the manifest-path must be a path to a Cargo.toml file" 91 | ), 92 | _ => unreachable!(), 93 | } 94 | } 95 | 96 | #[test] 97 | fn error2() { 98 | match MetadataCommand::new() 99 | .manifest_path("foo/Cargo.toml") 100 | .exec() 101 | { 102 | Err(Error::CargoMetadata { stderr }) => assert_eq!( 103 | stderr.trim(), 104 | "error: manifest path `foo/Cargo.toml` does not exist" 105 | ), 106 | _ => unreachable!(), 107 | } 108 | } 109 | 110 | #[test] 111 | fn cargo_path() { 112 | match MetadataCommand::new() 113 | .cargo_path("this does not exist") 114 | .exec() 115 | { 116 | Err(Error::Io(e)) => assert_eq!(e.kind(), std::io::ErrorKind::NotFound), 117 | _ => unreachable!(), 118 | } 119 | } 120 | 121 | #[test] 122 | fn metadata_deps() { 123 | std::env::set_var("CARGO_PROFILE", "3"); 124 | let metadata = MetadataCommand::new() 125 | .manifest_path("Cargo.toml") 126 | .exec() 127 | .unwrap(); 128 | let this_id = metadata 129 | .workspace_members 130 | .first() 131 | .expect("Did not find ourselves"); 132 | let this = &metadata[this_id]; 133 | 134 | assert_eq!(this.name.as_str(), "cargo_metadata"); 135 | 136 | let workspace_packages = metadata.workspace_packages(); 137 | assert_eq!(workspace_packages.len(), 1); 138 | assert_eq!(&workspace_packages[0].id, this_id); 139 | 140 | let lib = this 141 | .targets 142 | .iter() 143 | .find(|t| t.name == "cargo_metadata") 144 | .unwrap(); 145 | assert_eq!(lib.kind[0], "lib".into()); 146 | assert_eq!(lib.crate_types[0], "lib".into()); 147 | 148 | let selftest = this.targets.iter().find(|t| t.name == "selftest").unwrap(); 149 | assert_eq!(selftest.name, "selftest"); 150 | assert_eq!(selftest.kind[0], "test".into()); 151 | assert_eq!(selftest.crate_types[0], "bin".into()); 152 | 153 | let dependencies = &this.dependencies; 154 | 155 | let serde = dependencies 156 | .iter() 157 | .find(|dep| dep.name == "serde") 158 | .expect("Did not find serde dependency"); 159 | 160 | assert_eq!(serde.kind, cargo_metadata::DependencyKind::Normal); 161 | assert!(!serde.req.matches(&Version::parse("1.0.0").unwrap())); 162 | assert!(serde.req.matches(&Version::parse("1.99.99").unwrap())); 163 | assert!(!serde.req.matches(&Version::parse("2.0.0").unwrap())); 164 | } 165 | 166 | #[test] 167 | fn workspace_default_packages() { 168 | let metadata = MetadataCommand::new() 169 | .manifest_path("Cargo.toml") 170 | .exec() 171 | .unwrap(); 172 | let workspace_packages = metadata.workspace_packages(); 173 | // this will only trigger on cargo versions that expose 174 | // workspace_default_members (that is, cargo >= 1.71) 175 | if metadata.workspace_default_members.is_available() { 176 | let default_packages = metadata.workspace_default_packages(); 177 | assert_eq!(default_packages, workspace_packages); 178 | } 179 | } 180 | -------------------------------------------------------------------------------- /tests/test_samples.rs: -------------------------------------------------------------------------------- 1 | extern crate cargo_metadata; 2 | extern crate semver; 3 | #[macro_use] 4 | extern crate serde_json; 5 | 6 | use camino::Utf8PathBuf; 7 | use cargo_metadata::{ 8 | ArtifactDebuginfo, CargoOpt, DependencyKind, Edition, Message, Metadata, MetadataCommand, 9 | Source, 10 | }; 11 | use cargo_util_schemas::manifest::FeatureName; 12 | 13 | /// Output from oldest version ever supported (1.24). 14 | /// 15 | /// This intentionally has as many null fields as possible. 16 | /// 1.8 is when metadata was introduced. 17 | /// Older versions not supported because the following are required: 18 | /// - `workspace_members` added in 1.13 19 | /// - `target_directory` added in 1.19 20 | /// - `workspace_root` added in 1.24 21 | const JSON_OLD_MINIMAL: &str = r#" 22 | { 23 | "packages": [ 24 | { 25 | "name": "foo", 26 | "version": "0.1.0", 27 | "id": "foo 0.1.0 (path+file:///foo)", 28 | "license": null, 29 | "license_file": null, 30 | "description": null, 31 | "source": null, 32 | "dependencies": [ 33 | { 34 | "name": "somedep", 35 | "source": null, 36 | "req": "^1.0", 37 | "kind": null, 38 | "optional": false, 39 | "uses_default_features": true, 40 | "features": [], 41 | "target": null 42 | } 43 | ], 44 | "targets": [ 45 | { 46 | "kind": [ 47 | "bin" 48 | ], 49 | "crate_types": [ 50 | "bin" 51 | ], 52 | "name": "foo", 53 | "src_path": "/foo/src/main.rs" 54 | } 55 | ], 56 | "features": {}, 57 | "manifest_path": "/foo/Cargo.toml" 58 | } 59 | ], 60 | "workspace_members": [ 61 | "foo 0.1.0 (path+file:///foo)" 62 | ], 63 | "resolve": null, 64 | "target_directory": "/foo/target", 65 | "version": 1, 66 | "workspace_root": "/foo" 67 | } 68 | "#; 69 | 70 | #[test] 71 | fn old_minimal() { 72 | let meta: Metadata = serde_json::from_str(JSON_OLD_MINIMAL).unwrap(); 73 | assert_eq!(meta.packages.len(), 1); 74 | let pkg = &meta.packages[0]; 75 | assert_eq!(pkg.name.as_str(), "foo"); 76 | assert_eq!(pkg.version, semver::Version::parse("0.1.0").unwrap()); 77 | assert_eq!(pkg.authors.len(), 0); 78 | assert_eq!(pkg.id.to_string(), "foo 0.1.0 (path+file:///foo)"); 79 | assert_eq!(pkg.description, None); 80 | assert_eq!(pkg.license, None); 81 | assert_eq!(pkg.license_file, None); 82 | assert_eq!(pkg.default_run, None); 83 | assert_eq!(pkg.rust_version, None); 84 | assert_eq!(pkg.dependencies.len(), 1); 85 | let dep = &pkg.dependencies[0]; 86 | assert_eq!(dep.name, "somedep"); 87 | assert_eq!(dep.source, None); 88 | assert_eq!(dep.req, semver::VersionReq::parse("^1.0").unwrap()); 89 | assert_eq!(dep.kind, DependencyKind::Normal); 90 | assert!(!dep.optional); 91 | assert!(dep.uses_default_features); 92 | assert_eq!(dep.features.len(), 0); 93 | assert!(dep.target.is_none()); 94 | assert_eq!(dep.rename, None); 95 | assert_eq!(dep.registry, None); 96 | assert_eq!(pkg.targets.len(), 1); 97 | let target = &pkg.targets[0]; 98 | assert_eq!(target.name, "foo"); 99 | assert_eq!(target.kind, vec!["bin".into()]); 100 | assert_eq!(target.crate_types, vec!["bin".into()]); 101 | assert_eq!(target.required_features.len(), 0); 102 | assert_eq!(target.src_path, "/foo/src/main.rs"); 103 | assert_eq!(target.edition, Edition::E2015); 104 | assert!(target.doctest); 105 | assert!(target.test); 106 | assert!(target.doc); 107 | assert_eq!(pkg.features.len(), 0); 108 | assert_eq!(pkg.manifest_path, "/foo/Cargo.toml"); 109 | assert_eq!(pkg.categories.len(), 0); 110 | assert_eq!(pkg.keywords.len(), 0); 111 | assert_eq!(pkg.readme, None); 112 | assert_eq!(pkg.repository, None); 113 | assert_eq!(pkg.homepage, None); 114 | assert_eq!(pkg.documentation, None); 115 | assert_eq!(pkg.edition, Edition::E2015); 116 | assert_eq!(pkg.metadata, serde_json::Value::Null); 117 | assert_eq!(pkg.links, None); 118 | assert_eq!(pkg.publish, None); 119 | assert_eq!(meta.workspace_members.len(), 1); 120 | assert_eq!( 121 | meta.workspace_members[0].to_string(), 122 | "foo 0.1.0 (path+file:///foo)" 123 | ); 124 | assert!(meta.resolve.is_none()); 125 | assert_eq!(meta.workspace_root, "/foo"); 126 | assert_eq!(meta.workspace_metadata, serde_json::Value::Null); 127 | assert_eq!(meta.target_directory, "/foo/target"); 128 | 129 | assert!(!meta.workspace_default_members.is_available()); 130 | assert!(meta.workspace_default_members.is_missing()); 131 | 132 | let serialized = serde_json::to_value(meta).unwrap(); 133 | assert!(!serialized 134 | .as_object() 135 | .unwrap() 136 | .contains_key("workspace_default_members")); 137 | } 138 | 139 | macro_rules! sorted { 140 | ($e:expr) => {{ 141 | let mut v = $e.clone(); 142 | v.sort(); 143 | v 144 | }}; 145 | } 146 | 147 | macro_rules! features { 148 | ($($feat:expr),* $(,)?) => { 149 | ::std::vec![ 150 | $(::cargo_util_schemas::manifest::FeatureName::new(String::from($feat)).unwrap()),* 151 | ] 152 | }; 153 | } 154 | 155 | fn cargo_version() -> semver::Version { 156 | let output = std::process::Command::new("cargo") 157 | .arg("-V") 158 | .output() 159 | .expect("Failed to exec cargo."); 160 | let out = std::str::from_utf8(&output.stdout) 161 | .expect("invalid utf8") 162 | .trim(); 163 | let split: Vec<&str> = out.split_whitespace().collect(); 164 | assert!(split.len() >= 2, "cargo -V output is unexpected: {}", out); 165 | let mut ver = semver::Version::parse(split[1]).expect("cargo -V semver could not be parsed"); 166 | // Don't care about metadata, it is awkward to compare. 167 | ver.pre = semver::Prerelease::EMPTY; 168 | ver.build = semver::BuildMetadata::EMPTY; 169 | ver 170 | } 171 | 172 | #[derive(serde::Deserialize, PartialEq, Eq, Debug)] 173 | struct WorkspaceMetadata { 174 | testobject: TestObject, 175 | } 176 | 177 | #[derive(serde::Deserialize, PartialEq, Eq, Debug)] 178 | struct TestObject { 179 | myvalue: String, 180 | } 181 | 182 | #[test] 183 | fn all_the_fields() { 184 | // All the fields currently generated as of 1.60. This tries to exercise as 185 | // much as possible. 186 | let ver = cargo_version(); 187 | let minimum = semver::Version::parse("1.56.0").unwrap(); 188 | if ver < minimum { 189 | // edition added in 1.30 190 | // rename added in 1.31 191 | // links added in 1.33 192 | // doctest added in 1.37 193 | // publish added in 1.39 194 | // dep_kinds added in 1.41 195 | // test added in 1.47 196 | // homepage added in 1.49 197 | // documentation added in 1.49 198 | // doc added in 1.50 199 | // path added in 1.51 200 | // default_run added in 1.55 201 | // rust_version added in 1.58 202 | // workspace_default_members added in 1.71 203 | eprintln!("Skipping all_the_fields test, cargo {} is too old.", ver); 204 | return; 205 | } 206 | let meta = MetadataCommand::new() 207 | .manifest_path("tests/all/Cargo.toml") 208 | .exec() 209 | .unwrap(); 210 | assert_eq!(meta.workspace_root.file_name().unwrap(), "all"); 211 | assert_eq!( 212 | serde_json::from_value::(meta.workspace_metadata.clone()).unwrap(), 213 | WorkspaceMetadata { 214 | testobject: TestObject { 215 | myvalue: "abc".to_string() 216 | } 217 | } 218 | ); 219 | assert_eq!(meta.workspace_members.len(), 1); 220 | assert!(meta.workspace_members[0].to_string().contains("all")); 221 | if ver >= semver::Version::parse("1.71.0").unwrap() { 222 | assert_eq!(&*meta.workspace_default_members, &meta.workspace_members); 223 | } 224 | 225 | assert_eq!(meta.packages.len(), 9); 226 | let all = meta 227 | .packages 228 | .iter() 229 | .find(|p| p.name.as_str() == "all") 230 | .unwrap(); 231 | assert_eq!(all.version, semver::Version::parse("0.1.0").unwrap()); 232 | assert_eq!(all.authors, vec!["Jane Doe "]); 233 | assert!(all.id.to_string().contains("all")); 234 | assert_eq!(all.description, Some("Package description.".to_string())); 235 | assert_eq!(all.license, Some("MIT/Apache-2.0".to_string())); 236 | assert_eq!(all.license_file, Some(Utf8PathBuf::from("LICENSE"))); 237 | assert!(all.license_file().unwrap().ends_with("tests/all/LICENSE")); 238 | assert_eq!(all.publish, Some(vec![])); 239 | assert_eq!(all.links, Some("foo".to_string())); 240 | assert_eq!(all.default_run, Some("otherbin".to_string())); 241 | if ver >= semver::Version::parse("1.58.0").unwrap() { 242 | assert_eq!( 243 | all.rust_version, 244 | Some(semver::Version::parse("1.56.0").unwrap()) 245 | ); 246 | } 247 | 248 | assert_eq!(all.dependencies.len(), 8); 249 | let bitflags = all 250 | .dependencies 251 | .iter() 252 | .find(|d| d.name == "bitflags") 253 | .unwrap(); 254 | assert_eq!( 255 | bitflags.source, 256 | Some(Source { 257 | repr: "registry+https://github.com/rust-lang/crates.io-index".to_string() 258 | }) 259 | ); 260 | assert!(bitflags.optional); 261 | assert_eq!(bitflags.req, semver::VersionReq::parse("^1.0").unwrap()); 262 | 263 | let path_dep = all 264 | .dependencies 265 | .iter() 266 | .find(|d| d.name == "path-dep") 267 | .unwrap(); 268 | assert_eq!(path_dep.source, None); 269 | assert_eq!(path_dep.kind, DependencyKind::Normal); 270 | assert_eq!(path_dep.req, semver::VersionReq::parse("*").unwrap()); 271 | assert_eq!( 272 | path_dep.path.as_ref().map(|p| p.ends_with("path-dep")), 273 | Some(true), 274 | ); 275 | 276 | all.dependencies 277 | .iter() 278 | .find(|d| d.name == "namedep") 279 | .unwrap(); 280 | 281 | let featdep = all 282 | .dependencies 283 | .iter() 284 | .find(|d| d.name == "featdep") 285 | .unwrap(); 286 | assert_eq!(featdep.features, vec!["i128"]); 287 | assert!(!featdep.uses_default_features); 288 | 289 | let renamed = all 290 | .dependencies 291 | .iter() 292 | .find(|d| d.name == "oldname") 293 | .unwrap(); 294 | assert_eq!(renamed.rename, Some("newname".to_string())); 295 | 296 | let devdep = all 297 | .dependencies 298 | .iter() 299 | .find(|d| d.name == "devdep") 300 | .unwrap(); 301 | assert_eq!(devdep.kind, DependencyKind::Development); 302 | 303 | let bdep = all.dependencies.iter().find(|d| d.name == "bdep").unwrap(); 304 | assert_eq!(bdep.kind, DependencyKind::Build); 305 | 306 | let windep = all 307 | .dependencies 308 | .iter() 309 | .find(|d| d.name == "windep") 310 | .unwrap(); 311 | assert_eq!( 312 | windep.target.as_ref().map(|x| x.to_string()), 313 | Some("cfg(windows)".to_string()) 314 | ); 315 | 316 | macro_rules! get_file_name { 317 | ($v:expr) => { 318 | all.targets 319 | .iter() 320 | .find(|t| t.src_path.file_name().unwrap() == $v) 321 | .unwrap() 322 | }; 323 | } 324 | assert_eq!(all.targets.len(), 9); 325 | let lib = get_file_name!("lib.rs"); 326 | assert_eq!(lib.name, "all"); 327 | assert_eq!( 328 | sorted!(lib.kind), 329 | vec!["cdylib".into(), "rlib".into(), "staticlib".into()] 330 | ); 331 | assert_eq!( 332 | sorted!(lib.crate_types), 333 | vec!["cdylib".into(), "rlib".into(), "staticlib".into()] 334 | ); 335 | assert_eq!(lib.required_features.len(), 0); 336 | assert_eq!(lib.edition, Edition::E2018); 337 | assert!(lib.doctest); 338 | assert!(lib.test); 339 | assert!(lib.doc); 340 | 341 | let main = get_file_name!("main.rs"); 342 | assert_eq!(main.crate_types, vec!["bin".into()]); 343 | assert_eq!(main.kind, vec!["bin".into()]); 344 | assert!(!main.doctest); 345 | assert!(main.test); 346 | assert!(main.doc); 347 | 348 | let otherbin = get_file_name!("otherbin.rs"); 349 | assert_eq!(otherbin.edition, Edition::E2015); 350 | assert!(!otherbin.doc); 351 | 352 | let reqfeat = get_file_name!("reqfeat.rs"); 353 | assert_eq!(reqfeat.required_features, vec!["feat2"]); 354 | 355 | let reqfeat_slash = get_file_name!("reqfeat_slash.rs"); 356 | assert_eq!(reqfeat_slash.required_features, vec!["featdep/i128"]); 357 | 358 | let ex1 = get_file_name!("ex1.rs"); 359 | assert_eq!(ex1.kind, vec!["example".into()]); 360 | assert!(!ex1.test); 361 | 362 | let t1 = get_file_name!("t1.rs"); 363 | assert_eq!(t1.kind, vec!["test".into()]); 364 | 365 | let b1 = get_file_name!("b1.rs"); 366 | assert_eq!(b1.kind, vec!["bench".into()]); 367 | 368 | let build = get_file_name!("build.rs"); 369 | assert_eq!(build.kind, vec!["custom-build".into()]); 370 | 371 | if ver >= semver::Version::parse("1.60.0").unwrap() { 372 | // 1.60 now reports optional dependencies within the features table 373 | assert_eq!(all.features.len(), 4); 374 | assert_eq!(all.features["bitflags"], vec!["dep:bitflags"]); 375 | } else { 376 | assert_eq!(all.features.len(), 3); 377 | } 378 | assert_eq!(all.features["feat1"].len(), 0); 379 | assert_eq!(all.features["feat2"].len(), 0); 380 | assert_eq!(sorted!(all.features["default"]), vec!["bitflags", "feat1"]); 381 | 382 | assert!(all.manifest_path.ends_with("all/Cargo.toml")); 383 | assert_eq!(all.categories, vec!["command-line-utilities"]); 384 | assert_eq!(all.keywords, vec!["cli"]); 385 | assert_eq!(all.readme, Some(Utf8PathBuf::from("README.md"))); 386 | assert!(all.readme().unwrap().ends_with("tests/all/README.md")); 387 | assert_eq!( 388 | all.repository, 389 | Some("https://github.com/oli-obk/cargo_metadata/".to_string()) 390 | ); 391 | assert_eq!( 392 | all.homepage, 393 | Some("https://github.com/oli-obk/cargo_metadata/".to_string()) 394 | ); 395 | assert_eq!( 396 | all.documentation, 397 | Some("https://docs.rs/cargo_metadata/".to_string()) 398 | ); 399 | assert_eq!(all.edition, Edition::E2018); 400 | assert_eq!( 401 | all.metadata, 402 | json!({ 403 | "docs": { 404 | "rs": { 405 | "all-features": true, 406 | "default-target": "x86_64-unknown-linux-gnu", 407 | "rustc-args": ["--example-rustc-arg"] 408 | } 409 | } 410 | }) 411 | ); 412 | 413 | let resolve = meta.resolve.as_ref().unwrap(); 414 | assert!(resolve.root.as_ref().unwrap().to_string().contains("all")); 415 | 416 | assert_eq!(resolve.nodes.len(), 9); 417 | let path_dep = resolve 418 | .nodes 419 | .iter() 420 | .find(|n| n.id.to_string().contains("path-dep")) 421 | .unwrap(); 422 | assert_eq!(path_dep.deps.len(), 0); 423 | assert_eq!(path_dep.dependencies.len(), 0); 424 | assert_eq!(path_dep.features.len(), 0); 425 | 426 | let bitflags = resolve 427 | .nodes 428 | .iter() 429 | .find(|n| n.id.to_string().contains("bitflags")) 430 | .unwrap(); 431 | assert_eq!(bitflags.features, features!["default"]); 432 | 433 | let featdep = resolve 434 | .nodes 435 | .iter() 436 | .find(|n| n.id.to_string().contains("featdep")) 437 | .unwrap(); 438 | assert_eq!(featdep.features, features!["i128"]); 439 | 440 | let all = resolve 441 | .nodes 442 | .iter() 443 | .find(|n| n.id.to_string().contains("all")) 444 | .unwrap(); 445 | assert_eq!(all.dependencies.len(), 8); 446 | assert_eq!(all.deps.len(), 8); 447 | let newname = all.deps.iter().find(|d| &*d.name == "newname").unwrap(); 448 | assert!(newname.pkg.to_string().contains("oldname")); 449 | // Note the underscore here. 450 | let path_dep = all.deps.iter().find(|d| &*d.name == "path_dep").unwrap(); 451 | assert!(path_dep.pkg.to_string().contains("path-dep")); 452 | assert_eq!(path_dep.dep_kinds.len(), 1); 453 | let kind = &path_dep.dep_kinds[0]; 454 | assert_eq!(kind.kind, DependencyKind::Normal); 455 | assert!(kind.target.is_none()); 456 | 457 | let namedep = all 458 | .deps 459 | .iter() 460 | .find(|d| &*d.name == "different_name") 461 | .unwrap(); 462 | assert!(namedep.pkg.to_string().contains("namedep")); 463 | assert_eq!( 464 | sorted!(all.features), 465 | features!["bitflags", "default", "feat1"] 466 | ); 467 | 468 | let bdep = all.deps.iter().find(|d| &*d.name == "bdep").unwrap(); 469 | assert_eq!(bdep.dep_kinds.len(), 1); 470 | let kind = &bdep.dep_kinds[0]; 471 | assert_eq!(kind.kind, DependencyKind::Build); 472 | assert!(kind.target.is_none()); 473 | 474 | let devdep = all.deps.iter().find(|d| &*d.name == "devdep").unwrap(); 475 | assert_eq!(devdep.dep_kinds.len(), 1); 476 | let kind = &devdep.dep_kinds[0]; 477 | assert_eq!(kind.kind, DependencyKind::Development); 478 | assert!(kind.target.is_none()); 479 | 480 | let windep = all.deps.iter().find(|d| &*d.name == "windep").unwrap(); 481 | assert_eq!(windep.dep_kinds.len(), 1); 482 | let kind = &windep.dep_kinds[0]; 483 | assert_eq!(kind.kind, DependencyKind::Normal); 484 | assert_eq!( 485 | kind.target.as_ref().map(|x| x.to_string()), 486 | Some("cfg(windows)".to_string()) 487 | ); 488 | 489 | let serialized = serde_json::to_value(meta).unwrap(); 490 | if ver >= semver::Version::parse("1.71.0").unwrap() { 491 | assert!(serialized.as_object().unwrap()["workspace_default_members"] 492 | .as_array() 493 | .is_some()); 494 | } else { 495 | assert!(!serialized 496 | .as_object() 497 | .unwrap() 498 | .contains_key("workspace_default_members")); 499 | } 500 | } 501 | 502 | #[test] 503 | fn alt_registry() { 504 | // This is difficult to test (would need to set up a custom index). 505 | // Just manually check the JSON is handled. 506 | let json = r#" 507 | { 508 | "packages": [ 509 | { 510 | "name": "alt", 511 | "version": "0.1.0", 512 | "id": "alt 0.1.0 (path+file:///alt)", 513 | "source": null, 514 | "dependencies": [ 515 | { 516 | "name": "alt2", 517 | "source": "registry+https://example.com", 518 | "req": "^0.1", 519 | "kind": null, 520 | "rename": null, 521 | "optional": false, 522 | "uses_default_features": true, 523 | "features": [], 524 | "target": null, 525 | "registry": "https://example.com" 526 | } 527 | ], 528 | "targets": [ 529 | { 530 | "kind": [ 531 | "lib" 532 | ], 533 | "crate_types": [ 534 | "lib" 535 | ], 536 | "name": "alt", 537 | "src_path": "/alt/src/lib.rs", 538 | "edition": "2018" 539 | } 540 | ], 541 | "features": {}, 542 | "manifest_path": "/alt/Cargo.toml", 543 | "metadata": null, 544 | "authors": [], 545 | "categories": [], 546 | "keywords": [], 547 | "readme": null, 548 | "repository": null, 549 | "edition": "2018", 550 | "links": null 551 | } 552 | ], 553 | "workspace_members": [ 554 | "alt 0.1.0 (path+file:///alt)" 555 | ], 556 | "resolve": null, 557 | "target_directory": "/alt/target", 558 | "version": 1, 559 | "workspace_root": "/alt" 560 | } 561 | "#; 562 | let meta: Metadata = serde_json::from_str(json).unwrap(); 563 | assert_eq!(meta.packages.len(), 1); 564 | let alt = &meta.packages[0]; 565 | let deps = &alt.dependencies; 566 | assert_eq!(deps.len(), 1); 567 | let dep = &deps[0]; 568 | assert_eq!(dep.registry, Some("https://example.com".to_string())); 569 | } 570 | 571 | #[test] 572 | fn current_dir() { 573 | let meta = MetadataCommand::new() 574 | .current_dir("tests/all/namedep") 575 | .exec() 576 | .unwrap(); 577 | let namedep = meta 578 | .packages 579 | .iter() 580 | .find(|p| p.name.as_str() == "namedep") 581 | .unwrap(); 582 | assert!(namedep.name.starts_with("namedep")); 583 | } 584 | 585 | #[test] 586 | fn parse_stream_is_robust() { 587 | // Proc macros can print stuff to stdout, which naturally breaks JSON messages. 588 | // Let's check that we don't die horribly in this case, and report an error. 589 | 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} 590 | Evil proc macro was here! 591 | {"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} 592 | "##; 593 | let mut n_messages = 0; 594 | let mut text = String::new(); 595 | for message in cargo_metadata::Message::parse_stream(json_output.as_bytes()) { 596 | let message = message.unwrap(); 597 | match message { 598 | cargo_metadata::Message::TextLine(line) => text = line, 599 | _ => n_messages += 1, 600 | } 601 | } 602 | assert_eq!(n_messages, 2); 603 | assert_eq!(text, "Evil proc macro was here!"); 604 | } 605 | 606 | #[test] 607 | fn advanced_feature_configuration() { 608 | fn build_features &mut MetadataCommand>( 609 | func: F, 610 | ) -> Vec { 611 | let mut meta = MetadataCommand::new(); 612 | let meta = meta.manifest_path("tests/all/Cargo.toml"); 613 | 614 | let meta = func(meta); 615 | let meta = meta.exec().unwrap(); 616 | 617 | let resolve = meta.resolve.as_ref().unwrap(); 618 | 619 | let all = resolve 620 | .nodes 621 | .iter() 622 | .find(|n| !n.features.is_empty()) 623 | .unwrap(); 624 | 625 | all.features 626 | .clone() 627 | .into_iter() 628 | .map(FeatureName::into_inner) 629 | .collect() 630 | } 631 | 632 | // Default behavior; tested above 633 | let default_features = build_features(|meta| meta); 634 | assert_eq!( 635 | sorted!(default_features), 636 | vec!["bitflags", "default", "feat1"] 637 | ); 638 | 639 | // Manually specify the same default features 640 | let manual_features = build_features(|meta| { 641 | meta.features(CargoOpt::NoDefaultFeatures) 642 | .features(CargoOpt::SomeFeatures(vec![ 643 | "feat1".into(), 644 | "bitflags".into(), 645 | ])) 646 | }); 647 | assert_eq!(sorted!(manual_features), vec!["bitflags", "feat1"]); 648 | 649 | // Multiple SomeFeatures is same as one longer SomeFeatures 650 | let manual_features = build_features(|meta| { 651 | meta.features(CargoOpt::NoDefaultFeatures) 652 | .features(CargoOpt::SomeFeatures(vec!["feat1".into()])) 653 | .features(CargoOpt::SomeFeatures(vec!["feat2".into()])) 654 | }); 655 | assert_eq!(sorted!(manual_features), vec!["feat1", "feat2"]); 656 | 657 | // No features + All features == All features 658 | let all_features = build_features(|meta| { 659 | meta.features(CargoOpt::AllFeatures) 660 | .features(CargoOpt::NoDefaultFeatures) 661 | }); 662 | assert_eq!( 663 | sorted!(all_features), 664 | vec!["bitflags", "default", "feat1", "feat2"] 665 | ); 666 | 667 | // The '--all-features' flag supersedes other feature flags 668 | let all_flag_variants = build_features(|meta| { 669 | meta.features(CargoOpt::SomeFeatures(vec!["feat2".into()])) 670 | .features(CargoOpt::NoDefaultFeatures) 671 | .features(CargoOpt::AllFeatures) 672 | }); 673 | assert_eq!(sorted!(all_flag_variants), sorted!(all_features)); 674 | } 675 | 676 | #[test] 677 | fn depkind_to_string() { 678 | assert_eq!(DependencyKind::Normal.to_string(), "normal"); 679 | assert_eq!(DependencyKind::Development.to_string(), "dev"); 680 | assert_eq!(DependencyKind::Build.to_string(), "build"); 681 | assert_eq!(DependencyKind::Unknown.to_string(), "Unknown"); 682 | } 683 | 684 | #[test] 685 | fn basic_workspace_root_package_exists() { 686 | // First try with dependencies 687 | let meta = MetadataCommand::new() 688 | .manifest_path("tests/basic_workspace/Cargo.toml") 689 | .exec() 690 | .unwrap(); 691 | assert_eq!(meta.root_package().unwrap().name.as_str(), "ex_bin"); 692 | // Now with no_deps, it should still work exactly the same 693 | let meta = MetadataCommand::new() 694 | .manifest_path("tests/basic_workspace/Cargo.toml") 695 | .no_deps() 696 | .exec() 697 | .unwrap(); 698 | assert_eq!( 699 | meta.root_package() 700 | .expect("workspace root still exists when no_deps used") 701 | .name 702 | .as_str(), 703 | "ex_bin" 704 | ); 705 | } 706 | 707 | #[test] 708 | fn debuginfo_variants() { 709 | // Checks behavior for the different debuginfo variants. 710 | let variants = [ 711 | ("0", ArtifactDebuginfo::None), 712 | ("1", ArtifactDebuginfo::Limited), 713 | ("2", ArtifactDebuginfo::Full), 714 | ( 715 | "\"line-directives-only\"", 716 | ArtifactDebuginfo::LineDirectivesOnly, 717 | ), 718 | ("\"line-tables-only\"", ArtifactDebuginfo::LineTablesOnly), 719 | ("3", ArtifactDebuginfo::UnknownInt(3)), 720 | ( 721 | "\"abc\"", 722 | ArtifactDebuginfo::UnknownString("abc".to_string()), 723 | ), 724 | ("null", ArtifactDebuginfo::None), 725 | ]; 726 | for (value, expected) in variants { 727 | 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}"#; 728 | let message: Message = serde_json::from_str(&s.replace("DEBUGINFO", value)).unwrap(); 729 | match message { 730 | Message::CompilerArtifact(artifact) => { 731 | assert_eq!(artifact.profile.debuginfo, expected); 732 | let de_s = serde_json::to_string(&artifact.profile.debuginfo).unwrap(); 733 | // Note: Roundtrip does not retain null value. 734 | if value == "null" { 735 | assert_eq!(artifact.profile.debuginfo.to_string(), "0"); 736 | assert_eq!(de_s, "0"); 737 | } else { 738 | assert_eq!( 739 | artifact.profile.debuginfo.to_string(), 740 | value.trim_matches('"') 741 | ); 742 | assert_eq!(de_s, value); 743 | } 744 | } 745 | _ => panic!("unexpected {:?}", message), 746 | } 747 | } 748 | } 749 | 750 | #[test] 751 | #[should_panic = "WorkspaceDefaultMembers should only be dereferenced on Cargo versions >= 1.71"] 752 | fn missing_workspace_default_members() { 753 | let meta: Metadata = serde_json::from_str(JSON_OLD_MINIMAL).unwrap(); 754 | let _ = &*meta.workspace_default_members; 755 | } 756 | 757 | #[test] 758 | fn workspace_default_members_is_available() { 759 | // generated with cargo +1.71.0 metadata --format-version 1 760 | let json = r#" 761 | { 762 | "packages": [ 763 | { 764 | "name": "basic", 765 | "version": "0.1.0", 766 | "id": "basic 0.1.0 (path+file:///example)", 767 | "license": null, 768 | "license_file": null, 769 | "description": null, 770 | "source": null, 771 | "dependencies": [], 772 | "targets": [ 773 | { 774 | "kind": [ 775 | "lib" 776 | ], 777 | "crate_types": [ 778 | "lib" 779 | ], 780 | "name": "basic", 781 | "src_path": "/example/src/lib.rs", 782 | "edition": "2021", 783 | "doc": true, 784 | "doctest": true, 785 | "test": true 786 | } 787 | ], 788 | "features": {}, 789 | "manifest_path": "/example/Cargo.toml", 790 | "metadata": null, 791 | "publish": null, 792 | "authors": [], 793 | "categories": [], 794 | "keywords": [], 795 | "readme": null, 796 | "repository": null, 797 | "homepage": null, 798 | "documentation": null, 799 | "edition": "2021", 800 | "links": null, 801 | "default_run": null, 802 | "rust_version": null 803 | } 804 | ], 805 | "workspace_members": [ 806 | "basic 0.1.0 (path+file:///example)" 807 | ], 808 | "workspace_default_members": [ 809 | "basic 0.1.0 (path+file:///example)" 810 | ], 811 | "resolve": { 812 | "nodes": [ 813 | { 814 | "id": "basic 0.1.0 (path+file:///example)", 815 | "dependencies": [], 816 | "deps": [], 817 | "features": [] 818 | } 819 | ], 820 | "root": "basic 0.1.0 (path+file:///example)" 821 | }, 822 | "target_directory": "/example/target", 823 | "version": 1, 824 | "workspace_root": "/example", 825 | "metadata": null 826 | } 827 | "#; 828 | 829 | let meta: Metadata = serde_json::from_str(json).unwrap(); 830 | 831 | assert!(meta.workspace_default_members.is_available()); 832 | assert!(!meta.workspace_default_members.is_missing()); 833 | } 834 | 835 | #[test] 836 | fn workspace_default_members_is_missing() { 837 | // generated with cargo +1.70.0 metadata --format-version 1 838 | let json = r#" 839 | { 840 | "packages": [ 841 | { 842 | "name": "basic", 843 | "version": "0.1.0", 844 | "id": "basic 0.1.0 (path+file:///example)", 845 | "license": null, 846 | "license_file": null, 847 | "description": null, 848 | "source": null, 849 | "dependencies": [], 850 | "targets": [ 851 | { 852 | "kind": [ 853 | "lib" 854 | ], 855 | "crate_types": [ 856 | "lib" 857 | ], 858 | "name": "basic", 859 | "src_path": "/example/src/lib.rs", 860 | "edition": "2021", 861 | "doc": true, 862 | "doctest": true, 863 | "test": true 864 | } 865 | ], 866 | "features": {}, 867 | "manifest_path": "/example/Cargo.toml", 868 | "metadata": null, 869 | "publish": null, 870 | "authors": [], 871 | "categories": [], 872 | "keywords": [], 873 | "readme": null, 874 | "repository": null, 875 | "homepage": null, 876 | "documentation": null, 877 | "edition": "2021", 878 | "links": null, 879 | "default_run": null, 880 | "rust_version": null 881 | } 882 | ], 883 | "workspace_members": [ 884 | "basic 0.1.0 (path+file:///example)" 885 | ], 886 | "resolve": { 887 | "nodes": [ 888 | { 889 | "id": "basic 0.1.0 (path+file:///example)", 890 | "dependencies": [], 891 | "deps": [], 892 | "features": [] 893 | } 894 | ], 895 | "root": "basic 0.1.0 (path+file:///example)" 896 | }, 897 | "target_directory": "/example/target", 898 | "version": 1, 899 | "workspace_root": "/example", 900 | "metadata": null 901 | } 902 | "#; 903 | 904 | let meta: Metadata = serde_json::from_str(json).unwrap(); 905 | 906 | assert!(!meta.workspace_default_members.is_available()); 907 | assert!(meta.workspace_default_members.is_missing()); 908 | } 909 | 910 | #[test] 911 | fn test_unknown_target_kind_and_crate_type() { 912 | // Both kind and crate_type set to a type not yet known 913 | let json = r#" 914 | { 915 | "packages": [ 916 | { 917 | "name": "alt", 918 | "version": "0.1.0", 919 | "id": "alt 0.1.0 (path+file:///alt)", 920 | "source": null, 921 | "dependencies": [], 922 | "targets": [ 923 | { 924 | "kind": [ 925 | "future-kind" 926 | ], 927 | "crate_types": [ 928 | "future-type" 929 | ], 930 | "name": "alt", 931 | "src_path": "/alt/src/lib.rs", 932 | "edition": "2018" 933 | } 934 | ], 935 | "features": {}, 936 | "manifest_path": "/alt/Cargo.toml", 937 | "metadata": null, 938 | "authors": [], 939 | "categories": [], 940 | "keywords": [], 941 | "readme": null, 942 | "repository": null, 943 | "edition": "2018", 944 | "links": null 945 | } 946 | ], 947 | "workspace_members": [ 948 | "alt 0.1.0 (path+file:///alt)" 949 | ], 950 | "resolve": null, 951 | "target_directory": "/alt/target", 952 | "version": 1, 953 | "workspace_root": "/alt" 954 | } 955 | "#; 956 | let meta: Metadata = serde_json::from_str(json).unwrap(); 957 | assert_eq!(meta.packages.len(), 1); 958 | assert_eq!(meta.packages[0].targets.len(), 1); 959 | let target = &meta.packages[0].targets[0]; 960 | assert_eq!(target.kind[0], "future-kind".into()); 961 | assert_eq!(target.crate_types[0], "future-type".into()); 962 | } 963 | --------------------------------------------------------------------------------