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