1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
use std::path::PathBuf;

#[derive(Debug, thiserror::Error)]
pub enum PathError {
    #[error("Could not execute Cargo to find the project root directory.")]
    ExecutingCargo(std::io::Error),
    #[error("Cargo could not locate the project root directory.")]
    LocatingWorkspaceRoot,
    #[error("Path to the project root directory is not valid UTF8.")]
    InvalidPath,
    #[error("Could not get the current directory.")]
    CurrentDir,
}

/// Returns the path to the workspace directory of a Cargo workspace.
/// Reason for this workaround: https://github.com/rust-lang/cargo/issues/3946
pub fn get_cargo_root() -> Result<PathBuf, PathError> {
    let locate_project_output = std::process::Command::new(env!("CARGO"))
        .arg("locate-project")
        .arg("--workspace")
        .arg("--quiet")
        .arg("--message-format=plain")
        .current_dir(
            std::env::var("CARGO_MANIFEST_DIR")
                .map(PathBuf::from)
                .or(std::env::current_dir())
                .map_err(|_| PathError::CurrentDir)?,
        )
        .output()
        .map_err(PathError::ExecutingCargo)?;

    if locate_project_output.status.success() {
        let workspace_root = PathBuf::from(
            String::from_utf8(locate_project_output.stdout).map_err(|_| PathError::InvalidPath)?,
        );
        Ok(workspace_root
            .parent()
            .map(|p| p.to_path_buf())
            .unwrap_or_default())
    } else {
        Err(PathError::LocatingWorkspaceRoot)
    }
}

#[cfg(test)]
mod tests {
    use std::path::PathBuf;

    use super::get_cargo_root;

    #[test]
    fn workspace_root_of_mantra() {
        let workspace_root = get_cargo_root().unwrap().canonicalize().unwrap();
        let manifest_dir = std::env::var("CARGO_MANIFEST_DIR").unwrap();
        let expected_root = PathBuf::from(manifest_dir).canonicalize().unwrap();

        assert_eq!(
            workspace_root, expected_root,
            "Returned workspace root is wrong."
        );
    }
}