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, FromStr};
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, Hash, PartialOrd, Ord)]
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, PartialEq, Eq, Hash)]
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 is not available if running with a version of Cargo older than 1.71.
161 ///
162 /// You can check whether it is available or missing using respectively
163 /// [`WorkspaceDefaultMembers::is_available`] and [`WorkspaceDefaultMembers::is_missing`].
164 #[serde(default, skip_serializing_if = "WorkspaceDefaultMembers::is_missing")]
165 pub workspace_default_members: WorkspaceDefaultMembers,
166 /// Dependencies graph
167 pub resolve: Option<Resolve>,
168 /// Workspace root
169 pub workspace_root: Utf8PathBuf,
170 /// Build directory
171 pub target_directory: Utf8PathBuf,
172 /// The workspace-level metadata object. Null if non-existent.
173 #[serde(rename = "metadata", default, skip_serializing_if = "is_null")]
174 pub workspace_metadata: serde_json::Value,
175 /// The metadata format version
176 version: usize,
177}
178
179impl Metadata {
180 /// Get the workspace's root package of this metadata instance.
181 pub fn root_package(&self) -> Option<&Package> {
182 match &self.resolve {
183 Some(resolve) => {
184 // if dependencies are resolved, use Cargo's answer
185 let root = resolve.root.as_ref()?;
186 self.packages.iter().find(|pkg| &pkg.id == root)
187 }
188 None => {
189 // if dependencies aren't resolved, check for a root package manually
190 let root_manifest_path = self.workspace_root.join("Cargo.toml");
191 self.packages
192 .iter()
193 .find(|pkg| pkg.manifest_path == root_manifest_path)
194 }
195 }
196 }
197
198 /// Get the workspace packages.
199 pub fn workspace_packages(&self) -> Vec<&Package> {
200 self.packages
201 .iter()
202 .filter(|&p| self.workspace_members.contains(&p.id))
203 .collect()
204 }
205
206 /// Get the workspace default packages.
207 ///
208 /// # Panics
209 ///
210 /// This will panic if running with a version of Cargo older than 1.71.
211 pub fn workspace_default_packages(&self) -> Vec<&Package> {
212 self.packages
213 .iter()
214 .filter(|&p| self.workspace_default_members.contains(&p.id))
215 .collect()
216 }
217}
218
219impl<'a> std::ops::Index<&'a PackageId> for Metadata {
220 type Output = Package;
221
222 fn index(&self, idx: &'a PackageId) -> &Self::Output {
223 self.packages
224 .iter()
225 .find(|p| p.id == *idx)
226 .unwrap_or_else(|| panic!("no package with this id: {:?}", idx))
227 }
228}
229
230#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq, Hash, Default)]
231#[serde(transparent)]
232/// A list of default workspace members.
233///
234/// See [`Metadata::workspace_default_members`].
235///
236/// It is only available if running a version of Cargo of 1.71 or newer.
237///
238/// # Panics
239///
240/// Dereferencing when running an older version of Cargo will panic.
241pub struct WorkspaceDefaultMembers(Option<Vec<PackageId>>);
242
243impl WorkspaceDefaultMembers {
244 /// Return `true` if the list of workspace default members is supported by
245 /// the called cargo-metadata version and `false` otherwise.
246 ///
247 /// In particular useful when parsing the output of `cargo-metadata` for
248 /// versions of Cargo < 1.71, as dereferencing [`WorkspaceDefaultMembers`]
249 /// for these versions will panic.
250 ///
251 /// Opposite of [`WorkspaceDefaultMembers::is_missing`].
252 pub fn is_available(&self) -> bool {
253 self.0.is_some()
254 }
255
256 /// Return `false` if the list of workspace default members is supported by
257 /// the called cargo-metadata version and `true` otherwise.
258 ///
259 /// In particular useful when parsing the output of `cargo-metadata` for
260 /// versions of Cargo < 1.71, as dereferencing [`WorkspaceDefaultMembers`]
261 /// for these versions will panic.
262 ///
263 /// Opposite of [`WorkspaceDefaultMembers::is_available`].
264 pub fn is_missing(&self) -> bool {
265 self.0.is_none()
266 }
267}
268
269impl core::ops::Deref for WorkspaceDefaultMembers {
270 type Target = [PackageId];
271
272 fn deref(&self) -> &Self::Target {
273 self.0
274 .as_ref()
275 .expect("WorkspaceDefaultMembers should only be dereferenced on Cargo versions >= 1.71")
276 }
277}
278
279#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq, Hash)]
280#[cfg_attr(feature = "builder", derive(Builder))]
281#[non_exhaustive]
282#[cfg_attr(feature = "builder", builder(pattern = "owned", setter(into)))]
283/// A dependency graph
284pub struct Resolve {
285 /// Nodes in a dependencies graph
286 pub nodes: Vec<Node>,
287
288 /// The crate for which the metadata was read.
289 pub root: Option<PackageId>,
290}
291
292impl<'a> std::ops::Index<&'a PackageId> for Resolve {
293 type Output = Node;
294
295 fn index(&self, idx: &'a PackageId) -> &Self::Output {
296 self.nodes
297 .iter()
298 .find(|p| p.id == *idx)
299 .unwrap_or_else(|| panic!("no Node with this id: {:?}", idx))
300 }
301}
302
303#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq, Hash)]
304#[cfg_attr(feature = "builder", derive(Builder))]
305#[non_exhaustive]
306#[cfg_attr(feature = "builder", builder(pattern = "owned", setter(into)))]
307/// A node in a dependencies graph
308pub struct Node {
309 /// An opaque identifier for a package
310 pub id: PackageId,
311 /// Dependencies in a structured format.
312 ///
313 /// `deps` handles renamed dependencies whereas `dependencies` does not.
314 #[serde(default)]
315 pub deps: Vec<NodeDep>,
316
317 /// List of opaque identifiers for this node's dependencies.
318 /// It doesn't support renamed dependencies. See `deps`.
319 pub dependencies: Vec<PackageId>,
320
321 /// Features enabled on the crate
322 #[serde(default)]
323 pub features: Vec<String>,
324}
325
326#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq, Hash)]
327#[cfg_attr(feature = "builder", derive(Builder))]
328#[non_exhaustive]
329#[cfg_attr(feature = "builder", builder(pattern = "owned", setter(into)))]
330/// A dependency in a node
331pub struct NodeDep {
332 /// The name of the dependency's library target.
333 /// If the crate was renamed, it is the new name.
334 pub name: String,
335 /// Package ID (opaque unique identifier)
336 pub pkg: PackageId,
337 /// The kinds of dependencies.
338 ///
339 /// This field was added in Rust 1.41.
340 #[serde(default)]
341 pub dep_kinds: Vec<DepKindInfo>,
342}
343
344#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq, Hash)]
345#[cfg_attr(feature = "builder", derive(Builder))]
346#[non_exhaustive]
347#[cfg_attr(feature = "builder", builder(pattern = "owned", setter(into)))]
348/// Information about a dependency kind.
349pub struct DepKindInfo {
350 /// The kind of dependency.
351 #[serde(deserialize_with = "dependency::parse_dependency_kind")]
352 pub kind: DependencyKind,
353 /// The target platform for the dependency.
354 ///
355 /// This is `None` if it is not a target dependency.
356 ///
357 /// Use the [`Display`] trait to access the contents.
358 ///
359 /// By default all platform dependencies are included in the resolve
360 /// graph. Use Cargo's `--filter-platform` flag if you only want to
361 /// include dependencies for a specific platform.
362 ///
363 /// [`Display`]: std::fmt::Display
364 pub target: Option<dependency::Platform>,
365}
366
367#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq, Hash)]
368#[cfg_attr(feature = "builder", derive(Builder))]
369#[non_exhaustive]
370#[cfg_attr(feature = "builder", builder(pattern = "owned", setter(into)))]
371/// One or more crates described by a single `Cargo.toml`
372///
373/// Each [`target`][Package::targets] of a `Package` will be built as a crate.
374/// For more information, see <https://doc.rust-lang.org/book/ch07-01-packages-and-crates.html>.
375pub struct Package {
376 /// The [`name` field](https://doc.rust-lang.org/cargo/reference/manifest.html#the-name-field) as given in the `Cargo.toml`
377 // (We say "given in" instead of "specified in" since the `name` key cannot be inherited from the workspace.)
378 pub name: String,
379 /// The [`version` field](https://doc.rust-lang.org/cargo/reference/manifest.html#the-version-field) as specified in the `Cargo.toml`
380 pub version: Version,
381 /// The [`authors` field](https://doc.rust-lang.org/cargo/reference/manifest.html#the-authors-field) as specified in the `Cargo.toml`
382 #[serde(default)]
383 #[cfg_attr(feature = "builder", builder(default))]
384 pub authors: Vec<String>,
385 /// An opaque identifier for a package
386 pub id: PackageId,
387 /// The source of the package, e.g.
388 /// crates.io or `None` for local projects.
389 #[cfg_attr(feature = "builder", builder(default))]
390 pub source: Option<Source>,
391 /// The [`description` field](https://doc.rust-lang.org/cargo/reference/manifest.html#the-description-field) as specified in the `Cargo.toml`
392 #[cfg_attr(feature = "builder", builder(default))]
393 pub description: Option<String>,
394 /// List of dependencies of this particular package
395 #[cfg_attr(feature = "builder", builder(default))]
396 pub dependencies: Vec<Dependency>,
397 /// The [`license` field](https://doc.rust-lang.org/cargo/reference/manifest.html#the-license-and-license-file-fields) as specified in the `Cargo.toml`
398 #[cfg_attr(feature = "builder", builder(default))]
399 pub license: Option<String>,
400 /// 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`.
401 /// If the package is using a nonstandard license, this key may be specified instead of
402 /// `license`, and must point to a file relative to the manifest.
403 #[cfg_attr(feature = "builder", builder(default))]
404 pub license_file: Option<Utf8PathBuf>,
405 /// Targets provided by the crate (lib, bin, example, test, ...)
406 #[cfg_attr(feature = "builder", builder(default))]
407 pub targets: Vec<Target>,
408 /// Features provided by the crate, mapped to the features required by that feature.
409 #[cfg_attr(feature = "builder", builder(default))]
410 pub features: BTreeMap<String, Vec<String>>,
411 /// Path containing the `Cargo.toml`
412 pub manifest_path: Utf8PathBuf,
413 /// The [`categories` field](https://doc.rust-lang.org/cargo/reference/manifest.html#the-categories-field) as specified in the `Cargo.toml`
414 #[serde(default)]
415 #[cfg_attr(feature = "builder", builder(default))]
416 pub categories: Vec<String>,
417 /// The [`keywords` field](https://doc.rust-lang.org/cargo/reference/manifest.html#the-keywords-field) as specified in the `Cargo.toml`
418 #[serde(default)]
419 #[cfg_attr(feature = "builder", builder(default))]
420 pub keywords: Vec<String>,
421 /// The [`readme` field](https://doc.rust-lang.org/cargo/reference/manifest.html#the-readme-field) as specified in the `Cargo.toml`
422 #[cfg_attr(feature = "builder", builder(default))]
423 pub readme: Option<Utf8PathBuf>,
424 /// The [`repository` URL](https://doc.rust-lang.org/cargo/reference/manifest.html#the-repository-field) as specified in the `Cargo.toml`
425 // can't use `url::Url` because that requires a more recent stable compiler
426 #[cfg_attr(feature = "builder", builder(default))]
427 pub repository: Option<String>,
428 /// The [`homepage` URL](https://doc.rust-lang.org/cargo/reference/manifest.html#the-homepage-field) as specified in the `Cargo.toml`.
429 ///
430 /// On versions of cargo before 1.49, this will always be [`None`].
431 #[cfg_attr(feature = "builder", builder(default))]
432 pub homepage: Option<String>,
433 /// The [`documentation` URL](https://doc.rust-lang.org/cargo/reference/manifest.html#the-documentation-field) as specified in the `Cargo.toml`.
434 ///
435 /// On versions of cargo before 1.49, this will always be [`None`].
436 #[cfg_attr(feature = "builder", builder(default))]
437 pub documentation: Option<String>,
438 /// 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)
439 /// or defaulting to [`Edition::E2015`]).
440 ///
441 /// Beware that individual targets may specify their own edition in
442 /// [`Target::edition`].
443 #[serde(default)]
444 #[cfg_attr(feature = "builder", builder(default))]
445 pub edition: Edition,
446 /// Contents of the free form [`package.metadata` section](https://doc.rust-lang.org/cargo/reference/manifest.html#the-metadata-table).
447 ///
448 /// This contents can be serialized to a struct using serde:
449 ///
450 /// ```rust
451 /// use serde::Deserialize;
452 /// use serde_json::json;
453 ///
454 /// #[derive(Debug, Deserialize)]
455 /// struct SomePackageMetadata {
456 /// some_value: i32,
457 /// }
458 ///
459 /// let value = json!({
460 /// "some_value": 42,
461 /// });
462 ///
463 /// let package_metadata: SomePackageMetadata = serde_json::from_value(value).unwrap();
464 /// assert_eq!(package_metadata.some_value, 42);
465 ///
466 /// ```
467 #[serde(default, skip_serializing_if = "is_null")]
468 #[cfg_attr(feature = "builder", builder(default))]
469 pub metadata: serde_json::Value,
470 /// The name of a native library the package is linking to.
471 #[cfg_attr(feature = "builder", builder(default))]
472 pub links: Option<String>,
473 /// 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)).
474 ///
475 /// Publishing is unrestricted if `None`, and forbidden if the `Vec` is empty.
476 ///
477 /// This is always `None` if running with a version of Cargo older than 1.39.
478 #[cfg_attr(feature = "builder", builder(default))]
479 pub publish: Option<Vec<String>>,
480 /// The [`default-run` field](https://doc.rust-lang.org/cargo/reference/manifest.html#the-default-run-field) as given in the `Cargo.toml`
481 // (We say "given in" instead of "specified in" since the `default-run` key cannot be inherited from the workspace.)
482 /// The default binary to run by `cargo run`.
483 ///
484 /// This is always `None` if running with a version of Cargo older than 1.55.
485 #[cfg_attr(feature = "builder", builder(default))]
486 pub default_run: Option<String>,
487 /// The [`rust-version` field](https://doc.rust-lang.org/cargo/reference/manifest.html#the-rust-version-field) as specified in the `Cargo.toml`.
488 /// The minimum supported Rust version of this package.
489 ///
490 /// This is always `None` if running with a version of Cargo older than 1.58.
491 #[serde(default)]
492 #[serde(deserialize_with = "deserialize_rust_version")]
493 #[cfg_attr(feature = "builder", builder(default))]
494 pub rust_version: Option<Version>,
495}
496
497#[cfg(feature = "builder")]
498impl PackageBuilder {
499 /// Construct a new `PackageBuilder` with all required fields.
500 pub fn new(
501 name: impl Into<String>,
502 version: impl Into<Version>,
503 id: impl Into<PackageId>,
504 path: impl Into<Utf8PathBuf>,
505 ) -> Self {
506 Self::default()
507 .name(name)
508 .version(version)
509 .id(id)
510 .manifest_path(path)
511 }
512}
513
514impl Package {
515 /// Full path to the license file if one is present in the manifest
516 pub fn license_file(&self) -> Option<Utf8PathBuf> {
517 self.license_file.as_ref().map(|file| {
518 self.manifest_path
519 .parent()
520 .unwrap_or(&self.manifest_path)
521 .join(file)
522 })
523 }
524
525 /// Full path to the readme file if one is present in the manifest
526 pub fn readme(&self) -> Option<Utf8PathBuf> {
527 self.readme.as_ref().map(|file| {
528 self.manifest_path
529 .parent()
530 .unwrap_or(&self.manifest_path)
531 .join(file)
532 })
533 }
534}
535
536/// The source of a package such as crates.io.
537///
538/// It is possible to inspect the `repr` field, if the need arises, but its
539/// precise format is an implementation detail and is subject to change.
540#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq, Hash)]
541#[serde(transparent)]
542pub struct Source {
543 /// The underlying string representation of a source.
544 pub repr: String,
545}
546
547impl Source {
548 /// Returns true if the source is crates.io.
549 pub fn is_crates_io(&self) -> bool {
550 self.repr == "registry+https://github.com/rust-lang/crates.io-index"
551 }
552}
553
554impl fmt::Display for Source {
555 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
556 fmt::Display::fmt(&self.repr, f)
557 }
558}
559
560#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq, Hash)]
561#[cfg_attr(feature = "builder", derive(Builder))]
562#[cfg_attr(feature = "builder", builder(pattern = "owned", setter(into)))]
563#[non_exhaustive]
564/// A single target (lib, bin, example, ...) provided by a crate
565pub struct Target {
566 /// Name as given in the `Cargo.toml` or generated from the file name
567 pub name: String,
568 /// Kind of target.
569 ///
570 /// The possible values are `example`, `test`, `bench`, `custom-build` and
571 /// [Cargo crate types](https://doc.rust-lang.org/cargo/reference/cargo-targets.html#the-crate-type-field):
572 /// `bin`, `lib`, `rlib`, `dylib`, `cdylib`, `staticlib`, `proc-macro`.
573 ///
574 /// Other possible values may be added in the future.
575 pub kind: Vec<TargetKind>,
576 /// Similar to `kind`, but only reports the
577 /// [Cargo crate types](https://doc.rust-lang.org/cargo/reference/cargo-targets.html#the-crate-type-field):
578 /// `bin`, `lib`, `rlib`, `dylib`, `cdylib`, `staticlib`, `proc-macro`.
579 /// Everything that's not a proc macro or a library of some kind is reported as "bin".
580 ///
581 /// Other possible values may be added in the future.
582 #[serde(default)]
583 #[cfg_attr(feature = "builder", builder(default))]
584 pub crate_types: Vec<CrateType>,
585
586 #[serde(default)]
587 #[cfg_attr(feature = "builder", builder(default))]
588 #[serde(rename = "required-features")]
589 /// This target is built only if these features are enabled.
590 /// It doesn't apply to `lib` targets.
591 pub required_features: Vec<String>,
592 /// Path to the main source file of the target
593 pub src_path: Utf8PathBuf,
594 /// Rust edition for this target
595 #[serde(default)]
596 #[cfg_attr(feature = "builder", builder(default))]
597 pub edition: Edition,
598 /// Whether or not this target has doc tests enabled, and the target is
599 /// compatible with doc testing.
600 ///
601 /// This is always `true` if running with a version of Cargo older than 1.37.
602 #[serde(default = "default_true")]
603 #[cfg_attr(feature = "builder", builder(default = "true"))]
604 pub doctest: bool,
605 /// Whether or not this target is tested by default by `cargo test`.
606 ///
607 /// This is always `true` if running with a version of Cargo older than 1.47.
608 #[serde(default = "default_true")]
609 #[cfg_attr(feature = "builder", builder(default = "true"))]
610 pub test: bool,
611 /// Whether or not this target is documented by `cargo doc`.
612 ///
613 /// This is always `true` if running with a version of Cargo older than 1.50.
614 #[serde(default = "default_true")]
615 #[cfg_attr(feature = "builder", builder(default = "true"))]
616 pub doc: bool,
617}
618
619macro_rules! methods_target_is_kind {
620 ($($name:ident => $kind:expr),*) => {
621 $(
622 /// Return true if this target is of kind `$kind`.
623 pub fn $name(&self) -> bool {
624 self.is_kind($kind)
625 }
626 )*
627 }
628}
629
630impl Target {
631 /// Return true if this target is of the given kind.
632 pub fn is_kind(&self, name: TargetKind) -> bool {
633 self.kind.iter().any(|kind| kind == &name)
634 }
635
636 // Generate `is_*` methods for each `TargetKind`
637 methods_target_is_kind! {
638 is_lib => TargetKind::Lib,
639 is_bin => TargetKind::Bin,
640 is_example => TargetKind::Example,
641 is_test => TargetKind::Test,
642 is_bench => TargetKind::Bench,
643 is_custom_build => TargetKind::CustomBuild,
644 is_proc_macro => TargetKind::ProcMacro,
645 is_cdylib => TargetKind::CDyLib,
646 is_dylib => TargetKind::DyLib,
647 is_rlib => TargetKind::RLib,
648 is_staticlib => TargetKind::StaticLib
649 }
650}
651
652/// Kind of target.
653///
654/// The possible values are `example`, `test`, `bench`, `custom-build` and
655/// [Cargo crate types](https://doc.rust-lang.org/cargo/reference/cargo-targets.html#the-crate-type-field):
656/// `bin`, `lib`, `rlib`, `dylib`, `cdylib`, `staticlib`, `proc-macro`.
657///
658/// Other possible values may be added in the future.
659#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
660#[non_exhaustive]
661pub enum TargetKind {
662 /// `cargo bench` target
663 #[serde(rename = "bench")]
664 Bench,
665 /// Binary executable target
666 #[serde(rename = "bin")]
667 Bin,
668 /// Custom build target
669 #[serde(rename = "custom-build")]
670 CustomBuild,
671 /// Dynamic system library target
672 #[serde(rename = "cdylib")]
673 CDyLib,
674 /// Dynamic Rust library target
675 #[serde(rename = "dylib")]
676 DyLib,
677 /// Example target
678 #[serde(rename = "example")]
679 Example,
680 /// Rust library
681 #[serde(rename = "lib")]
682 Lib,
683 /// Procedural Macro
684 #[serde(rename = "proc-macro")]
685 ProcMacro,
686 /// Rust library for use as an intermediate artifact
687 #[serde(rename = "rlib")]
688 RLib,
689 /// Static system library
690 #[serde(rename = "staticlib")]
691 StaticLib,
692 /// Test target
693 #[serde(rename = "test")]
694 Test,
695 /// Unknown type
696 #[serde(untagged)]
697 Unknown(String),
698}
699
700impl From<&str> for TargetKind {
701 fn from(value: &str) -> Self {
702 match value {
703 "example" => TargetKind::Example,
704 "test" => TargetKind::Test,
705 "bench" => TargetKind::Bench,
706 "custom-build" => TargetKind::CustomBuild,
707 "bin" => TargetKind::Bin,
708 "lib" => TargetKind::Lib,
709 "rlib" => TargetKind::RLib,
710 "dylib" => TargetKind::DyLib,
711 "cdylib" => TargetKind::CDyLib,
712 "staticlib" => TargetKind::StaticLib,
713 "proc-macro" => TargetKind::ProcMacro,
714 x => TargetKind::Unknown(x.to_string()),
715 }
716 }
717}
718
719impl FromStr for TargetKind {
720 type Err = std::convert::Infallible;
721
722 fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
723 Ok(TargetKind::from(s))
724 }
725}
726
727impl fmt::Display for TargetKind {
728 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
729 match self {
730 Self::Bench => "bench".fmt(f),
731 Self::Bin => "bin".fmt(f),
732 Self::CustomBuild => "custom-build".fmt(f),
733 Self::CDyLib => "cdylib".fmt(f),
734 Self::DyLib => "dylib".fmt(f),
735 Self::Example => "example".fmt(f),
736 Self::Lib => "lib".fmt(f),
737 Self::ProcMacro => "proc-macro".fmt(f),
738 Self::RLib => "rlib".fmt(f),
739 Self::StaticLib => "staticlib".fmt(f),
740 Self::Test => "test".fmt(f),
741 Self::Unknown(x) => x.fmt(f),
742 }
743 }
744}
745
746/// Similar to `kind`, but only reports the
747/// [Cargo crate types](https://doc.rust-lang.org/cargo/reference/cargo-targets.html#the-crate-type-field):
748///
749/// `bin`, `lib`, `rlib`, `dylib`, `cdylib`, `staticlib`, `proc-macro`.
750/// Everything that's not a proc macro or a library of some kind is reported as "bin".
751///
752/// Other possible values may be added in the future.
753#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
754#[non_exhaustive]
755pub enum CrateType {
756 /// Binary executable target
757 #[serde(rename = "bin")]
758 Bin,
759 /// Dynamic system library target
760 #[serde(rename = "cdylib")]
761 CDyLib,
762 /// Dynamic Rust library target
763 #[serde(rename = "dylib")]
764 DyLib,
765 /// Rust library
766 #[serde(rename = "lib")]
767 Lib,
768 /// Procedural Macro
769 #[serde(rename = "proc-macro")]
770 ProcMacro,
771 /// Rust library for use as an intermediate artifact
772 #[serde(rename = "rlib")]
773 RLib,
774 /// Static system library
775 #[serde(rename = "staticlib")]
776 StaticLib,
777 /// Unkown type
778 #[serde(untagged)]
779 Unknown(String),
780}
781
782impl From<&str> for CrateType {
783 fn from(value: &str) -> Self {
784 match value {
785 "bin" => CrateType::Bin,
786 "lib" => CrateType::Lib,
787 "rlib" => CrateType::RLib,
788 "dylib" => CrateType::DyLib,
789 "cdylib" => CrateType::CDyLib,
790 "staticlib" => CrateType::StaticLib,
791 "proc-macro" => CrateType::ProcMacro,
792 x => CrateType::Unknown(x.to_string()),
793 }
794 }
795}
796
797impl FromStr for CrateType {
798 type Err = std::convert::Infallible;
799
800 fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
801 Ok(CrateType::from(s))
802 }
803}
804
805impl fmt::Display for CrateType {
806 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
807 match self {
808 Self::Bin => "bin".fmt(f),
809 Self::CDyLib => "cdylib".fmt(f),
810 Self::DyLib => "dylib".fmt(f),
811 Self::Lib => "lib".fmt(f),
812 Self::ProcMacro => "proc-macro".fmt(f),
813 Self::RLib => "rlib".fmt(f),
814 Self::StaticLib => "staticlib".fmt(f),
815 Self::Unknown(x) => x.fmt(f),
816 }
817 }
818}
819
820/// The Rust edition
821///
822/// As of writing this comment rust editions 2024, 2027 and 2030 are not actually a thing yet but are parsed nonetheless for future proofing.
823#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Hash, PartialOrd, Ord)]
824#[non_exhaustive]
825pub enum Edition {
826 /// Edition 2015
827 #[serde(rename = "2015")]
828 E2015,
829 /// Edition 2018
830 #[serde(rename = "2018")]
831 E2018,
832 /// Edition 2021
833 #[serde(rename = "2021")]
834 E2021,
835 /// Edition 2024
836 #[serde(rename = "2024")]
837 E2024,
838 #[doc(hidden)]
839 #[serde(rename = "2027")]
840 _E2027,
841 #[doc(hidden)]
842 #[serde(rename = "2030")]
843 _E2030,
844}
845
846impl Edition {
847 /// Return the string representation of the edition
848 pub fn as_str(&self) -> &'static str {
849 use Edition::*;
850 match self {
851 E2015 => "2015",
852 E2018 => "2018",
853 E2021 => "2021",
854 E2024 => "2024",
855 _E2027 => "2027",
856 _E2030 => "2030",
857 }
858 }
859}
860
861impl fmt::Display for Edition {
862 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
863 f.write_str(self.as_str())
864 }
865}
866
867impl Default for Edition {
868 fn default() -> Self {
869 Self::E2015
870 }
871}
872
873fn default_true() -> bool {
874 true
875}
876
877/// Cargo features flags
878#[derive(Debug, Clone)]
879pub enum CargoOpt {
880 /// Run cargo with `--features-all`
881 AllFeatures,
882 /// Run cargo with `--no-default-features`
883 NoDefaultFeatures,
884 /// Run cargo with `--features <FEATURES>`
885 SomeFeatures(Vec<String>),
886}
887
888/// A builder for configuring `cargo metadata` invocation.
889#[derive(Debug, Clone, Default)]
890pub struct MetadataCommand {
891 /// Path to `cargo` executable. If not set, this will use the
892 /// the `$CARGO` environment variable, and if that is not set, will
893 /// simply be `cargo`.
894 cargo_path: Option<PathBuf>,
895 /// Path to `Cargo.toml`
896 manifest_path: Option<PathBuf>,
897 /// Current directory of the `cargo metadata` process.
898 current_dir: Option<PathBuf>,
899 /// Output information only about workspace members and don't fetch dependencies.
900 no_deps: bool,
901 /// Collections of `CargoOpt::SomeFeatures(..)`
902 features: Vec<String>,
903 /// Latched `CargoOpt::AllFeatures`
904 all_features: bool,
905 /// Latched `CargoOpt::NoDefaultFeatures`
906 no_default_features: bool,
907 /// Arbitrary command line flags to pass to `cargo`. These will be added
908 /// to the end of the command line invocation.
909 other_options: Vec<String>,
910 /// Arbitrary environment variables to set when running `cargo`. These will be merged into
911 /// the calling environment, overriding any which clash.
912 env: BTreeMap<OsString, OsString>,
913 /// Show stderr
914 verbose: bool,
915}
916
917impl MetadataCommand {
918 /// Creates a default `cargo metadata` command, which will look for
919 /// `Cargo.toml` in the ancestors of the current directory.
920 pub fn new() -> MetadataCommand {
921 MetadataCommand::default()
922 }
923 /// Path to `cargo` executable. If not set, this will use the
924 /// the `$CARGO` environment variable, and if that is not set, will
925 /// simply be `cargo`.
926 pub fn cargo_path(&mut self, path: impl Into<PathBuf>) -> &mut MetadataCommand {
927 self.cargo_path = Some(path.into());
928 self
929 }
930 /// Path to `Cargo.toml`
931 pub fn manifest_path(&mut self, path: impl Into<PathBuf>) -> &mut MetadataCommand {
932 self.manifest_path = Some(path.into());
933 self
934 }
935 /// Current directory of the `cargo metadata` process.
936 pub fn current_dir(&mut self, path: impl Into<PathBuf>) -> &mut MetadataCommand {
937 self.current_dir = Some(path.into());
938 self
939 }
940 /// Output information only about workspace members and don't fetch dependencies.
941 pub fn no_deps(&mut self) -> &mut MetadataCommand {
942 self.no_deps = true;
943 self
944 }
945 /// Which features to include.
946 ///
947 /// Call this multiple times to specify advanced feature configurations:
948 ///
949 /// ```no_run
950 /// # use cargo_metadata::{CargoOpt, MetadataCommand};
951 /// MetadataCommand::new()
952 /// .features(CargoOpt::NoDefaultFeatures)
953 /// .features(CargoOpt::SomeFeatures(vec!["feat1".into(), "feat2".into()]))
954 /// .features(CargoOpt::SomeFeatures(vec!["feat3".into()]))
955 /// // ...
956 /// # ;
957 /// ```
958 ///
959 /// # Panics
960 ///
961 /// `cargo metadata` rejects multiple `--no-default-features` flags. Similarly, the `features()`
962 /// method panics when specifying multiple `CargoOpt::NoDefaultFeatures`:
963 ///
964 /// ```should_panic
965 /// # use cargo_metadata::{CargoOpt, MetadataCommand};
966 /// MetadataCommand::new()
967 /// .features(CargoOpt::NoDefaultFeatures)
968 /// .features(CargoOpt::NoDefaultFeatures) // <-- panic!
969 /// // ...
970 /// # ;
971 /// ```
972 ///
973 /// The method also panics for multiple `CargoOpt::AllFeatures` arguments:
974 ///
975 /// ```should_panic
976 /// # use cargo_metadata::{CargoOpt, MetadataCommand};
977 /// MetadataCommand::new()
978 /// .features(CargoOpt::AllFeatures)
979 /// .features(CargoOpt::AllFeatures) // <-- panic!
980 /// // ...
981 /// # ;
982 /// ```
983 pub fn features(&mut self, features: CargoOpt) -> &mut MetadataCommand {
984 match features {
985 CargoOpt::SomeFeatures(features) => self.features.extend(features),
986 CargoOpt::NoDefaultFeatures => {
987 assert!(
988 !self.no_default_features,
989 "Do not supply CargoOpt::NoDefaultFeatures more than once!"
990 );
991 self.no_default_features = true;
992 }
993 CargoOpt::AllFeatures => {
994 assert!(
995 !self.all_features,
996 "Do not supply CargoOpt::AllFeatures more than once!"
997 );
998 self.all_features = true;
999 }
1000 }
1001 self
1002 }
1003 /// Arbitrary command line flags to pass to `cargo`. These will be added
1004 /// to the end of the command line invocation.
1005 pub fn other_options(&mut self, options: impl Into<Vec<String>>) -> &mut MetadataCommand {
1006 self.other_options = options.into();
1007 self
1008 }
1009
1010 /// Arbitrary environment variables to set when running `cargo`. These will be merged into
1011 /// the calling environment, overriding any which clash.
1012 ///
1013 /// Some examples of when you may want to use this:
1014 /// 1. Setting cargo config values without needing a .cargo/config.toml file, e.g. to set
1015 /// `CARGO_NET_GIT_FETCH_WITH_CLI=true`
1016 /// 2. To specify a custom path to RUSTC if your rust toolchain components aren't laid out in
1017 /// the way cargo expects by default.
1018 ///
1019 /// ```no_run
1020 /// # use cargo_metadata::{CargoOpt, MetadataCommand};
1021 /// MetadataCommand::new()
1022 /// .env("CARGO_NET_GIT_FETCH_WITH_CLI", "true")
1023 /// .env("RUSTC", "/path/to/rustc")
1024 /// // ...
1025 /// # ;
1026 /// ```
1027 pub fn env<K: Into<OsString>, V: Into<OsString>>(
1028 &mut self,
1029 key: K,
1030 val: V,
1031 ) -> &mut MetadataCommand {
1032 self.env.insert(key.into(), val.into());
1033 self
1034 }
1035
1036 /// Set whether to show stderr
1037 pub fn verbose(&mut self, verbose: bool) -> &mut MetadataCommand {
1038 self.verbose = verbose;
1039 self
1040 }
1041
1042 /// Builds a command for `cargo metadata`. This is the first
1043 /// part of the work of `exec`.
1044 pub fn cargo_command(&self) -> Command {
1045 let cargo = self
1046 .cargo_path
1047 .clone()
1048 .or_else(|| env::var("CARGO").map(PathBuf::from).ok())
1049 .unwrap_or_else(|| PathBuf::from("cargo"));
1050 let mut cmd = Command::new(cargo);
1051 cmd.args(["metadata", "--format-version", "1"]);
1052
1053 if self.no_deps {
1054 cmd.arg("--no-deps");
1055 }
1056
1057 if let Some(path) = self.current_dir.as_ref() {
1058 cmd.current_dir(path);
1059 }
1060
1061 if !self.features.is_empty() {
1062 cmd.arg("--features").arg(self.features.join(","));
1063 }
1064 if self.all_features {
1065 cmd.arg("--all-features");
1066 }
1067 if self.no_default_features {
1068 cmd.arg("--no-default-features");
1069 }
1070
1071 if let Some(manifest_path) = &self.manifest_path {
1072 cmd.arg("--manifest-path").arg(manifest_path.as_os_str());
1073 }
1074 cmd.args(&self.other_options);
1075
1076 cmd.envs(&self.env);
1077
1078 cmd
1079 }
1080
1081 /// Parses `cargo metadata` output. `data` must have been
1082 /// produced by a command built with `cargo_command`.
1083 pub fn parse<T: AsRef<str>>(data: T) -> Result<Metadata> {
1084 let meta = serde_json::from_str(data.as_ref())?;
1085 Ok(meta)
1086 }
1087
1088 /// Runs configured `cargo metadata` and returns parsed `Metadata`.
1089 pub fn exec(&self) -> Result<Metadata> {
1090 let mut command = self.cargo_command();
1091 if self.verbose {
1092 command.stderr(Stdio::inherit());
1093 }
1094 let output = command.output()?;
1095 if !output.status.success() {
1096 return Err(Error::CargoMetadata {
1097 stderr: String::from_utf8(output.stderr)?,
1098 });
1099 }
1100 let stdout = from_utf8(&output.stdout)?
1101 .lines()
1102 .find(|line| line.starts_with('{'))
1103 .ok_or(Error::NoJson)?;
1104 Self::parse(stdout)
1105 }
1106}
1107
1108/// As per the Cargo Book the [`rust-version` field](https://doc.rust-lang.org/cargo/reference/manifest.html#the-rust-version-field) must:
1109///
1110/// > be a bare version number with two or three components;
1111/// > it cannot include semver operators or pre-release identifiers.
1112///
1113/// [`semver::Version`] however requires three components. This function takes
1114/// care of appending `.0` if the provided version number only has two components
1115/// and ensuring that it does not contain a pre-release version or build metadata.
1116fn deserialize_rust_version<'de, D>(
1117 deserializer: D,
1118) -> std::result::Result<Option<Version>, D::Error>
1119where
1120 D: Deserializer<'de>,
1121{
1122 let mut buf = match Option::<String>::deserialize(deserializer)? {
1123 None => return Ok(None),
1124 Some(buf) => buf,
1125 };
1126
1127 for char in buf.chars() {
1128 if char == '-' {
1129 return Err(serde::de::Error::custom(
1130 "pre-release identifiers are not supported in rust-version",
1131 ));
1132 } else if char == '+' {
1133 return Err(serde::de::Error::custom(
1134 "build metadata is not supported in rust-version",
1135 ));
1136 }
1137 }
1138
1139 if buf.matches('.').count() == 1 {
1140 // e.g. 1.0 -> 1.0.0
1141 buf.push_str(".0");
1142 }
1143
1144 Ok(Some(
1145 Version::parse(&buf).map_err(serde::de::Error::custom)?,
1146 ))
1147}
1148
1149#[cfg(test)]
1150mod test {
1151 use semver::Version;
1152
1153 #[derive(Debug, serde::Deserialize)]
1154 struct BareVersion(
1155 #[serde(deserialize_with = "super::deserialize_rust_version")] Option<semver::Version>,
1156 );
1157
1158 fn bare_version(str: &str) -> Version {
1159 serde_json::from_str::<BareVersion>(&format!(r#""{}""#, str))
1160 .unwrap()
1161 .0
1162 .unwrap()
1163 }
1164
1165 fn bare_version_err(str: &str) -> String {
1166 serde_json::from_str::<BareVersion>(&format!(r#""{}""#, str))
1167 .unwrap_err()
1168 .to_string()
1169 }
1170
1171 #[test]
1172 fn test_deserialize_rust_version() {
1173 assert_eq!(bare_version("1.2"), Version::new(1, 2, 0));
1174 assert_eq!(bare_version("1.2.0"), Version::new(1, 2, 0));
1175 assert_eq!(
1176 bare_version_err("1.2.0-alpha"),
1177 "pre-release identifiers are not supported in rust-version"
1178 );
1179 assert_eq!(
1180 bare_version_err("1.2.0+123"),
1181 "build metadata is not supported in rust-version"
1182 );
1183 }
1184}