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