use std::{
collections::HashMap,
ffi::{OsStr, OsString},
path::{Path, PathBuf},
process::Command,
};
use crate::{Error, Result};
enum NixTarget {
Function(OsString),
Flake(String),
Expr(String),
}
impl Default for NixTarget {
fn default() -> Self {
Self::Function(OsString::from("default.nix"))
}
}
pub struct Config {
target: NixTarget,
arg_exprs: Vec<(String, String)>,
arg_strs: Vec<(String, String)>,
impure: bool,
}
impl Default for Config {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, serde::Deserialize)]
pub struct Derivation {
#[serde(alias = "drvPath")]
pub drv_path: PathBuf,
pub outputs: HashMap<String, PathBuf>,
}
impl Derivation {
pub fn out(&self) -> Option<&PathBuf> {
self.outputs.get("out")
}
}
impl Config {
pub fn new() -> Self {
Self {
target: NixTarget::default(),
arg_exprs: vec![],
arg_strs: vec![],
impure: false,
}
}
pub fn arg_expr(&mut self, name: &str, value: &str) -> &mut Self {
self.arg_exprs.push((name.to_owned(), value.to_owned()));
self
}
pub fn arg_str(&mut self, name: &str, value: &str) -> &mut Self {
self.arg_strs.push((name.to_owned(), value.to_owned()));
self
}
pub fn target_file(&mut self, filename: impl AsRef<OsStr>) -> &mut Self {
self.target = NixTarget::Function(filename.as_ref().to_owned());
self
}
pub fn target_flake(&mut self, flake: &str) -> &mut Self {
self.target = NixTarget::Flake(flake.to_owned());
self
}
pub fn target_expr(&mut self, expr: &str) -> &mut Self {
self.target = NixTarget::Expr(expr.to_owned());
self
}
pub fn impure(&mut self, impure: bool) -> &mut Self {
self.impure = impure;
self
}
#[must_use]
pub fn build(&self) -> Result<Vec<Derivation>> {
let nix = crate::is_nix_available().ok_or(Error::NixNotAvailable)?;
let cwd = std::env::current_dir().unwrap();
let mut cmd = Command::new(nix);
cmd.current_dir(&cwd);
cmd.arg("build");
cmd.args(&["--no-link", "--json"]);
match &self.target {
NixTarget::Function(file) => {
cmd.args(&[OsStr::new("-f"), &file]);
println!(
"cargo:rerun-if-changed={}",
AsRef::<Path>::as_ref(file).display()
);
}
NixTarget::Flake(installable) => {
cmd.arg(installable);
if let Some(Ok(local_flake)) = cwd
.to_string_lossy()
.split_once('#')
.map(|(path, _)| path)
.map(std::fs::canonicalize)
{
println!(
"cargo:rerun-if-changed={}",
local_flake.join("flake.lock").display()
);
}
}
NixTarget::Expr(expr) => {
cmd.args(["--expr", expr.as_str()]);
}
}
for (key, val) in &self.arg_exprs {
cmd.args(&["--arg", &key, &val]);
}
for (key, val) in &self.arg_strs {
cmd.args(&["--argstr", &key, &val]);
}
if self.impure {
cmd.arg("--impure");
}
cmd.arg("-L");
cmd.args(&["--experimental-features", "nix-command flakes"]);
let output = cmd.output().map_err(|_| Error::BuildFailed)?;
if !output.status.success() {
return Err(Error::BuildFailed);
}
serde_json::from_slice(&output.stdout).map_err(|_| Error::UnknownOutput)
}
}