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