Skip to main content

unity_solution_generator/
paths.rs

1use std::ffi::{CStr, CString};
2use std::path::Path;
3
4pub const DEFAULT_GENERATOR_ROOT: &str = "Library/UnitySolutionGenerator";
5
6pub fn parent_directory(path: &str) -> &str {
7    match path.rfind('/') {
8        Some(i) => &path[..i],
9        None => "",
10    }
11}
12
13pub fn join_path(base: &str, component: &str) -> String {
14    if base.ends_with('/') {
15        format!("{}{}", base, component)
16    } else {
17        format!("{}/{}", base, component)
18    }
19}
20
21/// Resolve symlinks via libc::realpath. Returns the input unchanged on failure.
22pub fn resolve_real_path(path: &str) -> String {
23    let Ok(c) = CString::new(path) else {
24        return path.to_string();
25    };
26    unsafe {
27        let resolved = libc::realpath(c.as_ptr(), std::ptr::null_mut());
28        if resolved.is_null() {
29            return path.to_string();
30        }
31        let s = CStr::from_ptr(resolved).to_string_lossy().into_owned();
32        libc::free(resolved as *mut libc::c_void);
33        s
34    }
35}
36
37/// Resolve symlinks then climb up to the nearest ancestor containing
38/// `ProjectSettings/ProjectVersion.txt`. Falls back to the resolved input
39/// if no Unity root is found.
40pub fn resolve_project_root(path: &str) -> String {
41    let resolved = resolve_real_path(path);
42    let mut current = resolved.as_str();
43    while !current.is_empty() && current != "/" {
44        let marker = join_path(current, "ProjectSettings/ProjectVersion.txt");
45        if Path::new(&marker).exists() {
46            return current.to_string();
47        }
48        current = parent_directory(current);
49    }
50    resolved
51}
52
53pub fn lockfile_path(project_root: &str, generator_root: &str) -> String {
54    join_path(project_root, &format!("{}/csproj.lock", generator_root))
55}
56
57/// Per-user cache root for tarball-extracted Unity packages. Subdirectory by
58/// Unity version isolates installs (each ships its own bundled tarballs).
59/// Honours `XDG_CACHE_HOME`; falls back to `$HOME/.cache`. Panics if neither
60/// is set — landing extracted Unity code in a world-writable `/tmp` would let
61/// anyone tamper with our compile inputs.
62pub fn usg_cache_dir(unity_version: &str) -> String {
63    let cache_home = std::env::var("XDG_CACHE_HOME")
64        .ok()
65        .filter(|s| !s.is_empty())
66        .or_else(|| std::env::var("HOME").ok().map(|h| format!("{}/.cache", h)))
67        .expect("usg_cache_dir: neither XDG_CACHE_HOME nor HOME is set");
68    format!("{}/unity-solution-generator/{}", cache_home, unity_version)
69}