]> git.proxmox.com Git - rustc.git/blame - vendor/cargo_metadata-0.12.0/src/lib.rs
Merge tag 'debian/1.52.1+dfsg1-1_exp2' into proxmox/buster
[rustc.git] / vendor / cargo_metadata-0.12.0 / src / lib.rs
CommitLineData
f20569fa
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"])
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 std::collections::HashMap;
82use std::env;
83use std::fmt;
84use std::path::PathBuf;
85use std::process::Command;
86use std::str::from_utf8;
87
88pub use semver::Version;
89
90pub use dependency::{Dependency, DependencyKind};
91use diagnostic::Diagnostic;
92pub use errors::{Error, Result};
93#[allow(deprecated)]
94pub use messages::parse_messages;
95pub use messages::{
96 Artifact, ArtifactProfile, BuildFinished, BuildScript, CompilerMessage, Message, MessageIter,
97};
98use serde::{Deserialize, Serialize};
99
100mod dependency;
101pub mod diagnostic;
102mod errors;
103mod 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)]
112pub struct PackageId {
113 /// The underlying string representation of id.
114 pub repr: String,
115}
116
117impl std::fmt::Display for PackageId {
118 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
124fn 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`
133pub 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
153impl Metadata {
154 /// Get the root package of this metadata instance.
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
161impl<'a> std::ops::Index<&'a PackageId> for Metadata {
162 type Output = Package;
163
164 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
174pub 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
187pub 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
210pub 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.
228pub 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`]: https://doc.rust-lang.org/std/fmt/trait.Display.html
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/// A crate
251pub struct Package {
252 /// Name as given in the `Cargo.toml`
253 pub name: String,
254 /// Version given in the `Cargo.toml`
255 pub version: Version,
256 /// Authors given in the `Cargo.toml`
257 #[serde(default)]
258 pub authors: Vec<String>,
259 /// An opaque identifier for a package
260 pub id: PackageId,
261 /// The source of the package, e.g.
262 /// crates.io or `None` for local projects.
263 pub source: Option<Source>,
264 /// Description as given in the `Cargo.toml`
265 pub description: Option<String>,
266 /// List of dependencies of this particular package
267 pub dependencies: Vec<Dependency>,
268 /// License as given in the `Cargo.toml`
269 pub license: Option<String>,
270 /// If the package is using a nonstandard license, this key may be specified instead of
271 /// `license`, and must point to a file relative to the manifest.
272 pub license_file: Option<PathBuf>,
273 /// Targets provided by the crate (lib, bin, example, test, ...)
274 pub targets: Vec<Target>,
275 /// Features provided by the crate, mapped to the features required by that feature.
276 pub features: HashMap<String, Vec<String>>,
277 /// Path containing the `Cargo.toml`
278 pub manifest_path: PathBuf,
279 /// Categories as given in the `Cargo.toml`
280 #[serde(default)]
281 pub categories: Vec<String>,
282 /// Keywords as given in the `Cargo.toml`
283 #[serde(default)]
284 pub keywords: Vec<String>,
285 /// Readme as given in the `Cargo.toml`
286 pub readme: Option<PathBuf>,
287 /// Repository as given in the `Cargo.toml`
288 // can't use `url::Url` because that requires a more recent stable compiler
289 pub repository: Option<String>,
290 /// Default Rust edition for the package
291 ///
292 /// Beware that individual targets may specify their own edition in
293 /// [`Target::edition`](struct.Target.html#structfield.edition).
294 #[serde(default = "edition_default")]
295 pub edition: String,
296 /// Contents of the free form package.metadata section
297 ///
298 /// This contents can be serialized to a struct using serde:
299 ///
300 /// ```rust
301 /// use serde::Deserialize;
302 /// use serde_json::json;
303 ///
304 /// #[derive(Debug, Deserialize)]
305 /// struct SomePackageMetadata {
306 /// some_value: i32,
307 /// }
308 ///
309 /// fn main() {
310 /// let value = json!({
311 /// "some_value": 42,
312 /// });
313 ///
314 /// let package_metadata: SomePackageMetadata = serde_json::from_value(value).unwrap();
315 /// assert_eq!(package_metadata.some_value, 42);
316 /// }
317 ///
318 /// ```
319 #[serde(default, skip_serializing_if = "is_null")]
320 pub metadata: serde_json::Value,
321 /// The name of a native library the package is linking to.
322 pub links: Option<String>,
323 /// List of registries to which this package may be published.
324 ///
325 /// Publishing is unrestricted if `None`, and forbidden if the `Vec` is empty.
326 ///
327 /// This is always `None` if running with a version of Cargo older than 1.39.
328 pub publish: Option<Vec<String>>,
329 #[doc(hidden)]
330 #[serde(skip)]
331 __do_not_match_exhaustively: (),
332}
333
334impl Package {
335 /// Full path to the license file if one is present in the manifest
336 pub fn license_file(&self) -> Option<PathBuf> {
337 self.license_file.as_ref().map(|file| {
338 self.manifest_path
339 .parent()
340 .unwrap_or(&self.manifest_path)
341 .join(file)
342 })
343 }
344
345 /// Full path to the readme file if one is present in the manifest
346 pub fn readme(&self) -> Option<PathBuf> {
347 self.readme
348 .as_ref()
349 .map(|file| self.manifest_path.join(file))
350 }
351}
352
353/// The source of a package such as crates.io.
354#[derive(Clone, Serialize, Deserialize, Debug)]
355#[serde(transparent)]
356pub struct Source {
357 /// The underlying string representation of a source.
358 pub repr: String,
359}
360
361impl Source {
362 /// Returns true if the source is crates.io.
363 pub fn is_crates_io(&self) -> bool {
364 self.repr == "registry+https://github.com/rust-lang/crates.io-index"
365 }
366}
367
368impl std::fmt::Display for Source {
369 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
370 fmt::Display::fmt(&self.repr, f)
371 }
372}
373
374#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq, Hash)]
375/// A single target (lib, bin, example, ...) provided by a crate
376pub struct Target {
377 /// Name as given in the `Cargo.toml` or generated from the file name
378 pub name: String,
379 /// Kind of target ("bin", "example", "test", "bench", "lib")
380 pub kind: Vec<String>,
381 /// Almost the same as `kind`, except when an example is a library instead of an executable.
382 /// In that case `crate_types` contains things like `rlib` and `dylib` while `kind` is `example`
383 #[serde(default)]
384 pub crate_types: Vec<String>,
385
386 #[serde(default)]
387 #[serde(rename = "required-features")]
388 /// This target is built only if these features are enabled.
389 /// It doesn't apply to `lib` targets.
390 pub required_features: Vec<String>,
391 /// Path to the main source file of the target
392 pub src_path: PathBuf,
393 /// Rust edition for this target
394 #[serde(default = "edition_default")]
395 pub edition: String,
396 /// Whether or not this target has doc tests enabled, and the target is
397 /// compatible with doc testing.
398 ///
399 /// This is always `true` if running with a version of Cargo older than 1.37.
400 #[serde(default = "default_true")]
401 pub doctest: bool,
402 /// Whether or not this target is tested by default by `cargo test`.
403 ///
404 /// This is always `true` if running with a version of Cargo older than 1.47.
405 #[serde(default = "default_true")]
406 pub test: bool,
407 #[doc(hidden)]
408 #[serde(skip)]
409 __do_not_match_exhaustively: (),
410}
411
412fn default_true() -> bool {
413 true
414}
415
416fn edition_default() -> String {
417 "2015".to_string()
418}
419
420/// Cargo features flags
421#[derive(Debug, Clone)]
422pub enum CargoOpt {
423 /// Run cargo with `--features-all`
424 AllFeatures,
425 /// Run cargo with `--no-default-features`
426 NoDefaultFeatures,
427 /// Run cargo with `--features <FEATURES>`
428 SomeFeatures(Vec<String>),
429}
430
431/// A builder for configurating `cargo metadata` invocation.
432#[derive(Debug, Clone, Default)]
433pub struct MetadataCommand {
434 cargo_path: Option<PathBuf>,
435 manifest_path: Option<PathBuf>,
436 current_dir: Option<PathBuf>,
437 no_deps: bool,
438 /// Collections of `CargoOpt::SomeFeatures(..)`
439 features: Vec<String>,
440 /// Latched `CargoOpt::AllFeatures`
441 all_features: bool,
442 /// Latched `CargoOpt::NoDefaultFeatures`
443 no_default_features: bool,
444 other_options: Vec<String>,
445}
446
447impl MetadataCommand {
448 /// Creates a default `cargo metadata` command, which will look for
449 /// `Cargo.toml` in the ancestors of the current directory.
450 pub fn new() -> MetadataCommand {
451 MetadataCommand::default()
452 }
453 /// Path to `cargo` executable. If not set, this will use the
454 /// the `$CARGO` environment variable, and if that is not set, will
455 /// simply be `cargo`.
456 pub fn cargo_path(&mut self, path: impl Into<PathBuf>) -> &mut MetadataCommand {
457 self.cargo_path = Some(path.into());
458 self
459 }
460 /// Path to `Cargo.toml`
461 pub fn manifest_path(&mut self, path: impl Into<PathBuf>) -> &mut MetadataCommand {
462 self.manifest_path = Some(path.into());
463 self
464 }
465 /// Current directory of the `cargo metadata` process.
466 pub fn current_dir(&mut self, path: impl Into<PathBuf>) -> &mut MetadataCommand {
467 self.current_dir = Some(path.into());
468 self
469 }
470 /// Output information only about the root package and don't fetch dependencies.
471 pub fn no_deps(&mut self) -> &mut MetadataCommand {
472 self.no_deps = true;
473 self
474 }
475 /// Which features to include.
476 ///
477 /// Call this multiple times to specify advanced feature configurations:
478 ///
479 /// ```no_run
480 /// # use cargo_metadata::{CargoOpt, MetadataCommand};
481 /// MetadataCommand::new()
482 /// .features(CargoOpt::NoDefaultFeatures)
483 /// .features(CargoOpt::SomeFeatures(vec!["feat1".into(), "feat2".into()]))
484 /// .features(CargoOpt::SomeFeatures(vec!["feat3".into()]))
485 /// // ...
486 /// # ;
487 /// ```
488 ///
489 /// # Panics
490 ///
491 /// `cargo metadata` rejects multiple `--no-default-features` flags. Similarly, the `features()`
492 /// method panics when specifiying multiple `CargoOpt::NoDefaultFeatures`:
493 ///
494 /// ```should_panic
495 /// # use cargo_metadata::{CargoOpt, MetadataCommand};
496 /// MetadataCommand::new()
497 /// .features(CargoOpt::NoDefaultFeatures)
498 /// .features(CargoOpt::NoDefaultFeatures) // <-- panic!
499 /// // ...
500 /// # ;
501 /// ```
502 ///
503 /// The method also panics for multiple `CargoOpt::AllFeatures` arguments:
504 ///
505 /// ```should_panic
506 /// # use cargo_metadata::{CargoOpt, MetadataCommand};
507 /// MetadataCommand::new()
508 /// .features(CargoOpt::AllFeatures)
509 /// .features(CargoOpt::AllFeatures) // <-- panic!
510 /// // ...
511 /// # ;
512 /// ```
513 pub fn features(&mut self, features: CargoOpt) -> &mut MetadataCommand {
514 match features {
515 CargoOpt::SomeFeatures(features) => self.features.extend(features),
516 CargoOpt::NoDefaultFeatures => {
517 assert!(
518 !self.no_default_features,
519 "Do not supply CargoOpt::NoDefaultFeatures more than once!"
520 );
521 self.no_default_features = true;
522 }
523 CargoOpt::AllFeatures => {
524 assert!(
525 !self.all_features,
526 "Do not supply CargoOpt::AllFeatures more than once!"
527 );
528 self.all_features = true;
529 }
530 }
531 self
532 }
533 /// Arbitrary command line flags to pass to `cargo`. These will be added
534 /// to the end of the command line invocation.
535 pub fn other_options(&mut self, options: impl Into<Vec<String>>) -> &mut MetadataCommand {
536 self.other_options = options.into();
537 self
538 }
539
540 /// Builds a command for `cargo metadata`. This is the first
541 /// part of the work of `exec`.
542 pub fn cargo_command(&self) -> Result<Command> {
543 let cargo = self
544 .cargo_path
545 .clone()
546 .or_else(|| env::var("CARGO").map(PathBuf::from).ok())
547 .unwrap_or_else(|| PathBuf::from("cargo"));
548 let mut cmd = Command::new(cargo);
549 cmd.args(&["metadata", "--format-version", "1"]);
550
551 if self.no_deps {
552 cmd.arg("--no-deps");
553 }
554
555 if let Some(path) = self.current_dir.as_ref() {
556 cmd.current_dir(path);
557 }
558
559 if !self.features.is_empty() {
560 cmd.arg("--features").arg(self.features.join(","));
561 }
562 if self.all_features {
563 cmd.arg("--all-features");
564 }
565 if self.no_default_features {
566 cmd.arg("--no-default-features");
567 }
568
569 if let Some(manifest_path) = &self.manifest_path {
570 cmd.arg("--manifest-path").arg(manifest_path.as_os_str());
571 }
572 cmd.args(&self.other_options);
573
574 Ok(cmd)
575 }
576
577 /// Parses `cargo metadata` output. `data` must have been
578 /// produced by a command built with `cargo_command`.
579 pub fn parse<T: AsRef<str>>(data: T) -> Result<Metadata> {
580 let meta = serde_json::from_str(data.as_ref())?;
581 Ok(meta)
582 }
583
584 /// Runs configured `cargo metadata` and returns parsed `Metadata`.
585 pub fn exec(&self) -> Result<Metadata> {
586 let mut cmd = self.cargo_command()?;
587 let output = cmd.output()?;
588 if !output.status.success() {
589 return Err(Error::CargoMetadata {
590 stderr: String::from_utf8(output.stderr)?,
591 });
592 }
593 let stdout = from_utf8(&output.stdout)?
594 .lines()
595 .find(|line| line.starts_with('{'))
596 .ok_or_else(|| Error::NoJson)?;
597 Self::parse(stdout)
598 }
599}