Add direct flake evaluation support

This commit is contained in:
Zhaofeng Li 2024-11-07 15:24:37 -07:00
parent 1f669d4c78
commit dc80345dee
2 changed files with 118 additions and 12 deletions

View file

@ -10,7 +10,7 @@ use env_logger::fmt::WriteStyle;
use crate::{ use crate::{
command::{self, apply::DeployOpts}, command::{self, apply::DeployOpts},
error::ColmenaResult, error::ColmenaResult,
nix::{Hive, HivePath}, nix::{hive::EvaluationMethod, Hive, HivePath},
}; };
/// Base URL of the manual, without the trailing slash. /// Base URL of the manual, without the trailing slash.
@ -137,6 +137,21 @@ This only works when building locally.
value_names = ["NAME", "VALUE"], value_names = ["NAME", "VALUE"],
)] )]
nix_option: Vec<String>, nix_option: Vec<String>,
#[arg(
long,
default_value_t,
help = "Use direct flake evaluation (experimental)",
long_help = r#"If enabled, flakes will be evaluated using `nix eval`. This requires the flake to depend on Colmena as an input and expose a compatible `colmenaHive` output:
outputs = { self, colmena, ... }: {
colmenaHive = colmena.lib.makeHive self.outputs.colmena;
colmena = ...;
};
This is an experimental feature."#,
global = true
)]
experimental_flake_eval: bool,
#[arg( #[arg(
long, long,
value_name = "WHEN", value_name = "WHEN",
@ -262,6 +277,11 @@ async fn get_hive(opts: &Opts) -> ColmenaResult<Hive> {
hive.set_impure(true); hive.set_impure(true);
} }
if opts.experimental_flake_eval {
log::warn!("Using direct flake evaluation (experimental)");
hive.set_evaluation_method(EvaluationMethod::DirectFlakeEval);
}
for chunks in opts.nix_option.chunks_exact(2) { for chunks in opts.nix_option.chunks_exact(2) {
let [name, value] = chunks else { let [name, value] = chunks else {
unreachable!() unreachable!()

View file

@ -8,6 +8,7 @@ use std::convert::AsRef;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::str::FromStr; use std::str::FromStr;
use const_format::formatcp;
use tokio::process::Command; use tokio::process::Command;
use tokio::sync::OnceCell; use tokio::sync::OnceCell;
use validator::Validate; use validator::Validate;
@ -22,6 +23,21 @@ use crate::job::JobHandle;
use crate::util::{CommandExecution, CommandExt}; use crate::util::{CommandExecution, CommandExt};
use assets::Assets; use assets::Assets;
/// The version of the Hive schema we are compatible with.
///
/// Currently we are tied to one specific version.
const HIVE_SCHEMA: &str = "v0.20241006";
/// The snippet to be used for `nix eval --apply`.
const FLAKE_APPLY_SNIPPET: &str = formatcp!(
r#"with builtins; hive: assert (hive.__schema == "{}" || throw ''
The colmenaHive output (schema ${{hive.__schema}}) isn't compatible with this version of Colmena.
Hint: Use the same version of Colmena as in the Flake input.
''); "#,
HIVE_SCHEMA
);
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub enum HivePath { pub enum HivePath {
/// A Nix Flake. /// A Nix Flake.
@ -63,11 +79,33 @@ impl FromStr for HivePath {
} }
} }
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum EvaluationMethod {
/// Use nix-instantiate and specify the entire Nix expression.
///
/// This is the default method.
///
/// For flakes, we use `builtins.getFlakes`. Pure evaluation no longer works
/// with this method in Nix 2.21+.
NixInstantiate,
/// Use `nix eval --apply` on top of a flake.
///
/// This can be activated with --experimental-flake-eval.
///
/// In this method, we can no longer pull in our bundled assets and
/// the flake must expose a compatible `colmenaHive` output.
DirectFlakeEval,
}
#[derive(Debug)] #[derive(Debug)]
pub struct Hive { pub struct Hive {
/// Path to the hive. /// Path to the hive.
path: HivePath, path: HivePath,
/// Method to evaluate the hive with.
evaluation_method: EvaluationMethod,
/// Path to the context directory. /// Path to the context directory.
/// ///
/// Normally this is directory containing the "hive.nix" /// Normally this is directory containing the "hive.nix"
@ -134,6 +172,7 @@ impl Hive {
Ok(Self { Ok(Self {
path, path,
evaluation_method: EvaluationMethod::NixInstantiate,
context_dir, context_dir,
assets, assets,
show_trace: false, show_trace: false,
@ -158,6 +197,14 @@ impl Hive {
.await .await
} }
pub fn set_evaluation_method(&mut self, method: EvaluationMethod) {
if !self.is_flake() && method == EvaluationMethod::DirectFlakeEval {
return;
}
self.evaluation_method = method;
}
pub fn set_show_trace(&mut self, value: bool) { pub fn set_show_trace(&mut self, value: bool) {
self.show_trace = value; self.show_trace = value;
} }
@ -421,7 +468,10 @@ impl Hive {
/// Returns the base expression from which the evaluated Hive can be used. /// Returns the base expression from which the evaluated Hive can be used.
fn get_base_expression(&self) -> String { fn get_base_expression(&self) -> String {
self.assets.get_base_expression() match self.evaluation_method {
EvaluationMethod::NixInstantiate => self.assets.get_base_expression(),
EvaluationMethod::DirectFlakeEval => FLAKE_APPLY_SNIPPET.to_string(),
}
} }
/// Returns whether this Hive is a flake. /// Returns whether this Hive is a flake.
@ -444,6 +494,11 @@ impl<'hive> NixInstantiate<'hive> {
} }
fn instantiate(&self) -> Command { fn instantiate(&self) -> Command {
// TODO: Better error handling
if self.hive.evaluation_method == EvaluationMethod::DirectFlakeEval {
panic!("Instantiation is not supported with DirectFlakeEval");
}
let mut command = Command::new("nix-instantiate"); let mut command = Command::new("nix-instantiate");
if self.hive.is_flake() { if self.hive.is_flake() {
@ -462,17 +517,48 @@ impl<'hive> NixInstantiate<'hive> {
} }
fn eval(self) -> Command { fn eval(self) -> Command {
let mut command = self.instantiate();
let flags = self.hive.nix_flags(); let flags = self.hive.nix_flags();
command
.arg("--eval") match self.hive.evaluation_method {
.arg("--json") EvaluationMethod::NixInstantiate => {
.arg("--strict") let mut command = self.instantiate();
// Ensures the derivations are instantiated
// Required for system profile evaluation and IFD command
.arg("--read-write-mode") .arg("--eval")
.args(flags.to_args()); .arg("--json")
command .arg("--strict")
// Ensures the derivations are instantiated
// Required for system profile evaluation and IFD
.arg("--read-write-mode")
.args(flags.to_args());
command
}
EvaluationMethod::DirectFlakeEval => {
let mut command = Command::new("nix");
let flake = if let HivePath::Flake(flake) = self.hive.path() {
flake
} else {
panic!("The DirectFlakeEval evaluation method only support flakes");
};
let hive_installable = format!("{}#colmenaHive", flake.uri());
let mut full_expression = self.hive.get_base_expression();
full_expression += &self.expression;
command
.arg("eval") // nix eval
.args(["--extra-experimental-features", "flakes nix-command"])
.arg(hive_installable)
.arg("--json")
.arg("--apply")
.arg(&full_expression)
.args(flags.to_args());
command
}
}
} }
async fn instantiate_with_builders(self) -> ColmenaResult<Command> { async fn instantiate_with_builders(self) -> ColmenaResult<Command> {