1use std::borrow::Cow;1use std::borrow::Cow;
2use std::cell::RefCell;2use std::cell::RefCell;
3use std::ffi::{CStr, CString, c_char, c_int, c_uint, c_void};3use std::ffi::{CStr, CString, c_char, c_int, c_uint, c_void};
4use std::ptr::null_mut;4use std::ptr::{null, null_mut};
5use std::sync::LazyLock;5use std::sync::LazyLock;
6use std::{array, fmt, slice};
6use std::{collections::HashMap, path::PathBuf};7use std::{collections::HashMap, path::PathBuf};
7use std::{fmt, slice};
88
9use anyhow::{Context, anyhow, bail};9use anyhow::{Context, anyhow, bail};
10use itertools::Itertools;
10use serde::Serialize;11use serde::Serialize;
11use serde::de::DeserializeOwned;12use serde::de::DeserializeOwned;
1213
18use self::nix_raw::{19use self::nix_raw::{
19 BindingsBuilder as c_bindings_builder, EvalState as c_eval_state, GC_SUCCESS,20 BindingsBuilder as c_bindings_builder, EvalState as c_eval_state, GC_SUCCESS,
20 GC_allow_register_threads, GC_get_stack_base, GC_register_my_thread, GC_stack_base,21 GC_allow_register_threads, GC_get_stack_base, GC_register_my_thread, GC_stack_base,
21 GC_thread_is_registered, GC_unregister_my_thread, ListBuilder as c_list_builder,22 GC_thread_is_registered, GC_unregister_my_thread, ListBuilder as c_list_builder, PrimOp,
22 Store as c_store, StorePath as c_store_path, alloc_value, bindings_builder_free,23 PrimOpFun, Store as c_store, StorePath as c_store_path, alloc_primop, alloc_value,
23 bindings_builder_insert, c_context, c_context_create, c_context_free, clear_err, err_code,24 bindings_builder_free, bindings_builder_insert, c_context, c_context_create, c_context_free,
24 err_info_msg, err_msg, eval_state_build, eval_state_builder_load, eval_state_builder_new,25 clear_err, copy_value, err_code, err_info_msg, err_msg, eval_state_build,
25 eval_state_builder_set_eval_setting, expr_eval_from_string, fetchers_settings,26 eval_state_builder_load, eval_state_builder_new, eval_state_builder_set_eval_setting,
26 fetchers_settings_free, fetchers_settings_new, flake_lock, flake_lock_flags,27 expr_eval_from_string, fetchers_settings, fetchers_settings_free, fetchers_settings_new,
27 flake_lock_flags_free, flake_lock_flags_new, flake_reference,28 flake_lock, flake_lock_flags, flake_lock_flags_free, flake_lock_flags_new, flake_reference,
30 flake_reference_parse_flags_set_base_directory, flake_settings, flake_settings_free,31 flake_reference_parse_flags_set_base_directory, flake_settings, flake_settings_free,
31 flake_settings_new, gc_now as gc_now_raw, get_attr_byname, get_attr_name_byidx, get_attrs_size,32 flake_settings_new, gc_now as gc_now_raw, get_attr_byname, get_attr_name_byidx, get_attrs_size,
32 get_list_byidx, get_list_size, get_string, get_type, has_attr_byname, init_bool, init_int,33 get_list_byidx, get_list_size, get_string, get_type, has_attr_byname, init_bool, init_int,
33 init_string, libexpr_init, libstore_init, libutil_init, list_builder_free, list_builder_insert,34 init_primop, init_string, libexpr_init, libstore_init, libutil_init, list_builder_free,
34 locked_flake, locked_flake_free, locked_flake_get_output_attrs, make_attrs,35 list_builder_insert, locked_flake, locked_flake_free, locked_flake_get_output_attrs,
35 make_bindings_builder, make_list, make_list_builder, realised_string, realised_string_free,36 make_attrs, make_bindings_builder, make_list, make_list_builder, realised_string,
36 realised_string_get_buffer_size, realised_string_get_buffer_start,37 realised_string_free, realised_string_get_buffer_size, realised_string_get_buffer_start,
37 realised_string_get_store_path, realised_string_get_store_path_count, set_err_msg, setting_set,38 realised_string_get_store_path, realised_string_get_store_path_count, register_primop,
38 state_free, store_open, store_parse_path, store_path_free, store_path_name, string_realise,39 set_err_msg, setting_set, state_free, store_copy_closure, store_get_fs_closure, store_open,
39 value, value_call, value_decref, value_incref,40 store_parse_path, store_path_free, store_path_name, string_realise, value, value_call,
41 value_decref, value_incref,
40};42};
166 }168 }
167}169}
168170
171#[repr(transparent)]
169pub struct NixContext(*mut c_context);172pub struct NixContext(*mut c_context);
170impl NixContext {173impl NixContext {
171 pub fn set_err(&mut self, err: NixErrorKind, msg: &CStr) {174 pub fn set_err(&mut self, err: NixErrorKind, msg: &CStr) {
540 }543 }
541}544}
542545
546#[repr(transparent)]
543pub struct Value(*mut value);547pub struct Value(*mut value);
544548
545unsafe impl Send for Value {}549unsafe impl Send for Value {}
626}630}
627631
628impl Value {632impl Value {
633 pub fn new_primop(v: NativeFn) -> Self {
634 let out = Self::new_uninit();
635 with_default_context(|c, _| unsafe { init_primop(c, out.0, v.0) })
636 .expect("primop initialization should not fail");
637 out
638 }
629 pub fn new_attrs(v: HashMap<&str, Value>) -> Self {639 pub fn new_attrs(v: HashMap<&str, Value>) -> Self {
630 let out = Self::new_uninit();640 let out = Self::new_uninit();
631 let mut b = AttrsBuilder::new(v.len());641 let mut b = AttrsBuilder::new(v.len());
913 nix_logging_cxx::apply_tracing_logger();923 nix_logging_cxx::apply_tracing_logger();
914}924}
925
926unsafe extern "C" fn nix_primop_closure_adapter<const N: usize>(
927 user_data: *mut c_void,
928 context: *mut c_context,
929 state: *mut nix_raw::EvalState,
930 args: *mut *mut value,
931 ret: *mut value,
932) {
933 let user_closure: &UserClosure<N> = unsafe { &*user_data.cast_const().cast() };
934 let args: [&Value; N] = array::from_fn(|i| {
935 let v: &Value = unsafe { &*args.add(i).cast_const().cast() };
936 v
937 });
938 let ctx: &mut NixContext = unsafe { &mut *context.cast() };
939
940 match user_closure(args) {
941 Ok(v) => {
942 unsafe { copy_value(context, ret, v.0) };
943 }
944 Err(e) => {
945 ctx.set_err(
946 NixErrorKind::Unknown,
947 &CString::new(e.to_string()).expect("error should not contain internal nuls"),
948 );
949 }
950 }
951}
952
953type UserClosure<const N: usize> = Box<dyn Fn([&Value; N]) -> Result<Value>>;
954
955struct NativeFn(*mut PrimOp);
956impl NativeFn {
957 pub fn new<const N: usize>(
958 name: &'static CStr,
959 doc: &'static CStr,
960 args: [&'static CStr; N],
961 f: impl Fn([&Value; N]) -> Result<Value> + 'static,
962 ) -> Self {
963
964 let closure: Box<UserClosure<N>> = Box::new(Box::new(f));
965 let f: PrimOpFun = Some(nix_primop_closure_adapter::<N>);
966 let mut args = args.into_iter().map(|v| v.as_ptr()).collect_vec();
967 args.push(null());
968 let args = args.as_mut_ptr();
969 let primop = with_default_context(|c, _| unsafe {
970 alloc_primop(
971 c,
972 f,
973 N as i32,
974 name.as_ptr(),
975 args,
976 doc.as_ptr(),
977 Box::into_raw(closure).cast(),
978 )
979 })
980 .expect("primop allocation should not fail");
981
982 Self(primop)
983 }
984 pub fn register(self) {
985 with_default_context(|c, _| unsafe { register_primop(c, self.0) })
986 .expect("primop registration should not fail");
987 }
988}
915989
916struct StorePath(*mut c_store_path);990struct StorePath(*mut c_store_path);
917impl StorePath {}991impl StorePath {}
949 let s = nix_go!(attrs.packages["x86_64-linux"].fleet.drvPath);1023 let s = nix_go!(attrs.packages["x86_64-linux"].fleet.drvPath);
950 let s = CString::new(s.to_string()?).expect("path str is cstring");1024 let s = CString::new(s.to_string()?).expect("path str is cstring");
1025
1026 let uppercase_suffix = Value::new_primop(NativeFn::new(
1027 c"uppercase_suffix",
1028 c"make string uppercase and add suffix",
1029 [c"str", c"suffix"],
1030 |[str, suffix]: [&Value; 2]| {
1031 let str = str.to_string()?;
1032 let suffix = suffix.to_string()?;
1033 Ok(Value::new_str(&format!("{}{suffix}", str.to_uppercase())))
1034 },
1035 ));
1036
1037 let test_result: String = nix_go_json!(test_data.testPrimop(uppercase_suffix));
1038 assert_eq!(test_result, "PREFIX_BODY_SUFFIX");
9511039
952 let nix_ctx = NixContext::new();1040 let nix_ctx = NixContext::new();
953 let store = GLOBAL_STATE.store.parse_path(s.as_c_str())?;1041 let store = GLOBAL_STATE.store.parse_path(s.as_c_str())?;