use std::path::Path;
use crate::handlers::run_once::RunOnceCommand;
use crate::handlers::{ExecutionPhase, HANDLER_NIX};
const WRAPPER_EXPR: &str = r#"{ manifest }:
let
raw = import manifest;
m = if builtins.isFunction raw then raw {} else raw;
in
if builtins.isList m then m
else if builtins.isAttrs m && (m.type or null) == "derivation" then [ m ]
else if builtins.isAttrs m then builtins.attrValues m
else throw "packages.nix at ${manifest} evaluates to an unsupported shape (must be a list of derivations, a bare derivation, or an attribute set of derivations)""#;
const MANIFEST_ARG_NAME: &str = "manifest";
const IMPURE_FLAG: &str = "--impure";
const EXTRA_FEATURES_FLAG: &str = "--extra-experimental-features";
const EXTRA_FEATURES_VALUE: &str = "nix-command flakes";
pub struct NixCommand;
impl RunOnceCommand for NixCommand {
fn handler_name(&self) -> &str {
HANDLER_NIX
}
fn phase(&self) -> ExecutionPhase {
ExecutionPhase::Provision
}
fn command_for(&self, path: &Path) -> (String, Vec<String>) {
(
"nix".into(),
vec![
"profile".into(),
"install".into(),
IMPURE_FLAG.into(),
EXTRA_FEATURES_FLAG.into(),
EXTRA_FEATURES_VALUE.into(),
"--argstr".into(),
MANIFEST_ARG_NAME.into(),
path.to_string_lossy().into_owned(),
"--expr".into(),
WRAPPER_EXPR.into(),
],
)
}
fn status_deployed(&self) -> &str {
"nix packages installed"
}
fn status_pending(&self) -> &str {
"nix packages not installed"
}
fn status_ran_different(&self) -> &str {
"nix packages older version"
}
}
#[cfg(test)]
mod tests {
use super::*;
const MANIFEST_INDEX: usize = 7;
#[test]
fn nix_command_identity() {
assert_eq!(NixCommand.handler_name(), HANDLER_NIX);
assert_eq!(NixCommand.phase(), ExecutionPhase::Provision);
assert_eq!(NixCommand.status_deployed(), "nix packages installed");
assert_eq!(NixCommand.status_pending(), "nix packages not installed");
assert_eq!(
NixCommand.status_ran_different(),
"nix packages older version"
);
}
#[test]
fn command_for_emits_profile_install_with_wrapper_expression() {
let (exe, args) = NixCommand.command_for(Path::new("/p/tools/packages.nix"));
assert_eq!(exe, "nix");
assert_eq!(
args,
vec![
"profile",
"install",
"--impure",
"--extra-experimental-features",
"nix-command flakes",
"--argstr",
"manifest",
"/p/tools/packages.nix",
"--expr",
WRAPPER_EXPR,
]
);
}
#[test]
fn wrapper_expression_takes_the_manifest_as_an_argument() {
assert!(WRAPPER_EXPR.starts_with("{ manifest }:"));
assert!(WRAPPER_EXPR.contains("import manifest"));
assert!(WRAPPER_EXPR.contains("builtins.isFunction"));
assert!(WRAPPER_EXPR.contains("builtins.isList"));
assert!(WRAPPER_EXPR.contains("builtins.attrValues"));
}
#[test]
fn manifest_path_is_passed_verbatim_however_it_is_spelled() {
for path in [
"/p/tools/packages.nix",
"/weird\"name/packages.nix",
"/with\\backslash/packages.nix",
"/p/${weird}/packages.nix",
"/p/with space/packages.nix",
] {
let (_, args) = NixCommand.command_for(Path::new(path));
assert_eq!(args[MANIFEST_INDEX], path);
}
}
#[test]
fn command_for_is_shape_agnostic() {
let (e1, a1) = NixCommand.command_for(Path::new("/a/packages.nix"));
let (e2, a2) = NixCommand.command_for(Path::new("/b/packages.nix"));
assert_eq!(e1, e2);
for (i, (x, y)) in a1.iter().zip(a2.iter()).enumerate() {
if i == MANIFEST_INDEX {
assert_ne!(x, y);
} else {
assert_eq!(x, y);
}
}
assert_eq!(a1.len(), a2.len());
}
#[test]
fn every_invocation_forces_impure_evaluation() {
let (_, args) = NixCommand.command_for(Path::new("/p/tools/packages.nix"));
assert!(args.iter().any(|a| a == "--impure"));
}
#[test]
fn the_command_is_the_whole_specialization() {
use crate::provisioners::{descriptor_for, ExecutableLocation};
let ExecutableLocation::Candidates(candidates) =
descriptor_for(HANDLER_NIX).unwrap().location
else {
panic!("nix is located at fixed candidates");
};
assert!(candidates.len() > 1);
}
}