use std::path::Path;
use std::process::Command;
use anyhow::{bail, Context, Result};
fn run(cmd: &mut Command) -> Result<()> {
let program = cmd.get_program().to_string_lossy().to_string();
match cmd.status() {
Ok(status) if status.success() => Ok(()),
Ok(status) => bail!(
"{program} failed with exit code {}",
status.code().unwrap_or(-1)
),
Err(e) if e.kind() == std::io::ErrorKind::NotFound => {
bail!(
"{program} not found — is it installed and in your PATH?\n\
hint: if you haven't run darwin-rebuild yet, see: nex.styrene.io"
)
}
Err(e) => Err(e).with_context(|| format!("failed to run {program}")),
}
}
pub fn nix_eval_exists(pkg: &str) -> Result<bool> {
let output = Command::new("nix")
.args(["eval", &format!("nixpkgs#{pkg}.name"), "--raw"])
.stderr(std::process::Stdio::null())
.output()
.context("failed to run nix eval")?;
Ok(output.status.success())
}
pub fn nix_eval_version(pkg: &str) -> Result<Option<String>> {
let output = Command::new("nix")
.args(["eval", &format!("nixpkgs#{pkg}.version"), "--raw"])
.stderr(std::process::Stdio::null())
.output()
.context("failed to run nix eval")?;
if output.status.success() {
let version = String::from_utf8_lossy(&output.stdout).trim().to_string();
if version.is_empty() {
Ok(None)
} else {
Ok(Some(version))
}
} else {
Ok(None)
}
}
pub fn brew_cask_info(pkg: &str) -> Result<Option<String>> {
let output = Command::new("brew")
.args(["info", "--json=v2", "--cask", pkg])
.stderr(std::process::Stdio::null())
.output();
let output = match output {
Ok(o) => o,
Err(_) => return Ok(None), };
if !output.status.success() {
return Ok(None);
}
let json: serde_json::Value =
serde_json::from_slice(&output.stdout).unwrap_or(serde_json::Value::Null);
let version = json
.get("casks")
.and_then(|c| c.get(0))
.and_then(|c| c.get("version"))
.and_then(|v| v.as_str())
.map(String::from);
Ok(version)
}
pub fn brew_formula_info(pkg: &str) -> Result<Option<String>> {
let output = Command::new("brew")
.args(["info", "--json=v2", "--formula", pkg])
.stderr(std::process::Stdio::null())
.output();
let output = match output {
Ok(o) => o,
Err(_) => return Ok(None),
};
if !output.status.success() {
return Ok(None);
}
let json: serde_json::Value =
serde_json::from_slice(&output.stdout).unwrap_or(serde_json::Value::Null);
let version = json
.get("formulae")
.and_then(|f| f.get(0))
.and_then(|f| f.get("versions"))
.and_then(|v| v.get("stable"))
.and_then(|v| v.as_str())
.map(String::from);
Ok(version)
}
pub fn nix_search(query: &str) -> Result<()> {
run(Command::new("nix").args(["search", "nixpkgs", query]))
}
pub fn darwin_rebuild_switch(repo: &Path, hostname: &str) -> Result<()> {
run(Command::new("darwin-rebuild")
.args(["switch", "--flake", &format!(".#{hostname}")])
.current_dir(repo))
}
pub fn darwin_rebuild_build(repo: &Path, hostname: &str) -> Result<()> {
run(Command::new("darwin-rebuild")
.args(["build", "--flake", &format!(".#{hostname}")])
.current_dir(repo))
}
pub fn darwin_rebuild_rollback(repo: &Path, hostname: &str) -> Result<()> {
run(Command::new("darwin-rebuild")
.args(["switch", "--rollback", "--flake", &format!(".#{hostname}")])
.current_dir(repo))
}
pub fn nix_flake_update(repo: &Path) -> Result<()> {
run(Command::new("nix")
.args(["flake", "update"])
.current_dir(repo))
}
pub fn nix_shell(pkg: &str) -> Result<()> {
run(Command::new("nix").args(["shell", &format!("nixpkgs#{pkg}")]))
}
pub fn nix_diff_closures(repo: &Path) -> Result<()> {
run(Command::new("nix")
.args([
"store",
"diff-closures",
"/nix/var/nix/profiles/system",
"./result",
])
.current_dir(repo))
}
pub fn nix_gc() -> Result<()> {
run(Command::new("nix").args(["store", "gc"]))?;
run(Command::new("nix-collect-garbage").args(["-d"]))
}