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