Skip to main content

cargo_dev_install/
project.rs

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