Skip to main content

cargo_codesign/
discovery.rs

1use std::path::PathBuf;
2
3#[derive(Debug, thiserror::Error)]
4pub enum DiscoveryError {
5    #[error("failed to parse cargo metadata: {0}")]
6    ParseError(#[from] serde_json::Error),
7    #[error("failed to run cargo metadata: {0}")]
8    CargoMetadataFailed(String),
9    #[error("subprocess error: {0}")]
10    Subprocess(#[from] crate::subprocess::SubprocessError),
11}
12
13#[derive(Debug)]
14pub struct BinaryTarget {
15    pub name: String,
16    pub package_name: String,
17    pub package_version: String,
18    target_directory: PathBuf,
19}
20
21impl BinaryTarget {
22    /// Path where `cargo build --release` places the binary.
23    pub fn release_path(&self) -> PathBuf {
24        self.target_directory.join("release").join(&self.name)
25    }
26
27    /// Path where cargo-sign places the signed binary.
28    pub fn signed_release_path(&self) -> PathBuf {
29        self.target_directory
30            .join("signed")
31            .join("release")
32            .join(&self.name)
33    }
34}
35
36/// Parse `cargo metadata --format-version 1 --no-deps` JSON output
37/// and extract all binary targets.
38pub fn parse_metadata(json: &str) -> Result<Vec<BinaryTarget>, DiscoveryError> {
39    let meta: serde_json::Value = serde_json::from_str(json)?;
40
41    let target_directory = meta["target_directory"]
42        .as_str()
43        .unwrap_or("target")
44        .to_string();
45
46    let packages = meta["packages"].as_array();
47    let Some(packages) = packages else {
48        return Ok(Vec::new());
49    };
50
51    let mut binaries = Vec::new();
52
53    for pkg in packages {
54        let pkg_name = pkg["name"].as_str().unwrap_or_default().to_string();
55        let pkg_version = pkg["version"].as_str().unwrap_or_default().to_string();
56
57        let Some(targets) = pkg["targets"].as_array() else {
58            continue;
59        };
60
61        for target in targets {
62            let kinds = target["kind"].as_array();
63            let is_bin = kinds
64                .is_some_and(|kinds| kinds.iter().any(|k| k.as_str().is_some_and(|s| s == "bin")));
65
66            if is_bin {
67                let name = target["name"].as_str().unwrap_or_default().to_string();
68                binaries.push(BinaryTarget {
69                    name,
70                    package_name: pkg_name.clone(),
71                    package_version: pkg_version.clone(),
72                    target_directory: PathBuf::from(&target_directory),
73                });
74            }
75        }
76    }
77
78    Ok(binaries)
79}
80
81/// Run `cargo metadata` and extract binary targets.
82pub fn discover_binaries() -> Result<Vec<BinaryTarget>, DiscoveryError> {
83    let output = crate::subprocess::run(
84        "cargo",
85        &["metadata", "--format-version", "1", "--no-deps"],
86        false,
87    )?;
88    if !output.success {
89        return Err(DiscoveryError::CargoMetadataFailed(output.stderr));
90    }
91    parse_metadata(&output.stdout)
92}