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