/ src / root.rs
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  }