From 88b575b8cad0e52f880b7a2c053a7286fdafe3db Mon Sep 17 00:00:00 2001 From: _cry64 Date: Thu, 19 Mar 2026 00:39:44 +1000 Subject: [PATCH] idk, like a bunch --- nixide/src/context.rs | 196 +++++++-- nixide/src/error.rs | 42 +- nixide/src/expr/evalstate.rs | 22 +- nixide/src/expr/evalstatebuilder.rs | 18 +- nixide/src/expr/tests.rs | 12 +- nixide/src/expr/value.rs | 34 +- nixide/src/flake/eval_state_builder_ext.rs | 17 + nixide/src/flake/fetchers_settings.rs | 42 ++ nixide/src/flake/flake_lock_flags.rs | 61 +++ nixide/src/flake/flake_reference.rs | 43 ++ .../src/flake/flake_reference_parse_flags.rs | 45 ++ nixide/src/flake/flake_settings.rs | 52 +++ nixide/src/flake/flakeref.rs | 3 + nixide/src/flake/locked_flake.rs | 409 ++++++++++++++++++ nixide/src/flake/mod.rs | 15 + nixide/src/lib.rs | 20 +- nixide/src/store/mod.rs | 36 +- nixide/src/store/path.rs | 18 +- nixide/src/store/tests.rs | 6 +- nixide/src/util/bindings.rs | 16 +- nixide/src/verbosity.rs | 34 ++ 21 files changed, 1013 insertions(+), 128 deletions(-) create mode 100644 nixide/src/flake/eval_state_builder_ext.rs create mode 100644 nixide/src/flake/fetchers_settings.rs create mode 100644 nixide/src/flake/flake_lock_flags.rs create mode 100644 nixide/src/flake/flake_reference.rs create mode 100644 nixide/src/flake/flake_reference_parse_flags.rs create mode 100644 nixide/src/flake/flake_settings.rs create mode 100644 nixide/src/flake/flakeref.rs create mode 100644 nixide/src/flake/locked_flake.rs create mode 100644 nixide/src/flake/mod.rs create mode 100644 nixide/src/verbosity.rs diff --git a/nixide/src/context.rs b/nixide/src/context.rs index 189a246..2db8f8e 100644 --- a/nixide/src/context.rs +++ b/nixide/src/context.rs @@ -1,17 +1,56 @@ -use std::ptr::NonNull; +use std::ffi::{c_char, c_uint, CStr, CString}; +use std::ptr::{null_mut, NonNull}; -use crate::error::NixError; -use nixide_sys as sys; +use crate::error::NixErrorCode; +use crate::sys; +use crate::util::bindings::wrap_libnix_string_callback; -/// Nix context for managing library state. +// XXX: TODO: change this to a `Result` +type NixResult = Result; + +pub struct NixError { + pub code: NixErrorCode, + pub name: String, + pub msg: Option, + pub info_msg: Option, +} + +/// This object stores error state. /// -/// This is the root object for all Nix operations. It manages the lifetime -/// of the Nix C API context and provides automatic cleanup. -pub struct Context { +/// Passed as a first parameter to functions that can fail, to store error +/// information. +/// +/// # Warning +/// +/// These can be reused between different function calls, +/// but make sure not to use them for multiple calls simultaneously +/// (which can happen in callbacks). +/// +/// # `libnix` API Internals +/// +/// ```cpp +/// struct nix_c_context +/// { +/// nix_err last_err_code = NIX_OK; +/// /* WARNING: The last error message. Always check last_err_code. +/// WARNING: This may not have been cleared, so that clearing is fast. */ +/// std::optional last_err = {}; +/// std::optional info = {}; +/// std::string name = ""; +/// }; +/// ``` +/// +/// The [sys::nix_c_context] struct is laid out so that it can also be +/// cast to a [sys::nix_err] to inspect directly: +/// ```c +/// assert(*(nix_err*)ctx == NIX_OK); +/// ``` +/// +pub struct ErrorContext { inner: NonNull, } -impl Context { +impl ErrorContext { /// Create a new Nix context. /// /// This initializes the Nix C API context and the required libraries. @@ -19,30 +58,31 @@ impl Context { /// # Errors /// /// Returns an error if context creation or library initialization fails. - pub fn new() -> Result { + pub fn new() -> Result { // SAFETY: nix_c_context_create is safe to call let ctx_ptr = unsafe { sys::nix_c_context_create() }; - let ctx = Context { - inner: NonNull::new(ctx_ptr).ok_or(NixError::NullPtr { + let ctx = ErrorContext { + inner: NonNull::new(ctx_ptr).ok_or(NixErrorCode::NullPtr { location: "nix_c_context_create", })?, }; // Initialize required libraries - unsafe { - NixError::from( - sys::nix_libutil_init(ctx.inner.as_ptr()), - "nix_libutil_init", - )?; - NixError::from( - sys::nix_libstore_init(ctx.inner.as_ptr()), - "nix_libstore_init", - )?; - NixError::from( - sys::nix_libexpr_init(ctx.inner.as_ptr()), - "nix_libexpr_init", - )?; - }; + // XXX: TODO: move this to a separate init function (maybe a Nix::init() function) + // unsafe { + // NixErrorCode::from( + // sys::nix_libutil_init(ctx.inner.as_ptr()), + // "nix_libutil_init", + // )?; + // NixErrorCode::from( + // sys::nix_libstore_init(ctx.inner.as_ptr()), + // "nix_libstore_init", + // )?; + // NixErrorCode::from( + // sys::nix_libexpr_init(ctx.inner.as_ptr()), + // "nix_libexpr_init", + // )?; + // }; Ok(ctx) } @@ -51,13 +91,109 @@ impl Context { /// /// # Safety /// - /// The caller must ensure the pointer is used safely. - pub unsafe fn as_ptr(&self) -> *mut sys::nix_c_context { + /// Although this function isn't inherently `unsafe`, it is + /// marked as such intentionally to force calls to be wrapped + /// in `unsafe` blocks for clarity. + pub(crate) unsafe fn as_ptr(&self) -> *mut sys::nix_c_context { self.inner.as_ptr() } + + /// Check the error code and return an error if it's not `NIX_OK`. + /// + /// We recommend to use `check_call!` if possible. + pub fn peak(&self) -> Result<(), NixErrorCode> { + // NixError::from( unsafe { sys::nix_err_code(self.as_ptr())}, ""); + + let err = unsafe { sys::nix_err_code(self.inner.as_ptr()) }; + if err != sys::nix_err_NIX_OK { + // msgp is a borrowed pointer (pointing into the context), so we don't need to free it + let msgp = unsafe { sys::nix_err_msg(null_mut(), self.inner.as_ptr(), null_mut()) }; + // Turn the i8 pointer into a Rust string by copying + let msg: &str = unsafe { core::ffi::CStr::from_ptr(msgp).to_str()? }; + bail!("{}", msg); + } + Ok(()) + } + + pub fn pop(&mut self) -> Result<(), NixErrorCode> { + let result = self.peak(); + if result.is_err() { + self.clear(); + } + result + } + + pub fn clear(&mut self) { + unsafe { + // NOTE: previous nixops4 used the line: (maybe for compatability with old versions?) + // sys::nix_set_err_msg(self.inner.as_ptr(), sys::nix_err_NIX_OK, c"".as_ptr()); + sys::nix_clear_err(self.as_ptr()); + } + } + + /// + /// Never fails + pub(crate) fn get_code(&self) -> Result<(), NixErrorCode> { + NixErrorCode::from(unsafe { sys::nix_err_code(self.as_ptr()) }, "nix_err_code") + } + + pub(crate) fn get_name(&self, result: NixResult<()>) -> Option { + match result { + Err(code) => unsafe { + let ctx = null_mut(); + wrap_libnix_string_callback("nix_err_name", |callback, user_data| { + sys::nix_err_name(ctx, self.as_ptr(), Some(callback), user_data) + }) + }, + Ok(_) => None, + } + } + + /// # Note + /// On failure [sys::nix_err_name] does the following if the error + /// has the error code [sys::nix_err_NIX_OK]: + /// ```c + /// nix_set_err_msg(context, NIX_ERR_UNKNOWN, "No error message"); + /// return nullptr; + /// ``` + /// Hence we can just test whether the returned pointer is a `NULL` pointer, + /// and avoid passing in a [sys::nix_c_context] struct. + pub(crate) fn get_msg(&self, result: NixResult<()>) -> Option { + match result { + Err(_) => unsafe { + let ctx = null_mut(); + let msg_ptr: *const c_char = sys::nix_err_msg(ctx, self.as_ptr(), null_mut()); + + if msg_ptr.is_null() { + return None; + } + + match CStr::from_ptr(msg_ptr).to_str() { + Ok(msg_str) => Some(msg_str.to_string()), + Err(_) => None, + } + }, + Ok(_) => None, + } + } + + pub(crate) fn get_info_msg(&self) -> Option {} + + pub fn check_one_call_or_key_none(&mut self, f: F) -> Result, NixErrorCode> + where + F: FnOnce(*mut sys::nix_c_context) -> T, + { + let t = f(unsafe { self.as_ptr() }); + if unsafe { sys::nix_err_code(self.inner.as_ptr()) == sys::nix_err_NIX_ERR_KEY } { + self.clear(); + return Ok(None); + } + self.pop()?; + Ok(Some(t)) + } } -impl Drop for Context { +impl Drop for ErrorContext { fn drop(&mut self) { // SAFETY: We own the context and it's valid until drop unsafe { @@ -65,7 +201,3 @@ impl Drop for Context { } } } - -// SAFETY: Context can be shared between threads -unsafe impl Send for Context {} -unsafe impl Sync for Context {} diff --git a/nixide/src/error.rs b/nixide/src/error.rs index 0e2de14..1d0ce87 100644 --- a/nixide/src/error.rs +++ b/nixide/src/error.rs @@ -7,7 +7,7 @@ use crate::sys; /// Standard (nix_err) and some additional error codes /// produced by the libnix C API. #[derive(Debug)] -pub enum NixError { +pub enum NixErrorCode { /// A generic Nix error occurred. /// /// # Reason @@ -95,24 +95,24 @@ pub enum NixError { }, } -impl NixError { - pub fn from(err_code: sys::nix_err, location: &'static str) -> Result<(), NixError> { +impl NixErrorCode { + pub fn from(err_code: sys::nix_err, location: &'static str) -> Result<(), NixErrorCode> { #[allow(nonstandard_style)] match err_code { sys::nix_err_NIX_OK => Ok(()), - sys::nix_err_NIX_ERR_OVERFLOW => Err(NixError::Overflow { location }), - sys::nix_err_NIX_ERR_KEY => Err(NixError::KeyNotFound { + sys::nix_err_NIX_ERR_OVERFLOW => Err(NixErrorCode::Overflow { location }), + sys::nix_err_NIX_ERR_KEY => Err(NixErrorCode::KeyNotFound { location, key: None, }), - sys::nix_err_NIX_ERR_NIX_ERROR => Err(NixError::NixError { location }), + sys::nix_err_NIX_ERR_NIX_ERROR => Err(NixErrorCode::NixError { location }), - sys::nix_err_NIX_ERR_UNKNOWN => Err(NixError::Unknown { + sys::nix_err_NIX_ERR_UNKNOWN => Err(NixErrorCode::Unknown { location, reason: "Unknown error occurred".to_string(), }), - _ => Err(NixError::Undocumented { location, err_code }), + _ => Err(NixErrorCode::Undocumented { location, err_code }), } } @@ -120,29 +120,29 @@ impl NixError { result: Result, location: &'static str, ) -> Result { - result.or(Err(NixError::NulError { location })) + result.or(Err(NixErrorCode::NulError { location })) } pub fn new_nonnull(ptr: *mut T, location: &'static str) -> Result, Self> where T: Sized, { - NonNull::new(ptr).ok_or(NixError::NullPtr { location }) + NonNull::new(ptr).ok_or(NixErrorCode::NullPtr { location }) } } -impl std::error::Error for NixError {} +impl std::error::Error for NixErrorCode {} -impl Display for NixError { +impl Display for NixErrorCode { fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { let msg = match self { - NixError::NixError { location } => { + NixErrorCode::NixError { location } => { format!("[libnix] Generic error (at location `{location}`)") } - NixError::Overflow { location } => { + NixErrorCode::Overflow { location } => { format!("[libnix] Overflow error (at location `{location}`)") } - NixError::KeyNotFound { location, key } => format!( + NixErrorCode::KeyNotFound { location, key } => format!( "[libnix] Key not found {} (at location `{location}`)", match key { Some(key) => format!("`{key}`"), @@ -150,26 +150,26 @@ impl Display for NixError { } ), - NixError::Unknown { location, reason } => { + NixErrorCode::Unknown { location, reason } => { format!("Unknown error \"{reason}\" (at location `{location}`)") } - NixError::Undocumented { location, err_code } => { + NixErrorCode::Undocumented { location, err_code } => { format!( "[libnix] An undocumented nix_err was returned with {err_code} (at location `{location}`)" ) } - NixError::NulError { location } => { + NixErrorCode::NulError { location } => { format!("Nul error (at location `{location}`)") } - NixError::NullPtr { location } => { + NixErrorCode::NullPtr { location } => { format!("[libnix] Null pointer (at location `{location}`)") } - NixError::InvalidArg { location, reason } => { + NixErrorCode::InvalidArg { location, reason } => { format!("Invalid argument \"{reason}\" (at location `{location}`)") } - NixError::InvalidType { + NixErrorCode::InvalidType { location, expected, got, diff --git a/nixide/src/expr/evalstate.rs b/nixide/src/expr/evalstate.rs index b0f0220..ded2a10 100644 --- a/nixide/src/expr/evalstate.rs +++ b/nixide/src/expr/evalstate.rs @@ -4,7 +4,7 @@ use std::sync::Arc; use super::Value; use crate::sys; -use crate::{Context, NixError, Store}; +use crate::{ErrorContext, NixErrorCode, Store}; /// Nix evaluation state for evaluating expressions. /// @@ -14,7 +14,7 @@ pub struct EvalState { inner: NonNull, #[allow(dead_code)] store: Arc, - pub(super) context: Arc, + pub(super) context: Arc, } impl EvalState { @@ -22,7 +22,7 @@ impl EvalState { pub(super) fn new( inner: NonNull, store: Arc, - context: Arc, + context: Arc, ) -> Self { Self { inner, @@ -41,24 +41,24 @@ impl EvalState { /// # Errors /// /// Returns an error if evaluation fails. - pub fn eval_from_string(&self, expr: &str, path: &str) -> Result, NixError> { + pub fn eval_from_string(&self, expr: &str, path: &str) -> Result, NixErrorCode> { let expr_c = - NixError::from_nulerror(CString::new(expr), "nixide::EvalState::eval_from_string")?; + NixErrorCode::from_nulerror(CString::new(expr), "nixide::EvalState::eval_from_string")?; let path_c = - NixError::from_nulerror(CString::new(path), "nixide::EvalState::eval_from_string")?; + NixErrorCode::from_nulerror(CString::new(path), "nixide::EvalState::eval_from_string")?; // Allocate value for result // SAFETY: context and state are valid let value_ptr = unsafe { sys::nix_alloc_value(self.context.as_ptr(), self.inner.as_ptr()) }; if value_ptr.is_null() { - return Err(NixError::NullPtr { + return Err(NixErrorCode::NullPtr { location: "nix_alloc_value", }); } // Evaluate expression // SAFETY: all pointers are valid - NixError::from( + NixErrorCode::from( unsafe { sys::nix_expr_eval_from_string( self.context.as_ptr(), @@ -71,7 +71,7 @@ impl EvalState { "nix_expr_eval_from_string", )?; - let inner = NonNull::new(value_ptr).ok_or(NixError::NullPtr { + let inner = NonNull::new(value_ptr).ok_or(NixErrorCode::NullPtr { location: "nix_expr_eval_from_string", })?; @@ -83,10 +83,10 @@ impl EvalState { /// # Errors /// /// Returns an error if value allocation fails. - pub fn alloc_value(&self) -> Result, NixError> { + pub fn alloc_value(&self) -> Result, NixErrorCode> { // SAFETY: context and state are valid let value_ptr = unsafe { sys::nix_alloc_value(self.context.as_ptr(), self.inner.as_ptr()) }; - let inner = NonNull::new(value_ptr).ok_or(NixError::NullPtr { + let inner = NonNull::new(value_ptr).ok_or(NixErrorCode::NullPtr { location: "nix_alloc_value", })?; diff --git a/nixide/src/expr/evalstatebuilder.rs b/nixide/src/expr/evalstatebuilder.rs index 6ca906c..ef417db 100644 --- a/nixide/src/expr/evalstatebuilder.rs +++ b/nixide/src/expr/evalstatebuilder.rs @@ -3,7 +3,7 @@ use std::sync::Arc; use super::EvalState; use crate::sys; -use crate::{Context, NixError, Store}; +use crate::{ErrorContext, NixErrorCode, Store}; /// Builder for Nix evaluation state. /// @@ -12,7 +12,7 @@ use crate::{Context, NixError, Store}; pub struct EvalStateBuilder { inner: NonNull, store: Arc, - context: Arc, + context: Arc, } impl EvalStateBuilder { @@ -25,12 +25,12 @@ impl EvalStateBuilder { /// # Errors /// /// Returns an error if the builder cannot be created. - pub fn new(store: &Arc) -> Result { + pub fn new(store: &Arc) -> Result { // SAFETY: store context and store are valid let builder_ptr = unsafe { sys::nix_eval_state_builder_new(store._context.as_ptr(), store.as_ptr()) }; - let inner = NonNull::new(builder_ptr).ok_or(NixError::NullPtr { + let inner = NonNull::new(builder_ptr).ok_or(NixErrorCode::NullPtr { location: "nix_eval_state_builder_new", })?; @@ -46,10 +46,10 @@ impl EvalStateBuilder { /// # Errors /// /// Returns an error if the evaluation state cannot be built. - pub fn build(self) -> Result { + pub fn build(self) -> Result { // Load configuration first // SAFETY: context and builder are valid - NixError::from( + NixErrorCode::from( unsafe { sys::nix_eval_state_builder_load(self.context.as_ptr(), self.inner.as_ptr()) }, "nix_eval_state_builder_load", )?; @@ -59,7 +59,7 @@ impl EvalStateBuilder { let state_ptr = unsafe { sys::nix_eval_state_build(self.context.as_ptr(), self.inner.as_ptr()) }; - let inner = NonNull::new(state_ptr).ok_or(NixError::NullPtr { + let inner = NonNull::new(state_ptr).ok_or(NixErrorCode::NullPtr { location: "nix_eval_state_build", })?; @@ -70,6 +70,10 @@ impl EvalStateBuilder { self.context.clone(), )) } + + pub(crate) unsafe fn as_ptr(&self) -> *mut sys::nix_eval_state_builder { + self.inner.as_ptr() + } } impl Drop for EvalStateBuilder { diff --git a/nixide/src/expr/tests.rs b/nixide/src/expr/tests.rs index 688824d..054fce9 100644 --- a/nixide/src/expr/tests.rs +++ b/nixide/src/expr/tests.rs @@ -3,19 +3,19 @@ use std::sync::Arc; use serial_test::serial; use super::{EvalStateBuilder, ValueType}; -use crate::{Context, Store}; +use crate::{ErrorContext, Store}; #[test] #[serial] fn test_context_creation() { - let _ctx = Context::new().expect("Failed to create context"); + let _ctx = ErrorContext::new().expect("Failed to create context"); // Context should be dropped automatically } #[test] #[serial] fn test_eval_state_builder() { - let ctx = Arc::new(Context::new().expect("Failed to create context")); + let ctx = Arc::new(ErrorContext::new().expect("Failed to create context")); let store = Arc::new(Store::open(&ctx, None).expect("Failed to open store")); let _state = EvalStateBuilder::new(&store) .expect("Failed to create builder") @@ -27,7 +27,7 @@ fn test_eval_state_builder() { #[test] #[serial] fn test_simple_evaluation() { - let ctx = Arc::new(Context::new().expect("Failed to create context")); + let ctx = Arc::new(ErrorContext::new().expect("Failed to create context")); let store = Arc::new(Store::open(&ctx, None).expect("Failed to open store")); let state = EvalStateBuilder::new(&store) .expect("Failed to create builder") @@ -45,7 +45,7 @@ fn test_simple_evaluation() { #[test] #[serial] fn test_value_types() { - let ctx = Arc::new(Context::new().expect("Failed to create context")); + let ctx = Arc::new(ErrorContext::new().expect("Failed to create context")); let store = Arc::new(Store::open(&ctx, None).expect("Failed to open store")); let state = EvalStateBuilder::new(&store) .expect("Failed to create builder") @@ -77,7 +77,7 @@ fn test_value_types() { #[test] #[serial] fn test_value_formatting() { - let ctx = Arc::new(Context::new().expect("Failed to create context")); + let ctx = Arc::new(ErrorContext::new().expect("Failed to create context")); let store = Arc::new(Store::open(&ctx, None).expect("Failed to open store")); let state = EvalStateBuilder::new(&store) .expect("Failed to create builder") diff --git a/nixide/src/expr/value.rs b/nixide/src/expr/value.rs index fce1b1b..32b6220 100644 --- a/nixide/src/expr/value.rs +++ b/nixide/src/expr/value.rs @@ -3,7 +3,7 @@ use std::ptr::NonNull; use super::{EvalState, ValueType}; use crate::sys; -use crate::NixError; +use crate::NixErrorCode; /// A Nix value /// @@ -22,8 +22,8 @@ impl Value<'_> { /// # Errors /// /// Returns an error if evaluation fails. - pub fn force(&mut self) -> Result<(), NixError> { - NixError::from( + pub fn force(&mut self) -> Result<(), NixErrorCode> { + NixErrorCode::from( // SAFETY: context, state, and value are valid unsafe { sys::nix_value_force( @@ -43,8 +43,8 @@ impl Value<'_> { /// # Errors /// /// Returns an error if evaluation fails. - pub fn force_deep(&mut self) -> Result<(), NixError> { - NixError::from( + pub fn force_deep(&mut self) -> Result<(), NixErrorCode> { + NixErrorCode::from( // SAFETY: context, state, and value are valid unsafe { sys::nix_value_force_deep( @@ -70,9 +70,9 @@ impl Value<'_> { /// # Errors /// /// Returns an error if the value is not an integer. - pub fn as_int(&self) -> Result { + pub fn as_int(&self) -> Result { if self.value_type() != ValueType::Int { - return Err(NixError::InvalidType { + return Err(NixErrorCode::InvalidType { location: "nixide::Value::as_int", expected: "int", got: self.value_type().to_string(), @@ -90,9 +90,9 @@ impl Value<'_> { /// # Errors /// /// Returns an error if the value is not a float. - pub fn as_float(&self) -> Result { + pub fn as_float(&self) -> Result { if self.value_type() != ValueType::Float { - return Err(NixError::InvalidType { + return Err(NixErrorCode::InvalidType { location: "nixide::Value::as_float", expected: "float", got: self.value_type().to_string(), @@ -111,9 +111,9 @@ impl Value<'_> { /// # Errors /// /// Returns an error if the value is not a boolean. - pub fn as_bool(&self) -> Result { + pub fn as_bool(&self) -> Result { if self.value_type() != ValueType::Bool { - return Err(NixError::InvalidType { + return Err(NixErrorCode::InvalidType { location: "nixide::Value::as_bool", expected: "bool", got: self.value_type().to_string(), @@ -131,9 +131,9 @@ impl Value<'_> { /// # Errors /// /// Returns an error if the value is not a string. - pub fn as_string(&self) -> Result { + pub fn as_string(&self) -> Result { if self.value_type() != ValueType::String { - return Err(NixError::InvalidType { + return Err(NixErrorCode::InvalidType { location: "nixide::Value::as_string", expected: "string", got: self.value_type().to_string(), @@ -152,7 +152,7 @@ impl Value<'_> { }; if realised_str.is_null() { - return Err(NixError::NullPtr { + return Err(NixErrorCode::NullPtr { location: "nix_string_realise", }); } @@ -165,7 +165,7 @@ impl Value<'_> { unsafe { sys::nix_realised_string_free(realised_str); } - return Err(NixError::NullPtr { + return Err(NixErrorCode::NullPtr { location: "nix_realised_string_free", }); } @@ -173,7 +173,7 @@ impl Value<'_> { // SAFETY: buffer_start is non-null and buffer_size gives us the length let bytes = unsafe { std::slice::from_raw_parts(buffer_start.cast::(), buffer_size) }; let string = std::str::from_utf8(bytes) - .map_err(|_| NixError::Unknown { + .map_err(|_| NixErrorCode::Unknown { location: "nixide::Value::as_string", reason: "Invalid UTF-8 in string".to_string(), })? @@ -206,7 +206,7 @@ impl Value<'_> { /// /// Returns an error if the value cannot be converted to a string /// representation. - pub fn to_nix_string(&self) -> Result { + pub fn to_nix_string(&self) -> Result { match self.value_type() { ValueType::Int => Ok(self.as_int()?.to_string()), ValueType::Float => Ok(self.as_float()?.to_string()), diff --git a/nixide/src/flake/eval_state_builder_ext.rs b/nixide/src/flake/eval_state_builder_ext.rs new file mode 100644 index 0000000..28d095e --- /dev/null +++ b/nixide/src/flake/eval_state_builder_ext.rs @@ -0,0 +1,17 @@ +pub trait EvalStateBuilderExt { + /// Configures the eval state to provide flakes features such as `builtins.getFlake`. + fn flakes( + self, + settings: &FlakeSettings, + ) -> Result; +} +impl EvalStateBuilderExt for nix_bindings_expr::eval_state::EvalStateBuilder { + /// Configures the eval state to provide flakes features such as `builtins.getFlake`. + fn flakes( + mut self, + settings: &FlakeSettings, + ) -> Result { + settings.add_to_eval_state_builder(&mut self)?; + Ok(self) + } +} diff --git a/nixide/src/flake/fetchers_settings.rs b/nixide/src/flake/fetchers_settings.rs new file mode 100644 index 0000000..8332695 --- /dev/null +++ b/nixide/src/flake/fetchers_settings.rs @@ -0,0 +1,42 @@ +use std::ptr::NonNull; + +use crate::sys; +use crate::{ErrorContext, NixErrorCode}; + +pub(super) struct FetchersSettings { + pub(super) ptr: NonNull, +} + +impl FetchersSettings { + pub fn new() -> Result { + let ctx = ErrorContext::new()?; + let ptr = unsafe { sys::nix_fetchers_settings_new(ctx.as_ptr()) }; + Ok(FetchersSettings { + ptr: NonNull::new(ptr).ok_or(NixErrorCode::NullPtr { + location: "fetchers_settings_new", + })?, + }) + } + + pub(crate) unsafe fn as_ptr(&self) -> *mut sys::nix_fetchers_settings { + self.ptr.as_ptr() + } +} + +impl Drop for FetchersSettings { + fn drop(&mut self) { + unsafe { + sys::nix_fetchers_settings_free(self.as_ptr()); + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn fetchers_settings_new() { + let _ = FetchersSettings::new().unwrap(); + } +} diff --git a/nixide/src/flake/flake_lock_flags.rs b/nixide/src/flake/flake_lock_flags.rs new file mode 100644 index 0000000..464cc41 --- /dev/null +++ b/nixide/src/flake/flake_lock_flags.rs @@ -0,0 +1,61 @@ +/// Parameters that affect the locking of a flake. +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 }) + } + /// Configures [LockedFlake::lock] to make incremental changes to the lock file as needed. Changes are written to file. + pub fn set_mode_write_as_needed(&mut self) -> Result<()> { + let mut ctx = Context::new(); + unsafe { + context::check_call!(raw::flake_lock_flags_set_mode_write_as_needed( + &mut ctx, self.ptr + )) + }?; + Ok(()) + } + /// Make [LockedFlake::lock] check if the lock file is up to date. If not, an error is returned. + pub fn set_mode_check(&mut self) -> Result<()> { + let mut ctx = Context::new(); + unsafe { context::check_call!(raw::flake_lock_flags_set_mode_check(&mut ctx, self.ptr)) }?; + Ok(()) + } + /// Like `set_mode_write_as_needed`, but does not write to the lock file. + pub fn set_mode_virtual(&mut self) -> Result<()> { + let mut ctx = Context::new(); + unsafe { + context::check_call!(raw::flake_lock_flags_set_mode_virtual(&mut ctx, self.ptr)) + }?; + Ok(()) + } + /// Adds an input override to the lock file that will be produced. The [LockedFlake::lock] operation will not write to the lock file. + pub fn add_input_override( + &mut self, + override_path: &str, + override_ref: &FlakeReference, + ) -> Result<()> { + let mut ctx = Context::new(); + unsafe { + context::check_call!(raw::flake_lock_flags_add_input_override( + &mut ctx, + self.ptr, + CString::new(override_path) + .context("Failed to create CString for override_path")? + .as_ptr(), + override_ref.ptr.as_ptr() + )) + }?; + Ok(()) + } +} diff --git a/nixide/src/flake/flake_reference.rs b/nixide/src/flake/flake_reference.rs new file mode 100644 index 0000000..09c31d8 --- /dev/null +++ b/nixide/src/flake/flake_reference.rs @@ -0,0 +1,43 @@ +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 { + /// 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 string will contain the fragment. + 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 c_char, + 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 }, r?)) + } +} diff --git a/nixide/src/flake/flake_reference_parse_flags.rs b/nixide/src/flake/flake_reference_parse_flags.rs new file mode 100644 index 0000000..2353d94 --- /dev/null +++ b/nixide/src/flake/flake_reference_parse_flags.rs @@ -0,0 +1,45 @@ +use std::ptr::NonNull; + +use super::FlakeSettings; +use crate::sys; +use crate::{ErrorContext, NixErrorCode}; + +/// Parameters for parsing a flake reference. +pub struct FlakeReferenceParseFlags { + pub(crate) ptr: NonNull, +} +impl Drop for FlakeReferenceParseFlags { + fn drop(&mut self) { + unsafe { + sys::nix_flake_reference_parse_flags_free(self.ptr.as_ptr()); + } + } +} +impl FlakeReferenceParseFlags { + pub fn new(settings: &FlakeSettings) -> Result { + let mut ctx = ErrorContext::new(); + let ptr = unsafe { + context::check_call!(sys::nix_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 }) + } + /// Sets the [base directory](https://nix.dev/manual/nix/latest/glossary#gloss-base-directory) + /// for resolving local flake references. + pub fn set_base_directory(&mut self, base_directory: &str) -> Result<(), NixErrorCode> { + let mut ctx = ErrorContext::new(); + unsafe { + sys::context::check_call!(sys::nix_flake_reference_parse_flags_set_base_directory( + &mut ctx, + self.ptr.as_ptr(), + base_directory.as_ptr() as *const c_char, + base_directory.len() + )) + }?; + Ok(()) + } +} diff --git a/nixide/src/flake/flake_settings.rs b/nixide/src/flake/flake_settings.rs new file mode 100644 index 0000000..3cc2082 --- /dev/null +++ b/nixide/src/flake/flake_settings.rs @@ -0,0 +1,52 @@ +use std::ptr::NonNull; + +use crate::sys; +use crate::{ErrorContext, EvalStateBuilder, NixErrorCode}; + +/// Store settings for the flakes feature. +pub struct FlakeSettings { + pub(crate) inner: NonNull, +} + +impl FlakeSettings { + pub fn new() -> Result { + let ctx = ErrorContext::new()?; + let inner = NonNull::new(unsafe { sys::nix_flake_settings_new(ctx.as_ptr()) }).ok_or( + NixErrorCode::NullPtr { + location: "nix_flake_settings_new", + }, + )?; + Ok(FlakeSettings { inner }) + } + + fn add_to_eval_state_builder( + &self, + builder: &mut EvalStateBuilder, + ) -> Result<(), NixErrorCode> { + let ctx = ErrorContext::new()?; + NixErrorCode::from( + unsafe { + sys::nix_flake_settings_add_to_eval_state_builder( + ctx.as_ptr(), + self.as_ptr(), + builder.as_ptr(), + ) + }, + "nix_flake_settings_add_to_eval_state_builder", + )?; + + Ok(()) + } + + pub(crate) unsafe fn as_ptr(&self) -> *mut sys::nix_flake_settings { + self.inner.as_ptr() + } +} + +impl Drop for FlakeSettings { + fn drop(&mut self) { + unsafe { + sys::nix_flake_settings_free(self.as_ptr()); + } + } +} diff --git a/nixide/src/flake/flakeref.rs b/nixide/src/flake/flakeref.rs new file mode 100644 index 0000000..7d6f719 --- /dev/null +++ b/nixide/src/flake/flakeref.rs @@ -0,0 +1,3 @@ +pub struct FlakeRef {} + +impl FlakeRef {} diff --git a/nixide/src/flake/locked_flake.rs b/nixide/src/flake/locked_flake.rs new file mode 100644 index 0000000..8348319 --- /dev/null +++ b/nixide/src/flake/locked_flake.rs @@ -0,0 +1,409 @@ +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 }) + } + + /// Returns the outputs of the flake - the result of calling the `outputs` attribute. + 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_bindings_expr::value::__private::raw_value_new(r)) + } + } +} + +#[cfg(test)] +mod tests { + use nix_bindings_expr::eval_state::{gc_register_my_thread, EvalStateBuilder}; + use nix_bindings_store::store::Store; + + use super::*; + use std::sync::Once; + + static INIT: Once = Once::new(); + + fn init() { + // Only set experimental-features once to minimize the window where + // concurrent Nix operations might read the setting while it's being modified + INIT.call_once(|| { + nix_bindings_expr::eval_state::init().unwrap(); + nix_bindings_util::settings::set("experimental-features", "flakes").unwrap(); + }); + } + + #[test] + fn flake_settings_getflake_exists() { + init(); + let gc_registration = gc_register_my_thread(); + let store = Store::open(None, []).unwrap(); + let mut eval_state = EvalStateBuilder::new(store) + .unwrap() + .flakes(&FlakeSettings::new().unwrap()) + .unwrap() + .build() + .unwrap(); + + let v = eval_state + .eval_from_string("builtins?getFlake", "") + .unwrap(); + + let b = eval_state.require_bool(&v).unwrap(); + + assert!(b); + + drop(gc_registration); + } + + #[test] + fn flake_lock_load_flake() { + init(); + let gc_registration = gc_register_my_thread(); + let store = Store::open(None, []).unwrap(); + let fetchers_settings = FetchersSettings::new().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( + &fetchers_settings, + &flake_settings, + &FlakeReferenceParseFlags::new(&flake_settings).unwrap(), + &format!("path:{}#subthing", tmp_dir.path().display()), + ) + .unwrap(); + + assert_eq!(fragment, "subthing"); + + let locked_flake = LockedFlake::lock( + &fetchers_settings, + &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(fetchers_settings); + drop(tmp_dir); + drop(gc_registration); + } + + #[test] + fn flake_lock_load_flake_with_flags() { + init(); + let gc_registration = gc_register_my_thread(); + let store = Store::open(None, []).unwrap(); + let fetchers_settings = FetchersSettings::new().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(); + + let flake_dir_a = tmp_dir.path().join("a"); + let flake_dir_b = tmp_dir.path().join("b"); + let flake_dir_c = tmp_dir.path().join("c"); + + std::fs::create_dir_all(&flake_dir_a).unwrap(); + std::fs::create_dir_all(&flake_dir_b).unwrap(); + std::fs::create_dir_all(&flake_dir_c).unwrap(); + + let flake_dir_a_str = flake_dir_a.to_str().unwrap(); + let flake_dir_c_str = flake_dir_c.to_str().unwrap(); + assert!(!flake_dir_a_str.is_empty()); + assert!(!flake_dir_c_str.is_empty()); + + // a + std::fs::write( + tmp_dir.path().join("a/flake.nix"), + r#" + { + inputs.b.url = "@flake_dir_b@"; + outputs = { b, ... }: { + hello = b.hello; + }; + } + "# + .replace("@flake_dir_b@", flake_dir_b.to_str().unwrap()), + ) + .unwrap(); + + // b + std::fs::write( + tmp_dir.path().join("b/flake.nix"), + r#" + { + outputs = { ... }: { + hello = "BOB"; + }; + } + "#, + ) + .unwrap(); + + // c + std::fs::write( + tmp_dir.path().join("c/flake.nix"), + r#" + { + outputs = { ... }: { + hello = "Claire"; + }; + } + "#, + ) + .unwrap(); + + let mut flake_lock_flags = FlakeLockFlags::new(&flake_settings).unwrap(); + + let mut flake_reference_parse_flags = + FlakeReferenceParseFlags::new(&flake_settings).unwrap(); + + flake_reference_parse_flags + .set_base_directory(tmp_dir.path().to_str().unwrap()) + .unwrap(); + + let (flake_ref_a, fragment) = FlakeReference::parse_with_fragment( + &fetchers_settings, + &flake_settings, + &flake_reference_parse_flags, + &format!("path:{}", &flake_dir_a_str), + ) + .unwrap(); + + assert_eq!(fragment, ""); + + // Step 1: Do not update (check), fails + + flake_lock_flags.set_mode_check().unwrap(); + + let locked_flake = LockedFlake::lock( + &fetchers_settings, + &flake_settings, + &eval_state, + &flake_lock_flags, + &flake_ref_a, + ); + // Has not been locked and would need to write a lock file. + assert!(locked_flake.is_err()); + let saved_err = match locked_flake { + Ok(_) => panic!("Expected error, but got Ok"), + Err(e) => e, + }; + + // Step 2: Update but do not write, succeeds + flake_lock_flags.set_mode_virtual().unwrap(); + + let locked_flake = LockedFlake::lock( + &fetchers_settings, + &flake_settings, + &eval_state, + &flake_lock_flags, + &flake_ref_a, + ) + .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, "BOB"); + + // Step 3: The lock was not written, so Step 1 would fail again + + flake_lock_flags.set_mode_check().unwrap(); + + let locked_flake = LockedFlake::lock( + &fetchers_settings, + &flake_settings, + &eval_state, + &flake_lock_flags, + &flake_ref_a, + ); + // Has not been locked and would need to write a lock file. + assert!(locked_flake.is_err()); + match locked_flake { + Ok(_) => panic!("Expected error, but got Ok"), + Err(e) => { + assert_eq!(e.to_string(), saved_err.to_string()); + } + }; + + // Step 4: Update and write, succeeds + + flake_lock_flags.set_mode_write_as_needed().unwrap(); + + let locked_flake = LockedFlake::lock( + &fetchers_settings, + &flake_settings, + &eval_state, + &flake_lock_flags, + &flake_ref_a, + ) + .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, "BOB"); + + // Step 5: Lock was written, so Step 1 succeeds + + flake_lock_flags.set_mode_check().unwrap(); + + let locked_flake = LockedFlake::lock( + &fetchers_settings, + &flake_settings, + &eval_state, + &flake_lock_flags, + &flake_ref_a, + ) + .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, "BOB"); + + // Step 6: Lock with override, do not write + + // This shouldn't matter; write_as_needed will be overridden + flake_lock_flags.set_mode_write_as_needed().unwrap(); + + let (flake_ref_c, fragment) = FlakeReference::parse_with_fragment( + &fetchers_settings, + &flake_settings, + &flake_reference_parse_flags, + &format!("path:{}", &flake_dir_c_str), + ) + .unwrap(); + assert_eq!(fragment, ""); + + flake_lock_flags + .add_input_override("b", &flake_ref_c) + .unwrap(); + + let locked_flake = LockedFlake::lock( + &fetchers_settings, + &flake_settings, + &eval_state, + &flake_lock_flags, + &flake_ref_a, + ) + .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, "Claire"); + + // Can't delete overrides, so trash it + let mut flake_lock_flags = FlakeLockFlags::new(&flake_settings).unwrap(); + + // Step 7: Override was not written; lock still points to b + + flake_lock_flags.set_mode_check().unwrap(); + + let locked_flake = LockedFlake::lock( + &fetchers_settings, + &flake_settings, + &eval_state, + &flake_lock_flags, + &flake_ref_a, + ) + .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, "BOB"); + + drop(fetchers_settings); + drop(tmp_dir); + drop(gc_registration); + } +} diff --git a/nixide/src/flake/mod.rs b/nixide/src/flake/mod.rs new file mode 100644 index 0000000..b6a4133 --- /dev/null +++ b/nixide/src/flake/mod.rs @@ -0,0 +1,15 @@ +mod eval_state_builder_ext; +mod fetchers_settings; +mod flake_lock_flags; +mod flake_reference; +mod flake_reference_parse_flags; +mod flake_settings; +mod locked_flake; + +pub(self) use eval_state_builder_ext::EvalStateBuilderExt; +pub(self) use fetchers_settings::FetchersSettings; +pub(self) use flake_lock_flags::FlakeLockFlags; +pub(self) use flake_reference::FlakeReference; +pub(self) use flake_reference_parse_flags::FlakeReferenceParseFlags; +pub(self) use flake_settings::FlakeSettings; +pub(self) use locked_flake::LockedFlake; diff --git a/nixide/src/lib.rs b/nixide/src/lib.rs index 99cabca..3bb26ed 100644 --- a/nixide/src/lib.rs +++ b/nixide/src/lib.rs @@ -3,14 +3,28 @@ mod context; mod error; mod expr; +mod flake; mod store; pub mod util; +mod verbosity; mod version; -pub use context::Context; -pub use error::NixError; +pub use context::ErrorContext; +pub use error::NixErrorCode; pub use expr::{EvalState, EvalStateBuilder, Value, ValueType}; pub use store::{Store, StorePath}; -pub use version::*; +pub use verbosity::NixVerbosity; +pub use version::NixVersion; pub use nixide_sys as sys; + +/// Sets the verbosity level +/// +/// # Arguments +/// +/// * `context` - additional error context, used as an output +/// * `level` - verbosity level +pub fn set_verbosity() { + // nix_err nix_set_verbosity(nix_c_context * context, nix_verbosity level); + // XXX: TODO: (implement Context first) +} diff --git a/nixide/src/store/mod.rs b/nixide/src/store/mod.rs index 668f6b9..bbfe92e 100644 --- a/nixide/src/store/mod.rs +++ b/nixide/src/store/mod.rs @@ -23,7 +23,7 @@ use std::ptr::NonNull; use std::result::Result; use std::sync::Arc; -use super::{Context, NixError}; +use super::{ErrorContext, NixErrorCode}; use crate::util::bindings::{wrap_libnix_pathbuf_callback, wrap_libnix_string_callback}; use nixide_sys as sys; @@ -32,7 +32,7 @@ use nixide_sys as sys; /// The store provides access to Nix packages, derivations, and store paths. pub struct Store { pub(crate) inner: NonNull, - pub(crate) _context: Arc, + pub(crate) _context: Arc, } impl Store { @@ -46,10 +46,10 @@ impl Store { /// # Errors /// /// Returns an error if the store cannot be opened. - pub fn open(context: &Arc, uri: Option<&str>) -> Result { + pub fn open(context: &Arc, uri: Option<&str>) -> Result { let uri_cstring: CString; let uri_ptr = if let Some(uri) = uri { - uri_cstring = NixError::from_nulerror(CString::new(uri), "nixide::Store::open")?; + uri_cstring = NixErrorCode::from_nulerror(CString::new(uri), "nixide::Store::open")?; uri_cstring.as_ptr() } else { std::ptr::null() @@ -59,7 +59,7 @@ impl Store { let store_ptr = unsafe { sys::nix_store_open(context.as_ptr(), uri_ptr, std::ptr::null_mut()) }; - let inner = NonNull::new(store_ptr).ok_or(NixError::NullPtr { + let inner = NonNull::new(store_ptr).ok_or(NixErrorCode::NullPtr { location: "nix_store_open", })?; @@ -99,9 +99,13 @@ impl Store { &self, path: &StorePath, callback: fn(&str, &StorePath), - ) -> Result, NixError> { + ) -> Result, NixErrorCode> { // Type alias for our userdata: (outputs vector, context) - type Userdata = (Vec<(String, StorePath)>, Arc, fn(&str, &StorePath)); + type Userdata = ( + Vec<(String, StorePath)>, + Arc, + fn(&str, &StorePath), + ); // Callback function that will be called for each realized output unsafe extern "C" fn realise_callback( @@ -157,7 +161,7 @@ impl Store { ) }; - NixError::from(err, "nix_store_realise")?; + NixErrorCode::from(err, "nix_store_realise")?; // Return the collected outputs Ok(userdata.0) @@ -187,14 +191,14 @@ impl Store { /// # Ok(()) /// # } /// ``` - pub fn store_path(&self, path: &str) -> Result { + pub fn store_path(&self, path: &str) -> Result { StorePath::parse(&self._context, self, path) } /// Get the version of a Nix store /// /// If the store doesn't have a version (like the dummy store), returns None - pub fn version(&self) -> Result { + pub fn version(&self) -> Result { wrap_libnix_string_callback("nix_store_get_version", |callback, user_data| unsafe { sys::nix_store_get_version( self._context.as_ptr(), @@ -206,7 +210,7 @@ impl Store { } /// Get the URI of a Nix store - pub fn uri(&self) -> Result { + pub fn uri(&self) -> Result { wrap_libnix_string_callback("nix_store_get_uri", |callback, user_data| unsafe { sys::nix_store_get_uri( self._context.as_ptr(), @@ -217,7 +221,7 @@ impl Store { }) } - pub fn store_dir(&self) -> Result { + pub fn store_dir(&self) -> Result { wrap_libnix_pathbuf_callback("nix_store_get_storedir", |callback, user_data| unsafe { sys::nix_store_get_storedir( self._context.as_ptr(), @@ -232,7 +236,7 @@ impl Store { &self, dst_store: &Store, store_path: &StorePath, - ) -> Result<(), NixError> { + ) -> Result<(), NixErrorCode> { let err = unsafe { sys::nix_store_copy_closure( self._context.as_ptr(), @@ -241,14 +245,14 @@ impl Store { store_path.inner.as_ptr(), ) }; - NixError::from(err, "nix_store_copy_closure") + NixErrorCode::from(err, "nix_store_copy_closure") } pub fn copy_closure_from( &self, src_store: &Store, store_path: &StorePath, - ) -> Result<(), NixError> { + ) -> Result<(), NixErrorCode> { let err = unsafe { sys::nix_store_copy_closure( self._context.as_ptr(), @@ -257,7 +261,7 @@ impl Store { store_path.inner.as_ptr(), ) }; - NixError::from(err, "nix_store_copy_closure") + NixErrorCode::from(err, "nix_store_copy_closure") } } diff --git a/nixide/src/store/path.rs b/nixide/src/store/path.rs index 8b8b6e4..aecc896 100644 --- a/nixide/src/store/path.rs +++ b/nixide/src/store/path.rs @@ -5,7 +5,7 @@ use std::sync::Arc; use super::Store; use crate::util::bindings::{wrap_libnix_pathbuf_callback, wrap_libnix_string_callback}; -use crate::{Context, NixError}; +use crate::{ErrorContext, NixErrorCode}; use nixide_sys::{self as sys, nix_err_NIX_OK}; /// A path in the Nix store. @@ -13,7 +13,7 @@ use nixide_sys::{self as sys, nix_err_NIX_OK}; /// Represents a store path that can be realized, queried, or manipulated. pub struct StorePath { pub(crate) inner: NonNull, - pub(crate) _context: Arc, + pub(crate) _context: Arc, } impl StorePath { @@ -28,8 +28,12 @@ impl StorePath { /// # Errors /// /// Returns an error if the path cannot be parsed. - pub fn parse(context: &Arc, store: &Store, path: &str) -> Result { - let path_cstring = CString::new(path).or(Err(NixError::InvalidArg { + pub fn parse( + context: &Arc, + store: &Store, + path: &str, + ) -> Result { + let path_cstring = CString::new(path).or(Err(NixErrorCode::InvalidArg { location: "nixide::StorePath::parse", reason: "`path` contains NUL char", }))?; @@ -39,7 +43,7 @@ impl StorePath { sys::nix_store_parse_path(context.as_ptr(), store.as_ptr(), path_cstring.as_ptr()) }; - let inner = NonNull::new(path_ptr).ok_or(NixError::NullPtr { + let inner = NonNull::new(path_ptr).ok_or(NixErrorCode::NullPtr { location: "nix_store_parse_path", })?; @@ -57,7 +61,7 @@ impl StorePath { /// # Errors /// /// Returns an error if the name cannot be retrieved. - pub fn name(&self) -> Result { + pub fn name(&self) -> Result { wrap_libnix_string_callback("nix_store_path_name", |callback, user_data| unsafe { sys::nix_store_path_name(self.inner.as_ptr(), Some(callback), user_data); @@ -87,7 +91,7 @@ impl StorePath { /// /// * `store` - The store containing the path /// - pub fn real_path(&self, store: &Store) -> Result { + pub fn real_path(&self, store: &Store) -> Result { wrap_libnix_pathbuf_callback("nix_store_real_path", |callback, user_data| unsafe { sys::nix_store_real_path( self._context.as_ptr(), diff --git a/nixide/src/store/tests.rs b/nixide/src/store/tests.rs index e00c3c9..4fdef06 100644 --- a/nixide/src/store/tests.rs +++ b/nixide/src/store/tests.rs @@ -5,14 +5,14 @@ use super::*; #[test] #[serial] fn test_store_opening() { - let ctx = Arc::new(Context::new().expect("Failed to create context")); + let ctx = Arc::new(ErrorContext::new().expect("Failed to create context")); let _store = Store::open(&ctx, None).expect("Failed to open store"); } #[test] #[serial] fn test_store_path_parse() { - let ctx = Arc::new(Context::new().expect("Failed to create context")); + let ctx = Arc::new(ErrorContext::new().expect("Failed to create context")); let store = Store::open(&ctx, None).expect("Failed to open store"); // Try parsing a well-formed store path @@ -38,7 +38,7 @@ fn test_store_path_parse() { #[test] #[serial] fn test_store_path_clone() { - let ctx = Arc::new(Context::new().expect("Failed to create context")); + let ctx = Arc::new(ErrorContext::new().expect("Failed to create context")); let store = Store::open(&ctx, None).expect("Failed to open store"); // Try to get a valid store path by parsing diff --git a/nixide/src/util/bindings.rs b/nixide/src/util/bindings.rs index ea9b45f..cd8b4f8 100644 --- a/nixide/src/util/bindings.rs +++ b/nixide/src/util/bindings.rs @@ -1,9 +1,12 @@ use std::os::raw::{c_char, c_uint, c_void}; use std::path::PathBuf; -use crate::NixError; +use crate::NixErrorCode; -pub fn wrap_libnix_string_callback(name: &'static str, callback: F) -> Result +pub fn wrap_libnix_string_callback( + name: &'static str, + callback: F, +) -> Result where F: FnOnce(unsafe extern "C" fn(*const c_char, c_uint, *mut c_void), *mut c_void) -> i32, { @@ -22,11 +25,14 @@ where let mut result: Option = None; let user_data = &mut result as *mut _ as *mut c_void; - NixError::from(callback(wrapper_callback, user_data), name)?; - result.ok_or(NixError::NullPtr { location: name }) + NixErrorCode::from(callback(wrapper_callback, user_data), name)?; + result.ok_or(NixErrorCode::NullPtr { location: name }) } -pub fn wrap_libnix_pathbuf_callback(name: &'static str, callback: F) -> Result +pub fn wrap_libnix_pathbuf_callback( + name: &'static str, + callback: F, +) -> Result where F: FnOnce(unsafe extern "C" fn(*const c_char, c_uint, *mut c_void), *mut c_void) -> i32, { diff --git a/nixide/src/verbosity.rs b/nixide/src/verbosity.rs new file mode 100644 index 0000000..fb1918b --- /dev/null +++ b/nixide/src/verbosity.rs @@ -0,0 +1,34 @@ +use crate::sys; + +/// Verbosity level +/// +/// # NOTE +/// +/// This should be kept in sync with the C++ implementation (nix::Verbosity) +#[derive(Debug, Clone, Copy)] +pub enum NixVerbosity { + Error, + Warn, + Notice, + Info, + Talkative, + Chatty, + Debug, + Vomit, +} + +impl From for NixVerbosity { + fn from(level: sys::nix_verbosity) -> NixVerbosity { + match level { + sys::nix_verbosity_NIX_LVL_ERROR => NixVerbosity::Error, + sys::nix_verbosity_NIX_LVL_WARN => NixVerbosity::Warn, + sys::nix_verbosity_NIX_LVL_NOTICE => NixVerbosity::Notice, + sys::nix_verbosity_NIX_LVL_INFO => NixVerbosity::Info, + sys::nix_verbosity_NIX_LVL_TALKATIVE => NixVerbosity::Talkative, + sys::nix_verbosity_NIX_LVL_CHATTY => NixVerbosity::Chatty, + sys::nix_verbosity_NIX_LVL_DEBUG => NixVerbosity::Debug, + sys::nix_verbosity_NIX_LVL_VOMIT => NixVerbosity::Vomit, + _ => panic!("nixide encountered unknown `nix_verbosity` value, please submit this as an issue at https://github.com/cry128/nixide"), + } + } +}