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