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