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 
81 use std::collections::HashMap;
82 use std::env;
83 use std::fmt;
84 use std::path::PathBuf;
85 use std::process::Command;
86 use std::str::from_utf8;
87 
88 pub use semver::Version;
89 
90 pub use dependency::{Dependency, DependencyKind};
91 use diagnostic::Diagnostic;
92 pub use errors::{Error, Result};
93 #[allow(deprecated)]
94 pub use messages::parse_messages;
95 pub use messages::{
96     Artifact, ArtifactProfile, BuildFinished, BuildScript, CompilerMessage, Message, MessageIter,
97 };
98 use serde::{Deserialize, Serialize};
99 
100 mod dependency;
101 pub mod diagnostic;
102 mod errors;
103 mod messages;
104 
105 /// An "opaque" identifier for a package.
106 /// It is possible to inspect the `repr` field, if the need arises, but its
107 /// precise format is an implementation detail and is subject to change.
108 ///
109 /// `Metadata` can be indexed by `PackageId`.
110 #[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
111 #[serde(transparent)]
112 pub struct PackageId {
113     /// The underlying string representation of id.
114     pub repr: String,
115 }
116 
117 impl std::fmt::Display for PackageId {
fmt(&self, f: &mut fmt::Formatter) -> fmt::Result118     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
119         fmt::Display::fmt(&self.repr, f)
120     }
121 }
122 
123 // Helpers for default metadata fields
is_null(value: &serde_json::Value) -> bool124 fn is_null(value: &serde_json::Value) -> bool {
125     match value {
126         serde_json::Value::Null => true,
127         _ => false,
128     }
129 }
130 
131 #[derive(Clone, Serialize, Deserialize, Debug)]
132 /// Starting point for metadata returned by `cargo metadata`
133 pub struct Metadata {
134     /// A list of all crates referenced by this crate (and the crate itself)
135     pub packages: Vec<Package>,
136     /// A list of all workspace members
137     pub workspace_members: Vec<PackageId>,
138     /// Dependencies graph
139     pub resolve: Option<Resolve>,
140     /// Workspace root
141     pub workspace_root: PathBuf,
142     /// Build directory
143     pub target_directory: PathBuf,
144     /// The workspace-level metadata object. Null if non-existent.
145     #[serde(rename = "metadata", default, skip_serializing_if = "is_null")]
146     pub workspace_metadata: serde_json::Value,
147     version: usize,
148     #[doc(hidden)]
149     #[serde(skip)]
150     __do_not_match_exhaustively: (),
151 }
152 
153 impl Metadata {
154     /// Get the root package of this metadata instance.
root_package(&self) -> Option<&Package>155     pub fn root_package(&self) -> Option<&Package> {
156         let root = self.resolve.as_ref()?.root.as_ref()?;
157         self.packages.iter().find(|pkg| &pkg.id == root)
158     }
159 }
160 
161 impl<'a> std::ops::Index<&'a PackageId> for Metadata {
162     type Output = Package;
163 
index(&self, idx: &'a PackageId) -> &Package164     fn index(&self, idx: &'a PackageId) -> &Package {
165         self.packages
166             .iter()
167             .find(|p| p.id == *idx)
168             .unwrap_or_else(|| panic!("no package with this id: {:?}", idx))
169     }
170 }
171 
172 #[derive(Clone, Serialize, Deserialize, Debug)]
173 /// A dependency graph
174 pub struct Resolve {
175     /// Nodes in a dependencies graph
176     pub nodes: Vec<Node>,
177 
178     /// The crate for which the metadata was read.
179     pub root: Option<PackageId>,
180     #[doc(hidden)]
181     #[serde(skip)]
182     __do_not_match_exhaustively: (),
183 }
184 
185 #[derive(Clone, Serialize, Deserialize, Debug)]
186 /// A node in a dependencies graph
187 pub struct Node {
188     /// An opaque identifier for a package
189     pub id: PackageId,
190     /// Dependencies in a structured format.
191     ///
192     /// `deps` handles renamed dependencies whereas `dependencies` does not.
193     #[serde(default)]
194     pub deps: Vec<NodeDep>,
195 
196     /// List of opaque identifiers for this node's dependencies.
197     /// It doesn't support renamed dependencies. See `deps`.
198     pub dependencies: Vec<PackageId>,
199 
200     /// Features enabled on the crate
201     #[serde(default)]
202     pub features: Vec<String>,
203     #[doc(hidden)]
204     #[serde(skip)]
205     __do_not_match_exhaustively: (),
206 }
207 
208 #[derive(Clone, Serialize, Deserialize, Debug)]
209 /// A dependency in a node
210 pub struct NodeDep {
211     /// The name of the dependency's library target.
212     /// If the crate was renamed, it is the new name.
213     pub name: String,
214     /// Package ID (opaque unique identifier)
215     pub pkg: PackageId,
216     /// The kinds of dependencies.
217     ///
218     /// This field was added in Rust 1.41.
219     #[serde(default)]
220     pub dep_kinds: Vec<DepKindInfo>,
221     #[doc(hidden)]
222     #[serde(skip)]
223     __do_not_match_exhaustively: (),
224 }
225 
226 #[derive(Clone, Serialize, Deserialize, Debug)]
227 /// Information about a dependency kind.
228 pub struct DepKindInfo {
229     /// The kind of dependency.
230     #[serde(deserialize_with = "dependency::parse_dependency_kind")]
231     pub kind: DependencyKind,
232     /// The target platform for the dependency.
233     ///
234     /// This is `None` if it is not a target dependency.
235     ///
236     /// Use the [`Display`] trait to access the contents.
237     ///
238     /// By default all platform dependencies are included in the resolve
239     /// graph. Use Cargo's `--filter-platform` flag if you only want to
240     /// include dependencies for a specific platform.
241     ///
242     /// [`Display`]: std::fmt::Display
243     pub target: Option<dependency::Platform>,
244     #[doc(hidden)]
245     #[serde(skip)]
246     __do_not_match_exhaustively: (),
247 }
248 
249 #[derive(Clone, Serialize, Deserialize, Debug)]
250 /// One or more crates described by a single `Cargo.toml`
251 ///
252 /// Each [`target`][Package::targets] of a `Package` will be built as a crate.
253 /// For more information, see <https://doc.rust-lang.org/book/ch07-01-packages-and-crates.html>.
254 pub struct Package {
255     /// Name as given in the `Cargo.toml`
256     pub name: String,
257     /// Version given in the `Cargo.toml`
258     pub version: Version,
259     /// Authors given in the `Cargo.toml`
260     #[serde(default)]
261     pub authors: Vec<String>,
262     /// An opaque identifier for a package
263     pub id: PackageId,
264     /// The source of the package, e.g.
265     /// crates.io or `None` for local projects.
266     pub source: Option<Source>,
267     /// Description as given in the `Cargo.toml`
268     pub description: Option<String>,
269     /// List of dependencies of this particular package
270     pub dependencies: Vec<Dependency>,
271     /// License as given in the `Cargo.toml`
272     pub license: Option<String>,
273     /// If the package is using a nonstandard license, this key may be specified instead of
274     /// `license`, and must point to a file relative to the manifest.
275     pub license_file: Option<PathBuf>,
276     /// Targets provided by the crate (lib, bin, example, test, ...)
277     pub targets: Vec<Target>,
278     /// Features provided by the crate, mapped to the features required by that feature.
279     pub features: HashMap<String, Vec<String>>,
280     /// Path containing the `Cargo.toml`
281     pub manifest_path: PathBuf,
282     /// Categories as given in the `Cargo.toml`
283     #[serde(default)]
284     pub categories: Vec<String>,
285     /// Keywords as given in the `Cargo.toml`
286     #[serde(default)]
287     pub keywords: Vec<String>,
288     /// Readme as given in the `Cargo.toml`
289     pub readme: Option<PathBuf>,
290     /// Repository as given in the `Cargo.toml`
291     // can't use `url::Url` because that requires a more recent stable compiler
292     pub repository: Option<String>,
293     /// Homepage as given in the `Cargo.toml`
294     ///
295     /// On versions of cargo before 1.49, this will always be [`None`].
296     pub homepage: Option<String>,
297     /// Documentation URL as given in the `Cargo.toml`
298     ///
299     /// On versions of cargo before 1.49, this will always be [`None`].
300     pub documentation: Option<String>,
301     /// Default Rust edition for the package
302     ///
303     /// Beware that individual targets may specify their own edition in
304     /// [`Target::edition`].
305     #[serde(default = "edition_default")]
306     pub edition: String,
307     /// Contents of the free form package.metadata section
308     ///
309     /// This contents can be serialized to a struct using serde:
310     ///
311     /// ```rust
312     /// use serde::Deserialize;
313     /// use serde_json::json;
314     ///
315     /// #[derive(Debug, Deserialize)]
316     /// struct SomePackageMetadata {
317     ///     some_value: i32,
318     /// }
319     ///
320     /// fn main() {
321     ///     let value = json!({
322     ///         "some_value": 42,
323     ///     });
324     ///
325     ///     let package_metadata: SomePackageMetadata = serde_json::from_value(value).unwrap();
326     ///     assert_eq!(package_metadata.some_value, 42);
327     /// }
328     ///
329     /// ```
330     #[serde(default, skip_serializing_if = "is_null")]
331     pub metadata: serde_json::Value,
332     /// The name of a native library the package is linking to.
333     pub links: Option<String>,
334     /// List of registries to which this package may be published.
335     ///
336     /// Publishing is unrestricted if `None`, and forbidden if the `Vec` is empty.
337     ///
338     /// This is always `None` if running with a version of Cargo older than 1.39.
339     pub publish: Option<Vec<String>>,
340     #[doc(hidden)]
341     #[serde(skip)]
342     __do_not_match_exhaustively: (),
343 }
344 
345 impl Package {
346     /// Full path to the license file if one is present in the manifest
license_file(&self) -> Option<PathBuf>347     pub fn license_file(&self) -> Option<PathBuf> {
348         self.license_file.as_ref().map(|file| {
349             self.manifest_path
350                 .parent()
351                 .unwrap_or(&self.manifest_path)
352                 .join(file)
353         })
354     }
355 
356     /// Full path to the readme file if one is present in the manifest
readme(&self) -> Option<PathBuf>357     pub fn readme(&self) -> Option<PathBuf> {
358         self.readme
359             .as_ref()
360             .map(|file| self.manifest_path.join(file))
361     }
362 }
363 
364 /// The source of a package such as crates.io.
365 #[derive(Clone, Serialize, Deserialize, Debug)]
366 #[serde(transparent)]
367 pub struct Source {
368     /// The underlying string representation of a source.
369     pub repr: String,
370 }
371 
372 impl Source {
373     /// Returns true if the source is crates.io.
is_crates_io(&self) -> bool374     pub fn is_crates_io(&self) -> bool {
375         self.repr == "registry+https://github.com/rust-lang/crates.io-index"
376     }
377 }
378 
379 impl std::fmt::Display for Source {
fmt(&self, f: &mut fmt::Formatter) -> fmt::Result380     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
381         fmt::Display::fmt(&self.repr, f)
382     }
383 }
384 
385 #[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq, Hash)]
386 /// A single target (lib, bin, example, ...) provided by a crate
387 pub struct Target {
388     /// Name as given in the `Cargo.toml` or generated from the file name
389     pub name: String,
390     /// Kind of target ("bin", "example", "test", "bench", "lib")
391     pub kind: Vec<String>,
392     /// Almost the same as `kind`, except when an example is a library instead of an executable.
393     /// In that case `crate_types` contains things like `rlib` and `dylib` while `kind` is `example`
394     #[serde(default)]
395     pub crate_types: Vec<String>,
396 
397     #[serde(default)]
398     #[serde(rename = "required-features")]
399     /// This target is built only if these features are enabled.
400     /// It doesn't apply to `lib` targets.
401     pub required_features: Vec<String>,
402     /// Path to the main source file of the target
403     pub src_path: PathBuf,
404     /// Rust edition for this target
405     #[serde(default = "edition_default")]
406     pub edition: String,
407     /// Whether or not this target has doc tests enabled, and the target is
408     /// compatible with doc testing.
409     ///
410     /// This is always `true` if running with a version of Cargo older than 1.37.
411     #[serde(default = "default_true")]
412     pub doctest: bool,
413     /// Whether or not this target is tested by default by `cargo test`.
414     ///
415     /// This is always `true` if running with a version of Cargo older than 1.47.
416     #[serde(default = "default_true")]
417     pub test: bool,
418     #[doc(hidden)]
419     #[serde(skip)]
420     __do_not_match_exhaustively: (),
421 }
422 
default_true() -> bool423 fn default_true() -> bool {
424     true
425 }
426 
edition_default() -> String427 fn edition_default() -> String {
428     "2015".to_string()
429 }
430 
431 /// Cargo features flags
432 #[derive(Debug, Clone)]
433 pub enum CargoOpt {
434     /// Run cargo with `--features-all`
435     AllFeatures,
436     /// Run cargo with `--no-default-features`
437     NoDefaultFeatures,
438     /// Run cargo with `--features <FEATURES>`
439     SomeFeatures(Vec<String>),
440 }
441 
442 /// A builder for configurating `cargo metadata` invocation.
443 #[derive(Debug, Clone, Default)]
444 pub struct MetadataCommand {
445     cargo_path: Option<PathBuf>,
446     manifest_path: Option<PathBuf>,
447     current_dir: Option<PathBuf>,
448     no_deps: bool,
449     /// Collections of `CargoOpt::SomeFeatures(..)`
450     features: Vec<String>,
451     /// Latched `CargoOpt::AllFeatures`
452     all_features: bool,
453     /// Latched `CargoOpt::NoDefaultFeatures`
454     no_default_features: bool,
455     other_options: Vec<String>,
456 }
457 
458 impl MetadataCommand {
459     /// Creates a default `cargo metadata` command, which will look for
460     /// `Cargo.toml` in the ancestors of the current directory.
new() -> MetadataCommand461     pub fn new() -> MetadataCommand {
462         MetadataCommand::default()
463     }
464     /// Path to `cargo` executable.  If not set, this will use the
465     /// the `$CARGO` environment variable, and if that is not set, will
466     /// simply be `cargo`.
cargo_path(&mut self, path: impl Into<PathBuf>) -> &mut MetadataCommand467     pub fn cargo_path(&mut self, path: impl Into<PathBuf>) -> &mut MetadataCommand {
468         self.cargo_path = Some(path.into());
469         self
470     }
471     /// Path to `Cargo.toml`
manifest_path(&mut self, path: impl Into<PathBuf>) -> &mut MetadataCommand472     pub fn manifest_path(&mut self, path: impl Into<PathBuf>) -> &mut MetadataCommand {
473         self.manifest_path = Some(path.into());
474         self
475     }
476     /// Current directory of the `cargo metadata` process.
current_dir(&mut self, path: impl Into<PathBuf>) -> &mut MetadataCommand477     pub fn current_dir(&mut self, path: impl Into<PathBuf>) -> &mut MetadataCommand {
478         self.current_dir = Some(path.into());
479         self
480     }
481     /// Output information only about the root package and don't fetch dependencies.
no_deps(&mut self) -> &mut MetadataCommand482     pub fn no_deps(&mut self) -> &mut MetadataCommand {
483         self.no_deps = true;
484         self
485     }
486     /// Which features to include.
487     ///
488     /// Call this multiple times to specify advanced feature configurations:
489     ///
490     /// ```no_run
491     /// # use cargo_metadata::{CargoOpt, MetadataCommand};
492     /// MetadataCommand::new()
493     ///     .features(CargoOpt::NoDefaultFeatures)
494     ///     .features(CargoOpt::SomeFeatures(vec!["feat1".into(), "feat2".into()]))
495     ///     .features(CargoOpt::SomeFeatures(vec!["feat3".into()]))
496     ///     // ...
497     ///     # ;
498     /// ```
499     ///
500     /// # Panics
501     ///
502     /// `cargo metadata` rejects multiple `--no-default-features` flags. Similarly, the `features()`
503     /// method panics when specifiying multiple `CargoOpt::NoDefaultFeatures`:
504     ///
505     /// ```should_panic
506     /// # use cargo_metadata::{CargoOpt, MetadataCommand};
507     /// MetadataCommand::new()
508     ///     .features(CargoOpt::NoDefaultFeatures)
509     ///     .features(CargoOpt::NoDefaultFeatures) // <-- panic!
510     ///     // ...
511     ///     # ;
512     /// ```
513     ///
514     /// The method also panics for multiple `CargoOpt::AllFeatures` arguments:
515     ///
516     /// ```should_panic
517     /// # use cargo_metadata::{CargoOpt, MetadataCommand};
518     /// MetadataCommand::new()
519     ///     .features(CargoOpt::AllFeatures)
520     ///     .features(CargoOpt::AllFeatures) // <-- panic!
521     ///     // ...
522     ///     # ;
523     /// ```
features(&mut self, features: CargoOpt) -> &mut MetadataCommand524     pub fn features(&mut self, features: CargoOpt) -> &mut MetadataCommand {
525         match features {
526             CargoOpt::SomeFeatures(features) => self.features.extend(features),
527             CargoOpt::NoDefaultFeatures => {
528                 assert!(
529                     !self.no_default_features,
530                     "Do not supply CargoOpt::NoDefaultFeatures more than once!"
531                 );
532                 self.no_default_features = true;
533             }
534             CargoOpt::AllFeatures => {
535                 assert!(
536                     !self.all_features,
537                     "Do not supply CargoOpt::AllFeatures more than once!"
538                 );
539                 self.all_features = true;
540             }
541         }
542         self
543     }
544     /// Arbitrary command line flags to pass to `cargo`.  These will be added
545     /// to the end of the command line invocation.
other_options(&mut self, options: impl Into<Vec<String>>) -> &mut MetadataCommand546     pub fn other_options(&mut self, options: impl Into<Vec<String>>) -> &mut MetadataCommand {
547         self.other_options = options.into();
548         self
549     }
550 
551     /// Builds a command for `cargo metadata`.  This is the first
552     /// part of the work of `exec`.
cargo_command(&self) -> Command553     pub fn cargo_command(&self) -> Command {
554         let cargo = self
555             .cargo_path
556             .clone()
557             .or_else(|| env::var("CARGO").map(PathBuf::from).ok())
558             .unwrap_or_else(|| PathBuf::from("cargo"));
559         let mut cmd = Command::new(cargo);
560         cmd.args(&["metadata", "--format-version", "1"]);
561 
562         if self.no_deps {
563             cmd.arg("--no-deps");
564         }
565 
566         if let Some(path) = self.current_dir.as_ref() {
567             cmd.current_dir(path);
568         }
569 
570         if !self.features.is_empty() {
571             cmd.arg("--features").arg(self.features.join(","));
572         }
573         if self.all_features {
574             cmd.arg("--all-features");
575         }
576         if self.no_default_features {
577             cmd.arg("--no-default-features");
578         }
579 
580         if let Some(manifest_path) = &self.manifest_path {
581             cmd.arg("--manifest-path").arg(manifest_path.as_os_str());
582         }
583         cmd.args(&self.other_options);
584 
585         cmd
586     }
587 
588     /// Parses `cargo metadata` output.  `data` must have been
589     /// produced by a command built with `cargo_command`.
parse<T: AsRef<str>>(data: T) -> Result<Metadata>590     pub fn parse<T: AsRef<str>>(data: T) -> Result<Metadata> {
591         let meta = serde_json::from_str(data.as_ref())?;
592         Ok(meta)
593     }
594 
595     /// Runs configured `cargo metadata` and returns parsed `Metadata`.
exec(&self) -> Result<Metadata>596     pub fn exec(&self) -> Result<Metadata> {
597         let output = self.cargo_command().output()?;
598         if !output.status.success() {
599             return Err(Error::CargoMetadata {
600                 stderr: String::from_utf8(output.stderr)?,
601             });
602         }
603         let stdout = from_utf8(&output.stdout)?
604             .lines()
605             .find(|line| line.starts_with('{'))
606             .ok_or_else(|| Error::NoJson)?;
607         Self::parse(stdout)
608     }
609 }
610