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