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;4950#[doc(hidden)]51pub mod __macro_support {52 pub use std::collections::hash_map::HashMap;5354 pub use anyhow::Context;55 pub use tokio::task::block_in_place;56}57pub mod util;5859#[allow(60 non_upper_case_globals,61 non_camel_case_types,62 non_snake_case,63 dead_code64)]65mod nix_raw {66 include!(concat!(env!("OUT_DIR"), "/bindings.rs"));67}68#[cxx::bridge]69pub mod nix_cxx {70 unsafe extern "C++" {71 type nix_fetchers_settings;72 include!("nix-eval/src/lib.hh");7374 #[allow(clippy::missing_safety_doc)]75 unsafe fn set_fetcher_setting(76 settings: *mut nix_fetchers_settings,77 setting: *const c_char,78 value: *const c_char,79 );80 }81}8283#[derive(Debug, PartialEq, Eq)]84pub enum NixType {85 Thunk,86 Int,87 Float,88 Bool,89 String,90 Path,91 Null,92 Attrs,93 List,94 Function,95 External,96}97impl NixType {98 fn from_int(c: c_uint) -> Self {99 match c {100 0 => Self::Thunk,101 1 => Self::Int,102 2 => Self::Float,103 3 => Self::Bool,104 4 => Self::String,105 5 => Self::Path,106 6 => Self::Null,107 7 => Self::Attrs,108 8 => Self::List,109 9 => Self::Function,110 10 => Self::External,111 _ => unreachable!("unknown nix type: {c}"),112 }113 }114}115116enum FunctorKind {117 Function,118 Functor,119}120121#[derive(Debug)]122#[repr(i32)]123pub enum NixErrorKind {124 Unknown = err_NIX_ERR_UNKNOWN,125 Overflow = err_NIX_ERR_OVERFLOW,126 Key = err_NIX_ERR_KEY,127 Generic = err_NIX_ERR_NIX_ERROR,128}129impl NixErrorKind {130 fn from_int(v: c_int) -> Option<Self> {131 Some(match v {132 0 => return None,133 nix_raw::err_NIX_ERR_UNKNOWN => Self::Unknown,134 nix_raw::err_NIX_ERR_OVERFLOW => Self::Overflow,135 nix_raw::err_NIX_ERR_KEY => Self::Key,136 nix_raw::err_NIX_ERR_NIX_ERROR => Self::Generic,137 _ => {138 debug_assert!(false, "unexpected nix error kind: {v}");139 Self::Unknown140 }141 })142 }143}144145pub fn gc_now() {146 unsafe { gc_now_raw() };147}148149pub fn gc_register_my_thread() {150 assert_eq!(unsafe { GC_thread_is_registered() }, 0);151152 let mut sb = GC_stack_base {153 mem_base: null_mut(),154 };155 let r = unsafe { GC_get_stack_base(&mut sb) };156 if r as u32 != GC_SUCCESS {157 panic!("failed to get thread stack base");158 }159 unsafe { GC_register_my_thread(&sb) };160}161pub fn gc_unregister_my_thread() {162 assert_eq!(unsafe { GC_thread_is_registered() }, 1);163164 unsafe { GC_unregister_my_thread() };165}166167pub struct ThreadRegisterGuard {}168impl ThreadRegisterGuard {169 #[allow(clippy::new_without_default)]170 pub fn new() -> Self {171 gc_register_my_thread();172 Self {}173 }174}175impl Drop for ThreadRegisterGuard {176 fn drop(&mut self) {177 gc_unregister_my_thread();178 }179}180181#[repr(transparent)]182pub struct NixContext(*mut c_context);183impl NixContext {184 pub fn set_err_raw(&mut self, err: NixErrorKind, msg: &CStr) {185 unsafe { set_err_msg(self.0, err as c_int, msg.as_ptr()) };186 }187 pub fn set_err(&mut self, err: anyhow::Error) {188 let mut fmt = format!("{err:?}").replace("\0", "\\0");189 self.set_err_raw(190 NixErrorKind::Generic,191 &CString::new(fmt).expect("NUL bytes were just replaced"),192 );193 }194 pub fn new() -> Self {195 let ctx = unsafe { c_context_create() };196 Self(ctx)197 }198 fn error_kind(&self) -> Option<NixErrorKind> {199 let code = unsafe { err_code(self.0) };200 NixErrorKind::from_int(code)201 }202 fn error<'t>(&self) -> Option<(Cow<'t, str>, Option<Box<ErrorInfoBuilder>>)> {203 if let NixErrorKind::Generic = self.error_kind()? {204 let ei = unsafe { logging::nix_logging_cxx::extract_error_info(self.0) };205 let mut err_out = String::new();206 unsafe {207 err_info_msg(208 null_mut(),209 self.0,210 Some(copy_nix_str),211 (&raw mut err_out).cast(),212 )213 };214 return Some((Cow::Owned(err_out), Some(ei)));215 };216217 218 219 let str = unsafe { err_msg(null_mut(), self.0, null_mut()) };220 Some((unsafe { CStr::from_ptr(str) }.to_string_lossy(), None))221 }222 fn clean_err(&mut self) {223 unsafe {224 clear_err(self.0);225 }226 }227228 fn bail_if_error(&self) -> Result<()> {229 if let Some((err, stack)) = self.error() {230 let mut e = Err(anyhow!("{err}"));231 if let Some(stack) = stack {232 for ele in stack.stack_frames {233 e = e.with_context(|| {234 if ele.pos.is_empty() {235 ele.msg236 } else {237 format!("{} at {}", ele.msg, ele.pos)238 }239 })240 }241 }242 return e.context("<nix frames>");243 };244 Ok(())245 }246247 fn run_in_context<T>(&mut self, f: impl FnOnce(*mut c_context) -> T) -> Result<T> {248 self.clean_err();249 let o = f(self.0);250 self.bail_if_error()?;251 self.clean_err();252 Ok(o)253 }254}255256impl Default for NixContext {257 fn default() -> Self {258 Self::new()259 }260}261impl Drop for NixContext {262 fn drop(&mut self) {263 unsafe {264 c_context_free(self.0);265 }266 }267}268struct GlobalState {269 270 #[allow(dead_code)]271 store: Store,272 state: EvalState,273}274impl GlobalState {275 fn new() -> Result<Self> {276 let mut ctx = NixContext::new();277 let store = ctx278 .run_in_context(|c| unsafe { store_open(c, c"auto".as_ptr(), null_mut()) })279 .map(Store)?;280281 let builder = ctx.run_in_context(|c| unsafe { eval_state_builder_new(c, store.0) })?;282 ctx.run_in_context(|c| unsafe { eval_state_builder_load(c, builder) })?;283 ctx.run_in_context(|c| unsafe {284 eval_state_builder_set_eval_setting(285 c,286 builder,287 c"lazy-trees".as_ptr(),288 c"true".as_ptr(),289 )290 })?;291 ctx.run_in_context(|c| unsafe {292 eval_state_builder_set_eval_setting(293 c,294 builder,295 c"lazy-locks".as_ptr(),296 c"true".as_ptr(),297 )298 })?;299 let state = ctx300 .run_in_context(|c| unsafe { eval_state_build(c, builder) })301 .map(EvalState)?;302303 Ok(Self { store, state })304 }305}306307struct ThreadState {308 ctx: NixContext,309}310impl ThreadState {311 fn new() -> Result<Self> {312 let ctx = NixContext::new();313314 Ok(Self { ctx })315 }316}317318static GLOBAL_STATE: LazyLock<GlobalState> =319 LazyLock::new(|| GlobalState::new().expect("global state init shouldn't fail"));320321thread_local! {322 static THREAD_STATE: RefCell<ThreadState> = RefCell::new(ThreadState::new().expect("thread state init shouldn't fail"));323}324fn with_default_context<T>(f: impl FnOnce(*mut c_context, *mut c_eval_state) -> T) -> Result<T> {325 let global = &GLOBAL_STATE.state;326 let (ctx, state) = THREAD_STATE.with_borrow_mut(|w| (w.ctx.0, global.0));327 let mut ctx = NixContext(ctx);328 let v = ctx.run_in_context(|c| f(c, state));329 330 std::mem::forget(ctx);331 v332}333334pub fn set_setting(s: &CStr, v: &CStr) -> Result<()> {335 with_default_context(|c, _| unsafe { setting_set(c, s.as_ptr(), v.as_ptr()) }).map(|_| ())336}337338pub struct FetchSettings(*mut fetchers_settings);339impl FetchSettings {340 pub fn new() -> Self {341 Self::try_new().expect("allocation should not fail")342 }343 fn try_new() -> Result<Self> {344 with_default_context(|c, _| unsafe { fetchers_settings_new(c) }).map(Self)345 }346 pub fn set(&mut self, setting: &CStr, value: &CStr) {347 unsafe {348 set_fetcher_setting(self.0.cast(), setting.as_ptr(), value.as_ptr());349 };350 }351}352unsafe impl Send for FetchSettings {}353unsafe impl Sync for FetchSettings {}354355impl Default for FetchSettings {356 fn default() -> Self {357 Self::new()358 }359}360361impl Drop for FetchSettings {362 fn drop(&mut self) {363 unsafe { fetchers_settings_free(self.0) };364 }365}366pub struct FlakeSettings(*mut flake_settings);367impl FlakeSettings {368 pub fn new() -> Result<Self> {369 with_default_context(|c, _| unsafe { flake_settings_new(c) }).map(Self)370 }371}372unsafe impl Send for FlakeSettings {}373unsafe impl Sync for FlakeSettings {}374impl Drop for FlakeSettings {375 fn drop(&mut self) {376 unsafe {377 flake_settings_free(self.0);378 }379 }380}381382pub struct FlakeReferenceParseFlags(*mut flake_reference_parse_flags);383impl FlakeReferenceParseFlags {384 pub fn new(settings: &FlakeSettings) -> Result<Self> {385 with_default_context(|c, _| unsafe { flake_reference_parse_flags_new(c, settings.0) })386 .map(Self)387 }388 pub fn set_base_dir(&mut self, dir: &str) -> Result<()> {389 with_default_context(|c, _| {390 unsafe {391 flake_reference_parse_flags_set_base_directory(392 c,393 self.0,394 dir.as_ptr().cast(),395 dir.len(),396 )397 };398 })399 }400}401impl Drop for FlakeReferenceParseFlags {402 fn drop(&mut self) {403 unsafe {404 flake_reference_parse_flags_free(self.0);405 }406 }407}408pub struct FlakeLockFlags(*mut flake_lock_flags);409impl FlakeLockFlags {410 pub fn new(settings: &FlakeSettings) -> Result<Self> {411 let o = with_default_context(|c, _| unsafe { flake_lock_flags_new(c, settings.0) })412 .map(Self)?;413 414415 Ok(o)416 }417}418impl Drop for FlakeLockFlags {419 fn drop(&mut self) {420 unsafe {421 flake_lock_flags_free(self.0);422 }423 }424}425426unsafe extern "C" fn copy_nix_str(start: *const c_char, n: c_uint, user_data: *mut c_void) {427 let s = unsafe { slice::from_raw_parts(start.cast::<u8>(), n as usize) };428 let s = std::str::from_utf8(s).expect("c string has invalid utf-8");429 unsafe { *user_data.cast::<String>() = s.to_owned() };430}431432struct Store(*mut c_store);433unsafe impl Send for Store {}434unsafe impl Sync for Store {}435436impl Store {437 fn parse_path(&self, path: &CStr) -> Result<StorePath> {438 with_default_context(|c, _| {439 StorePath(unsafe { store_parse_path(c, self.0, path.as_ptr()) })440 })441 }442}443444#[repr(transparent)]445pub struct EvalState(*mut c_eval_state);446unsafe impl Send for EvalState {}447unsafe impl Sync for EvalState {}448449impl Drop for EvalState {450 fn drop(&mut self) {451 unsafe {452 state_free(self.0);453 }454 }455}456457pub struct FlakeReference(*mut flake_reference);458impl FlakeReference {459 #[instrument(name = "new-flake-reference", skip(flake, parse, fetch))]460 pub fn new(461 s: &str,462 flake: &FlakeSettings,463 parse: &FlakeReferenceParseFlags,464 fetch: &FetchSettings,465 ) -> Result<(Self, String)> {466 let mut out = null_mut();467 let mut fragment = String::new();468 469 with_default_context(|c, _| unsafe {470 flake_reference_and_fragment_from_string(471 c,472 fetch.0,473 flake.0,474 parse.0,475 s.as_ptr().cast(),476 s.len(),477 &mut out,478 Some(copy_nix_str),479 (&raw mut fragment).cast(),480 )481 })?;482 assert!(!out.is_null());483484 Ok((Self(out), fragment))485 }486 #[instrument(name = "lock-flake", skip(self, fetch, flake, lock))]487 pub fn lock(488 &mut self,489 fetch: &FetchSettings,490 flake: &FlakeSettings,491 lock: &FlakeLockFlags,492 ) -> Result<LockedFlake> {493 with_default_context(|c, es| unsafe { flake_lock(c, fetch.0, flake.0, es, lock.0, self.0) })494 .map(LockedFlake)495 }496}497unsafe impl Send for FlakeReference {}498unsafe impl Sync for FlakeReference {}499500pub struct LockedFlake(*mut locked_flake);501impl LockedFlake {502 pub fn get_attrs(&self, settings: &mut FlakeSettings) -> Result<Value> {503 with_default_context(|c, es| unsafe {504 locked_flake_get_output_attrs(c, settings.0, es, self.0)505 })506 .map(Value)507 }508}509unsafe impl Send for LockedFlake {}510unsafe impl Sync for LockedFlake {}511impl Drop for LockedFlake {512 fn drop(&mut self) {513 unsafe {514 locked_flake_free(self.0);515 };516 }517}518519type FieldName = [u8; 64];520fn init_field_name(v: &str) -> FieldName {521 let mut f = [0; 64];522 assert!(v.len() < 64, "max field name is 63 chars");523 assert!(524 v.bytes().all(|v| v != 0),525 "nul bytes are unsupported in field name"526 );527 f[0..v.len()].copy_from_slice(v.as_bytes());528 f529}530531pub struct RealisedString(*mut realised_string);532impl fmt::Debug for RealisedString {533 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {534 self.as_str().fmt(f)535 }536}537538impl RealisedString {539 pub fn as_str(&self) -> &str {540 let len = unsafe { realised_string_get_buffer_size(self.0) };541 let data: *const u8 = unsafe { realised_string_get_buffer_start(self.0) }.cast();542 let data = unsafe { slice::from_raw_parts(data, len) };543 std::str::from_utf8(data).expect("non-utf8 strings not supported")544 }545 pub fn path_count(&self) -> usize {546 unsafe { realised_string_get_store_path_count(self.0) }547 }548 pub fn path(&self, i: usize) -> String {549 assert!(i < self.path_count());550 let path = unsafe { realised_string_get_store_path(self.0, i) };551 let mut err_out = String::new();552 unsafe { store_path_name(path, Some(copy_nix_str), (&raw mut err_out).cast()) };553 err_out554 }555}556557unsafe impl Send for RealisedString {}558impl Drop for RealisedString {559 fn drop(&mut self) {560 unsafe { realised_string_free(self.0) }561 }562}563564#[repr(transparent)]565pub struct Value(*mut value);566567unsafe impl Send for Value {}568unsafe impl Sync for Value {}569570pub trait AsFieldName {571 fn as_field_name<T>(&self, v: impl FnOnce(FieldName) -> Result<T>) -> Result<T>;572 fn to_field_name(&self) -> Result<String>;573}574impl AsFieldName for Value {575 fn as_field_name<T>(&self, v: impl FnOnce(FieldName) -> Result<T>) -> Result<T> {576 let f = self.to_string()?;577 v(init_field_name(&f))578 }579 fn to_field_name(&self) -> Result<String> {580 self.to_string()581 }582}583impl<E> AsFieldName for E584where585 E: AsRef<str>,586{587 fn as_field_name<T>(&self, v: impl FnOnce(FieldName) -> Result<T>) -> Result<T> {588 let f = self.as_ref();589 v(init_field_name(f))590 }591 fn to_field_name(&self) -> Result<String> {592 Ok(self.as_ref().to_owned())593 }594}595596struct AttrsBuilder(*mut c_bindings_builder);597impl AttrsBuilder {598 fn new(capacity: usize) -> Self {599 with_default_context(|c, es| unsafe { make_bindings_builder(c, es, capacity) })600 .map(Self)601 .expect("alloc should not fail")602 }603 fn insert(&mut self, k: &impl AsFieldName, v: Value) {604 k.as_field_name(|name| {605 with_default_context(|c, _| unsafe {606 bindings_builder_insert(c, self.0, name.as_ptr().cast(), v.0);607 608 })609 })610 .expect("builder insert shouldn't fail");611 }612}613impl Drop for AttrsBuilder {614 fn drop(&mut self) {615 unsafe { bindings_builder_free(self.0) };616 }617}618619struct ListBuilder(*mut c_list_builder, c_uint);620impl ListBuilder {621 fn new(capacity: usize) -> Self {622 with_default_context(|c, es| unsafe { make_list_builder(c, es, capacity) })623 .map(|l| Self(l, 0))624 .expect("alloc should not fail")625 }626}627impl ListBuilder {628 fn push(&mut self, v: Value) {629 with_default_context(|c, _| unsafe {630 list_builder_insert(631 c,632 self.0,633 {634 let v = self.1;635 self.1 += 1;636 v637 },638 v.0,639 )640 })641 .expect("list insert shouldn't fail");642 }643}644impl Drop for ListBuilder {645 fn drop(&mut self) {646 unsafe { list_builder_free(self.0) };647 }648}649650impl Value {651 pub fn new_primop(v: NativeFn) -> Self {652 let out = Self::new_uninit();653 with_default_context(|c, _| unsafe { init_primop(c, out.0, v.0) })654 .expect("primop initialization should not fail");655 out656 }657 pub fn new_attrs(v: HashMap<&str, Value>) -> Self {658 let out = Self::new_uninit();659 let mut b = AttrsBuilder::new(v.len());660 for (k, v) in v {661 b.insert(&k, v);662 }663 with_default_context(|c, _| unsafe { make_attrs(c, out.0, b.0) })664 .expect("attrs initialization should not fail");665666 out667 }668 fn new_list<T: Into<Self>>(v: Vec<T>) -> Self {669 let out = Self::new_uninit();670 let mut b = ListBuilder::new(v.len());671 for v in v {672 b.push(v.into());673 }674 with_default_context(|c, _| unsafe { make_list(c, b.0, out.0) })675 .expect("list initialization should not fail");676677 out678 }679 fn new_uninit() -> Self {680 let out = with_default_context(|c, es| unsafe { alloc_value(c, es) })681 .expect("value allocation should not fail");682 Self(out)683 }684 pub fn new_str(v: &str) -> Self {685 let s = CString::new(v).expect("string should not contain NULs");686 let out = Self::new_uninit();687 688 with_default_context(|c, _| unsafe { init_string(c, out.0, s.as_ptr()) })689 .expect("string initialization should not fail");690 out691 }692 pub fn new_int(i: i64) -> Self {693 let out = Self::new_uninit();694 with_default_context(|c, _| unsafe { init_int(c, out.0, i) })695 .expect("int initialization should not fail");696 out697 }698 pub fn new_bool(v: bool) -> Self {699 let out = Self::new_uninit();700 with_default_context(|c, _| unsafe { init_bool(c, out.0, v) })701 .expect("bool initialization should not fail");702 out703 }704 705 706 707 708 709 pub fn type_of(&self) -> NixType {710 let ty = with_default_context(|c, _| unsafe { get_type(c, self.0) })711 .expect("get_type should not fail");712 NixType::from_int(ty)713 }714 fn builtin_to_string(&self) -> Result<Self> {715 let builtin = Self::eval("builtins.toString")?;716 builtin.call(self.clone())717 }718 fn force(&mut self, s: *mut nix_raw::EvalState) -> Result<()> {719 with_default_context(|c, _| unsafe { value_force(c, s, self.0) })?;720 Ok(())721 }722 pub fn to_string(&self) -> Result<String> {723 let ty = self.type_of();724 if !matches!(ty, NixType::String) {725 bail!("unexpected type: {ty:?}, expected string");726 }727 let mut str_out = String::new();728 with_default_context(|c, _| unsafe {729 get_string(c, self.0, Some(copy_nix_str), (&raw mut str_out).cast())730 })?;731732 Ok(str_out)733 }734 pub fn to_realised_string(&self) -> Result<RealisedString> {735 with_default_context(|c, es| unsafe { string_realise(c, es, self.0, false) })736 .map(RealisedString)737738 739 740 741 742 743 744 745 }746747 pub fn has_field(&self, field: &str) -> Result<bool> {748 if !matches!(self.type_of(), NixType::Attrs) {749 bail!("invalid type: expected attrs");750 }751752 let f = init_field_name(field);753 with_default_context(|c, es| unsafe { has_attr_byname(c, self.0, es, f.as_ptr().cast()) })754 }755 756 757 758 pub fn list_fields(&self) -> Result<Vec<String>> {759 if !matches!(self.type_of(), NixType::Attrs) {760 bail!("invalid type: expected attrs");761 }762763 let len = with_default_context(|c, _| unsafe { get_attrs_size(c, self.0) })?;764 let mut out = Vec::with_capacity(len as usize);765766 for i in 0..len {767 let name =768 with_default_context(|c, es| unsafe { get_attr_name_byidx(c, self.0, es, i) })?;769 let c = unsafe { CStr::from_ptr(name) };770 out.push(c.to_str().expect("nix field names are utf-8").to_owned());771 }772 Ok(out)773 }774 pub fn get_elem(&self, v: usize) -> Result<Self> {775 if !matches!(self.type_of(), NixType::List) {776 bail!("invalid type: expected list");777 }778 let len = with_default_context(|c, _| unsafe { get_list_size(c, self.0) })? as usize;779 if v >= len {780 bail!("oob list get: {v} >= {len}");781 }782783 with_default_context(|c, es| unsafe { get_list_byidx(c, self.0, es, v as u32) }).map(Self)784 }785 pub fn attrs_update(self, other: Value ) -> Result<Self> {786 let attrs_update_fn = Self::eval("a: b: a // b")?;787788 attrs_update_fn789 .call(self)?790 .call(other)791 .context("attrs update")792 }793 pub fn get_field(&self, name: impl AsFieldName) -> Result<Self> {794 if !matches!(self.type_of(), NixType::Attrs) {795 bail!("invalid type: expected attrs");796 }797798 name.as_field_name(|name| {799 with_default_context(|c, es| unsafe {800 get_attr_byname(c, self.0, es, name.as_ptr().cast())801 })802 .map(Self)803 })804 .with_context(|| format!("getting field {:?}", name.to_field_name()))805 }806 pub fn call(&self, v: Value) -> Result<Self> {807 let kind = self808 .functor_kind()809 .ok_or_else(|| anyhow!("can only call function or functor"))?;810811 let function = match kind {812 FunctorKind::Function => self.clone(),813 FunctorKind::Functor => {814 let f = self815 .get_field("__functor")816 .context("getting functor value")?;817 assert_eq!(818 f.type_of(),819 NixType::Function,820 "invalid functor encountered"821 );822 f823 }824 };825826 let out = Value::new_uninit();827 with_default_context(|c, es| unsafe { value_call(c, es, function.0, v.0, out.0) })?;828829 Ok(out)830 }831 pub fn eval(v: &str) -> Result<Self> {832 let s = CString::new(v).expect("expression shouldn't have internal NULs");833 let out = Self::new_uninit();834 with_default_context(|c, es| unsafe {835 expr_eval_from_string(c, es, s.as_ptr(), c"/root".as_ptr(), out.0)836 })?;837 Ok(out)838 }839 pub fn build(&self, output: &str) -> Result<PathBuf> {840 if !self.is_derivation() {841 bail!("expected derivation to build")842 }843 let output_name = self844 .get_field("outputName")845 .context("getting output name field")?846 .to_string()?;847 let v = if output_name != output {848 let out = self.get_field(output).context("getting target output")?;849 if !out.is_derivation() {850 bail!("unknown output: {output}");851 }852 out853 } else {854 self.clone()855 };856 857 let s = v.builtin_to_string()?;858 let rs = s.to_realised_string()?;859 let drv_path = rs.as_str().to_owned();860 Ok(PathBuf::from(drv_path))861 }862 pub fn as_json<T: DeserializeOwned>(&self) -> Result<T> {863 let to_json = Self::eval("builtins.toJSON")?;864 let s = to_json.call(self.clone())?.to_string()?;865 Ok(serde_json::from_str(&s)?)866 }867 pub fn serialized<T: Serialize>(v: &T) -> Result<Self> {868 Self::eval(&nixlike::serialize(v)?)869 }870871 872 873 874 875 876877 fn is_derivation(&self) -> bool {878 if !matches!(self.type_of(), NixType::Attrs) {879 return false;880 }881 let Some(ty) = self.get_field("type").ok() else {882 return false;883 };884 matches!(ty.to_string().as_deref(), Ok("derivation"))885 }886 fn functor_kind(&self) -> Option<FunctorKind> {887 match self.type_of() {888 NixType::Attrs => self889 .has_field("__functor")890 .expect("has_field shouldn't fail for attrs")891 .then_some(FunctorKind::Functor),892 NixType::Function => Some(FunctorKind::Function),893 _ => None,894 }895 }896 pub fn is_function(&self) -> bool {897 self.functor_kind().is_some()898 }899 pub fn is_null(&self) -> bool {900 matches!(self.type_of(), NixType::Null)901 }902 pub fn is_string(&self) -> bool {903 matches!(self.type_of(), NixType::String)904 }905 pub fn is_attrs(&self) -> bool {906 matches!(self.type_of(), NixType::Attrs)907 }908}909910impl From<String> for Value {911 fn from(value: String) -> Self {912 Value::new_str(&value)913 }914}915impl From<bool> for Value {916 fn from(value: bool) -> Self {917 Value::new_bool(value)918 }919}920impl From<&str> for Value {921 fn from(value: &str) -> Self {922 Value::new_str(value)923 }924}925impl<T> From<Vec<T>> for Value926where927 T: Into<Value>,928{929 fn from(value: Vec<T>) -> Self {930 Value::new_list(value)931 }932}933934impl Clone for Value {935 fn clone(&self) -> Self {936 with_default_context(|c, _| unsafe { value_incref(c, self.0) })937 .expect("value incref should not fail");938 Self(self.0)939 }940}941impl Drop for Value {942 fn drop(&mut self) {943 with_default_context(|c, _| unsafe { value_decref(c, self.0) })944 .expect("value drop should not fail");945 }946}947948static TOKIO_FOR_NIX: OnceLock<Arc<tokio::runtime::Runtime>> = OnceLock::new();949950pub fn init_libraries() {951 unsafe { GC_allow_register_threads() };952953 let mut ctx = NixContext::new();954 ctx.run_in_context(|c| unsafe { libutil_init(c) })955 .expect("util init should not fail");956 ctx.run_in_context(|c| unsafe { libstore_init(c) })957 .expect("store init should not fail");958 ctx.run_in_context(|c| unsafe { libexpr_init(c) })959 .expect("expr init should not fail");960961 nix_logging_cxx::apply_tracing_logger();962}963964pub fn init_tokio_for_nix(tokio: Arc<tokio::runtime::Runtime>) {965 TOKIO_FOR_NIX966 .set(tokio)967 .expect("tokio for nix should only be initialized once");968}969970pub fn await_in_nix<F: Send + 'static>(f: impl Future<Output = F> + Send + 'static) -> F {971 972 let runtime = TOKIO_FOR_NIX973 .get()974 .expect("init_tokio_for_nix was not called");975 std::thread::spawn(move || runtime.block_on(f))976 .join()977 .expect("await_in_nix inner thread panicked")978}979980unsafe extern "C" fn nix_primop_closure_adapter<const N: usize>(981 user_data: *mut c_void,982 mut context: *mut c_context,983 state: *mut nix_raw::EvalState,984 args: *mut *mut value,985 ret: *mut value,986) {987 let user_closure: &UserClosure<N> = unsafe { &*user_data.cast_const().cast() };988 let args: [&Value; N] = array::from_fn(|i| {989 let v: &mut Value = unsafe { &mut *args.add(i).cast() };990 v as &Value991 });992 let ctx: &mut NixContext = unsafe { transmute(&mut context) };993994 let state: &EvalState = unsafe { std::mem::transmute(&state) };995996 match user_closure(state, args) {997 Ok(v) => {998 unsafe { copy_value(context, ret, v.0) };999 }1000 Err(e) => {1001 ctx.set_err(e);1002 }1003 }1004}10051006type UserClosure<const N: usize> = Box<dyn Fn(&EvalState, [&Value; N]) -> Result<Value>>;10071008pub struct NativeFn(*mut PrimOp);1009impl NativeFn {1010 pub fn new<const N: usize>(1011 name: &'static CStr,1012 doc: &'static CStr,1013 args: [&'static CStr; N],1014 f: impl Fn(&EvalState, [&Value; N]) -> Result<Value> + 'static,1015 ) -> Self {1016 1017 let closure: Box<UserClosure<N>> = Box::new(Box::new(f));1018 let f: PrimOpFun = Some(nix_primop_closure_adapter::<N>);1019 let mut args = args.into_iter().map(|v| v.as_ptr()).collect_vec();1020 args.push(null());1021 let args = args.as_mut_ptr();1022 let primop = unsafe {1023 alloc_primop(1024 null_mut(),1025 f,1026 N as i32,1027 name.as_ptr(),1028 args,1029 doc.as_ptr(),1030 Box::into_raw(closure).cast(),1031 )1032 };10331034 assert!(!primop.is_null(), "primop allocation should not fail");10351036 Self(primop)1037 }1038 pub fn register(self) {1039 unsafe { register_primop(null_mut(), self.0) };1040 }1041}10421043struct StorePath(*mut c_store_path);1044impl StorePath {}10451046impl Drop for StorePath {1047 fn drop(&mut self) {1048 unsafe { store_path_free(self.0) }1049 }1050}10511052#[test_log::test]1053fn test_native() -> Result<()> {1054 init_libraries();1055 NativeFn::new(1056 c"__uppercaseSuffix2",1057 c"make string uppercase and add suffix",1058 [c"str", c"suffix"],1059 |_, [str, suffix]: [&Value; 2]| {1060 let str = str.to_string()?;1061 let suffix = suffix.to_string()?;1062 Ok(Value::new_str(&format!("{}{suffix}", str.to_uppercase())))1063 },1064 )1065 .register();10661067 let mut fetch_settings = FetchSettings::new();1068 fetch_settings.set(c"warn-dirty", c"false");10691070 let manifest = format!("git+file://{}/../../", env!("CARGO_MANIFEST_DIR"));1071 let flake = FlakeSettings::new()?;1072 let parse = FlakeReferenceParseFlags::new(&flake)?;1073 let (mut r, _) = FlakeReference::new(&manifest, &flake, &parse, &fetch_settings)?;1074 let lock = FlakeLockFlags::new(&flake)?;1075 let locked = r.lock(&fetch_settings, &flake, &lock)?;1076 let attrs = locked.get_attrs(&mut FlakeSettings::new()?)?;10771078 let builtins = Value::eval("builtins")?;1079 assert_eq!(builtins.type_of(), NixType::Attrs);10801081 assert_eq!(attrs.type_of(), NixType::Attrs);1082 let test_data = nix_go!(attrs.testData);10831084 let test_string: String = nix_go_json!(test_data.testString);1085 assert_eq!(test_string, "hello");10861087 let s = nix_go!(attrs.packages["x86_64-linux"].fleet.drvPath);1088 let s = CString::new(s.to_string()?).expect("path str is cstring");10891090 let uppercase_suffix = Value::new_primop(NativeFn::new(1091 c"uppercase_suffix",1092 c"make string uppercase and add suffix",1093 [c"str", c"suffix"],1094 |es, [str, suffix]: [&Value; 2]| {1095 let str = str.to_string()?;1096 let suffix = suffix.to_string()?;1097 Ok(Value::new_str(&format!("{}{suffix}", str.to_uppercase())))1098 },1099 ));11001101 let test_result: String = nix_go_json!(test_data.testPrimop(uppercase_suffix));1102 assert_eq!(test_result, "PREFIX_BODY_SUFFIX");1103 let test_result: String = nix_go_json!(builtins.uppercaseSuffix2("test")("suffix"));1104 assert_eq!(test_result, "TESTsuffix");11051106 let nix_ctx = NixContext::new();1107 let store = GLOBAL_STATE.store.parse_path(s.as_c_str())?;11081109 11101111 Ok(())1112}1113111411151116111711181119112011211122112311241125112611271128112911301131