use std::path::Path;
use crate::cmd::{cmd, pkexec_guix_cmd};
use crate::error::GuixError;
use crate::operation::ExitClassifier;
use tokio::process::Command;
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub enum Privilege {
#[default]
Pkexec,
AlreadyRoot,
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct BuildOptions {
pub substitute_urls: Vec<String>,
pub no_substitutes: bool,
pub fallback: bool,
pub cores: Option<u32>,
pub max_jobs: Option<u32>,
pub system: Option<String>,
}
impl BuildOptions {
pub(crate) fn append_args(&self, args: &mut Vec<String>) {
if !self.substitute_urls.is_empty() {
args.push(format!(
"--substitute-urls={}",
self.substitute_urls.join(" ")
));
}
if self.no_substitutes {
args.push("--no-substitutes".into());
}
if self.fallback {
args.push("--fallback".into());
}
if let Some(c) = self.cores {
args.push(format!("--cores={c}"));
}
if let Some(j) = self.max_jobs {
args.push(format!("--max-jobs={j}"));
}
if let Some(s) = &self.system {
args.push(format!("--system={s}"));
}
}
}
pub(crate) fn privileged_guix_cmd(
privilege: Privilege,
binary: &Path,
args: &[String],
) -> Result<(Command, ExitClassifier), GuixError> {
match privilege {
Privilege::Pkexec => Ok((pkexec_guix_cmd(args)?, ExitClassifier::Pkexec)),
Privilege::AlreadyRoot => {
let mut c = cmd(binary);
c.kill_on_drop(true);
for a in args {
c.arg(a);
}
Ok((c, ExitClassifier::Standard))
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn default_build_options_emit_nothing() {
let mut args = Vec::new();
BuildOptions::default().append_args(&mut args);
assert!(args.is_empty());
}
#[test]
fn build_options_emit_all_flags_in_order() {
let opts = BuildOptions {
substitute_urls: vec![
"https://ci.guix.gnu.org".into(),
"https://bordeaux.guix.gnu.org".into(),
],
no_substitutes: true,
fallback: true,
cores: Some(4),
max_jobs: Some(2),
system: Some("x86_64-linux".into()),
};
let mut args = Vec::new();
opts.append_args(&mut args);
assert_eq!(
args,
vec![
"--substitute-urls=https://ci.guix.gnu.org https://bordeaux.guix.gnu.org",
"--no-substitutes",
"--fallback",
"--cores=4",
"--max-jobs=2",
"--system=x86_64-linux",
]
);
}
#[test]
fn empty_substitute_urls_emit_nothing() {
let opts = BuildOptions {
substitute_urls: vec![],
cores: Some(8),
..Default::default()
};
let mut args = Vec::new();
opts.append_args(&mut args);
assert_eq!(args, vec!["--cores=8"]);
}
#[test]
fn privilege_defaults_to_pkexec() {
assert_eq!(Privilege::default(), Privilege::Pkexec);
}
#[test]
fn already_root_spawns_binary_directly_with_standard_classifier() {
let (cmd, classifier) = privileged_guix_cmd(
Privilege::AlreadyRoot,
Path::new("/run/current-system/profile/bin/guix"),
&["system".into(), "init".into()],
)
.expect("already-root cmd");
let std = cmd.as_std();
assert_eq!(
std.get_program(),
std::ffi::OsStr::new("/run/current-system/profile/bin/guix")
);
let argv: Vec<_> = std.get_args().collect();
assert_eq!(argv, vec!["system", "init"]);
assert!(matches!(classifier, ExitClassifier::Standard));
}
}