1use std::borrow::Cow;2use std::cell::RefCell;3use std::ffi::{CStr, CString, c_char, c_int, c_uint, c_void};4use std::ptr::{null, null_mut};5use std::sync::{Arc, LazyLock, OnceLock};6use std::{array, fmt, slice};7use std::{collections::HashMap, path::PathBuf};89use anyhow::{Context, anyhow, bail};10use itertools::Itertools;11use serde::Serialize;12use serde::de::DeserializeOwned;13use std::mem::transmute;1415pub use anyhow::Result;16use tracing::{Instrument, info, instrument, warn};1718use self::logging::{ErrorInfoBuilder, nix_logging_cxx};19use self::nix_cxx::set_fetcher_setting;20use self::nix_raw::{21 BindingsBuilder as c_bindings_builder, EvalState as c_eval_state, GC_SUCCESS,22 GC_allow_register_threads, GC_get_stack_base, GC_register_my_thread, GC_stack_base,23 GC_thread_is_registered, GC_unregister_my_thread, ListBuilder as c_list_builder, PrimOp,24 PrimOpFun, Store as c_store, StorePath as c_store_path, alloc_primop, alloc_value,25 bindings_builder_free, bindings_builder_insert, c_context, c_context_create, c_context_free,26 clear_err, copy_value, err_NIX_ERR_KEY, err_NIX_ERR_NIX_ERROR, err_NIX_ERR_OVERFLOW,27 err_NIX_ERR_UNKNOWN, err_code, err_info_msg, err_msg, eval_state_build,28 eval_state_builder_load, eval_state_builder_new, eval_state_builder_set_eval_setting,29 expr_eval_from_string, fetchers_settings, fetchers_settings_free, fetchers_settings_new,30 flake_lock, flake_lock_flags, flake_lock_flags_free, flake_lock_flags_new, flake_reference,31 flake_reference_and_fragment_from_string, flake_reference_parse_flags,32 flake_reference_parse_flags_free, flake_reference_parse_flags_new,33 flake_reference_parse_flags_set_base_directory, flake_settings, flake_settings_free,34 flake_settings_new, gc_now as gc_now_raw, get_attr_byname, get_attr_name_byidx, get_attrs_size,35 get_list_byidx, get_list_size, get_string, get_type, has_attr_byname, init_bool, init_int,36 init_primop, init_string, libexpr_init, libstore_init, libutil_init, list_builder_free,37 list_builder_insert, locked_flake, locked_flake_free, locked_flake_get_output_attrs,38 make_attrs, make_bindings_builder, make_list, make_list_builder, realised_string,39 realised_string_free, realised_string_get_buffer_size, realised_string_get_buffer_start,40 realised_string_get_store_path, realised_string_get_store_path_count, register_primop,41 set_err_msg, setting_set, state_free, store_open, store_parse_path, store_path_free,42 store_path_name, string_realise, value, value_call, value_decref, value_force, value_incref,43};444546pub mod logging;47#[doc(hidden)]48pub mod macros;49pub mod util;5051#[allow(52 non_upper_case_globals,53 non_camel_case_types,54 non_snake_case,55 dead_code56)]57mod nix_raw {58 include!(concat!(env!("OUT_DIR"), "/bindings.rs"));59}60#[cxx::bridge]61pub mod nix_cxx {62 unsafe extern "C++" {63 type nix_fetchers_settings;64 include!("nix-eval/src/lib.hh");6566 #[allow(clippy::missing_safety_doc)]67 unsafe fn set_fetcher_setting(68 settings: *mut nix_fetchers_settings,69 setting: *const c_char,70 value: *const c_char,71 );72 }73}7475#[derive(Debug, PartialEq, Eq)]76pub enum NixType {77 Thunk,78 Int,79 Float,80 Bool,81 String,82 Path,83 Null,84 Attrs,85 List,86 Function,87 External,88}89impl NixType {90 fn from_int(c: c_uint) -> Self {91 match c {92 0 => Self::Thunk,93 1 => Self::Int,94 2 => Self::Float,95 3 => Self::Bool,96 4 => Self::String,97 5 => Self::Path,98 6 => Self::Null,99 7 => Self::Attrs,100 8 => Self::List,101 9 => Self::Function,102 10 => Self::External,103 _ => unreachable!("unknown nix type: {c}"),104 }105 }106}107108enum FunctorKind {109 Function,110 Functor,111}112113#[derive(Debug)]114#[repr(i32)]115pub enum NixErrorKind {116 Unknown = err_NIX_ERR_UNKNOWN,117 Overflow = err_NIX_ERR_OVERFLOW,118 Key = err_NIX_ERR_KEY,119 Generic = err_NIX_ERR_NIX_ERROR,120}121impl NixErrorKind {122 fn from_int(v: c_int) -> Option<Self> {123 Some(match v {124 0 => return None,125 nix_raw::err_NIX_ERR_UNKNOWN => Self::Unknown,126 nix_raw::err_NIX_ERR_OVERFLOW => Self::Overflow,127 nix_raw::err_NIX_ERR_KEY => Self::Key,128 nix_raw::err_NIX_ERR_NIX_ERROR => Self::Generic,129 _ => {130 debug_assert!(false, "unexpected nix error kind: {v}");131 Self::Unknown132 }133 })134 }135}136137pub fn gc_now() {138 unsafe { gc_now_raw() };139}140141pub fn gc_register_my_thread() {142 assert_eq!(unsafe { GC_thread_is_registered() }, 0);143144 let mut sb = GC_stack_base {145 mem_base: null_mut(),146 };147 let r = unsafe { GC_get_stack_base(&mut sb) };148 if r as u32 != GC_SUCCESS {149 panic!("failed to get thread stack base");150 }151 unsafe { GC_register_my_thread(&sb) };152}153pub fn gc_unregister_my_thread() {154 assert_eq!(unsafe { GC_thread_is_registered() }, 1);155156 unsafe { GC_unregister_my_thread() };157}158159pub struct ThreadRegisterGuard {}160impl ThreadRegisterGuard {161 #[allow(clippy::new_without_default)]162 pub fn new() -> Self {163 gc_register_my_thread();164 Self {}165 }166}167impl Drop for ThreadRegisterGuard {168 fn drop(&mut self) {169 gc_unregister_my_thread();170 }171}172173#[repr(transparent)]174pub struct NixContext(*mut c_context);175impl NixContext {176 pub fn set_err_raw(&mut self, err: NixErrorKind, msg: &CStr) {177 unsafe { set_err_msg(self.0, err as c_int, msg.as_ptr()) };178 }179 pub fn set_err(&mut self, err: anyhow::Error) {180 let mut fmt = format!("{err:?}").replace("\0", "\\0");181 self.set_err_raw(182 NixErrorKind::Generic,183 &CString::new(fmt).expect("NUL bytes were just replaced"),184 );185 }186 pub fn new() -> Self {187 let ctx = unsafe { c_context_create() };188 Self(ctx)189 }190 fn error_kind(&self) -> Option<NixErrorKind> {191 let code = unsafe { err_code(self.0) };192 NixErrorKind::from_int(code)193 }194 fn error<'t>(&self) -> Option<(Cow<'t, str>, Option<Box<ErrorInfoBuilder>>)> {195 if let NixErrorKind::Generic = self.error_kind()? {196 let ei = unsafe { logging::nix_logging_cxx::extract_error_info(self.0) };197 let mut err_out = String::new();198 unsafe {199 err_info_msg(200 null_mut(),201 self.0,202 Some(copy_nix_str),203 (&raw mut err_out).cast(),204 )205 };206 return Some((Cow::Owned(err_out), Some(ei)));207 };208209 210 211 let str = unsafe { err_msg(null_mut(), self.0, null_mut()) };212 Some((unsafe { CStr::from_ptr(str) }.to_string_lossy(), None))213 }214 fn clean_err(&mut self) {215 unsafe {216 clear_err(self.0);217 }218 }219220 fn bail_if_error(&self) -> Result<()> {221 if let Some((err, stack)) = self.error() {222 let mut e = Err(anyhow!("{err}"));223 if let Some(stack) = stack {224 for ele in stack.stack_frames {225 e = e.with_context(|| {226 if ele.pos.is_empty() {227 ele.msg228 } else {229 format!("{} at {}", ele.msg, ele.pos)230 }231 })232 }233 }234 return e.context("<nix frames>");235 };236 Ok(())237 }238239 fn run_in_context<T>(&mut self, f: impl FnOnce(*mut c_context) -> T) -> Result<T> {240 self.clean_err();241 let o = f(self.0);242 self.bail_if_error()?;243 self.clean_err();244 Ok(o)245 }246}247248impl Default for NixContext {249 fn default() -> Self {250 Self::new()251 }252}253impl Drop for NixContext {254 fn drop(&mut self) {255 unsafe {256 c_context_free(self.0);257 }258 }259}260struct GlobalState {261 262 #[allow(dead_code)]263 store: Store,264 state: EvalState,265}266impl GlobalState {267 fn new() -> Result<Self> {268 let mut ctx = NixContext::new();269 let store = ctx270 .run_in_context(|c| unsafe { store_open(c, c"auto".as_ptr(), null_mut()) })271 .map(Store)?;272273 let builder = ctx.run_in_context(|c| unsafe { eval_state_builder_new(c, store.0) })?;274 ctx.run_in_context(|c| unsafe { eval_state_builder_load(c, builder) })?;275 ctx.run_in_context(|c| unsafe {276 eval_state_builder_set_eval_setting(277 c,278 builder,279 c"lazy-trees".as_ptr(),280 c"true".as_ptr(),281 )282 })?;283 ctx.run_in_context(|c| unsafe {284 eval_state_builder_set_eval_setting(285 c,286 builder,287 c"lazy-locks".as_ptr(),288 c"true".as_ptr(),289 )290 })?;291 let state = ctx292 .run_in_context(|c| unsafe { eval_state_build(c, builder) })293 .map(EvalState)?;294295 Ok(Self { store, state })296 }297}298299struct ThreadState {300 ctx: NixContext,301}302impl ThreadState {303 fn new() -> Result<Self> {304 let ctx = NixContext::new();305306 Ok(Self { ctx })307 }308}309310static GLOBAL_STATE: LazyLock<GlobalState> = LazyLock::new(|| {311 GlobalState::new().expect("global state init shouldn't fail")312});313314thread_local! {315 static THREAD_STATE: RefCell<ThreadState> = RefCell::new(ThreadState::new().expect("thread state init shouldn't fail"));316}317fn with_default_context<T>(f: impl FnOnce(*mut c_context, *mut c_eval_state) -> T) -> Result<T> {318 let global = &GLOBAL_STATE.state;319 let (ctx, state) = THREAD_STATE.with_borrow_mut(|w| (w.ctx.0, global.0));320 let mut ctx = NixContext(ctx);321 let v = ctx.run_in_context(|c| f(c, state));322 323 std::mem::forget(ctx);324 v325}326327pub fn set_setting(s: &CStr, v: &CStr) -> Result<()> {328 with_default_context(|c, _| unsafe { setting_set(c, s.as_ptr(), v.as_ptr()) }).map(|_| ())329}330331pub struct FetchSettings(*mut fetchers_settings);332impl FetchSettings {333 pub fn new() -> Self {334 Self::try_new().expect("allocation should not fail")335 }336 fn try_new() -> Result<Self> {337 with_default_context(|c, _| unsafe { fetchers_settings_new(c) }).map(Self)338 }339 pub fn set(&mut self, setting: &CStr, value: &CStr) {340 unsafe {341 set_fetcher_setting(self.0.cast(), setting.as_ptr(), value.as_ptr());342 };343 }344}345unsafe impl Send for FetchSettings {}346unsafe impl Sync for FetchSettings {}347348impl Default for FetchSettings {349 fn default() -> Self {350 Self::new()351 }352}353354impl Drop for FetchSettings {355 fn drop(&mut self) {356 unsafe { fetchers_settings_free(self.0) };357 }358}359pub struct FlakeSettings(*mut flake_settings);360impl FlakeSettings {361 pub fn new() -> Result<Self> {362 with_default_context(|c, _| unsafe { flake_settings_new(c) }).map(Self)363 }364}365unsafe impl Send for FlakeSettings {}366unsafe impl Sync for FlakeSettings {}367impl Drop for FlakeSettings {368 fn drop(&mut self) {369 unsafe {370 flake_settings_free(self.0);371 }372 }373}374375pub struct FlakeReferenceParseFlags(*mut flake_reference_parse_flags);376impl FlakeReferenceParseFlags {377 pub fn new(settings: &FlakeSettings) -> Result<Self> {378 with_default_context(|c, _| unsafe { flake_reference_parse_flags_new(c, settings.0) })379 .map(Self)380 }381 pub fn set_base_dir(&mut self, dir: &str) -> Result<()> {382 with_default_context(|c, _| {383 unsafe {384 flake_reference_parse_flags_set_base_directory(385 c,386 self.0,387 dir.as_ptr().cast(),388 dir.len(),389 )390 };391 })392 }393}394impl Drop for FlakeReferenceParseFlags {395 fn drop(&mut self) {396 unsafe {397 flake_reference_parse_flags_free(self.0);398 }399 }400}401pub struct FlakeLockFlags(*mut flake_lock_flags);402impl FlakeLockFlags {403 pub fn new(settings: &FlakeSettings) -> Result<Self> {404 let o = with_default_context(|c, _| unsafe { flake_lock_flags_new(c, settings.0) })405 .map(Self)?;406 407408 Ok(o)409 }410}411impl Drop for FlakeLockFlags {412 fn drop(&mut self) {413 unsafe {414 flake_lock_flags_free(self.0);415 }416 }417}418419unsafe extern "C" fn copy_nix_str(start: *const c_char, n: c_uint, user_data: *mut c_void) {420 let s = unsafe { slice::from_raw_parts(start.cast::<u8>(), n as usize) };421 let s = std::str::from_utf8(s).expect("c string has invalid utf-8");422 unsafe { *user_data.cast::<String>() = s.to_owned() };423}424425struct Store(*mut c_store);426unsafe impl Send for Store {}427unsafe impl Sync for Store {}428429impl Store {430 fn parse_path(&self, path: &CStr) -> Result<StorePath> {431 with_default_context(|c, _| {432 StorePath(unsafe { store_parse_path(c, self.0, path.as_ptr()) })433 })434 }435}436437#[repr(transparent)]438pub struct EvalState(*mut c_eval_state);439unsafe impl Send for EvalState {}440unsafe impl Sync for EvalState {}441442impl Drop for EvalState {443 fn drop(&mut self) {444 unsafe {445 state_free(self.0);446 }447 }448}449450pub struct FlakeReference(*mut flake_reference);451impl FlakeReference {452 #[instrument(name = "new-flake-reference", skip(flake, parse, fetch))]453 pub fn new(454 s: &str,455 flake: &FlakeSettings,456 parse: &FlakeReferenceParseFlags,457 fetch: &FetchSettings,458 ) -> Result<(Self, String)> {459 let mut out = null_mut();460 let mut fragment = String::new();461 462 with_default_context(|c, _| unsafe {463 flake_reference_and_fragment_from_string(464 c,465 fetch.0,466 flake.0,467 parse.0,468 s.as_ptr().cast(),469 s.len(),470 &mut out,471 Some(copy_nix_str),472 (&raw mut fragment).cast(),473 )474 })?;475 assert!(!out.is_null());476477 Ok((Self(out), fragment))478 }479 #[instrument(name = "lock-flake", skip(self, fetch, flake, lock))]480 pub fn lock(481 &mut self,482 fetch: &FetchSettings,483 flake: &FlakeSettings,484 lock: &FlakeLockFlags,485 ) -> Result<LockedFlake> {486 with_default_context(|c, es| unsafe { flake_lock(c, fetch.0, flake.0, es, lock.0, self.0) })487 .map(LockedFlake)488 }489}490unsafe impl Send for FlakeReference {}491unsafe impl Sync for FlakeReference {}492493pub struct LockedFlake(*mut locked_flake);494impl LockedFlake {495 pub fn get_attrs(&self, settings: &mut FlakeSettings) -> Result<Value> {496 with_default_context(|c, es| unsafe {497 locked_flake_get_output_attrs(c, settings.0, es, self.0)498 })499 .map(Value)500 }501}502unsafe impl Send for LockedFlake {}503unsafe impl Sync for LockedFlake {}504impl Drop for LockedFlake {505 fn drop(&mut self) {506 unsafe {507 locked_flake_free(self.0);508 };509 }510}511512type FieldName = [u8; 64];513fn init_field_name(v: &str) -> FieldName {514 let mut f = [0; 64];515 assert!(v.len() < 64, "max field name is 63 chars");516 assert!(517 v.bytes().all(|v| v != 0),518 "nul bytes are unsupported in field name"519 );520 f[0..v.len()].copy_from_slice(v.as_bytes());521 f522}523524pub struct RealisedString(*mut realised_string);525impl fmt::Debug for RealisedString {526 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {527 self.as_str().fmt(f)528 }529}530531impl RealisedString {532 pub fn as_str(&self) -> &str {533 let len = unsafe { realised_string_get_buffer_size(self.0) };534 let data: *const u8 = unsafe { realised_string_get_buffer_start(self.0) }.cast();535 let data = unsafe { slice::from_raw_parts(data, len) };536 std::str::from_utf8(data).expect("non-utf8 strings not supported")537 }538 pub fn path_count(&self) -> usize {539 unsafe { realised_string_get_store_path_count(self.0) }540 }541 pub fn path(&self, i: usize) -> String {542 assert!(i < self.path_count());543 let path = unsafe { realised_string_get_store_path(self.0, i) };544 let mut err_out = String::new();545 unsafe { store_path_name(path, Some(copy_nix_str), (&raw mut err_out).cast()) };546 err_out547 }548}549550unsafe impl Send for RealisedString {}551impl Drop for RealisedString {552 fn drop(&mut self) {553 unsafe { realised_string_free(self.0) }554 }555}556557#[repr(transparent)]558pub struct Value(*mut value);559560unsafe impl Send for Value {}561unsafe impl Sync for Value {}562563pub trait AsFieldName {564 fn as_field_name<T>(&self, v: impl FnOnce(FieldName) -> Result<T>) -> Result<T>;565 fn to_field_name(&self) -> Result<String>;566}567impl AsFieldName for Value {568 fn as_field_name<T>(&self, v: impl FnOnce(FieldName) -> Result<T>) -> Result<T> {569 let f = self.to_string()?;570 v(init_field_name(&f))571 }572 fn to_field_name(&self) -> Result<String> {573 self.to_string()574 }575}576impl<E> AsFieldName for E577where578 E: AsRef<str>,579{580 fn as_field_name<T>(&self, v: impl FnOnce(FieldName) -> Result<T>) -> Result<T> {581 let f = self.as_ref();582 v(init_field_name(f))583 }584 fn to_field_name(&self) -> Result<String> {585 Ok(self.as_ref().to_owned())586 }587}588589struct AttrsBuilder(*mut c_bindings_builder);590impl AttrsBuilder {591 fn new(capacity: usize) -> Self {592 with_default_context(|c, es| unsafe { make_bindings_builder(c, es, capacity) })593 .map(Self)594 .expect("alloc should not fail")595 }596 fn insert(&mut self, k: &impl AsFieldName, v: Value) {597 k.as_field_name(|name| {598 with_default_context(|c, _| unsafe {599 bindings_builder_insert(c, self.0, name.as_ptr().cast(), v.0);600 601 })602 })603 .expect("builder insert shouldn't fail");604 }605}606impl Drop for AttrsBuilder {607 fn drop(&mut self) {608 unsafe { bindings_builder_free(self.0) };609 }610}611612struct ListBuilder(*mut c_list_builder, c_uint);613impl ListBuilder {614 fn new(capacity: usize) -> Self {615 with_default_context(|c, es| unsafe { make_list_builder(c, es, capacity) })616 .map(|l| Self(l, 0))617 .expect("alloc should not fail")618 }619}620impl ListBuilder {621 fn push(&mut self, v: Value) {622 with_default_context(|c, _| unsafe {623 list_builder_insert(624 c,625 self.0,626 {627 let v = self.1;628 self.1 += 1;629 v630 },631 v.0,632 )633 })634 .expect("list insert shouldn't fail");635 }636}637impl Drop for ListBuilder {638 fn drop(&mut self) {639 unsafe { list_builder_free(self.0) };640 }641}642643impl Value {644 pub fn new_primop(v: NativeFn) -> Self {645 let out = Self::new_uninit();646 with_default_context(|c, _| unsafe { init_primop(c, out.0, v.0) })647 .expect("primop initialization should not fail");648 out649 }650 pub fn new_attrs(v: HashMap<&str, Value>) -> Self {651 let out = Self::new_uninit();652 let mut b = AttrsBuilder::new(v.len());653 for (k, v) in v {654 b.insert(&k, v);655 }656 with_default_context(|c, _| unsafe { make_attrs(c, out.0, b.0) })657 .expect("attrs initialization should not fail");658659 out660 }661 fn new_list<T: Into<Self>>(v: Vec<T>) -> Self {662 let out = Self::new_uninit();663 let mut b = ListBuilder::new(v.len());664 for v in v {665 b.push(v.into());666 }667 with_default_context(|c, _| unsafe { make_list(c, b.0, out.0) })668 .expect("list initialization should not fail");669670 out671 }672 fn new_uninit() -> Self {673 let out = with_default_context(|c, es| unsafe { alloc_value(c, es) })674 .expect("value allocation should not fail");675 Self(out)676 }677 pub fn new_str(v: &str) -> Self {678 let s = CString::new(v).expect("string should not contain NULs");679 let out = Self::new_uninit();680 681 with_default_context(|c, _| unsafe { init_string(c, out.0, s.as_ptr()) })682 .expect("string initialization should not fail");683 out684 }685 pub fn new_int(i: i64) -> Self {686 let out = Self::new_uninit();687 with_default_context(|c, _| unsafe { init_int(c, out.0, i) })688 .expect("int initialization should not fail");689 out690 }691 pub fn new_bool(v: bool) -> Self {692 let out = Self::new_uninit();693 with_default_context(|c, _| unsafe { init_bool(c, out.0, v) })694 .expect("bool initialization should not fail");695 out696 }697 698 699 700 701 702 pub fn type_of(&self) -> NixType {703 let ty = with_default_context(|c, _| unsafe { get_type(c, self.0) })704 .expect("get_type should not fail");705 NixType::from_int(ty)706 }707 fn builtin_to_string(&self) -> Result<Self> {708 let builtin = Self::eval("builtins.toString")?;709 builtin.call(self.clone())710 }711 fn force(&mut self, s: *mut nix_raw::EvalState) -> Result<()> {712 with_default_context(|c, _| unsafe { value_force(c, s, self.0) })?;713 Ok(())714 }715 pub fn to_string(&self) -> Result<String> {716 let ty = self.type_of();717 if !matches!(ty, NixType::String) {718 bail!("unexpected type: {ty:?}, expected string");719 }720 let mut str_out = String::new();721 with_default_context(|c, _| unsafe {722 get_string(c, self.0, Some(copy_nix_str), (&raw mut str_out).cast())723 })?;724725 Ok(str_out)726 }727 pub fn to_realised_string(&self) -> Result<RealisedString> {728 with_default_context(|c, es| unsafe { string_realise(c, es, self.0, false) })729 .map(RealisedString)730731 732 733 734 735 736 737 738 }739740 pub fn has_field(&self, field: &str) -> Result<bool> {741 if !matches!(self.type_of(), NixType::Attrs) {742 bail!("invalid type: expected attrs");743 }744745 let f = init_field_name(field);746 with_default_context(|c, es| unsafe { has_attr_byname(c, self.0, es, f.as_ptr().cast()) })747 }748 749 750 751 pub fn list_fields(&self) -> Result<Vec<String>> {752 if !matches!(self.type_of(), NixType::Attrs) {753 bail!("invalid type: expected attrs");754 }755756 let len = with_default_context(|c, _| unsafe { get_attrs_size(c, self.0) })?;757 let mut out = Vec::with_capacity(len as usize);758759 for i in 0..len {760 let name =761 with_default_context(|c, es| unsafe { get_attr_name_byidx(c, self.0, es, i) })?;762 let c = unsafe { CStr::from_ptr(name) };763 out.push(c.to_str().expect("nix field names are utf-8").to_owned());764 }765 Ok(out)766 }767 pub fn get_elem(&self, v: usize) -> Result<Self> {768 if !matches!(self.type_of(), NixType::List) {769 bail!("invalid type: expected list");770 }771 let len = with_default_context(|c, _| unsafe { get_list_size(c, self.0) })? as usize;772 if v >= len {773 bail!("oob list get: {v} >= {len}");774 }775776 with_default_context(|c, es| unsafe { get_list_byidx(c, self.0, es, v as u32) }).map(Self)777 }778 pub fn attrs_update(self, other: Value ) -> Result<Self> {779 let attrs_update_fn = Self::eval("a: b: a // b")?;780781 attrs_update_fn782 .call(self)?783 .call(other)784 .context("attrs update")785 }786 pub fn get_field(&self, name: impl AsFieldName) -> Result<Self> {787 if !matches!(self.type_of(), NixType::Attrs) {788 bail!("invalid type: expected attrs");789 }790791 name.as_field_name(|name| {792 with_default_context(|c, es| unsafe {793 get_attr_byname(c, self.0, es, name.as_ptr().cast())794 })795 .map(Self)796 })797 .with_context(|| format!("getting field {:?}", name.to_field_name()))798 }799 pub fn call(&self, v: Value) -> Result<Self> {800 let kind = self801 .functor_kind()802 .ok_or_else(|| anyhow!("can only call function or functor"))?;803804 let function = match kind {805 FunctorKind::Function => self.clone(),806 FunctorKind::Functor => {807 let f = self808 .get_field("__functor")809 .context("getting functor value")?;810 assert_eq!(811 f.type_of(),812 NixType::Function,813 "invalid functor encountered"814 );815 f816 }817 };818819 let out = Value::new_uninit();820 with_default_context(|c, es| unsafe { value_call(c, es, function.0, v.0, out.0) })?;821822 Ok(out)823 }824 pub fn eval(v: &str) -> Result<Self> {825 let s = CString::new(v).expect("expression shouldn't have internal NULs");826 let out = Self::new_uninit();827 with_default_context(|c, es| unsafe {828 expr_eval_from_string(c, es, s.as_ptr(), c"/root".as_ptr(), out.0)829 })?;830 Ok(out)831 }832 pub fn build(&self, output: &str) -> Result<PathBuf> {833 if !self.is_derivation() {834 bail!("expected derivation to build")835 }836 let output_name = self837 .get_field("outputName")838 .context("getting output name field")?839 .to_string()?;840 let v = if output_name != output {841 let out = self.get_field(output).context("getting target output")?;842 if !out.is_derivation() {843 bail!("unknown output: {output}");844 }845 out846 } else {847 self.clone()848 };849 850 let s = v.builtin_to_string()?;851 let rs = s.to_realised_string()?;852 let drv_path = rs.as_str().to_owned();853 Ok(PathBuf::from(drv_path))854 }855 pub fn as_json<T: DeserializeOwned>(&self) -> Result<T> {856 let to_json = Self::eval("builtins.toJSON")?;857 let s = to_json.call(self.clone())?.to_string()?;858 Ok(serde_json::from_str(&s)?)859 }860 pub fn serialized<T: Serialize>(v: &T) -> Result<Self> {861 Self::eval(&nixlike::serialize(v)?)862 }863864 865 866 867 868 869870 fn is_derivation(&self) -> bool {871 if !matches!(self.type_of(), NixType::Attrs) {872 return false;873 }874 let Some(ty) = self.get_field("type").ok() else {875 return false;876 };877 matches!(ty.to_string().as_deref(), Ok("derivation"))878 }879 fn functor_kind(&self) -> Option<FunctorKind> {880 match self.type_of() {881 NixType::Attrs => self882 .has_field("__functor")883 .expect("has_field shouldn't fail for attrs")884 .then_some(FunctorKind::Functor),885 NixType::Function => Some(FunctorKind::Function),886 _ => None,887 }888 }889 pub fn is_function(&self) -> bool {890 self.functor_kind().is_some()891 }892 pub fn is_null(&self) -> bool {893 matches!(self.type_of(), NixType::Null)894 }895 pub fn is_string(&self) -> bool {896 matches!(self.type_of(), NixType::String)897 }898 pub fn is_attrs(&self) -> bool {899 matches!(self.type_of(), NixType::Attrs)900 }901}902903impl From<String> for Value {904 fn from(value: String) -> Self {905 Value::new_str(&value)906 }907}908impl From<bool> for Value {909 fn from(value: bool) -> Self {910 Value::new_bool(value)911 }912}913impl From<&str> for Value {914 fn from(value: &str) -> Self {915 Value::new_str(value)916 }917}918impl<T> From<Vec<T>> for Value919where920 T: Into<Value>,921{922 fn from(value: Vec<T>) -> Self {923 Value::new_list(value)924 }925}926927impl Clone for Value {928 fn clone(&self) -> Self {929 with_default_context(|c, _| unsafe { value_incref(c, self.0) })930 .expect("value incref should not fail");931 Self(self.0)932 }933}934impl Drop for Value {935 fn drop(&mut self) {936 with_default_context(|c, _| unsafe { value_decref(c, self.0) })937 .expect("value drop should not fail");938 }939}940941static TOKIO_FOR_NIX: OnceLock<Arc<tokio::runtime::Runtime>> = OnceLock::new();942943pub fn init_libraries() {944 unsafe { GC_allow_register_threads() };945946 let mut ctx = NixContext::new();947 ctx.run_in_context(|c| unsafe { libutil_init(c) })948 .expect("util init should not fail");949 ctx.run_in_context(|c| unsafe { libstore_init(c) })950 .expect("store init should not fail");951 ctx.run_in_context(|c| unsafe { libexpr_init(c) })952 .expect("expr init should not fail");953954 nix_logging_cxx::apply_tracing_logger();955}956957pub fn init_tokio_for_nix(tokio: Arc<tokio::runtime::Runtime>) {958 TOKIO_FOR_NIX959 .set(tokio)960 .expect("tokio for nix should only be initialized once");961}962963pub fn await_in_nix<F: Send + 'static>(f: impl Future<Output = F> + Send + 'static) -> F {964 965 let runtime = TOKIO_FOR_NIX966 .get()967 .expect("init_tokio_for_nix was not called");968 std::thread::spawn(move || runtime.block_on(f)).join().expect("await_in_nix inner thread panicked")969}970971unsafe extern "C" fn nix_primop_closure_adapter<const N: usize>(972 user_data: *mut c_void,973 mut context: *mut c_context,974 state: *mut nix_raw::EvalState,975 args: *mut *mut value,976 ret: *mut value,977) {978 let user_closure: &UserClosure<N> = unsafe { &*user_data.cast_const().cast() };979 let args: [&Value; N] = array::from_fn(|i| {980 let v: &mut Value = unsafe { &mut *args.add(i).cast() };981 v as &Value982 });983 let ctx: &mut NixContext = unsafe { transmute(&mut context) };984985 let state: &EvalState = unsafe { std::mem::transmute(&state) };986987 match user_closure(state, args) {988 Ok(v) => {989 unsafe { copy_value(context, ret, v.0) };990 }991 Err(e) => {992 ctx.set_err(e);993 }994 }995}996997type UserClosure<const N: usize> = Box<dyn Fn(&EvalState, [&Value; N]) -> Result<Value>>;998999pub struct NativeFn(*mut PrimOp);1000impl NativeFn {1001 pub fn new<const N: usize>(1002 name: &'static CStr,1003 doc: &'static CStr,1004 args: [&'static CStr; N],1005 f: impl Fn(&EvalState, [&Value; N]) -> Result<Value> + 'static,1006 ) -> Self {1007 1008 let closure: Box<UserClosure<N>> = Box::new(Box::new(f));1009 let f: PrimOpFun = Some(nix_primop_closure_adapter::<N>);1010 let mut args = args.into_iter().map(|v| v.as_ptr()).collect_vec();1011 args.push(null());1012 let args = args.as_mut_ptr();1013 let primop = unsafe {1014 alloc_primop(1015 null_mut(),1016 f,1017 N as i32,1018 name.as_ptr(),1019 args,1020 doc.as_ptr(),1021 Box::into_raw(closure).cast(),1022 )1023 };10241025 assert!(!primop.is_null(), "primop allocation should not fail");10261027 Self(primop)1028 }1029 pub fn register(self) {1030 unsafe { register_primop(null_mut(), self.0) };1031 }1032}10331034struct StorePath(*mut c_store_path);1035impl StorePath {}10361037impl Drop for StorePath {1038 fn drop(&mut self) {1039 unsafe { store_path_free(self.0) }1040 }1041}10421043#[test_log::test]1044fn test_native() -> Result<()> {1045 init_libraries();1046 NativeFn::new(1047 c"__uppercaseSuffix2",1048 c"make string uppercase and add suffix",1049 [c"str", c"suffix"],1050 |_, [str, suffix]: [&Value; 2]| {1051 let str = str.to_string()?;1052 let suffix = suffix.to_string()?;1053 Ok(Value::new_str(&format!("{}{suffix}", str.to_uppercase())))1054 },1055 )1056 .register();10571058 let mut fetch_settings = FetchSettings::new();1059 fetch_settings.set(c"warn-dirty", c"false");10601061 let manifest = format!("git+file://{}/../../", env!("CARGO_MANIFEST_DIR"));1062 let flake = FlakeSettings::new()?;1063 let parse = FlakeReferenceParseFlags::new(&flake)?;1064 let (mut r, _) = FlakeReference::new(&manifest, &flake, &parse, &fetch_settings)?;1065 let lock = FlakeLockFlags::new(&flake)?;1066 let locked = r.lock(&fetch_settings, &flake, &lock)?;1067 let attrs = locked.get_attrs(&mut FlakeSettings::new()?)?;10681069 let builtins = Value::eval("builtins")?;1070 assert_eq!(builtins.type_of(), NixType::Attrs);10711072 assert_eq!(attrs.type_of(), NixType::Attrs);1073 let test_data = nix_go!(attrs.testData);10741075 let test_string: String = nix_go_json!(test_data.testString);1076 assert_eq!(test_string, "hello");10771078 let s = nix_go!(attrs.packages["x86_64-linux"].fleet.drvPath);1079 let s = CString::new(s.to_string()?).expect("path str is cstring");10801081 let uppercase_suffix = Value::new_primop(NativeFn::new(1082 c"uppercase_suffix",1083 c"make string uppercase and add suffix",1084 [c"str", c"suffix"],1085 |es, [str, suffix]: [&Value; 2]| {1086 let str = str.to_string()?;1087 let suffix = suffix.to_string()?;1088 Ok(Value::new_str(&format!("{}{suffix}", str.to_uppercase())))1089 },1090 ));10911092 let test_result: String = nix_go_json!(test_data.testPrimop(uppercase_suffix));1093 assert_eq!(test_result, "PREFIX_BODY_SUFFIX");1094 let test_result: String = nix_go_json!(builtins.uppercaseSuffix2("test")("suffix"));1095 assert_eq!(test_result, "TESTsuffix");10961097 let nix_ctx = NixContext::new();1098 let store = GLOBAL_STATE.store.parse_path(s.as_c_str())?;10991100 11011102 Ok(())1103}1104110511061107110811091110111111121113111411151116111711181119112011211122