check_deprule/metadata/
mod.rs1use anyhow::{Context, Error, anyhow};
2use cargo_metadata::Metadata;
3use std::env;
4use std::ffi::OsString;
5use std::process::{Command, Stdio};
6
7#[derive(Debug, Clone)]
8pub struct CollectMetadataConfig {
9 pub quiet: bool,
10 pub features: Option<String>,
11 pub all_features: bool,
12 pub no_default_features: bool,
13 pub all_targets: bool,
14 pub target: Option<String>,
15 pub manifest_path: Option<String>,
16 pub verbose: u32,
17 pub color: Option<String>,
18 pub frozen: bool,
19 pub locked: bool,
20 pub offline: bool,
21 pub unstable_flags: Vec<String>,
22}
23impl Default for CollectMetadataConfig {
24 fn default() -> Self {
25 Self {
26 quiet: false,
27 features: None,
28 all_features: true,
29 no_default_features: false,
30 all_targets: true,
31 target: None,
32 manifest_path: None,
33 verbose: 0,
34 color: None,
35 frozen: false,
36 locked: false,
37 offline: false,
38 unstable_flags: Vec::new(),
39 }
40 }
41}
42
43#[tracing::instrument(skip_all, fields(manifest_path = ?config.manifest_path))]
44pub fn collect_metadata(config: CollectMetadataConfig) -> Result<Metadata, Error> {
45 let cargo = env::var_os("CARGO").unwrap_or_else(|| OsString::from("cargo"));
46
47 let mut command = Command::new(cargo);
48 command.arg("metadata").arg("--format-version").arg("1");
49
50 if config.quiet {
51 command.arg("-q");
52 }
53
54 if let Some(features) = &config.features {
55 command.arg("--features").arg(features);
56 }
57 if config.all_features {
58 command.arg("--all-features");
59 }
60 if config.no_default_features {
61 command.arg("--no-default-features");
62 }
63
64 if !config.all_targets {
65 command.arg("--filter-platform");
66 match &config.target {
67 Some(target) => {
68 command.arg(target);
69 }
70 None => {
71 let target = default_target()?;
72 command.arg(target);
73 }
74 }
75 }
76
77 if let Some(path) = &config.manifest_path {
78 command.arg("--manifest-path").arg(path);
79 }
80
81 for _ in 0..config.verbose {
82 command.arg("-v");
83 }
84
85 if let Some(color) = &config.color {
86 command.arg("--color").arg(color);
87 }
88
89 if config.frozen {
90 command.arg("--frozen");
91 }
92 if config.locked {
93 command.arg("--locked");
94 }
95 if config.offline {
96 command.arg("--offline");
97 }
98
99 for flag in &config.unstable_flags {
100 command.arg("-Z").arg(flag);
101 }
102
103 let output = output(&mut command, "cargo metadata")?;
104
105 serde_json::from_str(&output).context("error parsing cargo metadata output")
106}
107
108fn default_target() -> Result<String, Error> {
109 let rustc = env::var_os("RUSTC").unwrap_or_else(|| OsString::from("rustc"));
110 let output = output(Command::new(rustc).arg("-Vv"), "rustc")?;
111
112 for line in output.lines() {
113 let prefix = "host: ";
114 if let Some(stripped_line) = line.strip_prefix(prefix) {
115 return Ok(stripped_line.trim().to_string());
116 }
117 }
118
119 Err(anyhow!("host missing from rustc output"))
120}
121
122fn output(command: &mut Command, job: &str) -> Result<String, Error> {
123 let output = command
124 .stderr(Stdio::inherit())
125 .output()
126 .with_context(|| format!("error running {job}"))?;
127
128 if !output.status.success() {
129 return Err(anyhow!("{} returned {}", job, output.status));
130 }
131
132 String::from_utf8(output.stdout).with_context(|| format!("error parsing {job} output"))
133}