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