cargo_metadata/lib.rs
1#![deny(missing_docs)]
2//! Structured access to the output of `cargo metadata` and `cargo --message-format=json`.
3//! Usually used from within a `cargo-*` executable
4//!
5//! See the [cargo book](https://doc.rust-lang.org/cargo/index.html) for
6//! details on cargo itself.
7//!
8//! ## Examples
9//!
10//! ```rust
11//! # extern crate cargo_metadata;
12//! # use std::path::Path;
13//! let mut args = std::env::args().skip_while(|val| !val.starts_with("--manifest-path"));
14//!
15//! let mut cmd = cargo_metadata::MetadataCommand::new();
16//! let manifest_path = match args.next() {
17//! Some(ref p) if p == "--manifest-path" => {
18//! cmd.manifest_path(args.next().unwrap());
19//! }
20//! Some(p) => {
21//! cmd.manifest_path(p.trim_start_matches("--manifest-path="));
22//! }
23//! None => {}
24//! };
25//!
26//! let _metadata = cmd.exec().unwrap();
27//! ```
28//!
29//! Pass features flags
30//!
31//! ```rust
32//! # // This should be kept in sync with the equivalent example in the readme.
33//! # extern crate cargo_metadata;
34//! # use std::path::Path;
35//! # fn main() {
36//! use cargo_metadata::{MetadataCommand, CargoOpt};
37//!
38//! let _metadata = MetadataCommand::new()
39//! .manifest_path("./Cargo.toml")
40//! .features(CargoOpt::AllFeatures)
41//! .exec()
42//! .unwrap();
43//! # }
44//! ```
45//!
46//! Parse message-format output:
47//!
48//! ```
49//! # extern crate cargo_metadata;
50//! use std::process::{Stdio, Command};
51//! use cargo_metadata::Message;
52//!
53//! let mut command = Command::new("cargo")
54//! .args(&["build", "--message-format=json-render-diagnostics"])
55//! .stdout(Stdio::piped())
56//! .spawn()
57//! .unwrap();
58//!
59//! let reader = std::io::BufReader::new(command.stdout.take().unwrap());
60//! for message in cargo_metadata::Message::parse_stream(reader) {
61//! match message.unwrap() {
62//! Message::CompilerMessage(msg) => {
63//! println!("{:?}", msg);
64//! },
65//! Message::CompilerArtifact(artifact) => {
66//! println!("{:?}", artifact);
67//! },
68//! Message::BuildScriptExecuted(script) => {
69//! println!("{:?}", script);
70//! },
71//! Message::BuildFinished(finished) => {
72//! println!("{:?}", finished);
73//! },
74//! _ => () // Unknown message
75//! }
76//! }
77//!
78//! let output = command.wait().expect("Couldn't get cargo's exit status");
79//! ```
80
81use camino::Utf8PathBuf;
82#[cfg(feature = "builder")]
83use derive_builder::Builder;
84use std::collections::BTreeMap;
85use std::env;
86use std::ffi::OsString;
87use std::fmt;
88use std::hash::Hash;
89use std::path::PathBuf;
90use std::process::{Command, Stdio};
91use std::str::from_utf8;
92
93pub use camino;
94pub use semver;
95use semver::Version;
96
97#[cfg(feature = "builder")]
98pub use dependency::DependencyBuilder;
99pub use dependency::{Dependency, DependencyKind};
100use diagnostic::Diagnostic;
101pub use errors::{Error, Result};
102#[cfg(feature = "unstable")]
103pub use libtest::TestMessage;
104#[allow(deprecated)]
105pub use messages::parse_messages;
106pub use messages::{
107 Artifact, ArtifactDebuginfo, ArtifactProfile, BuildFinished, BuildScript, CompilerMessage,
108 Message, MessageIter,
109};
110#[cfg(feature = "builder")]
111pub use messages::{
112 ArtifactBuilder, ArtifactProfileBuilder, BuildFinishedBuilder, BuildScriptBuilder,
113 CompilerMessageBuilder,
114};
115use serde::{Deserialize, Deserializer, Serialize};
116
117mod dependency;
118pub mod diagnostic;
119mod errors;
120#[cfg(feature = "unstable")]
121pub mod libtest;
122mod messages;
123
124/// An "opaque" identifier for a package.
125///
126/// It is possible to inspect the `repr` field, if the need arises, but its
127/// precise format is an implementation detail and is subject to change.
128///
129/// `Metadata` can be indexed by `PackageId`.
130#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
131#[serde(transparent)]
132pub struct PackageId {
133 /// The underlying string representation of id.
134 pub repr: String,
135}
136
137impl fmt::Display for PackageId {
138 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
139 fmt::Display::fmt(&self.repr, f)
140 }
141}
142
143/// Helpers for default metadata fields
144fn is_null(value: &serde_json::Value) -> bool {
145 matches!(value, serde_json::Value::Null)
146}
147
148#[derive(Clone, Serialize, Deserialize, Debug)]
149#[cfg_attr(feature = "builder", derive(Builder))]
150#[non_exhaustive]
151#[cfg_attr(feature = "builder", builder(pattern = "owned", setter(into)))]
152/// Starting point for metadata returned by `cargo metadata`
153pub struct Metadata {
154 /// A list of all crates referenced by this crate (and the crate itself)
155 pub packages: Vec<Package>,
156 /// A list of all workspace members
157 pub workspace_members: Vec<PackageId>,
158 /// The list of default workspace members
159 ///
160 /// This not available if running with a version of Cargo older than 1.71.
161 #[serde(skip_serializing_if = "workspace_default_members_is_missing")]
162 pub workspace_default_members: WorkspaceDefaultMembers,
163 /// Dependencies graph
164 pub resolve: Option<Resolve>,
165 /// Workspace root
166 pub workspace_root: Utf8PathBuf,
167 /// Build directory
168 pub target_directory: Utf8PathBuf,
169 /// The workspace-level metadata object. Null if non-existent.
170 #[serde(rename = "metadata", default, skip_serializing_if = "is_null")]
171 pub workspace_metadata: serde_json::Value,
172 /// The metadata format version
173 version: usize,
174}
175
176impl Metadata {
177 /// Get the workspace's root package of this metadata instance.
178 pub fn root_package(&self) -> Option<&Package> {
179 match &self.resolve {
180 Some(resolve) => {
181 // if dependencies are resolved, use Cargo's answer
182 let root = resolve.root.as_ref()?;
183 self.packages.iter().find(|pkg| &pkg.id == root)
184 }
185 None => {
186 // if dependencies aren't resolved, check for a root package manually
187 let root_manifest_path = self.workspace_root.join("Cargo.toml");
188 self.packages
189 .iter()
190 .find(|pkg| pkg.manifest_path == root_manifest_path)
191 }
192 }
193 }
194
195 /// Get the workspace packages.
196 pub fn workspace_packages(&self) -> Vec<&Package> {
197 self.packages
198 .iter()
199 .filter(|&p| self.workspace_members.contains(&p.id))
200 .collect()
201 }
202
203 /// Get the workspace default packages.
204 ///
205 /// # Panics
206 ///
207 /// This will panic if running with a version of Cargo older than 1.71.
208 pub fn workspace_default_packages(&self) -> Vec<&Package> {
209 self.packages
210 .iter()
211 .filter(|&p| self.workspace_default_members.contains(&p.id))
212 .collect()
213 }
214}
215
216impl<'a> std::ops::Index<&'a PackageId> for Metadata {
217 type Output = Package;
218
219 fn index(&self, idx: &'a PackageId) -> &Package {
220 self.packages
221 .iter()
222 .find(|p| p.id == *idx)
223 .unwrap_or_else(|| panic!("no package with this id: {:?}", idx))
224 }
225}
226
227#[derive(Clone, Debug, Deserialize, Serialize)]
228#[serde(transparent)]
229/// A list of default workspace members.
230///
231/// See [`Metadata::workspace_default_members`].
232///
233/// It is only available if running a version of Cargo of 1.71 or newer.
234///
235/// # Panics
236///
237/// Dereferencing when running an older version of Cargo will panic.
238pub struct WorkspaceDefaultMembers(Option<Vec<PackageId>>);
239
240impl core::ops::Deref for WorkspaceDefaultMembers {
241 type Target = [PackageId];
242
243 fn deref(&self) -> &Self::Target {
244 self.0
245 .as_ref()
246 .expect("WorkspaceDefaultMembers should only be dereferenced on Cargo versions >= 1.71")
247 }
248}
249
250/// Return true if a valid value for [`WorkspaceDefaultMembers`] is missing, and
251/// dereferencing it would panic.
252///
253/// Internal helper for `skip_serializing_if` and test code. Might be removed in
254/// the future.
255#[doc(hidden)]
256pub fn workspace_default_members_is_missing(
257 workspace_default_members: &WorkspaceDefaultMembers,
258) -> bool {
259 workspace_default_members.0.is_none()
260}
261
262#[derive(Clone, Serialize, Deserialize, Debug)]
263#[cfg_attr(feature = "builder", derive(Builder))]
264#[non_exhaustive]
265#[cfg_attr(feature = "builder", builder(pattern = "owned", setter(into)))]
266/// A dependency graph
267pub struct Resolve {
268 /// Nodes in a dependencies graph
269 pub nodes: Vec<Node>,
270
271 /// The crate for which the metadata was read.
272 pub root: Option<PackageId>,
273}
274
275#[derive(Clone, Serialize, Deserialize, Debug)]
276#[cfg_attr(feature = "builder", derive(Builder))]
277#[non_exhaustive]
278#[cfg_attr(feature = "builder", builder(pattern = "owned", setter(into)))]
279/// A node in a dependencies graph
280pub struct Node {
281 /// An opaque identifier for a package
282 pub id: PackageId,
283 /// Dependencies in a structured format.
284 ///
285 /// `deps` handles renamed dependencies whereas `dependencies` does not.
286 #[serde(default)]
287 pub deps: Vec<NodeDep>,
288
289 /// List of opaque identifiers for this node's dependencies.
290 /// It doesn't support renamed dependencies. See `deps`.
291 pub dependencies: Vec<PackageId>,
292
293 /// Features enabled on the crate
294 #[serde(default)]
295 pub features: Vec<String>,
296}
297
298#[derive(Clone, Serialize, Deserialize, Debug)]
299#[cfg_attr(feature = "builder", derive(Builder))]
300#[non_exhaustive]
301#[cfg_attr(feature = "builder", builder(pattern = "owned", setter(into)))]
302/// A dependency in a node
303pub struct NodeDep {
304 /// The name of the dependency's library target.
305 /// If the crate was renamed, it is the new name.
306 pub name: String,
307 /// Package ID (opaque unique identifier)
308 pub pkg: PackageId,
309 /// The kinds of dependencies.
310 ///
311 /// This field was added in Rust 1.41.
312 #[serde(default)]
313 pub dep_kinds: Vec<DepKindInfo>,
314}
315
316#[derive(Clone, Serialize, Deserialize, Debug)]
317#[cfg_attr(feature = "builder", derive(Builder))]
318#[non_exhaustive]
319#[cfg_attr(feature = "builder", builder(pattern = "owned", setter(into)))]
320/// Information about a dependency kind.
321pub struct DepKindInfo {
322 /// The kind of dependency.
323 #[serde(deserialize_with = "dependency::parse_dependency_kind")]
324 pub kind: DependencyKind,
325 /// The target platform for the dependency.
326 ///
327 /// This is `None` if it is not a target dependency.
328 ///
329 /// Use the [`Display`] trait to access the contents.
330 ///
331 /// By default all platform dependencies are included in the resolve
332 /// graph. Use Cargo's `--filter-platform` flag if you only want to
333 /// include dependencies for a specific platform.
334 ///
335 /// [`Display`]: std::fmt::Display
336 pub target: Option<dependency::Platform>,
337}
338
339#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq)]
340#[cfg_attr(feature = "builder", derive(Builder))]
341#[non_exhaustive]
342#[cfg_attr(feature = "builder", builder(pattern = "owned", setter(into)))]
343/// One or more crates described by a single `Cargo.toml`
344///
345/// Each [`target`][Package::targets] of a `Package` will be built as a crate.
346/// For more information, see <https://doc.rust-lang.org/book/ch07-01-packages-and-crates.html>.
347pub struct Package {
348 /// The [`name` field](https://doc.rust-lang.org/cargo/reference/manifest.html#the-name-field) as given in the `Cargo.toml`
349 // (We say "given in" instead of "specified in" since the `name` key cannot be inherited from the workspace.)
350 pub name: String,
351 /// The [`version` field](https://doc.rust-lang.org/cargo/reference/manifest.html#the-version-field) as specified in the `Cargo.toml`
352 pub version: Version,
353 /// The [`authors` field](https://doc.rust-lang.org/cargo/reference/manifest.html#the-authors-field) as specified in the `Cargo.toml`
354 #[serde(default)]
355 pub authors: Vec<String>,
356 /// An opaque identifier for a package
357 pub id: PackageId,
358 /// The source of the package, e.g.
359 /// crates.io or `None` for local projects.
360 pub source: Option<Source>,
361 /// The [`description` field](https://doc.rust-lang.org/cargo/reference/manifest.html#the-description-field) as specified in the `Cargo.toml`
362 pub description: Option<String>,
363 /// List of dependencies of this particular package
364 pub dependencies: Vec<Dependency>,
365 /// The [`license` field](https://doc.rust-lang.org/cargo/reference/manifest.html#the-license-and-license-file-fields) as specified in the `Cargo.toml`
366 pub license: Option<String>,
367 /// 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`.
368 /// If the package is using a nonstandard license, this key may be specified instead of
369 /// `license`, and must point to a file relative to the manifest.
370 pub license_file: Option<Utf8PathBuf>,
371 /// Targets provided by the crate (lib, bin, example, test, ...)
372 pub targets: Vec<Target>,
373 /// Features provided by the crate, mapped to the features required by that feature.
374 pub features: BTreeMap<String, Vec<String>>,
375 /// Path containing the `Cargo.toml`
376 pub manifest_path: Utf8PathBuf,
377 /// The [`categories` field](https://doc.rust-lang.org/cargo/reference/manifest.html#the-categories-field) as specified in the `Cargo.toml`
378 #[serde(default)]
379 pub categories: Vec<String>,
380 /// The [`keywords` field](https://doc.rust-lang.org/cargo/reference/manifest.html#the-keywords-field) as specified in the `Cargo.toml`
381 #[serde(default)]
382 pub keywords: Vec<String>,
383 /// The [`readme` field](https://doc.rust-lang.org/cargo/reference/manifest.html#the-readme-field) as specified in the `Cargo.toml`
384 pub readme: Option<Utf8PathBuf>,
385 /// The [`repository` URL](https://doc.rust-lang.org/cargo/reference/manifest.html#the-repository-field) as specified in the `Cargo.toml`
386 // can't use `url::Url` because that requires a more recent stable compiler
387 pub repository: Option<String>,
388 /// The [`homepage` URL](https://doc.rust-lang.org/cargo/reference/manifest.html#the-homepage-field) as specified in the `Cargo.toml`.
389 ///
390 /// On versions of cargo before 1.49, this will always be [`None`].
391 pub homepage: Option<String>,
392 /// The [`documentation` URL](https://doc.rust-lang.org/cargo/reference/manifest.html#the-documentation-field) as specified in the `Cargo.toml`.
393 ///
394 /// On versions of cargo before 1.49, this will always be [`None`].
395 pub documentation: Option<String>,
396 /// 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)
397 /// or defaulting to [`Edition::E2015`]).
398 ///
399 /// Beware that individual targets may specify their own edition in
400 /// [`Target::edition`].
401 #[serde(default)]
402 pub edition: Edition,
403 /// Contents of the free form [`package.metadata` section](https://doc.rust-lang.org/cargo/reference/manifest.html#the-metadata-table).
404 ///
405 /// This contents can be serialized to a struct using serde:
406 ///
407 /// ```rust
408 /// use serde::Deserialize;
409 /// use serde_json::json;
410 ///
411 /// #[derive(Debug, Deserialize)]
412 /// struct SomePackageMetadata {
413 /// some_value: i32,
414 /// }
415 ///
416 /// fn main() {
417 /// let value = json!({
418 /// "some_value": 42,
419 /// });
420 ///
421 /// let package_metadata: SomePackageMetadata = serde_json::from_value(value).unwrap();
422 /// assert_eq!(package_metadata.some_value, 42);
423 /// }
424 ///
425 /// ```
426 #[serde(default, skip_serializing_if = "is_null")]
427 pub metadata: serde_json::Value,
428 /// The name of a native library the package is linking to.
429 pub links: Option<String>,
430 /// 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)).
431 ///
432 /// Publishing is unrestricted if `None`, and forbidden if the `Vec` is empty.
433 ///
434 /// This is always `None` if running with a version of Cargo older than 1.39.
435 pub publish: Option<Vec<String>>,
436 /// The [`default-run` field](https://doc.rust-lang.org/cargo/reference/manifest.html#the-default-run-field) as given in the `Cargo.toml`
437 // (We say "given in" instead of "specified in" since the `default-run` key cannot be inherited from the workspace.)
438 /// The default binary to run by `cargo run`.
439 ///
440 /// This is always `None` if running with a version of Cargo older than 1.55.
441 pub default_run: Option<String>,
442 /// The [`rust-version` field](https://doc.rust-lang.org/cargo/reference/manifest.html#the-rust-version-field) as specified in the `Cargo.toml`.
443 /// The minimum supported Rust version of this package.
444 ///
445 /// This is always `None` if running with a version of Cargo older than 1.58.
446 #[serde(default)]
447 #[serde(deserialize_with = "deserialize_rust_version")]
448 pub rust_version: Option<Version>,
449}
450
451impl Package {
452 /// Full path to the license file if one is present in the manifest
453 pub fn license_file(&self) -> Option<Utf8PathBuf> {
454 self.license_file.as_ref().map(|file| {
455 self.manifest_path
456 .parent()
457 .unwrap_or(&self.manifest_path)
458 .join(file)
459 })
460 }
461
462 /// Full path to the readme file if one is present in the manifest
463 pub fn readme(&self) -> Option<Utf8PathBuf> {
464 self.readme.as_ref().map(|file| {
465 self.manifest_path
466 .parent()
467 .unwrap_or(&self.manifest_path)
468 .join(file)
469 })
470 }
471}
472
473/// The source of a package such as crates.io.
474///
475/// It is possible to inspect the `repr` field, if the need arises, but its
476/// precise format is an implementation detail and is subject to change.
477#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq)]
478#[serde(transparent)]
479pub struct Source {
480 /// The underlying string representation of a source.
481 pub repr: String,
482}
483
484impl Source {
485 /// Returns true if the source is crates.io.
486 pub fn is_crates_io(&self) -> bool {
487 self.repr == "registry+https://github.com/rust-lang/crates.io-index"
488 }
489}
490
491impl fmt::Display for Source {
492 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
493 fmt::Display::fmt(&self.repr, f)
494 }
495}
496
497#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq, Hash)]
498#[cfg_attr(feature = "builder", derive(Builder))]
499#[cfg_attr(feature = "builder", builder(pattern = "owned", setter(into)))]
500#[non_exhaustive]
501/// A single target (lib, bin, example, ...) provided by a crate
502pub struct Target {
503 /// Name as given in the `Cargo.toml` or generated from the file name
504 pub name: String,
505 /// Kind of target ("bin", "example", "test", "bench", "lib", "custom-build")
506 pub kind: Vec<String>,
507 /// Almost the same as `kind`, except when an example is a library instead of an executable.
508 /// In that case `crate_types` contains things like `rlib` and `dylib` while `kind` is `example`
509 #[serde(default)]
510 #[cfg_attr(feature = "builder", builder(default))]
511 pub crate_types: Vec<String>,
512
513 #[serde(default)]
514 #[cfg_attr(feature = "builder", builder(default))]
515 #[serde(rename = "required-features")]
516 /// This target is built only if these features are enabled.
517 /// It doesn't apply to `lib` targets.
518 pub required_features: Vec<String>,
519 /// Path to the main source file of the target
520 pub src_path: Utf8PathBuf,
521 /// Rust edition for this target
522 #[serde(default)]
523 #[cfg_attr(feature = "builder", builder(default))]
524 pub edition: Edition,
525 /// Whether or not this target has doc tests enabled, and the target is
526 /// compatible with doc testing.
527 ///
528 /// This is always `true` if running with a version of Cargo older than 1.37.
529 #[serde(default = "default_true")]
530 #[cfg_attr(feature = "builder", builder(default = "true"))]
531 pub doctest: bool,
532 /// Whether or not this target is tested by default by `cargo test`.
533 ///
534 /// This is always `true` if running with a version of Cargo older than 1.47.
535 #[serde(default = "default_true")]
536 #[cfg_attr(feature = "builder", builder(default = "true"))]
537 pub test: bool,
538 /// Whether or not this target is documented by `cargo doc`.
539 ///
540 /// This is always `true` if running with a version of Cargo older than 1.50.
541 #[serde(default = "default_true")]
542 #[cfg_attr(feature = "builder", builder(default = "true"))]
543 pub doc: bool,
544}
545
546impl Target {
547 fn is_kind(&self, name: &str) -> bool {
548 self.kind.iter().any(|kind| kind == name)
549 }
550
551 /// Return true if this target is of kind "lib".
552 pub fn is_lib(&self) -> bool {
553 self.is_kind("lib")
554 }
555
556 /// Return true if this target is of kind "bin".
557 pub fn is_bin(&self) -> bool {
558 self.is_kind("bin")
559 }
560
561 /// Return true if this target is of kind "example".
562 pub fn is_example(&self) -> bool {
563 self.is_kind("example")
564 }
565
566 /// Return true if this target is of kind "test".
567 pub fn is_test(&self) -> bool {
568 self.is_kind("test")
569 }
570
571 /// Return true if this target is of kind "bench".
572 pub fn is_bench(&self) -> bool {
573 self.is_kind("bench")
574 }
575
576 /// Return true if this target is of kind "custom-build".
577 pub fn is_custom_build(&self) -> bool {
578 self.is_kind("custom-build")
579 }
580}
581
582/// The Rust edition
583///
584/// As of writing this comment rust editions 2024, 2027 and 2030 are not actually a thing yet but are parsed nonetheless for future proofing.
585#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord, Hash)]
586#[non_exhaustive]
587pub enum Edition {
588 /// Edition 2015
589 #[serde(rename = "2015")]
590 E2015,
591 /// Edition 2018
592 #[serde(rename = "2018")]
593 E2018,
594 /// Edition 2021
595 #[serde(rename = "2021")]
596 E2021,
597 #[doc(hidden)]
598 #[serde(rename = "2024")]
599 _E2024,
600 #[doc(hidden)]
601 #[serde(rename = "2027")]
602 _E2027,
603 #[doc(hidden)]
604 #[serde(rename = "2030")]
605 _E2030,
606}
607
608impl Edition {
609 /// Return the string representation of the edition
610 pub fn as_str(&self) -> &'static str {
611 use Edition::*;
612 match self {
613 E2015 => "2015",
614 E2018 => "2018",
615 E2021 => "2021",
616 _E2024 => "2024",
617 _E2027 => "2027",
618 _E2030 => "2030",
619 }
620 }
621}
622
623impl fmt::Display for Edition {
624 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
625 f.write_str(self.as_str())
626 }
627}
628
629impl Default for Edition {
630 fn default() -> Self {
631 Self::E2015
632 }
633}
634
635fn default_true() -> bool {
636 true
637}
638
639/// Cargo features flags
640#[derive(Debug, Clone)]
641pub enum CargoOpt {
642 /// Run cargo with `--features-all`
643 AllFeatures,
644 /// Run cargo with `--no-default-features`
645 NoDefaultFeatures,
646 /// Run cargo with `--features <FEATURES>`
647 SomeFeatures(Vec<String>),
648}
649
650/// A builder for configurating `cargo metadata` invocation.
651#[derive(Debug, Clone, Default)]
652pub struct MetadataCommand {
653 /// Path to `cargo` executable. If not set, this will use the
654 /// the `$CARGO` environment variable, and if that is not set, will
655 /// simply be `cargo`.
656 cargo_path: Option<PathBuf>,
657 /// Path to `Cargo.toml`
658 manifest_path: Option<PathBuf>,
659 /// Current directory of the `cargo metadata` process.
660 current_dir: Option<PathBuf>,
661 /// Output information only about workspace members and don't fetch dependencies.
662 no_deps: bool,
663 /// Collections of `CargoOpt::SomeFeatures(..)`
664 features: Vec<String>,
665 /// Latched `CargoOpt::AllFeatures`
666 all_features: bool,
667 /// Latched `CargoOpt::NoDefaultFeatures`
668 no_default_features: bool,
669 /// Arbitrary command line flags to pass to `cargo`. These will be added
670 /// to the end of the command line invocation.
671 other_options: Vec<String>,
672 /// Arbitrary environment variables to set when running `cargo`. These will be merged into
673 /// the calling environment, overriding any which clash.
674 env: BTreeMap<OsString, OsString>,
675 /// Show stderr
676 verbose: bool,
677}
678
679impl MetadataCommand {
680 /// Creates a default `cargo metadata` command, which will look for
681 /// `Cargo.toml` in the ancestors of the current directory.
682 pub fn new() -> MetadataCommand {
683 MetadataCommand::default()
684 }
685 /// Path to `cargo` executable. If not set, this will use the
686 /// the `$CARGO` environment variable, and if that is not set, will
687 /// simply be `cargo`.
688 pub fn cargo_path(&mut self, path: impl Into<PathBuf>) -> &mut MetadataCommand {
689 self.cargo_path = Some(path.into());
690 self
691 }
692 /// Path to `Cargo.toml`
693 pub fn manifest_path(&mut self, path: impl Into<PathBuf>) -> &mut MetadataCommand {
694 self.manifest_path = Some(path.into());
695 self
696 }
697 /// Current directory of the `cargo metadata` process.
698 pub fn current_dir(&mut self, path: impl Into<PathBuf>) -> &mut MetadataCommand {
699 self.current_dir = Some(path.into());
700 self
701 }
702 /// Output information only about workspace members and don't fetch dependencies.
703 pub fn no_deps(&mut self) -> &mut MetadataCommand {
704 self.no_deps = true;
705 self
706 }
707 /// Which features to include.
708 ///
709 /// Call this multiple times to specify advanced feature configurations:
710 ///
711 /// ```no_run
712 /// # use cargo_metadata::{CargoOpt, MetadataCommand};
713 /// MetadataCommand::new()
714 /// .features(CargoOpt::NoDefaultFeatures)
715 /// .features(CargoOpt::SomeFeatures(vec!["feat1".into(), "feat2".into()]))
716 /// .features(CargoOpt::SomeFeatures(vec!["feat3".into()]))
717 /// // ...
718 /// # ;
719 /// ```
720 ///
721 /// # Panics
722 ///
723 /// `cargo metadata` rejects multiple `--no-default-features` flags. Similarly, the `features()`
724 /// method panics when specifying multiple `CargoOpt::NoDefaultFeatures`:
725 ///
726 /// ```should_panic
727 /// # use cargo_metadata::{CargoOpt, MetadataCommand};
728 /// MetadataCommand::new()
729 /// .features(CargoOpt::NoDefaultFeatures)
730 /// .features(CargoOpt::NoDefaultFeatures) // <-- panic!
731 /// // ...
732 /// # ;
733 /// ```
734 ///
735 /// The method also panics for multiple `CargoOpt::AllFeatures` arguments:
736 ///
737 /// ```should_panic
738 /// # use cargo_metadata::{CargoOpt, MetadataCommand};
739 /// MetadataCommand::new()
740 /// .features(CargoOpt::AllFeatures)
741 /// .features(CargoOpt::AllFeatures) // <-- panic!
742 /// // ...
743 /// # ;
744 /// ```
745 pub fn features(&mut self, features: CargoOpt) -> &mut MetadataCommand {
746 match features {
747 CargoOpt::SomeFeatures(features) => self.features.extend(features),
748 CargoOpt::NoDefaultFeatures => {
749 assert!(
750 !self.no_default_features,
751 "Do not supply CargoOpt::NoDefaultFeatures more than once!"
752 );
753 self.no_default_features = true;
754 }
755 CargoOpt::AllFeatures => {
756 assert!(
757 !self.all_features,
758 "Do not supply CargoOpt::AllFeatures more than once!"
759 );
760 self.all_features = true;
761 }
762 }
763 self
764 }
765 /// Arbitrary command line flags to pass to `cargo`. These will be added
766 /// to the end of the command line invocation.
767 pub fn other_options(&mut self, options: impl Into<Vec<String>>) -> &mut MetadataCommand {
768 self.other_options = options.into();
769 self
770 }
771
772 /// Arbitrary environment variables to set when running `cargo`. These will be merged into
773 /// the calling environment, overriding any which clash.
774 ///
775 /// Some examples of when you may want to use this:
776 /// 1. Setting cargo config values without needing a .cargo/config.toml file, e.g. to set
777 /// `CARGO_NET_GIT_FETCH_WITH_CLI=true`
778 /// 2. To specify a custom path to RUSTC if your rust toolchain components aren't laid out in
779 /// the way cargo expects by default.
780 ///
781 /// ```no_run
782 /// # use cargo_metadata::{CargoOpt, MetadataCommand};
783 /// MetadataCommand::new()
784 /// .env("CARGO_NET_GIT_FETCH_WITH_CLI", "true")
785 /// .env("RUSTC", "/path/to/rustc")
786 /// // ...
787 /// # ;
788 /// ```
789 pub fn env<K: Into<OsString>, V: Into<OsString>>(
790 &mut self,
791 key: K,
792 val: V,
793 ) -> &mut MetadataCommand {
794 self.env.insert(key.into(), val.into());
795 self
796 }
797
798 /// Set whether to show stderr
799 pub fn verbose(&mut self, verbose: bool) -> &mut MetadataCommand {
800 self.verbose = verbose;
801 self
802 }
803
804 /// Builds a command for `cargo metadata`. This is the first
805 /// part of the work of `exec`.
806 pub fn cargo_command(&self) -> Command {
807 let cargo = self
808 .cargo_path
809 .clone()
810 .or_else(|| env::var("CARGO").map(PathBuf::from).ok())
811 .unwrap_or_else(|| PathBuf::from("cargo"));
812 let mut cmd = Command::new(cargo);
813 cmd.args(["metadata", "--format-version", "1"]);
814
815 if self.no_deps {
816 cmd.arg("--no-deps");
817 }
818
819 if let Some(path) = self.current_dir.as_ref() {
820 cmd.current_dir(path);
821 }
822
823 if !self.features.is_empty() {
824 cmd.arg("--features").arg(self.features.join(","));
825 }
826 if self.all_features {
827 cmd.arg("--all-features");
828 }
829 if self.no_default_features {
830 cmd.arg("--no-default-features");
831 }
832
833 if let Some(manifest_path) = &self.manifest_path {
834 cmd.arg("--manifest-path").arg(manifest_path.as_os_str());
835 }
836 cmd.args(&self.other_options);
837
838 cmd.envs(&self.env);
839
840 cmd
841 }
842
843 /// Parses `cargo metadata` output. `data` must have been
844 /// produced by a command built with `cargo_command`.
845 pub fn parse<T: AsRef<str>>(data: T) -> Result<Metadata> {
846 let meta = serde_json::from_str(data.as_ref())?;
847 Ok(meta)
848 }
849
850 /// Runs configured `cargo metadata` and returns parsed `Metadata`.
851 pub fn exec(&self) -> Result<Metadata> {
852 let mut command = self.cargo_command();
853 if self.verbose {
854 command.stderr(Stdio::inherit());
855 }
856 let output = command.output()?;
857 if !output.status.success() {
858 return Err(Error::CargoMetadata {
859 stderr: String::from_utf8(output.stderr)?,
860 });
861 }
862 let stdout = from_utf8(&output.stdout)?
863 .lines()
864 .find(|line| line.starts_with('{'))
865 .ok_or(Error::NoJson)?;
866 Self::parse(stdout)
867 }
868}
869
870/// As per the Cargo Book the [`rust-version` field](https://doc.rust-lang.org/cargo/reference/manifest.html#the-rust-version-field) must:
871///
872/// > be a bare version number with two or three components;
873/// > it cannot include semver operators or pre-release identifiers.
874///
875/// [`semver::Version`] however requires three components. This function takes
876/// care of appending `.0` if the provided version number only has two components
877/// and ensuring that it does not contain a pre-release version or build metadata.
878fn deserialize_rust_version<'de, D>(
879 deserializer: D,
880) -> std::result::Result<Option<Version>, D::Error>
881where
882 D: Deserializer<'de>,
883{
884 let mut buf = match Option::<String>::deserialize(deserializer)? {
885 None => return Ok(None),
886 Some(buf) => buf,
887 };
888
889 for char in buf.chars() {
890 if char == '-' {
891 return Err(serde::de::Error::custom(
892 "pre-release identifiers are not supported in rust-version",
893 ));
894 } else if char == '+' {
895 return Err(serde::de::Error::custom(
896 "build metadata is not supported in rust-version",
897 ));
898 }
899 }
900
901 if buf.matches('.').count() == 1 {
902 // e.g. 1.0 -> 1.0.0
903 buf.push_str(".0");
904 }
905
906 Ok(Some(
907 Version::parse(&buf).map_err(serde::de::Error::custom)?,
908 ))
909}
910
911#[cfg(test)]
912mod test {
913 use semver::Version;
914
915 #[derive(Debug, serde::Deserialize)]
916 struct BareVersion(
917 #[serde(deserialize_with = "super::deserialize_rust_version")] Option<semver::Version>,
918 );
919
920 fn bare_version(str: &str) -> Version {
921 serde_json::from_str::<BareVersion>(&format!(r#""{}""#, str))
922 .unwrap()
923 .0
924 .unwrap()
925 }
926
927 fn bare_version_err(str: &str) -> String {
928 serde_json::from_str::<BareVersion>(&format!(r#""{}""#, str))
929 .unwrap_err()
930 .to_string()
931 }
932
933 #[test]
934 fn test_deserialize_rust_version() {
935 assert_eq!(bare_version("1.2"), Version::new(1, 2, 0));
936 assert_eq!(bare_version("1.2.0"), Version::new(1, 2, 0));
937 assert_eq!(
938 bare_version_err("1.2.0-alpha"),
939 "pre-release identifiers are not supported in rust-version"
940 );
941 assert_eq!(
942 bare_version_err("1.2.0+123"),
943 "build metadata is not supported in rust-version"
944 );
945 }
946}