cargo_run_bin/
metadata.rs

1use std::collections::HashMap;
2use std::env;
3use std::fs;
4use std::path::PathBuf;
5
6use anyhow::anyhow;
7use anyhow::bail;
8use anyhow::Result;
9use serde::Deserialize;
10use toml_edit::Document;
11use toml_edit::Item;
12
13#[cfg(test)]
14#[path = "metadata_test.rs"]
15mod metadata_test;
16
17#[derive(Deserialize, Debug, PartialEq)]
18struct MetadataValue {
19    version: String,
20    git: Option<String>,
21    branch: Option<String>,
22    tag: Option<String>,
23    rev: Option<String>,
24    path: Option<String>,
25    locked: Option<bool>,
26    bins: Option<Vec<String>>,
27    #[serde(alias = "default-features")]
28    default_features: Option<bool>,
29    features: Option<Vec<String>>,
30}
31
32type MetadataBins = HashMap<String, MetadataValue>;
33
34#[derive(Clone)]
35pub struct BinaryPackage {
36    pub bin_target: Option<String>,
37    pub package: String,
38    pub locked: Option<bool>,
39    pub version: String,
40    pub git: Option<String>,
41    pub branch: Option<String>,
42    pub tag: Option<String>,
43    pub rev: Option<String>,
44    pub path: Option<String>,
45    pub default_features: Option<bool>,
46    pub features: Option<Vec<String>>,
47}
48
49pub fn get_project_root() -> Result<PathBuf> {
50    let path = env::current_dir()?;
51    let path_ancestors = path.as_path().ancestors();
52
53    for p in path_ancestors {
54        let has_cargo = fs::read_dir(p)?.any(|p| return p.unwrap().file_name() == *"Cargo.lock");
55
56        if has_cargo {
57            return Ok(PathBuf::from(p));
58        }
59    }
60
61    return Err(anyhow!("Root directory for rust project not found."));
62}
63
64fn toml_has_path(doc: &Item, keys: Vec<&str>) -> bool {
65    let mut item = doc;
66    for key in keys {
67        if item.get(key).is_none() {
68            return false;
69        }
70        item = &item[key];
71    }
72
73    return true;
74}
75
76fn get_metadata_binaries() -> Result<MetadataBins> {
77    let toml_str: String = fs::read_to_string(get_project_root()?.join("Cargo.toml"))?.parse()?;
78    let doc = toml_str.parse::<Document>()?;
79
80    let mut metadata_str = "".to_string();
81    if toml_has_path(doc.as_item(), vec!["package", "metadata", "bin"]) {
82        metadata_str = doc["package"]["metadata"]["bin"].to_string();
83    } else if toml_has_path(doc.as_item(), vec!["workspace", "metadata", "bin"]) {
84        metadata_str = doc["workspace"]["metadata"]["bin"].to_string();
85    }
86
87    if metadata_str.is_empty() {
88        bail!("No binaries configured in Cargo.toml");
89    }
90
91    let metadata_res: Result<MetadataBins, toml::de::Error> = toml::from_str(&metadata_str);
92    return Ok(metadata_res?);
93}
94
95/// Returns all configured binary packages set in Cargo.toml.
96pub fn get_binary_packages() -> Result<Vec<BinaryPackage>> {
97    let metadata = get_metadata_binaries()?;
98
99    let mut binary_details: Vec<BinaryPackage> = Vec::new();
100
101    for (pkg_name, pkg_details) in metadata.into_iter() {
102        if let Some(pkg_bins) = pkg_details.bins {
103            for bin_target in pkg_bins.iter() {
104                binary_details.push(BinaryPackage {
105                    bin_target: Some(bin_target.to_string()),
106                    package: pkg_name.clone(),
107                    locked: pkg_details.locked,
108                    version: pkg_details.version.clone(),
109                    git: pkg_details.git.clone(),
110                    branch: pkg_details.branch.clone(),
111                    tag: pkg_details.tag.clone(),
112                    rev: pkg_details.rev.clone(),
113                    path: pkg_details.path.clone(),
114                    default_features: pkg_details.default_features,
115                    features: pkg_details.features.clone(),
116                });
117            }
118        } else {
119            binary_details.push(BinaryPackage {
120                bin_target: None,
121                package: pkg_name,
122                locked: pkg_details.locked,
123                version: pkg_details.version,
124                git: pkg_details.git,
125                branch: pkg_details.branch,
126                tag: pkg_details.tag,
127                rev: pkg_details.rev,
128                path: pkg_details.path,
129                default_features: pkg_details.default_features,
130                features: pkg_details.features,
131            });
132        }
133    }
134
135    binary_details.sort_by_key(|e| {
136        if e.bin_target.is_some() {
137            return e.bin_target.as_ref().unwrap().to_string();
138        }
139        return e.package.to_string();
140    });
141
142    return Ok(binary_details);
143}