use std::{
collections::HashSet,
fs,
os::unix,
path::{Path, PathBuf},
};
use anyhow::{Context, Result};
pub struct ShimManager {
bin_dir: PathBuf,
releases_dir: PathBuf,
}
impl ShimManager {
pub fn new(bin_dir: PathBuf, releases_dir: PathBuf) -> Result<Self> {
Ok(Self {
bin_dir,
releases_dir,
})
}
pub fn rewrite_shims(&self, binaries: &[&str], target_dir: &Path) -> Result<()> {
fs::create_dir_all(&self.bin_dir)
.with_context(|| format!("failed to create bin dir at {}", self.bin_dir.display()))?;
self.remove_shims_except(binaries)?;
for &bin in binaries {
if bin == "rialoman" {
eprintln!("`rialoman` cannot be a managed shim, skipping");
continue;
}
let shim_path = self.bin_dir.join(bin);
let target = target_dir.join("bin").join(bin);
let tmp_path = self.bin_dir.join(format!(".{bin}.tmp"));
let _lingering_file_removed = fs::remove_file(&tmp_path);
unix::fs::symlink(&target, &tmp_path).with_context(|| {
format!(
"failed to create shim symlink {} -> {}",
tmp_path.display(),
target.display()
)
})?;
fs::rename(&tmp_path, &shim_path)
.with_context(|| format!("failed to update shim {}", shim_path.display()))?;
}
Ok(())
}
pub fn clear_all(&self) -> Result<()> {
if !self.bin_dir.exists() {
return Ok(());
}
self.remove_shims_except(&[])?;
Ok(())
}
fn remove_shims_except(&self, keep: &[&str]) -> Result<()> {
let keep_set: HashSet<_> = keep.iter().collect();
for entry in fs::read_dir(&self.bin_dir)? {
let entry = entry?;
let name = entry.file_name();
let Some(name) = name.to_str() else { continue };
if keep_set.contains(&name) || name == "rialoman" {
continue;
}
let path = entry.path();
if self.is_managed_shim(&path) {
fs::remove_file(&path)
.with_context(|| format!("failed to remove old shim {}", path.display()))?;
}
}
Ok(())
}
fn is_managed_shim(&self, path: &Path) -> bool {
match fs::read_link(path) {
Ok(link) => link.starts_with(&self.releases_dir),
Err(..) => false,
}
}
}