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