snow-cli test

This commit is contained in:
do butterflies cry? 2026-03-13 00:42:54 +10:00
parent 9c188c46c9
commit 03c72f7582
Signed by: cry
GPG key ID: F68745A836CA0412
13 changed files with 488 additions and 133 deletions

102
snow/Cargo.lock generated
View file

@ -315,6 +315,15 @@ dependencies = [
"serde_core",
]
[[package]]
name = "indoc"
version = "2.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "79cf5c93f93228cf8efb3ba362535fb11199ac548a09ce117c9b1adc3030d706"
dependencies = [
"rustversion",
]
[[package]]
name = "is_terminal_polyfill"
version = "1.70.2"
@ -403,7 +412,7 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
[[package]]
name = "nix-bindings-bdwgc-sys"
version = "0.2.1"
source = "git+https://github.com/nixops4/nix-bindings-rust#7de15fa26057c8cf0b6178ff166e529c09ea89a7"
source = "git+https://tearforge.net/cry/nixide#716c028bb159e62d3d295c5101d50ee117f027c6"
dependencies = [
"bindgen",
"pkg-config",
@ -412,7 +421,7 @@ dependencies = [
[[package]]
name = "nix-bindings-expr"
version = "0.2.1"
source = "git+https://github.com/nixops4/nix-bindings-rust#7de15fa26057c8cf0b6178ff166e529c09ea89a7"
source = "git+https://tearforge.net/cry/nixide#716c028bb159e62d3d295c5101d50ee117f027c6"
dependencies = [
"anyhow",
"cstr",
@ -430,7 +439,7 @@ dependencies = [
[[package]]
name = "nix-bindings-expr-sys"
version = "0.2.1"
source = "git+https://github.com/nixops4/nix-bindings-rust#7de15fa26057c8cf0b6178ff166e529c09ea89a7"
source = "git+https://tearforge.net/cry/nixide#716c028bb159e62d3d295c5101d50ee117f027c6"
dependencies = [
"bindgen",
"nix-bindings-store-sys",
@ -438,10 +447,64 @@ dependencies = [
"pkg-config",
]
[[package]]
name = "nix-bindings-fetchers"
version = "0.2.1"
source = "git+https://tearforge.net/cry/nixide#716c028bb159e62d3d295c5101d50ee117f027c6"
dependencies = [
"anyhow",
"cstr",
"ctor",
"nix-bindings-fetchers-sys",
"nix-bindings-store",
"nix-bindings-util",
"tempfile",
]
[[package]]
name = "nix-bindings-fetchers-sys"
version = "0.2.1"
source = "git+https://tearforge.net/cry/nixide#716c028bb159e62d3d295c5101d50ee117f027c6"
dependencies = [
"bindgen",
"nix-bindings-util-sys",
"pkg-config",
]
[[package]]
name = "nix-bindings-flake"
version = "0.2.1"
source = "git+https://tearforge.net/cry/nixide#716c028bb159e62d3d295c5101d50ee117f027c6"
dependencies = [
"anyhow",
"cstr",
"ctor",
"nix-bindings-expr",
"nix-bindings-fetchers",
"nix-bindings-flake-sys",
"nix-bindings-store",
"nix-bindings-util",
"tempfile",
]
[[package]]
name = "nix-bindings-flake-sys"
version = "0.2.1"
source = "git+https://tearforge.net/cry/nixide#716c028bb159e62d3d295c5101d50ee117f027c6"
dependencies = [
"bindgen",
"nix-bindings-bdwgc-sys",
"nix-bindings-expr-sys",
"nix-bindings-fetchers-sys",
"nix-bindings-store-sys",
"nix-bindings-util-sys",
"pkg-config",
]
[[package]]
name = "nix-bindings-store"
version = "0.2.1"
source = "git+https://github.com/nixops4/nix-bindings-rust#7de15fa26057c8cf0b6178ff166e529c09ea89a7"
source = "git+https://tearforge.net/cry/nixide#716c028bb159e62d3d295c5101d50ee117f027c6"
dependencies = [
"anyhow",
"nix-bindings-store-sys",
@ -454,7 +517,7 @@ dependencies = [
[[package]]
name = "nix-bindings-store-sys"
version = "0.2.1"
source = "git+https://github.com/nixops4/nix-bindings-rust#7de15fa26057c8cf0b6178ff166e529c09ea89a7"
source = "git+https://tearforge.net/cry/nixide#716c028bb159e62d3d295c5101d50ee117f027c6"
dependencies = [
"bindgen",
"nix-bindings-util-sys",
@ -465,7 +528,7 @@ dependencies = [
[[package]]
name = "nix-bindings-util"
version = "0.2.1"
source = "git+https://github.com/nixops4/nix-bindings-rust#7de15fa26057c8cf0b6178ff166e529c09ea89a7"
source = "git+https://tearforge.net/cry/nixide#716c028bb159e62d3d295c5101d50ee117f027c6"
dependencies = [
"anyhow",
"nix-bindings-util-sys",
@ -474,7 +537,7 @@ dependencies = [
[[package]]
name = "nix-bindings-util-sys"
version = "0.2.1"
source = "git+https://github.com/nixops4/nix-bindings-rust#7de15fa26057c8cf0b6178ff166e529c09ea89a7"
source = "git+https://tearforge.net/cry/nixide#716c028bb159e62d3d295c5101d50ee117f027c6"
dependencies = [
"bindgen",
"pkg-config",
@ -492,9 +555,9 @@ dependencies = [
[[package]]
name = "once_cell"
version = "1.21.3"
version = "1.21.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
[[package]]
name = "once_cell_polyfill"
@ -603,6 +666,12 @@ dependencies = [
"windows-sys 0.61.2",
]
[[package]]
name = "rustversion"
version = "1.0.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
[[package]]
name = "semver"
version = "1.0.27"
@ -665,8 +734,11 @@ dependencies = [
"clap",
"fern",
"humantime",
"indoc",
"log",
"nix-bindings-expr",
"nix-bindings-fetchers",
"nix-bindings-flake",
"nix-bindings-store",
]
@ -689,9 +761,9 @@ dependencies = [
[[package]]
name = "tempfile"
version = "3.26.0"
version = "3.27.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "82a72c767771b47409d2345987fda8628641887d5466101319899796367354a0"
checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd"
dependencies = [
"fastrand",
"getrandom",
@ -960,18 +1032,18 @@ dependencies = [
[[package]]
name = "zerocopy"
version = "0.8.40"
version = "0.8.42"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a789c6e490b576db9f7e6b6d661bcc9799f7c0ac8352f56ea20193b2681532e5"
checksum = "f2578b716f8a7a858b7f02d5bd870c14bf4ddbbcf3a4c05414ba6503640505e3"
dependencies = [
"zerocopy-derive",
]
[[package]]
name = "zerocopy-derive"
version = "0.8.40"
version = "0.8.42"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f65c489a7071a749c849713807783f70672b28094011623e200cb86dcb835953"
checksum = "7e6cc098ea4d3bd6246687de65af3f920c430e236bee1e3bf2e441463f08a02f"
dependencies = [
"proc-macro2",
"quote",

View file

@ -6,10 +6,14 @@ authors = ["_cry64 <them@dobutterfliescry.net>"]
edition = "2024"
[dependencies]
nix-bindings-store = { git = "https://github.com/nixops4/nix-bindings-rust" }
nix-bindings-expr = { git = "https://github.com/nixops4/nix-bindings-rust" }
nix-bindings-expr = { git = "https://tearforge.net/cry/nixide" }
nix-bindings-flake = { git = "https://tearforge.net/cry/nixide" }
nix-bindings-fetchers = { git = "https://tearforge.net/cry/nixide" }
nix-bindings-store = { git = "https://tearforge.net/cry/nixide" }
indoc = "2"
anyhow = "1.0.102"
clap = { version = "4.5.60", features = ["derive"] }
log = "0.4.29"
fern = { version="0.7.1", features = ["colored"] }

View file

@ -1,3 +1,5 @@
mod nix;
use nix_bindings_expr::eval_state::{gc_register_my_thread, init, EvalState};
use nix_bindings_store::store::Store;
use std::collections::HashMap;

114
snow/src/nix/flake.rs Normal file
View file

@ -0,0 +1,114 @@
use std::{cell::RefCell, collections::HashMap, rc::Rc};
use anyhow::Result;
use nix_bindings_expr::{eval_state::EvalState, value::Value};
use nix_bindings_fetchers::FetchersSettings;
use nix_bindings_flake::{FlakeLockFlags, FlakeSettings, LockedFlake};
use nix_bindings_store::store::Store;
use crate::nix::FlakeRef;
#[derive(Debug, Clone)]
pub enum FlakeLockMode {
/// Configures [LockedFlake::lock] to make incremental changes to the lock file as needed. Changes are written to file.
WriteAsNeeded,
/// Like [FlakeLockMode::WriteAsNeeded], but does not write to the lock file.
Virtual,
/// Make [LockedFlake::lock] check if the lock file is up to date. If not, an error is returned.
Check,
}
pub struct FlakeBuilder {
flake_ref: Rc<RefCell<FlakeRef>>,
lock_mode: FlakeLockMode,
lock_flags: FlakeLockFlags,
eval_state: Option<EvalState>,
}
pub struct Flake {
flake_ref: Rc<RefCell<FlakeRef>>,
locked_flake: LockedFlake,
lock_mode: FlakeLockMode,
eval_state: EvalState,
}
impl FlakeBuilder {
pub fn new(flake_ref: Rc<RefCell<FlakeRef>>, lock_mode: FlakeLockMode) -> Result<Self> {
let mut lock_flags = FlakeLockFlags::new(&flake_ref.as_ref().borrow().flake_settings)?;
match lock_mode {
FlakeLockMode::WriteAsNeeded => lock_flags.set_mode_write_as_needed(),
FlakeLockMode::Virtual => lock_flags.set_mode_virtual(),
FlakeLockMode::Check => lock_flags.set_mode_check(),
}?;
Ok(FlakeBuilder {
flake_ref: flake_ref,
lock_mode,
lock_flags,
eval_state: None,
})
}
/// Adds an input override to the lock file that will be produced.
/// The [LockedFlake::lock] operation will not write to the lock file.
///
/// # Arguments
///
/// * `path` - The input name/path to override (must not be empty)
/// * `flake_ref` - The flake reference to use as the override
pub fn override_input(mut self, path: &str, flake_ref: &FlakeRef) -> Result<Self> {
assert!(
!path.is_empty(),
"The input path for `FlakeBuilder::override_input` cannot be an empty string slice!"
);
self.lock_flags.add_input_override(path, &flake_ref.ref_)?;
Ok(self)
}
pub fn build(&mut self) -> Result<Flake> {
let eval_state = match self.eval_state.take() {
Some(state) => state,
None => {
let store = Store::open(None, HashMap::new())?;
EvalState::new(store, [])?
}
};
let locked_flake = LockedFlake::lock(
&FetchersSettings::new()?,
&FlakeSettings::new()?,
&eval_state,
&self.lock_flags,
&self.flake_ref.as_ref().borrow().ref_,
)?;
Ok(Flake {
flake_ref: Rc::clone(&self.flake_ref),
locked_flake,
lock_mode: self.lock_mode.clone(),
eval_state,
})
}
}
impl Flake {
pub fn new(flake_ref: Rc<RefCell<FlakeRef>>, lock_mode: FlakeLockMode) -> Result<Self> {
let mut builder = FlakeBuilder::new(flake_ref, lock_mode)?;
builder.build()
}
pub fn outputs(&mut self) -> Result<Value> {
self.locked_flake.outputs(
&self.flake_ref.as_ref().borrow().flake_settings,
&mut self.eval_state,
)
}
}

79
snow/src/nix/flakeref.rs Normal file
View file

@ -0,0 +1,79 @@
use std::path::{Path, PathBuf};
use anyhow::{Context, Result};
use indoc::indoc;
use nix_bindings_fetchers::FetchersSettings;
use nix_bindings_flake::{FlakeReference, FlakeReferenceParseFlags, FlakeSettings};
pub struct FlakeRef {
pub(super) ref_: FlakeReference,
pub path: PathBuf,
pub flake_settings: FlakeSettings,
pub fetch_settings: FetchersSettings,
}
impl FlakeRef {
/// Parse a flake reference from a string.
/// The string must be a valid flake reference, such as `github:owner/repo`.
/// It may also be suffixed with a `#` and a fragment, such as `github:owner/repo#something`,
/// in which case, the returned `flake_ref.path` will contain the fragment.
pub fn new<P: AsRef<Path>>(reference: &str, base_directory: Option<P>) -> Result<FlakeRef> {
let flake_settings = FlakeSettings::new()?;
let fetch_settings = FetchersSettings::new()?;
let mut flags = FlakeReferenceParseFlags::new(&flake_settings)?;
if let Some(base_directory) = base_directory {
flags.set_base_directory(
base_directory
.as_ref()
.to_str()
.context("The given flake reference path is not provided as valid unicode")?,
)?;
} else {
// TODO: try see if libnix uses the cwd as a fallback (aka is this assert pointless?)
assert!(
reference.starts_with("."),
indoc! {"
Attempted to construct FlakeRef from relative path without declaring `base_directory`!
Call to `FlakeRef::absolute(&str)` should actually be `FlakeRef::relative(&str, dyn AsRef<Path>)`.
"}
);
}
FlakeReference::parse_with_fragment(&fetch_settings, &flake_settings, &flags, reference)
.map(|(reference, path)| FlakeRef {
ref_: reference,
path: PathBuf::from(path),
flake_settings,
fetch_settings,
})
}
/// >[!WARNING]
/// > Do not use [FlakeRef::absolute(&str)](FlakeRef::absolute) to construct a [FlakeRef] from a relative path!
/// > Use [FlakeRef::relative(&str, dyn AsRef<Path>)](FlakeRef::relative) instead to declare the `base_directory`.
///
/// Parse a flake reference from a string.
/// The string must be a valid flake reference, such as `github:owner/repo`.
/// It may also be suffixed with a `#` and a fragment, such as `github:owner/repo#something`,
/// in which case, the returned `flake_ref.path` will contain the fragment.
pub fn absolute(reference: &str) -> Result<FlakeRef> {
FlakeRef::new(reference, None::<&Path>)
}
/// Parse a flake reference from a string.
/// The string must be a valid flake reference, such as `github:owner/repo`.
/// It may also be suffixed with a `#` and a fragment, such as `github:owner/repo#something`,
/// in which case, the returned `flake_ref.path` will contain the fragment.
pub fn relative<P: AsRef<Path>>(reference: &str, base_directory: P) -> Result<FlakeRef> {
FlakeRef::new(reference, Some(base_directory))
}
}
impl Into<FlakeReference> for FlakeRef {
fn into(self) -> FlakeReference {
self.ref_
}
}

7
snow/src/nix/mod.rs Normal file
View file

@ -0,0 +1,7 @@
mod flake;
mod flakeref;
mod nix;
pub use flake::{Flake, FlakeBuilder, FlakeLockMode};
pub use flakeref::FlakeRef;
pub use nix::Nix;

10
snow/src/nix/nix.rs Normal file
View file

@ -0,0 +1,10 @@
use std::path::Path;
use anyhow::{Context, Result};
use derive_more::From;
use nix_bindings_fetchers::FetchersSettings;
use nix_bindings_flake::{FlakeReference, FlakeReferenceParseFlags, FlakeSettings};
pub struct Nix {}
impl Nix {}