cargo_dev_install/
project.rs1use cargo_metadata::{MetadataCommand, TargetKind};
2use std::path::{Path, PathBuf};
3
4pub fn find_crate_root(cwd: &Path) -> Result<PathBuf, String> {
5 let mut current = if cwd.is_absolute() {
6 cwd.to_path_buf()
7 } else {
8 std::env::current_dir()
9 .map_err(|err| format!("failed to resolve current dir: {err}"))?
10 .join(cwd)
11 };
12
13 loop {
14 let manifest = current.join("Cargo.toml");
15 if manifest.is_file() {
16 return Ok(current);
17 }
18
19 if !current.pop() {
20 return Err("Cargo.toml not found in this directory or any parent".to_string());
21 }
22 }
23}
24
25pub fn list_bins(manifest_path: &Path) -> Result<Vec<String>, String> {
26 let metadata = MetadataCommand::new()
27 .manifest_path(manifest_path)
28 .no_deps()
29 .exec()
30 .map_err(|err| format!("failed to load cargo metadata: {err}"))?;
31
32 let root = metadata
33 .root_package()
34 .ok_or_else(|| "no root package found in Cargo metadata".to_string())?;
35
36 Ok(root
37 .targets
38 .iter()
39 .filter(|target| {
40 target
41 .kind
42 .iter()
43 .any(|kind| matches!(kind, TargetKind::Bin))
44 })
45 .map(|target| target.name.clone())
46 .collect())
47}
48
49#[cfg(test)]
50mod tests {
51 use super::*;
52 use std::fs;
53
54 fn write_file(path: &Path, contents: &str) {
55 if let Some(parent) = path.parent() {
56 fs::create_dir_all(parent).expect("create parent");
57 }
58 fs::write(path, contents).expect("write file");
59 }
60
61 #[test]
62 fn find_crate_root_in_current_dir() {
63 let dir = tempfile::tempdir().expect("tempdir");
64 write_file(
65 &dir.path().join("Cargo.toml"),
66 "[package]\nname = \"demo\"\n",
67 );
68
69 let root = find_crate_root(dir.path()).expect("crate root");
70 assert_eq!(root, dir.path());
71 }
72
73 #[test]
74 fn find_crate_root_in_parent_dir() {
75 let dir = tempfile::tempdir().expect("tempdir");
76 write_file(
77 &dir.path().join("Cargo.toml"),
78 "[package]\nname = \"demo\"\n",
79 );
80 let nested = dir.path().join("nested/child");
81 fs::create_dir_all(&nested).expect("create nested");
82
83 let root = find_crate_root(&nested).expect("crate root");
84 assert_eq!(root, dir.path());
85 }
86
87 #[test]
88 fn find_crate_root_errors_when_missing() {
89 let dir = tempfile::tempdir().expect("tempdir");
90 let err = find_crate_root(dir.path()).expect_err("expected error");
91 assert!(err.contains("Cargo.toml not found"));
92 }
93
94 #[test]
95 fn list_bins_returns_single_bin() {
96 let dir = tempfile::tempdir().expect("tempdir");
97 write_file(
98 &dir.path().join("Cargo.toml"),
99 "[package]\nname = \"demo\"\nversion = \"0.1.0\"\nedition = \"2021\"\n\n[[bin]]\nname = \"demo\"\npath = \"src/main.rs\"\n",
100 );
101 write_file(&dir.path().join("src/main.rs"), "fn main() {}\n");
102
103 let bins = list_bins(&dir.path().join("Cargo.toml")).expect("bins");
104 assert_eq!(bins, vec!["demo".to_string()]);
105 }
106
107 #[test]
108 fn list_bins_returns_multiple_bins() {
109 let dir = tempfile::tempdir().expect("tempdir");
110 write_file(
111 &dir.path().join("Cargo.toml"),
112 "[package]\nname = \"demo\"\nversion = \"0.1.0\"\nedition = \"2021\"\n\n[[bin]]\nname = \"alpha\"\npath = \"src/main.rs\"\n\n[[bin]]\nname = \"beta\"\npath = \"src/bin/beta.rs\"\n",
113 );
114 write_file(&dir.path().join("src/main.rs"), "fn main() {}\n");
115 write_file(&dir.path().join("src/bin/beta.rs"), "fn main() {}\n");
116
117 let mut bins = list_bins(&dir.path().join("Cargo.toml")).expect("bins");
118 bins.sort();
119 assert_eq!(bins, vec!["alpha".to_string(), "beta".to_string()]);
120 }
121}