Skip to main content

check_deprule/metadata/
mod.rs

1use 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}