From b77a3a20858c579e6c7a9abdfe3e1ce8aacb0c0e Mon Sep 17 00:00:00 2001 From: Robert Hensing Date: Wed, 2 Apr 2025 18:52:29 +0200 Subject: [PATCH] feat: nix-flake: Basic locking (cherry picked from commit cfd374f9deda7d40229198911516b22bc3d82626) --- rust/nix-flake/src/lib.rs | 218 +++++++++++++++++++++++++++++++++++++- 1 file changed, 216 insertions(+), 2 deletions(-) diff --git a/rust/nix-flake/src/lib.rs b/rust/nix-flake/src/lib.rs index 2c86450..fc7ae72 100644 --- a/rust/nix-flake/src/lib.rs +++ b/rust/nix-flake/src/lib.rs @@ -1,6 +1,14 @@ -use anyhow::Result; +use std::ptr::NonNull; + +use anyhow::{Context as _, Result}; use nix_c_raw as raw; -use nix_util::context::{self, Context}; +use nix_expr::eval_state::EvalState; +use nix_fetchers::FetchersSettings; +use nix_util::{ + context::{self, Context}, + result_string_init, + string_return::{callback_get_result_string, callback_get_result_string_data}, +}; pub struct FlakeSettings { pub(crate) ptr: *mut raw::flake_settings, @@ -47,6 +55,149 @@ impl EvalStateBuilderExt for nix_expr::eval_state::EvalStateBuilder { } } +pub struct FlakeReferenceParseFlags { + pub(crate) ptr: NonNull, +} +impl Drop for FlakeReferenceParseFlags { + fn drop(&mut self) { + unsafe { + raw::flake_reference_parse_flags_free(self.ptr.as_ptr()); + } + } +} +impl FlakeReferenceParseFlags { + pub fn new(settings: &FlakeSettings) -> Result { + let mut ctx = Context::new(); + let ptr = unsafe { + context::check_call!(raw::flake_reference_parse_flags_new(&mut ctx, settings.ptr)) + }?; + let ptr = NonNull::new(ptr) + .context("flake_reference_parse_flags_new unexpectedly returned null")?; + Ok(FlakeReferenceParseFlags { ptr }) + } + pub fn set_base_directory(&mut self, base_directory: &str) -> Result<()> { + let mut ctx = Context::new(); + unsafe { + context::check_call!(raw::flake_reference_parse_flags_set_base_directory( + &mut ctx, + self.ptr.as_ptr(), + base_directory.as_ptr() as *const i8, + base_directory.len() + )) + }?; + Ok(()) + } +} + +pub struct FlakeReference { + pub(crate) ptr: NonNull, +} +impl Drop for FlakeReference { + fn drop(&mut self) { + unsafe { + raw::flake_reference_free(self.ptr.as_ptr()); + } + } +} +impl FlakeReference { + pub fn parse_with_fragment( + fetch_settings: &FetchersSettings, + flake_settings: &FlakeSettings, + flags: &FlakeReferenceParseFlags, + reference: &str, + ) -> Result<(FlakeReference, String)> { + let mut ctx = Context::new(); + let mut r = result_string_init!(); + let mut ptr: *mut raw::flake_reference = std::ptr::null_mut(); + unsafe { + context::check_call!(raw::flake_reference_and_fragment_from_string( + &mut ctx, + fetch_settings.raw_ptr(), + flake_settings.ptr, + flags.ptr.as_ptr(), + reference.as_ptr() as *const i8, + reference.len(), + // pointer to ptr + &mut ptr, + Some(callback_get_result_string), + callback_get_result_string_data(&mut r) + )) + }?; + let ptr = NonNull::new(ptr) + .context("flake_reference_and_fragment_from_string unexpectedly returned null")?; + Ok((FlakeReference { ptr: ptr }, r?)) + } +} + +pub struct FlakeLockFlags { + pub(crate) ptr: *mut raw::flake_lock_flags, +} +impl Drop for FlakeLockFlags { + fn drop(&mut self) { + unsafe { + raw::flake_lock_flags_free(self.ptr); + } + } +} +impl FlakeLockFlags { + pub fn new(settings: &FlakeSettings) -> Result { + let mut ctx = Context::new(); + let s = unsafe { context::check_call!(raw::flake_lock_flags_new(&mut ctx, settings.ptr)) }?; + Ok(FlakeLockFlags { ptr: s }) + } +} + +pub struct LockedFlake { + pub(crate) ptr: NonNull, +} +impl Drop for LockedFlake { + fn drop(&mut self) { + unsafe { + raw::locked_flake_free(self.ptr.as_ptr()); + } + } +} +impl LockedFlake { + pub fn lock( + fetch_settings: &FetchersSettings, + flake_settings: &FlakeSettings, + eval_state: &EvalState, + flags: &FlakeLockFlags, + flake_ref: &FlakeReference, + ) -> Result { + let mut ctx = Context::new(); + let ptr = unsafe { + context::check_call!(raw::flake_lock( + &mut ctx, + fetch_settings.raw_ptr(), + flake_settings.ptr, + eval_state.raw_ptr(), + flags.ptr, + flake_ref.ptr.as_ptr() + )) + }?; + let ptr = NonNull::new(ptr).context("flake_lock unexpectedly returned null")?; + Ok(LockedFlake { ptr }) + } + + pub fn outputs( + &self, + flake_settings: &FlakeSettings, + eval_state: &mut EvalState, + ) -> Result { + let mut ctx = Context::new(); + unsafe { + let r = context::check_call!(raw::locked_flake_get_output_attrs( + &mut ctx, + flake_settings.ptr, + eval_state.raw_ptr(), + self.ptr.as_ptr() + ))?; + Ok(nix_expr::value::__private::raw_value_new(r)) + } + } +} + #[cfg(test)] mod tests { use nix_expr::eval_state::{gc_register_my_thread, EvalStateBuilder}; @@ -80,4 +231,67 @@ mod tests { drop(gc_registration); } + + #[test] + fn flake_lock_load_flake() { + init(); + let gc_registration = gc_register_my_thread(); + let store = Store::open(None, []).unwrap(); + let flake_settings = FlakeSettings::new().unwrap(); + let mut eval_state = EvalStateBuilder::new(store) + .unwrap() + .flakes(&flake_settings) + .unwrap() + .build() + .unwrap(); + + let tmp_dir = tempfile::tempdir().unwrap(); + + // Create flake.nix + let flake_nix = tmp_dir.path().join("flake.nix"); + std::fs::write( + &flake_nix, + r#" +{ + outputs = { ... }: { + hello = "potato"; + }; +} + "#, + ) + .unwrap(); + + let flake_lock_flags = FlakeLockFlags::new(&flake_settings).unwrap(); + + let (flake_ref, fragment) = FlakeReference::parse_with_fragment( + &FetchersSettings::new().unwrap(), + &flake_settings, + &FlakeReferenceParseFlags::new(&flake_settings).unwrap(), + &format!("path:{}#subthing", tmp_dir.path().display()), + ) + .unwrap(); + + assert_eq!(fragment, "subthing"); + + let locked_flake = LockedFlake::lock( + &FetchersSettings::new().unwrap(), + &flake_settings, + &eval_state, + &flake_lock_flags, + &flake_ref, + ) + .unwrap(); + + let outputs = locked_flake + .outputs(&flake_settings, &mut eval_state) + .unwrap(); + + let hello = eval_state.require_attrs_select(&outputs, &"hello").unwrap(); + let hello = eval_state.require_string(&hello).unwrap(); + + assert_eq!(hello, "potato"); + + drop(tmp_dir); + drop(gc_registration); + } }