forked from DGNum/colmena
Make flake evaluation pure
This seems to be the easiest way to get pure-eval working with existing evaluation mechinery (nix-instantiate, nix-eval-jobs). Now `--pure-eval` is forced for flakes with user being able to add `--impure` as needed.
This commit is contained in:
parent
092e5848ab
commit
8aca525788
7 changed files with 70 additions and 29 deletions
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
## Release 0.4.0 (Unreleased)
|
## Release 0.4.0 (Unreleased)
|
||||||
|
|
||||||
|
- Flake evaluation is now actually pure by default. To enable impure expressions, pass `--impure`.
|
||||||
- `--reboot` is added to trigger a reboot and wait for the node to come back up.
|
- `--reboot` is added to trigger a reboot and wait for the node to come back up.
|
||||||
- The target user is no longer explicitly set when `deployment.targetUser` is null ([#91](https://github.com/zhaofengli/colmena/pull/91)).
|
- The target user is no longer explicitly set when `deployment.targetUser` is null ([#91](https://github.com/zhaofengli/colmena/pull/91)).
|
||||||
- In `apply-local`, we now only escalate privileges during activation ([#85](https://github.com/zhaofengli/colmena/issues/85)).
|
- In `apply-local`, we now only escalate privileges during activation ([#85](https://github.com/zhaofengli/colmena/issues/85)).
|
||||||
|
|
|
@ -10,7 +10,7 @@ use tokio::process::Command;
|
||||||
use super::{ColmenaError, ColmenaResult, NixCheck};
|
use super::{ColmenaError, ColmenaResult, NixCheck};
|
||||||
|
|
||||||
/// A Nix Flake.
|
/// A Nix Flake.
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct Flake {
|
pub struct Flake {
|
||||||
/// The flake metadata.
|
/// The flake metadata.
|
||||||
metadata: FlakeMetadata,
|
metadata: FlakeMetadata,
|
||||||
|
@ -20,7 +20,7 @@ pub struct Flake {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A `nix flake metadata --json` invocation.
|
/// A `nix flake metadata --json` invocation.
|
||||||
#[derive(Deserialize, Debug)]
|
#[derive(Deserialize, Debug, Clone)]
|
||||||
struct FlakeMetadata {
|
struct FlakeMetadata {
|
||||||
/// The resolved URL of the flake.
|
/// The resolved URL of the flake.
|
||||||
#[serde(rename = "resolvedUrl")]
|
#[serde(rename = "resolvedUrl")]
|
||||||
|
@ -105,3 +105,20 @@ impl FlakeMetadata {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Quietly locks the dependencies of a flake.
|
||||||
|
pub async fn lock_flake_quiet(uri: &str) -> ColmenaResult<()> {
|
||||||
|
let status = Command::new("nix")
|
||||||
|
.args(&["flake", "lock"])
|
||||||
|
.args(&["--experimental-features", "nix-command flakes"])
|
||||||
|
.arg(uri)
|
||||||
|
.stderr(Stdio::null())
|
||||||
|
.status()
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
if !status.success() {
|
||||||
|
return Err(status.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
|
@ -13,8 +13,9 @@ use tempfile::TempDir;
|
||||||
|
|
||||||
use super::{Flake, HivePath};
|
use super::{Flake, HivePath};
|
||||||
use crate::error::ColmenaResult;
|
use crate::error::ColmenaResult;
|
||||||
|
use crate::nix::flake::lock_flake_quiet;
|
||||||
|
|
||||||
const FLAKE_NIX: &[u8] = include_bytes!("flake.nix");
|
const FLAKE_NIX: &str = include_str!("flake.nix");
|
||||||
const EVAL_NIX: &[u8] = include_bytes!("eval.nix");
|
const EVAL_NIX: &[u8] = include_bytes!("eval.nix");
|
||||||
const OPTIONS_NIX: &[u8] = include_bytes!("options.nix");
|
const OPTIONS_NIX: &[u8] = include_bytes!("options.nix");
|
||||||
const MODULES_NIX: &[u8] = include_bytes!("modules.nix");
|
const MODULES_NIX: &[u8] = include_bytes!("modules.nix");
|
||||||
|
@ -22,6 +23,9 @@ const MODULES_NIX: &[u8] = include_bytes!("modules.nix");
|
||||||
/// Static files required to evaluate a Hive configuration.
|
/// Static files required to evaluate a Hive configuration.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub(super) struct Assets {
|
pub(super) struct Assets {
|
||||||
|
/// Path to the hive being evaluated.
|
||||||
|
hive_path: HivePath,
|
||||||
|
|
||||||
/// Temporary directory holding the files.
|
/// Temporary directory holding the files.
|
||||||
temp_dir: TempDir,
|
temp_dir: TempDir,
|
||||||
|
|
||||||
|
@ -30,7 +34,7 @@ pub(super) struct Assets {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Assets {
|
impl Assets {
|
||||||
pub async fn new(flake: bool) -> ColmenaResult<Self> {
|
pub async fn new(hive_path: HivePath) -> ColmenaResult<Self> {
|
||||||
let temp_dir = TempDir::new().unwrap();
|
let temp_dir = TempDir::new().unwrap();
|
||||||
|
|
||||||
create_file(&temp_dir, "eval.nix", false, EVAL_NIX)?;
|
create_file(&temp_dir, "eval.nix", false, EVAL_NIX)?;
|
||||||
|
@ -39,27 +43,30 @@ impl Assets {
|
||||||
|
|
||||||
let mut assets_flake_uri = None;
|
let mut assets_flake_uri = None;
|
||||||
|
|
||||||
if flake {
|
if let HivePath::Flake(hive_flake) = &hive_path {
|
||||||
// Emit a temporary flake, then resolve the locked URI
|
// Emit a temporary flake, then resolve the locked URI
|
||||||
create_file(&temp_dir, "flake.nix", false, FLAKE_NIX)?;
|
let flake_nix = FLAKE_NIX.replace("%hive%", hive_flake.locked_uri());
|
||||||
|
create_file(&temp_dir, "flake.nix", false, flake_nix.as_bytes())?;
|
||||||
|
|
||||||
// We explicitly specify `path:` instead of letting Nix resolve
|
// We explicitly specify `path:` instead of letting Nix resolve
|
||||||
// automatically, which would involve checking parent directories
|
// automatically, which would involve checking parent directories
|
||||||
// for a git repository.
|
// for a git repository.
|
||||||
let uri = format!("path:{}", temp_dir.path().to_str().unwrap());
|
let uri = format!("path:{}", temp_dir.path().to_str().unwrap());
|
||||||
|
let _ = lock_flake_quiet(&uri).await;
|
||||||
let assets_flake = Flake::from_uri(uri).await?;
|
let assets_flake = Flake::from_uri(uri).await?;
|
||||||
assets_flake_uri = Some(assets_flake.locked_uri().to_owned());
|
assets_flake_uri = Some(assets_flake.locked_uri().to_owned());
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
|
hive_path,
|
||||||
temp_dir,
|
temp_dir,
|
||||||
assets_flake_uri,
|
assets_flake_uri,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the base expression from which the evaluated Hive can be used.
|
/// Returns the base expression from which the evaluated Hive can be used.
|
||||||
pub fn get_base_expression(&self, hive_path: &HivePath) -> String {
|
pub fn get_base_expression(&self) -> String {
|
||||||
match hive_path {
|
match &self.hive_path {
|
||||||
HivePath::Legacy(path) => {
|
HivePath::Legacy(path) => {
|
||||||
format!(
|
format!(
|
||||||
"with builtins; let eval = import {eval_nix}; hive = eval {{ rawHive = import {path}; colmenaOptions = import {options_nix}; colmenaModules = import {modules_nix}; }}; in ",
|
"with builtins; let eval = import {eval_nix}; hive = eval {{ rawHive = import {path}; colmenaOptions = import {options_nix}; colmenaModules = import {modules_nix}; }}; in ",
|
||||||
|
@ -69,11 +76,10 @@ impl Assets {
|
||||||
modules_nix = self.get_path("modules.nix"),
|
modules_nix = self.get_path("modules.nix"),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
HivePath::Flake(flake) => {
|
HivePath::Flake(_) => {
|
||||||
format!(
|
format!(
|
||||||
"with builtins; let assets = getFlake \"{assets_flake_uri}\"; hive = assets.lib.colmenaEval {{ flakeUri = \"{flake_uri}\"; }}; in ",
|
"with builtins; let assets = getFlake \"{assets_flake_uri}\"; hive = assets.colmenaEval; in ",
|
||||||
assets_flake_uri = self.assets_flake_uri.as_ref().expect("The assets flake must have been initialized"),
|
assets_flake_uri = self.assets_flake_uri.as_ref().expect("The assets flake must have been initialized"),
|
||||||
flake_uri = flake.locked_uri(),
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{ rawHive ? null # Colmena Hive attrset
|
{ rawHive ? null # Colmena Hive attrset
|
||||||
, flakeUri ? null # Nix Flake URI with `outputs.colmena`
|
, rawFlake ? null # Nix Flake attrset with `outputs.colmena`
|
||||||
, hermetic ? flakeUri != null # Whether we are allowed to use <nixpkgs>
|
, hermetic ? rawFlake != null # Whether we are allowed to use <nixpkgs>
|
||||||
, colmenaOptions
|
, colmenaOptions
|
||||||
, colmenaModules
|
, colmenaModules
|
||||||
}:
|
}:
|
||||||
|
@ -19,10 +19,8 @@ let
|
||||||
|
|
||||||
|
|
||||||
uncheckedHive = let
|
uncheckedHive = let
|
||||||
flakeToHive = flakeUri: let
|
flakeToHive = rawFlake:
|
||||||
flake = builtins.getFlake flakeUri;
|
if rawFlake.outputs ? colmena then rawFlake.outputs.colmena else throw "Flake must define outputs.colmena.";
|
||||||
hive = if flake.outputs ? colmena then flake.outputs.colmena else throw "Flake must define outputs.colmena.";
|
|
||||||
in hive;
|
|
||||||
|
|
||||||
rawToHive = rawHive:
|
rawToHive = rawHive:
|
||||||
if typeOf rawHive == "lambda" || rawHive ? __functor then rawHive {}
|
if typeOf rawHive == "lambda" || rawHive ? __functor then rawHive {}
|
||||||
|
@ -30,8 +28,8 @@ let
|
||||||
else throw "The config must evaluate to an attribute set.";
|
else throw "The config must evaluate to an attribute set.";
|
||||||
in
|
in
|
||||||
if rawHive != null then rawToHive rawHive
|
if rawHive != null then rawToHive rawHive
|
||||||
else if flakeUri != null then flakeToHive flakeUri
|
else if rawFlake != null then flakeToHive rawFlake
|
||||||
else throw "Either an attribute set or a flake URI must be specified.";
|
else throw "Either a plain Hive attribute set or a Nix Flake attribute set must be specified.";
|
||||||
|
|
||||||
uncheckedUserMeta =
|
uncheckedUserMeta =
|
||||||
if uncheckedHive ? meta && uncheckedHive ? network then
|
if uncheckedHive ? meta && uncheckedHive ? network then
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
{
|
{
|
||||||
description = "Internal Colmena expressions";
|
description = "Internal Colmena expressions";
|
||||||
|
|
||||||
outputs = { ... }: {
|
inputs = {
|
||||||
lib.colmenaEval = {
|
hive.url = "%hive%";
|
||||||
rawHive ? null,
|
};
|
||||||
flakeUri ? null,
|
|
||||||
hermetic ? flakeUri != null,
|
outputs = { self, hive }: {
|
||||||
}: import ./eval.nix {
|
colmenaEval = import ./eval.nix {
|
||||||
inherit rawHive flakeUri hermetic;
|
rawFlake = hive;
|
||||||
colmenaOptions = import ./options.nix;
|
colmenaOptions = import ./options.nix;
|
||||||
colmenaModules = import ./modules.nix;
|
colmenaModules = import ./modules.nix;
|
||||||
};
|
};
|
||||||
|
|
|
@ -21,7 +21,7 @@ use crate::job::JobHandle;
|
||||||
use crate::util::{CommandExecution, CommandExt};
|
use crate::util::{CommandExecution, CommandExt};
|
||||||
use assets::Assets;
|
use assets::Assets;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Clone)]
|
||||||
pub enum HivePath {
|
pub enum HivePath {
|
||||||
/// A Nix Flake.
|
/// A Nix Flake.
|
||||||
///
|
///
|
||||||
|
@ -93,7 +93,7 @@ impl HivePath {
|
||||||
impl Hive {
|
impl Hive {
|
||||||
pub async fn new(path: HivePath) -> ColmenaResult<Self> {
|
pub async fn new(path: HivePath) -> ColmenaResult<Self> {
|
||||||
let context_dir = path.context_dir();
|
let context_dir = path.context_dir();
|
||||||
let assets = Assets::new(path.is_flake()).await?;
|
let assets = Assets::new(path.clone()).await?;
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
path,
|
path,
|
||||||
|
@ -127,6 +127,7 @@ impl Hive {
|
||||||
pub fn nix_options(&self) -> NixOptions {
|
pub fn nix_options(&self) -> NixOptions {
|
||||||
let mut options = NixOptions::default();
|
let mut options = NixOptions::default();
|
||||||
options.set_show_trace(self.show_trace);
|
options.set_show_trace(self.show_trace);
|
||||||
|
options.set_pure_eval(self.path.is_flake());
|
||||||
options
|
options
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -361,7 +362,7 @@ 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(self.path())
|
self.assets.get_base_expression()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns whether this Hive is a flake.
|
/// Returns whether this Hive is a flake.
|
||||||
|
|
|
@ -97,6 +97,12 @@ pub struct NixOptions {
|
||||||
/// Whether to pass --show-trace.
|
/// Whether to pass --show-trace.
|
||||||
show_trace: bool,
|
show_trace: bool,
|
||||||
|
|
||||||
|
/// Whether to pass --pure-eval.
|
||||||
|
pure_eval: bool,
|
||||||
|
|
||||||
|
/// Whether to pass --impure.
|
||||||
|
impure: bool,
|
||||||
|
|
||||||
/// Designated builders.
|
/// Designated builders.
|
||||||
///
|
///
|
||||||
/// See <https://nixos.org/manual/nix/stable/advanced-topics/distributed-builds.html>.
|
/// See <https://nixos.org/manual/nix/stable/advanced-topics/distributed-builds.html>.
|
||||||
|
@ -187,6 +193,10 @@ impl NixOptions {
|
||||||
self.show_trace = show_trace;
|
self.show_trace = show_trace;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn set_pure_eval(&mut self, pure_eval: bool) {
|
||||||
|
self.pure_eval = pure_eval;
|
||||||
|
}
|
||||||
|
|
||||||
pub fn set_builders(&mut self, builders: Option<String>) {
|
pub fn set_builders(&mut self, builders: Option<String>) {
|
||||||
self.builders = builders;
|
self.builders = builders;
|
||||||
}
|
}
|
||||||
|
@ -206,6 +216,14 @@ impl NixOptions {
|
||||||
options.push("--show-trace".to_string());
|
options.push("--show-trace".to_string());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if self.pure_eval {
|
||||||
|
options.push("--pure-eval".to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.impure {
|
||||||
|
options.push("--impure".to_string());
|
||||||
|
}
|
||||||
|
|
||||||
options
|
options
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue