root.rs
1 use crate::tmp::TmpDirPath; 2 use std::{ 3 env, 4 error::Error, 5 fmt::{Debug, Display}, 6 fs, 7 hash::{DefaultHasher, Hash, Hasher}, 8 io::Write, 9 path::{Path, PathBuf}, 10 }; 11 use tempfile::NamedTempFile; 12 13 #[derive(Debug, Hash)] 14 pub struct Root { 15 path: Box<Path>, 16 gen_path: Box<Path>, 17 tmp_path: Box<Path>, 18 cas_path: Box<Path>, 19 } 20 21 impl Root { 22 pub fn discover() -> Self { 23 let path = find_root(); 24 let build_path = path.join("build-out").into_boxed_path(); 25 26 let gen_path = build_path.join("gen").into_boxed_path(); 27 let tmp_path = build_path.join("tmp").into_boxed_path(); 28 let cas_path = build_path.join("cas").into_boxed_path(); 29 30 fs::create_dir_all(&gen_path).expect("Failed to create path in build output directory"); 31 fs::create_dir_all(&tmp_path).expect("Failed to create path in build output directory"); 32 fs::create_dir_all(&cas_path).expect("Failed to create path in build output directory"); 33 34 Self { 35 path, 36 gen_path, 37 tmp_path, 38 cas_path, 39 } 40 } 41 42 pub fn enter(&self) { 43 env::set_current_dir(&self.path).expect("Failed to change directory to root"); 44 } 45 46 pub fn path(&self) -> &Path { 47 &self.path 48 } 49 50 pub fn tmp_dir<Ctx: Hash, Prefix: Display>(&self, prefix: Prefix, context: &Ctx) -> TmpDirPath { 51 let mut hasher = DefaultHasher::new(); 52 context.hash(&mut hasher); 53 let hash = hasher.finish(); 54 let segment = format!("{prefix}-{hash:x}"); 55 let path = self.tmp_path.join(segment).into_boxed_path(); 56 TmpDirPath::create(path) 57 } 58 59 pub fn gen_path<Ctx: Hash, Prefix: Display>(&self, prefix: Prefix, context: &Ctx) -> PathBuf { 60 let mut hasher = DefaultHasher::new(); 61 context.hash(&mut hasher); 62 let hash = hasher.finish(); 63 let segment = format!("{prefix}-{hash:x}"); 64 self.gen_path.join(segment) 65 } 66 67 pub fn store_cas<Content: AsRef<[u8]>>(&self, content: Content) -> Box<Path> { 68 let content = content.as_ref(); 69 70 let mut hasher = DefaultHasher::new(); 71 content.hash(&mut hasher); 72 let hash = hasher.finish(); 73 74 let name = format!("{hash:x}"); 75 let path = self.cas_path.join(name); 76 77 if path.exists() { 78 return path.into_boxed_path(); 79 } 80 81 let write_result: Result<(), Box<dyn Error>> = try { 82 let (mut src_file, src_path) = NamedTempFile::new()?.keep()?; 83 src_file.write_all(content)?; 84 fs::rename(src_path, &path)?; 85 }; 86 write_result.expect("Failed to write temporary file for CAS"); 87 88 path.into_boxed_path() 89 } 90 } 91 92 fn find_root_in(path: &Path) -> Box<Path> { 93 let build_path = path.join(".build"); 94 95 if build_path.exists() { 96 return path.to_owned().into_boxed_path(); 97 } 98 99 let parent = path.parent().expect("Could not find build root"); 100 find_root_in(parent) 101 } 102 103 fn find_root() -> Box<Path> { 104 let cwd = env::current_dir().expect("Failed to get current directory"); 105 find_root_in(cwd.as_path()) 106 }