2024-11-27 14:13:54 +01:00
use anyhow ::{ bail , Error , Result } ;
2024-02-22 22:32:50 +01:00
use lazy_static ::lazy_static ;
2025-10-04 02:44:22 +02:00
use nix_bindings_bindgen_raw as raw ;
use nix_bindings_util ::context ::Context ;
2025-12-05 16:51:15 -05:00
use nix_bindings_util ::string_return ::{
callback_get_result_string , callback_get_result_string_data ,
} ;
2025-10-04 02:44:22 +02:00
use nix_bindings_util ::{ check_call , result_string_init } ;
2025-12-09 22:14:02 +01:00
#[ cfg(nix_at_least = " 2.33.0pre " ) ]
2025-10-21 23:18:33 +02:00
use std ::collections ::BTreeMap ;
2025-12-05 16:51:15 -05:00
use std ::collections ::HashMap ;
2024-05-24 00:16:10 +02:00
use std ::ffi ::{ c_char , CString } ;
2024-02-22 22:32:50 +01:00
use std ::ptr ::null_mut ;
use std ::ptr ::NonNull ;
2024-11-27 14:13:54 +01:00
use std ::sync ::{ Arc , Mutex , Weak } ;
2024-02-22 22:32:50 +01:00
2025-12-09 22:14:02 +01:00
#[ cfg(nix_at_least = " 2.33.0pre " ) ]
2025-10-16 00:40:52 +02:00
use crate ::derivation ::Derivation ;
2024-12-17 09:54:28 +01:00
use crate ::path ::StorePath ;
2024-02-22 22:32:50 +01:00
/* TODO make Nix itself thread safe */
lazy_static! {
2024-06-15 14:51:26 +02:00
static ref INIT : Result < ( ) > = unsafe {
2024-07-02 16:35:32 -04:00
check_call! ( raw ::libstore_init ( & mut Context ::new ( ) ) ) ? ;
2024-06-15 14:51:26 +02:00
Ok ( ( ) )
2024-02-22 22:32:50 +01:00
} ;
}
struct StoreRef {
inner : NonNull < raw ::Store > ,
}
impl StoreRef {
2024-12-17 10:40:03 +01:00
/// # Safety
///
/// The returned pointer is only valid as long as the `StoreRef` is alive.
pub unsafe fn ptr ( & self ) -> * mut raw ::Store {
2024-02-22 22:32:50 +01:00
self . inner . as_ptr ( )
}
}
impl Drop for StoreRef {
fn drop ( & mut self ) {
unsafe {
2024-05-09 12:29:32 -04:00
raw ::store_free ( self . inner . as_ptr ( ) ) ;
2024-02-22 22:32:50 +01:00
}
}
}
2024-10-02 14:50:47 +02:00
unsafe impl Send for StoreRef { }
2024-11-27 14:13:54 +01:00
/// Unlike pointers in general, operations on raw::Store are thread safe and it is therefore safe to share them between threads.
unsafe impl Sync for StoreRef { }
2024-02-22 22:32:50 +01:00
2024-06-27 15:57:43 +02:00
/// A [Weak] reference to a store.
pub struct StoreWeak {
inner : Weak < StoreRef > ,
}
impl StoreWeak {
/// Upgrade the weak reference to a proper [Store].
2024-06-27 16:01:40 +02:00
///
/// If no normal reference to the [Store] is around anymore elsewhere, this fails by returning `None`.
2024-06-27 15:57:43 +02:00
pub fn upgrade ( & self ) -> Option < Store > {
self . inner . upgrade ( ) . map ( | inner | Store {
inner ,
context : Context ::new ( ) ,
} )
}
}
2024-11-27 14:13:54 +01:00
/// Protects against https://github.com/NixOS/nix/issues/11979 (unless different parameters are passed, in which case it's up to luck, but you do get your own parameters as you asked for).
2024-12-13 00:54:35 +01:00
type StoreCacheMap = HashMap < ( Option < String > , Vec < ( String , String ) > ) , StoreWeak > ;
2024-11-27 14:13:54 +01:00
lazy_static! {
static ref STORE_CACHE : Arc < Mutex < StoreCacheMap > > = Arc ::new ( Mutex ::new ( HashMap ::new ( ) ) ) ;
}
2025-12-09 22:14:02 +01:00
#[ cfg(nix_at_least = " 2.33.0pre " ) ]
2025-10-15 18:18:20 +02:00
unsafe extern " C " fn callback_get_result_store_path_set (
2025-10-16 00:40:52 +02:00
_context : * mut raw ::c_context ,
2025-10-15 18:18:20 +02:00
user_data : * mut std ::os ::raw ::c_void ,
store_path : * const raw ::StorePath ,
) {
let ret = user_data as * mut Vec < StorePath > ;
let ret : & mut Vec < StorePath > = & mut * ret ;
let store_path = raw ::store_path_clone ( store_path ) ;
let store_path =
NonNull ::new ( store_path ) . expect ( " nix_store_parse_path returned a null pointer " ) ;
let store_path = StorePath ::new_raw ( store_path ) ;
ret . push ( store_path ) ;
}
2025-12-09 22:14:02 +01:00
#[ cfg(nix_at_least = " 2.33.0pre " ) ]
2025-10-15 18:18:20 +02:00
fn callback_get_result_store_path_set_data ( vec : & mut Vec < StorePath > ) -> * mut std ::os ::raw ::c_void {
vec as * mut Vec < StorePath > as * mut std ::os ::raw ::c_void
}
2024-02-22 22:32:50 +01:00
pub struct Store {
2024-06-26 21:00:22 +02:00
inner : Arc < StoreRef > ,
2024-02-22 22:32:50 +01:00
/* An error context to reuse. This way we don't have to allocate them for each store operation. */
context : Context ,
}
impl Store {
2024-12-13 00:54:35 +01:00
/// Open a store.
///
/// See [nix_c_raw::store_open] for more information.
2024-12-16 12:51:43 +01:00
#[ doc(alias = " nix_store_open " ) ]
2024-05-24 00:16:10 +02:00
pub fn open < ' a , ' b > (
2024-12-13 00:54:35 +01:00
url : Option < & str > ,
2024-05-24 00:16:10 +02:00
params : impl IntoIterator < Item = ( & ' a str , & ' b str ) > ,
2024-11-27 14:13:54 +01:00
) -> Result < Self > {
let params = params
. into_iter ( )
. map ( | ( k , v ) | ( k . to_owned ( ) , v . to_owned ( ) ) )
. collect ::< Vec < ( String , String ) > > ( ) ;
let params2 = params . clone ( ) ;
let mut store_cache = STORE_CACHE
. lock ( )
. map_err ( | _ | Error ::msg ( " Failed to lock store cache. This should never happen. " ) ) ? ;
2024-12-13 00:54:35 +01:00
match store_cache . entry ( ( url . map ( Into ::into ) , params ) ) {
2024-11-27 14:13:54 +01:00
std ::collections ::hash_map ::Entry ::Occupied ( mut e ) = > {
if let Some ( store ) = e . get ( ) . upgrade ( ) {
Ok ( store )
} else {
let store = Self ::open_uncached (
url ,
params2 . iter ( ) . map ( | ( k , v ) | ( k . as_str ( ) , v . as_str ( ) ) ) ,
) ? ;
e . insert ( store . weak_ref ( ) ) ;
Ok ( store )
}
}
std ::collections ::hash_map ::Entry ::Vacant ( e ) = > {
let store = Self ::open_uncached (
url ,
params2 . iter ( ) . map ( | ( k , v ) | ( k . as_str ( ) , v . as_str ( ) ) ) ,
) ? ;
e . insert ( store . weak_ref ( ) ) ;
Ok ( store )
}
}
}
fn open_uncached < ' a , ' b > (
2024-12-13 00:54:35 +01:00
url : Option < & str > ,
2024-11-27 14:13:54 +01:00
params : impl IntoIterator < Item = ( & ' a str , & ' b str ) > ,
2024-05-24 00:16:10 +02:00
) -> Result < Self > {
2024-02-22 22:32:50 +01:00
let x = INIT . as_ref ( ) ;
match x {
Ok ( _ ) = > { }
Err ( e ) = > {
// Couldn't just clone the error, so we have to print it here.
bail! ( " nix_libstore_init error: {} " , e ) ;
}
}
2024-06-15 12:40:45 +02:00
let mut context : Context = Context ::new ( ) ;
2024-02-22 22:32:50 +01:00
2024-12-13 00:54:35 +01:00
let uri_cstring = match url {
Some ( url ) = > Some ( CString ::new ( url ) ? ) ,
None = > None ,
} ;
let uri_ptr = uri_cstring
. as_ref ( )
. map ( | s | s . as_ptr ( ) )
. unwrap_or ( null_mut ( ) ) ;
2024-05-24 00:16:10 +02:00
// this intermediate value must be here and must not be moved
// because it owns the data the `*const c_char` pointers point to.
let params : Vec < ( CString , CString ) > = params
. into_iter ( )
. map ( | ( k , v ) | Ok ( ( CString ::new ( k ) ? , CString ::new ( v ) ? ) ) ) // to do. context
. collect ::< Result < _ > > ( ) ? ;
// this intermediate value owns the data the `*mut *const c_char` pointer points to.
let mut params : Vec < _ > = params
. iter ( )
. map ( | ( k , v ) | [ k . as_ptr ( ) , v . as_ptr ( ) ] )
. collect ( ) ;
// this intermediate value owns the data the `*mut *mut *const c_char` pointer points to.
let mut params : Vec < * mut * const c_char > = params
. iter_mut ( )
. map ( | t | t . as_mut_ptr ( ) )
. chain ( std ::iter ::once ( null_mut ( ) ) ) // signal the end of the array
. collect ( ) ;
2024-12-13 00:54:35 +01:00
let store =
unsafe { check_call! ( raw ::store_open ( & mut context , uri_ptr , params . as_mut_ptr ( ) ) ) } ? ;
2024-02-22 22:32:50 +01:00
if store . is_null ( ) {
2024-05-09 12:41:34 -04:00
panic! ( " nix_c_store_open returned a null pointer without an error " ) ;
2024-02-22 22:32:50 +01:00
}
let store = Store {
2024-06-26 21:00:22 +02:00
inner : Arc ::new ( StoreRef {
2024-02-22 22:32:50 +01:00
inner : NonNull ::new ( store ) . unwrap ( ) ,
2024-06-26 21:00:22 +02:00
} ) ,
2024-02-22 22:32:50 +01:00
context ,
} ;
Ok ( store )
}
2024-12-17 10:40:03 +01:00
/// # Safety
///
/// The returned pointer is only valid as long as the `Store` is alive.
pub unsafe fn raw_ptr ( & self ) -> * mut raw ::Store {
2024-03-19 14:43:01 +01:00
self . inner . ptr ( )
}
2024-12-16 12:51:43 +01:00
#[ doc(alias = " nix_store_get_uri " ) ]
2024-06-15 12:40:45 +02:00
pub fn get_uri ( & mut self ) -> Result < String > {
2024-05-22 14:45:12 +02:00
let mut r = result_string_init! ( ) ;
2024-06-15 14:51:26 +02:00
unsafe {
2024-07-02 16:35:32 -04:00
check_call! ( raw ::store_get_uri (
& mut self . context ,
self . inner . ptr ( ) ,
Some ( callback_get_result_string ) ,
callback_get_result_string_data ( & mut r )
) )
2024-12-16 12:52:15 +01:00
} ? ;
r
}
#[ cfg(nix_at_least = " 2.26 " ) ]
#[ doc(alias = " nix_store_get_storedir " ) ]
pub fn get_storedir ( & mut self ) -> Result < String > {
let mut r = result_string_init! ( ) ;
unsafe {
check_call! ( raw ::store_get_storedir (
& mut self . context ,
self . inner . ptr ( ) ,
Some ( callback_get_result_string ) ,
callback_get_result_string_data ( & mut r )
) )
2024-06-15 14:51:26 +02:00
} ? ;
2024-05-22 14:45:12 +02:00
r
2024-02-22 22:32:50 +01:00
}
2024-06-27 15:57:43 +02:00
2024-12-17 09:54:28 +01:00
#[ doc(alias = " nix_store_parse_path " ) ]
pub fn parse_store_path ( & mut self , path : & str ) -> Result < StorePath > {
let path = CString ::new ( path ) ? ;
unsafe {
let store_path = check_call! ( raw ::store_parse_path (
& mut self . context ,
self . inner . ptr ( ) ,
path . as_ptr ( )
) ) ? ;
let store_path =
NonNull ::new ( store_path ) . expect ( " nix_store_parse_path returned a null pointer " ) ;
Ok ( StorePath ::new_raw ( store_path ) )
}
}
2025-09-22 12:25:23 -07:00
#[ doc(alias = " nix_store_real_path " ) ]
pub fn real_path ( & mut self , path : & StorePath ) -> Result < String > {
let mut r = result_string_init! ( ) ;
unsafe {
check_call! ( raw ::store_real_path (
& mut self . context ,
self . inner . ptr ( ) ,
path . as_ptr ( ) ,
Some ( callback_get_result_string ) ,
callback_get_result_string_data ( & mut r )
) )
} ? ;
r
}
2025-10-16 00:40:52 +02:00
/// Parse a derivation from JSON.
///
2025-10-21 23:18:33 +02:00
/// **Requires Nix 2.33 or later.**
///
2025-10-16 00:40:52 +02:00
/// The JSON format follows the [Nix derivation JSON schema](https://nix.dev/manual/nix/latest/protocols/json/derivation.html).
/// Note that this format is experimental as of writing.
/// The derivation is not added to the store; use [`Store::add_derivation`] for that.
///
/// # Parameters
/// - `json`: A JSON string representing the derivation
///
/// # Returns
/// A [`Derivation`] object if parsing succeeds, or an error if the JSON is invalid
/// or malformed.
2025-12-09 22:14:02 +01:00
#[ cfg(nix_at_least = " 2.33.0pre " ) ]
2025-10-16 00:40:52 +02:00
#[ doc(alias = " nix_derivation_from_json " ) ]
pub fn derivation_from_json ( & mut self , json : & str ) -> Result < Derivation > {
let json_cstr = CString ::new ( json ) ? ;
unsafe {
let drv = check_call! ( raw ::derivation_from_json (
& mut self . context ,
self . inner . ptr ( ) ,
json_cstr . as_ptr ( )
) ) ? ;
let inner = NonNull ::new ( drv )
. ok_or_else ( | | Error ::msg ( " derivation_from_json returned null " ) ) ? ;
Ok ( Derivation ::new_raw ( inner ) )
}
}
/// Add a derivation to the store.
///
2025-10-21 23:18:33 +02:00
/// **Requires Nix 2.33 or later.**
///
2025-10-16 00:40:52 +02:00
/// This computes the store path for the derivation and registers it in the store.
/// The derivation itself is written to the store as a `.drv` file.
///
/// # Parameters
/// - `drv`: The derivation to add
///
/// # Returns
/// The store path of the derivation (ending in `.drv`).
2025-12-09 22:14:02 +01:00
#[ cfg(nix_at_least = " 2.33.0pre " ) ]
2025-10-16 00:40:52 +02:00
#[ doc(alias = " nix_add_derivation " ) ]
pub fn add_derivation ( & mut self , drv : & Derivation ) -> Result < StorePath > {
unsafe {
let path = check_call! ( raw ::add_derivation (
& mut self . context ,
self . inner . ptr ( ) ,
drv . inner . as_ptr ( )
) ) ? ;
2025-12-05 16:51:15 -05:00
let path =
NonNull ::new ( path ) . ok_or_else ( | | Error ::msg ( " add_derivation returned null " ) ) ? ;
2025-10-16 00:40:52 +02:00
Ok ( StorePath ::new_raw ( path ) )
}
}
/// Build a derivation and return its outputs.
///
2025-10-21 23:18:33 +02:00
/// **Requires Nix 2.33 or later.**
///
2025-10-16 00:40:52 +02:00
/// This builds the derivation at the given store path and returns a map of output
/// names to their realized store paths. The derivation must already exist in the store
/// (see [`Store::add_derivation`]).
///
/// # Parameters
/// - `path`: The store path of the derivation to build (typically ending in `.drv`)
///
/// # Returns
/// A [`BTreeMap`] mapping output names (e.g., "out", "dev", "doc") to their store paths.
/// The map is ordered alphabetically by output name for deterministic iteration.
2025-12-09 22:14:02 +01:00
#[ cfg(nix_at_least = " 2.33.0pre " ) ]
2025-10-16 00:40:52 +02:00
#[ doc(alias = " nix_store_realise " ) ]
pub fn realise ( & mut self , path : & StorePath ) -> Result < BTreeMap < String , StorePath > > {
let mut outputs = BTreeMap ::new ( ) ;
2025-12-05 16:51:15 -05:00
let userdata =
& mut outputs as * mut BTreeMap < String , StorePath > as * mut std ::os ::raw ::c_void ;
2025-10-16 00:40:52 +02:00
unsafe extern " C " fn callback (
userdata : * mut std ::os ::raw ::c_void ,
outname : * const c_char ,
out_path : * const raw ::StorePath ,
) {
let outputs = userdata as * mut BTreeMap < String , StorePath > ;
let outputs = & mut * outputs ;
let name = std ::ffi ::CStr ::from_ptr ( outname )
. to_string_lossy ( )
. into_owned ( ) ;
let path = raw ::store_path_clone ( out_path ) ;
let path = NonNull ::new ( path ) . expect ( " store_path_clone returned null " ) ;
let path = StorePath ::new_raw ( path ) ;
outputs . insert ( name , path ) ;
}
unsafe {
check_call! ( raw ::store_realise (
& mut self . context ,
self . inner . ptr ( ) ,
path . as_ptr ( ) ,
userdata ,
Some ( callback )
) ) ? ;
}
Ok ( outputs )
}
2025-10-16 01:30:14 +02:00
/// Get the closure of a specific store path.
///
2025-10-21 23:18:33 +02:00
/// **Requires Nix 2.33 or later.**
///
2025-10-16 01:30:14 +02:00
/// Computes the filesystem closure (dependency graph) of a store path, with options
/// to control the direction and which related paths to include.
///
/// # Parameters
/// - `store_path`: The path to compute the closure from
/// - `flip_direction`: If false, compute the forward closure (paths referenced by this path).
/// If true, compute the backward closure (paths that reference this path).
/// - `include_outputs`: When `flip_direction` is false: for any derivation in the closure, include its outputs.
/// When `flip_direction` is true: for any output in the closure, include derivations that produce it.
/// - `include_derivers`: When `flip_direction` is false: for any output in the closure, include the derivation that produced it.
/// When `flip_direction` is true: for any derivation in the closure, include its outputs.
///
/// # Returns
/// A vector of store paths in the closure, in no particular order.
2025-12-09 22:14:02 +01:00
#[ cfg(nix_at_least = " 2.33.0pre " ) ]
2025-10-15 18:18:20 +02:00
#[ doc(alias = " nix_store_get_fs_closure " ) ]
pub fn get_fs_closure (
& mut self ,
store_path : & StorePath ,
flip_direction : bool ,
include_outputs : bool ,
include_derivers : bool ,
) -> Result < Vec < StorePath > > {
let mut r = Vec ::new ( ) ;
unsafe {
check_call! ( raw ::store_get_fs_closure (
& mut self . context ,
self . inner . ptr ( ) ,
store_path . as_ptr ( ) ,
flip_direction ,
include_outputs ,
include_derivers ,
callback_get_result_store_path_set_data ( & mut r ) ,
Some ( callback_get_result_store_path_set )
) )
} ? ;
Ok ( r )
}
2024-06-27 15:57:43 +02:00
pub fn weak_ref ( & self ) -> StoreWeak {
StoreWeak {
inner : Arc ::downgrade ( & self . inner ) ,
}
}
2024-02-22 22:32:50 +01:00
}
2024-06-26 21:00:22 +02:00
impl Clone for Store {
fn clone ( & self ) -> Self {
Store {
inner : self . inner . clone ( ) ,
context : Context ::new ( ) ,
}
}
}
2024-02-22 22:32:50 +01:00
#[ cfg(test) ]
mod tests {
2025-10-16 00:40:52 +02:00
use ctor ::ctor ;
2025-12-05 16:51:15 -05:00
use std ::collections ::HashMap ;
2024-05-24 00:17:01 +02:00
2024-02-22 22:32:50 +01:00
use super ::* ;
2025-10-16 00:40:52 +02:00
#[ ctor ]
fn test_setup ( ) {
// Initialize settings for tests
let _ = INIT . as_ref ( ) ;
// Enable ca-derivations for all tests
nix_bindings_util ::settings ::set ( " experimental-features " , " ca-derivations " ) . ok ( ) ;
// Disable build hooks to prevent test recursion
nix_bindings_util ::settings ::set ( " build-hook " , " " ) . ok ( ) ;
// Set custom build dir for sandbox
if cfg! ( target_os = " linux " ) {
2025-12-05 16:51:15 -05:00
nix_bindings_util ::settings ::set ( " sandbox-build-dir " , " /custom-build-dir-for-test " )
. ok ( ) ;
2025-10-16 00:40:52 +02:00
}
std ::env ::set_var ( " _NIX_TEST_NO_SANDBOX " , " 1 " ) ;
// Tests run offline
nix_bindings_util ::settings ::set ( " substituters " , " " ) . ok ( ) ;
}
2024-12-13 00:54:35 +01:00
#[ test ]
fn none_works ( ) {
let res = Store ::open ( None , HashMap ::new ( ) ) ;
res . unwrap ( ) ;
}
2024-02-22 22:32:50 +01:00
#[ test ]
fn auto_works ( ) {
2024-12-13 00:54:35 +01:00
// This is not actually a given.
// Maybe whatever is in NIX_REMOTE or nix.conf is really important.
let res = Store ::open ( Some ( " auto " ) , HashMap ::new ( ) ) ;
2024-03-19 18:01:23 +01:00
res . unwrap ( ) ;
2024-02-22 22:32:50 +01:00
}
#[ test ]
fn invalid_uri_fails ( ) {
2024-12-13 00:54:35 +01:00
let res = Store ::open ( Some ( " invalid://uri " ) , HashMap ::new ( ) ) ;
2024-02-22 22:32:50 +01:00
assert! ( res . is_err ( ) ) ;
}
#[ test ]
fn get_uri ( ) {
2024-12-13 00:54:35 +01:00
let mut store = Store ::open ( None , HashMap ::new ( ) ) . unwrap ( ) ;
2024-02-22 22:32:50 +01:00
let uri = store . get_uri ( ) . unwrap ( ) ;
assert! ( ! uri . is_empty ( ) ) ;
// must be ascii
assert! ( uri . is_ascii ( ) ) ;
// usually something like "daemon", but that's not something we can check here.
println! ( " uri: {} " , uri ) ;
}
#[ test ]
2024-03-19 18:02:04 +01:00
#[ ignore ] // Needs network access
2024-02-22 22:32:50 +01:00
fn get_uri_nixos_cache ( ) {
2024-12-13 00:54:35 +01:00
let mut store = Store ::open ( Some ( " https://cache.nixos.org/ " ) , HashMap ::new ( ) ) . unwrap ( ) ;
2024-02-22 22:32:50 +01:00
let uri = store . get_uri ( ) . unwrap ( ) ;
assert_eq! ( uri , " https://cache.nixos.org " ) ;
}
2024-06-27 15:57:43 +02:00
2024-12-17 09:54:28 +01:00
#[ test ]
#[ cfg(nix_at_least = " 2.26 " /* get_storedir */) ]
fn parse_store_path_ok ( ) {
2024-12-13 00:54:35 +01:00
let mut store = crate ::store ::Store ::open ( Some ( " dummy:// " ) , [ ] ) . unwrap ( ) ;
2024-12-17 09:54:28 +01:00
let store_dir = store . get_storedir ( ) . unwrap ( ) ;
let store_path_string =
format! ( " {store_dir} /rdd4pnr4x9rqc9wgbibhngv217w2xvxl-bash-interactive-5.2p26 " ) ;
let store_path = store . parse_store_path ( store_path_string . as_str ( ) ) . unwrap ( ) ;
2025-09-22 12:25:23 -07:00
let real_store_path = store . real_path ( & store_path ) . unwrap ( ) ;
2024-12-17 09:54:28 +01:00
assert_eq! ( store_path . name ( ) . unwrap ( ) , " bash-interactive-5.2p26 " ) ;
2025-09-22 12:25:23 -07:00
assert_eq! ( real_store_path , store_path_string ) ;
2024-12-17 09:54:28 +01:00
}
#[ test ]
fn parse_store_path_fail ( ) {
2024-12-13 00:54:35 +01:00
let mut store = crate ::store ::Store ::open ( Some ( " dummy:// " ) , [ ] ) . unwrap ( ) ;
2024-12-17 09:54:28 +01:00
let store_path_string = format! ( " bash-interactive-5.2p26 " ) ;
let r = store . parse_store_path ( store_path_string . as_str ( ) ) ;
match r {
Err ( e ) = > {
assert! ( e . to_string ( ) . contains ( " bash-interactive-5.2p26 " ) ) ;
}
_ = > panic! ( " Expected error " ) ,
}
}
2024-06-27 15:57:43 +02:00
#[ test ]
fn weak_ref ( ) {
2024-12-13 00:54:35 +01:00
let mut store = Store ::open ( None , HashMap ::new ( ) ) . unwrap ( ) ;
2024-06-27 15:57:43 +02:00
let uri = store . get_uri ( ) . unwrap ( ) ;
let weak = store . weak_ref ( ) ;
let mut store2 = weak . upgrade ( ) . unwrap ( ) ;
assert_eq! ( store2 . get_uri ( ) . unwrap ( ) , uri ) ;
}
#[ test ]
fn weak_ref_gone ( ) {
let weak = {
2024-11-27 14:13:54 +01:00
// Concurrent tests calling Store::open will keep the weak reference to auto alive,
// so for this test we need to bypass the global cache.
2024-12-13 00:54:35 +01:00
let store = Store ::open_uncached ( None , HashMap ::new ( ) ) . unwrap ( ) ;
2024-06-27 15:57:43 +02:00
store . weak_ref ( )
} ;
assert! ( weak . upgrade ( ) . is_none ( ) ) ;
assert! ( weak . inner . upgrade ( ) . is_none ( ) ) ;
}
2025-10-16 00:40:52 +02:00
2025-12-09 22:14:02 +01:00
#[ cfg(nix_at_least = " 2.33.0pre " ) ]
2025-10-16 00:40:52 +02:00
fn create_temp_store ( ) -> ( Store , tempfile ::TempDir ) {
let temp_dir = tempfile ::tempdir ( ) . unwrap ( ) ;
let store_dir = temp_dir . path ( ) . join ( " store " ) ;
let state_dir = temp_dir . path ( ) . join ( " state " ) ;
let log_dir = temp_dir . path ( ) . join ( " log " ) ;
let store_dir_str = store_dir . to_str ( ) . unwrap ( ) ;
let state_dir_str = state_dir . to_str ( ) . unwrap ( ) ;
let log_dir_str = log_dir . to_str ( ) . unwrap ( ) ;
let params = vec! [
( " store " , store_dir_str ) ,
( " state " , state_dir_str ) ,
( " log " , log_dir_str ) ,
] ;
let store = Store ::open ( Some ( " local " ) , params ) . unwrap ( ) ;
( store , temp_dir )
}
fn current_system ( ) -> Result < String > {
nix_bindings_util ::settings ::get ( " system " )
}
2025-10-21 23:18:33 +02:00
#[ cfg(nix_at_least = " 2.33 " ) ]
2025-10-16 00:40:52 +02:00
fn create_test_derivation_json ( ) -> String {
let system = current_system ( ) . unwrap_or_else ( | _ | {
// Fallback to Rust's platform detection
format! ( " {} - {} " , std ::env ::consts ::ARCH , std ::env ::consts ::OS )
} ) ;
format! (
r #" {{
" args " : [ " -c " , " echo $name foo > $out " ] ,
" builder " : " /bin/sh " ,
" env " : { {
" builder " : " /bin/sh " ,
" name " : " myname " ,
" out " : " /1rz4g4znpzjwh1xymhjpm42vipw92pr73vdgl6xs1hycac8kf2n9 " ,
" system " : " {} "
} } ,
2025-11-26 21:34:37 -05:00
" inputs " : { {
" drvs " : { { } } ,
" srcs " : [ ]
} } ,
2025-10-16 00:40:52 +02:00
" name " : " myname " ,
" outputs " : { {
" out " : { {
" hashAlgo " : " sha256 " ,
" method " : " nar "
} }
} } ,
" system " : " {} " ,
2025-11-26 21:34:37 -05:00
" version " : 4
2025-10-16 00:40:52 +02:00
} } " #,
system , system
)
}
#[ test ]
2025-10-21 23:18:33 +02:00
#[ cfg(nix_at_least = " 2.33 " ) ]
2025-10-16 00:40:52 +02:00
fn derivation_from_json ( ) {
let ( mut store , temp_dir ) = create_temp_store ( ) ;
let drv_json = create_test_derivation_json ( ) ;
let drv = store . derivation_from_json ( & drv_json ) . unwrap ( ) ;
// If we got here, parsing succeeded
drop ( drv ) ;
drop ( store ) ;
drop ( temp_dir ) ;
}
#[ test ]
2025-12-09 22:14:02 +01:00
#[ cfg(nix_at_least = " 2.33.0pre " ) ]
2025-10-16 00:40:52 +02:00
fn derivation_from_invalid_json ( ) {
let ( mut store , temp_dir ) = create_temp_store ( ) ;
let result = store . derivation_from_json ( " not valid json " ) ;
assert! ( result . is_err ( ) ) ;
drop ( store ) ;
drop ( temp_dir ) ;
}
#[ test ]
2025-10-21 23:18:33 +02:00
#[ cfg(nix_at_least = " 2.33 " ) ]
2025-10-16 00:40:52 +02:00
fn add_derivation ( ) {
let ( mut store , temp_dir ) = create_temp_store ( ) ;
let drv_json = create_test_derivation_json ( ) ;
let drv = store . derivation_from_json ( & drv_json ) . unwrap ( ) ;
let drv_path = store . add_derivation ( & drv ) . unwrap ( ) ;
// Verify we got a .drv path
let name = drv_path . name ( ) . unwrap ( ) ;
assert! ( name . ends_with ( " .drv " ) ) ;
drop ( store ) ;
drop ( temp_dir ) ;
}
#[ test ]
2025-10-21 23:18:33 +02:00
#[ cfg(nix_at_least = " 2.33 " ) ]
2025-10-16 00:40:52 +02:00
fn realise ( ) {
let ( mut store , temp_dir ) = create_temp_store ( ) ;
let drv_json = create_test_derivation_json ( ) ;
let drv = store . derivation_from_json ( & drv_json ) . unwrap ( ) ;
let drv_path = store . add_derivation ( & drv ) . unwrap ( ) ;
// Build the derivation
let outputs = store . realise ( & drv_path ) . unwrap ( ) ;
// Verify we got the expected output
assert! ( outputs . contains_key ( " out " ) ) ;
let out_path = & outputs [ " out " ] ;
let out_name = out_path . name ( ) . unwrap ( ) ;
assert_eq! ( out_name , " myname " ) ;
drop ( store ) ;
drop ( temp_dir ) ;
}
2025-10-21 23:18:33 +02:00
#[ cfg(nix_at_least = " 2.33 " ) ]
2025-10-16 00:40:52 +02:00
fn create_multi_output_derivation_json ( ) -> String {
2025-12-05 16:51:15 -05:00
let system = current_system ( )
. unwrap_or_else ( | _ | format! ( " {} - {} " , std ::env ::consts ::ARCH , std ::env ::consts ::OS ) ) ;
2025-10-16 00:40:52 +02:00
format! (
r #" {{
2025-11-26 21:34:37 -05:00
" version " : 4 ,
2025-10-16 00:40:52 +02:00
" name " : " multi-output-test " ,
" system " : " {} " ,
" builder " : " /bin/sh " ,
" args " : [ " -c " , " echo a > $outa; echo b > $outb; echo c > $outc; echo d > $outd; echo e > $oute; echo f > $outf; echo g > $outg; echo h > $outh; echo i > $outi; echo j > $outj " ] ,
" env " : { {
" builder " : " /bin/sh " ,
" name " : " multi-output-test " ,
" system " : " {} " ,
" outf " : " /1vkfzqpwk313b51x0xjyh5s7w1lx141mr8da3dr9wqz5aqjyr2fh " ,
" outd " : " /1ypxifgmbzp5sd0pzsp2f19aq68x5215260z3lcrmy5fch567lpm " ,
" outi " : " /1wmasjnqi12j1mkjbxazdd0qd0ky6dh1qry12fk8qyp5kdamhbdx " ,
" oute " : " /1f9r2k1s168js509qlw8a9di1qd14g5lqdj5fcz8z7wbqg11qp1f " ,
" outh " : " /1rkx1hmszslk5nq9g04iyvh1h7bg8p92zw0hi4155hkjm8bpdn95 " ,
" outc " : " /1rj4nsf9pjjqq9jsq58a2qkwa7wgvgr09kgmk7mdyli6h1plas4w " ,
" outb " : " /1p7i1dxifh86xq97m5kgb44d7566gj7rfjbw7fk9iij6ca4akx61 " ,
" outg " : " /14f8qi0r804vd6a6v40ckylkk1i6yl6fm243qp6asywy0km535lc " ,
" outj " : " /0gkw1366qklqfqb2lw1pikgdqh3cmi3nw6f1z04an44ia863nxaz " ,
" outa " : " /039akv9zfpihrkrv4pl54f3x231x362bll9afblsgfqgvx96h198 "
} } ,
2025-11-26 21:34:37 -05:00
" inputs " : { {
" drvs " : { { } } ,
" srcs " : [ ]
} } ,
2025-10-16 00:40:52 +02:00
" outputs " : { {
" outd " : { { " hashAlgo " : " sha256 " , " method " : " nar " } } ,
" outf " : { { " hashAlgo " : " sha256 " , " method " : " nar " } } ,
" outg " : { { " hashAlgo " : " sha256 " , " method " : " nar " } } ,
" outb " : { { " hashAlgo " : " sha256 " , " method " : " nar " } } ,
" outc " : { { " hashAlgo " : " sha256 " , " method " : " nar " } } ,
" outi " : { { " hashAlgo " : " sha256 " , " method " : " nar " } } ,
" outj " : { { " hashAlgo " : " sha256 " , " method " : " nar " } } ,
" outh " : { { " hashAlgo " : " sha256 " , " method " : " nar " } } ,
" outa " : { { " hashAlgo " : " sha256 " , " method " : " nar " } } ,
" oute " : { { " hashAlgo " : " sha256 " , " method " : " nar " } }
} }
} } " #,
system , system
)
}
#[ test ]
2025-10-21 23:18:33 +02:00
#[ cfg(nix_at_least = " 2.33 " ) ]
2025-10-16 00:40:52 +02:00
fn realise_multi_output_ordering ( ) {
let ( mut store , temp_dir ) = create_temp_store ( ) ;
let drv_json = create_multi_output_derivation_json ( ) ;
let drv = store . derivation_from_json ( & drv_json ) . unwrap ( ) ;
let drv_path = store . add_derivation ( & drv ) . unwrap ( ) ;
// Build the derivation
let outputs = store . realise ( & drv_path ) . unwrap ( ) ;
// Verify outputs are complete (BTreeMap guarantees ordering)
let output_names : Vec < & String > = outputs . keys ( ) . collect ( ) ;
2025-12-05 16:51:15 -05:00
let expected_order = vec! [
" outa " , " outb " , " outc " , " outd " , " oute " , " outf " , " outg " , " outh " , " outi " , " outj " ,
] ;
2025-10-16 00:40:52 +02:00
assert_eq! ( output_names , expected_order ) ;
drop ( store ) ;
drop ( temp_dir ) ;
}
#[ test ]
2025-10-21 23:18:33 +02:00
#[ cfg(nix_at_least = " 2.33 " ) ]
2025-10-16 00:40:52 +02:00
fn realise_invalid_system ( ) {
let ( mut store , temp_dir ) = create_temp_store ( ) ;
// Create a derivation with an invalid system
let system = " bogus65-bogusos " ;
let drv_json = format! (
r #" {{
" args " : [ " -c " , " echo $name foo > $out " ] ,
" builder " : " /bin/sh " ,
" env " : { {
" builder " : " /bin/sh " ,
" name " : " myname " ,
" out " : " /1rz4g4znpzjwh1xymhjpm42vipw92pr73vdgl6xs1hycac8kf2n9 " ,
" system " : " {} "
} } ,
2025-11-26 21:34:37 -05:00
" inputs " : { {
" drvs " : { { } } ,
" srcs " : [ ]
} } ,
2025-10-16 00:40:52 +02:00
" name " : " myname " ,
" outputs " : { {
" out " : { {
" hashAlgo " : " sha256 " ,
" method " : " nar "
} }
} } ,
" system " : " {} " ,
2025-11-26 21:34:37 -05:00
" version " : 4
2025-10-16 00:40:52 +02:00
} } " #,
system , system
) ;
let drv = store . derivation_from_json ( & drv_json ) . unwrap ( ) ;
let drv_path = store . add_derivation ( & drv ) . unwrap ( ) ;
// Try to build - should fail
let result = store . realise ( & drv_path ) ;
let err = match result {
Ok ( _ ) = > panic! ( " Build should fail with invalid system " ) ,
Err ( e ) = > e . to_string ( ) ,
} ;
assert! (
err . contains ( " required system or feature not available " ) ,
" Error should mention system not available, got: {} " ,
err
) ;
drop ( store ) ;
drop ( temp_dir ) ;
}
#[ test ]
2025-10-21 23:18:33 +02:00
#[ cfg(nix_at_least = " 2.33 " ) ]
2025-10-16 00:40:52 +02:00
fn realise_builder_fails ( ) {
let ( mut store , temp_dir ) = create_temp_store ( ) ;
2025-12-05 16:51:15 -05:00
let system = current_system ( )
. unwrap_or_else ( | _ | format! ( " {} - {} " , std ::env ::consts ::ARCH , std ::env ::consts ::OS ) ) ;
2025-10-16 00:40:52 +02:00
// Create a derivation where the builder exits with error
let drv_json = format! (
r #" {{
" args " : [ " -c " , " exit 1 " ] ,
" builder " : " /bin/sh " ,
" env " : { {
" builder " : " /bin/sh " ,
" name " : " failing " ,
" out " : " /1rz4g4znpzjwh1xymhjpm42vipw92pr73vdgl6xs1hycac8kf2n9 " ,
" system " : " {} "
} } ,
2025-11-26 21:34:37 -05:00
" inputs " : { {
" drvs " : { { } } ,
" srcs " : [ ]
} } ,
2025-10-16 00:40:52 +02:00
" name " : " failing " ,
" outputs " : { {
" out " : { {
" hashAlgo " : " sha256 " ,
" method " : " nar "
} }
} } ,
" system " : " {} " ,
2025-11-26 21:34:37 -05:00
" version " : 4
2025-10-16 00:40:52 +02:00
} } " #,
system , system
) ;
let drv = store . derivation_from_json ( & drv_json ) . unwrap ( ) ;
let drv_path = store . add_derivation ( & drv ) . unwrap ( ) ;
// Try to build - should fail
let result = store . realise ( & drv_path ) ;
let err = match result {
Ok ( _ ) = > panic! ( " Build should fail when builder exits with error " ) ,
Err ( e ) = > e . to_string ( ) ,
} ;
assert! (
err . contains ( " builder failed with exit code 1 " ) ,
" Error should mention builder failed with exit code, got: {} " ,
err
) ;
drop ( store ) ;
drop ( temp_dir ) ;
}
#[ test ]
2025-10-21 23:18:33 +02:00
#[ cfg(nix_at_least = " 2.33 " ) ]
2025-10-16 00:40:52 +02:00
fn realise_builder_no_output ( ) {
let ( mut store , temp_dir ) = create_temp_store ( ) ;
2025-12-05 16:51:15 -05:00
let system = current_system ( )
. unwrap_or_else ( | _ | format! ( " {} - {} " , std ::env ::consts ::ARCH , std ::env ::consts ::OS ) ) ;
2025-10-16 00:40:52 +02:00
// Create a derivation where the builder succeeds but produces no output
let drv_json = format! (
r #" {{
" args " : [ " -c " , " true " ] ,
" builder " : " /bin/sh " ,
" env " : { {
" builder " : " /bin/sh " ,
" name " : " no-output " ,
" out " : " /1rz4g4znpzjwh1xymhjpm42vipw92pr73vdgl6xs1hycac8kf2n9 " ,
" system " : " {} "
} } ,
2025-11-26 21:34:37 -05:00
" inputs " : { {
" drvs " : { { } } ,
" srcs " : [ ]
} } ,
2025-10-16 00:40:52 +02:00
" name " : " no-output " ,
" outputs " : { {
" out " : { {
" hashAlgo " : " sha256 " ,
" method " : " nar "
} }
} } ,
" system " : " {} " ,
2025-11-26 21:34:37 -05:00
" version " : 4
2025-10-16 00:40:52 +02:00
} } " #,
system , system
) ;
let drv = store . derivation_from_json ( & drv_json ) . unwrap ( ) ;
let drv_path = store . add_derivation ( & drv ) . unwrap ( ) ;
// Try to build - should fail
let result = store . realise ( & drv_path ) ;
let err = match result {
Ok ( _ ) = > panic! ( " Build should fail when builder produces no output " ) ,
Err ( e ) = > e . to_string ( ) ,
} ;
assert! (
err . contains ( " failed to produce output path " ) ,
" Error should mention failed to produce output, got: {} " ,
err
) ;
drop ( store ) ;
drop ( temp_dir ) ;
}
2025-10-16 01:30:14 +02:00
#[ test ]
2025-10-21 23:18:33 +02:00
#[ cfg(nix_at_least = " 2.33 " ) ]
2025-10-16 01:30:14 +02:00
fn get_fs_closure_with_outputs ( ) {
let ( mut store , temp_dir ) = create_temp_store ( ) ;
let drv_json = create_test_derivation_json ( ) ;
let drv = store . derivation_from_json ( & drv_json ) . unwrap ( ) ;
let drv_path = store . add_derivation ( & drv ) . unwrap ( ) ;
// Build the derivation to get the output path
let outputs = store . realise ( & drv_path ) . unwrap ( ) ;
let out_path = & outputs [ " out " ] ;
let out_path_name = out_path . name ( ) . unwrap ( ) ;
// Get closure with include_outputs=true
let closure = store . get_fs_closure ( & drv_path , false , true , false ) . unwrap ( ) ;
// The closure should contain at least the derivation and its output
2025-12-05 16:51:15 -05:00
assert! (
closure . len ( ) > = 2 ,
" Closure should contain at least drv and output "
) ;
2025-10-16 01:30:14 +02:00
// Verify the output path is in the closure
let out_in_closure = closure . iter ( ) . any ( | p | p . name ( ) . unwrap ( ) = = out_path_name ) ;
2025-12-05 16:51:15 -05:00
assert! (
out_in_closure ,
" Output path should be in closure when include_outputs=true "
) ;
2025-10-16 01:30:14 +02:00
drop ( store ) ;
drop ( temp_dir ) ;
}
#[ test ]
2025-10-21 23:18:33 +02:00
#[ cfg(nix_at_least = " 2.33 " ) ]
2025-10-16 01:30:14 +02:00
fn get_fs_closure_without_outputs ( ) {
let ( mut store , temp_dir ) = create_temp_store ( ) ;
let drv_json = create_test_derivation_json ( ) ;
let drv = store . derivation_from_json ( & drv_json ) . unwrap ( ) ;
let drv_path = store . add_derivation ( & drv ) . unwrap ( ) ;
// Build the derivation to get the output path
let outputs = store . realise ( & drv_path ) . unwrap ( ) ;
let out_path = & outputs [ " out " ] ;
let out_path_name = out_path . name ( ) . unwrap ( ) ;
// Get closure with include_outputs=false
2025-12-05 16:51:15 -05:00
let closure = store
. get_fs_closure ( & drv_path , false , false , false )
. unwrap ( ) ;
2025-10-16 01:30:14 +02:00
// Verify the output path is NOT in the closure
let out_in_closure = closure . iter ( ) . any ( | p | p . name ( ) . unwrap ( ) = = out_path_name ) ;
2025-12-05 16:51:15 -05:00
assert! (
! out_in_closure ,
" Output path should not be in closure when include_outputs=false "
) ;
2025-10-16 01:30:14 +02:00
drop ( store ) ;
drop ( temp_dir ) ;
}
#[ test ]
2025-10-21 23:18:33 +02:00
#[ cfg(nix_at_least = " 2.33 " ) ]
2025-10-16 01:30:14 +02:00
fn get_fs_closure_flip_direction ( ) {
let ( mut store , temp_dir ) = create_temp_store ( ) ;
let drv_json = create_test_derivation_json ( ) ;
let drv = store . derivation_from_json ( & drv_json ) . unwrap ( ) ;
let drv_path = store . add_derivation ( & drv ) . unwrap ( ) ;
// Build the derivation to get the output path
let outputs = store . realise ( & drv_path ) . unwrap ( ) ;
let out_path = & outputs [ " out " ] ;
let out_path_name = out_path . name ( ) . unwrap ( ) ;
// Get closure with flip_direction=true (reverse dependencies)
let closure = store . get_fs_closure ( & drv_path , true , true , false ) . unwrap ( ) ;
// Verify the output path is NOT in the closure when direction is flipped
let out_in_closure = closure . iter ( ) . any ( | p | p . name ( ) . unwrap ( ) = = out_path_name ) ;
2025-12-05 16:51:15 -05:00
assert! (
! out_in_closure ,
" Output path should not be in closure when flip_direction=true "
) ;
2025-10-16 01:30:14 +02:00
drop ( store ) ;
drop ( temp_dir ) ;
}
#[ test ]
2025-10-21 23:18:33 +02:00
#[ cfg(nix_at_least = " 2.33 " ) ]
2025-10-16 01:30:14 +02:00
fn get_fs_closure_include_derivers ( ) {
let ( mut store , temp_dir ) = create_temp_store ( ) ;
let drv_json = create_test_derivation_json ( ) ;
let drv = store . derivation_from_json ( & drv_json ) . unwrap ( ) ;
let drv_path = store . add_derivation ( & drv ) . unwrap ( ) ;
let drv_path_name = drv_path . name ( ) . unwrap ( ) ;
// Build the derivation to get the output path
let outputs = store . realise ( & drv_path ) . unwrap ( ) ;
let out_path = & outputs [ " out " ] ;
// Get closure of the output path with include_derivers=true
let closure = store . get_fs_closure ( out_path , false , false , true ) . unwrap ( ) ;
// Verify the derivation path is in the closure
let drv_in_closure = closure . iter ( ) . any ( | p | p . name ( ) . unwrap ( ) = = drv_path_name ) ;
2025-12-05 16:51:15 -05:00
assert! (
drv_in_closure ,
" Derivation should be in closure when include_derivers=true "
) ;
2025-10-16 01:30:14 +02:00
drop ( store ) ;
drop ( temp_dir ) ;
}
2024-02-22 22:32:50 +01:00
}