cargo_run_bin/
metadata.rs1use 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
95pub 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}