Skip to main content

cargo_bin/
project.rs

1use anyhow::{Context, Result};
2use std::collections::HashSet;
3use std::env;
4use std::fs;
5use std::io::Read;
6use std::path::{Path, PathBuf};
7use syn::Item;
8
9const CARGO_TOML: &str = "Cargo.toml";
10
11/// return cargo project root path (absolute path)
12pub fn root_path() -> Result<PathBuf> {
13    let manifest = search_manifest()?;
14    let root = manifest
15        .parent()
16        .with_context(|| format!("{:?} has no parent", manifest))?;
17    // to absolute path
18    fs::canonicalize(root).context("root path convert to absolute err")
19}
20
21/// search from current_dir() with name Cargo.toml
22pub fn search_manifest() -> Result<PathBuf> {
23    search_manifest_from(
24        &env::current_dir().context("get current dir err")?,
25        CARGO_TOML,
26    )
27}
28
29/// search manifest from dir with specified filename (absolute path)
30pub fn search_manifest_from(start_dir: &PathBuf, file_name: &str) -> Result<PathBuf> {
31    let mut path = start_dir.as_path();
32    loop {
33        let toml = path.join(file_name);
34        if toml.exists() {
35            return fs::canonicalize(toml).context("manifest path convert to absolute path err");
36        }
37
38        path = path
39            .parent()
40            .with_context(|| format!("Cargo.toml not found search from: {:?}", start_dir))?
41    }
42}
43
44/// find rust source file with main() from the specified dir
45/// TODO for now ignore some folders like target, .git, .github
46pub fn find_main_file(dir: &Path) -> Result<Vec<PathBuf>> {
47    let mut ignored_folders = HashSet::new();
48    for folder in ["target", "src/bin", ".git", ".github"].iter() {
49        ignored_folders.insert(dir.join(*folder));
50    }
51    let mut files = vec![];
52
53    fn find(dir: &Path, files: &mut Vec<PathBuf>, ignored: &HashSet<PathBuf>) -> Result<()> {
54        if !dir.is_dir() {
55            return Ok(());
56        }
57        if ignored.contains(&dir.to_path_buf()) {
58            return Ok(());
59        }
60        for entry in fs::read_dir(dir).with_context(|| format!("read_dir err, dir: {:?}", dir))? {
61            let entry = entry.with_context(|| "dir entry err")?;
62            let path = entry.path();
63            if path.is_dir() {
64                find(&path, files, ignored)?;
65                continue;
66            }
67
68            let ext = path
69                .as_path()
70                .extension()
71                .map_or("", |v| v.to_str().unwrap_or(""));
72            if ext != "rs" {
73                continue;
74            }
75
76            if contains_main(&path)? {
77                files.push(path)
78            }
79        }
80        Ok(())
81    }
82
83    find(dir, &mut files, &ignored_folders)?;
84
85    Ok(files)
86}
87
88// parse file and see if the file contains fn main()
89fn contains_main(path: &Path) -> Result<bool> {
90    let mut file = fs::File::open(path).with_context(|| format!("open file {:?} err", path))?;
91    let mut content = String::new();
92    file.read_to_string(&mut content)
93        .with_context(|| format!("read file {:?} err", path))?;
94
95    let ast = syn::parse_file(&content)?;
96
97    let is_main = ast.items.iter().any(|v| match v {
98        Item::Fn(item) => item.sig.ident == "main",
99        _ => false,
100    });
101
102    Ok(is_main)
103}
104
105#[cfg(test)]
106mod tests {
107    use super::*;
108
109    #[test]
110    fn search() {
111        let dir = env::current_dir().unwrap().join("misc");
112        let file_path = search_manifest_from(&dir, "test-cargo.toml").expect("search should be ok");
113        println!("file_path: {:?}", file_path);
114    }
115}