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::{Span, 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 drv;47pub mod logging;48#[doc(hidden)]49pub mod macros;5051#[doc(hidden)]52pub mod __macro_support {53 pub use std::collections::hash_map::HashMap;5455 pub use anyhow::Context;56 pub use tokio::task::block_in_place;57}58pub mod util;5960#[allow(61 non_upper_case_globals,62 non_camel_case_types,63 non_snake_case,64 dead_code65)]66mod nix_raw {67 include!(concat!(env!("OUT_DIR"), "/bindings.rs"));68}69#[cxx::bridge]70pub mod nix_cxx {71 unsafe extern "C++" {72 type nix_fetchers_settings;73 include!("nix-eval/src/lib.hh");7475 #[allow(clippy::missing_safety_doc)]76 unsafe fn set_fetcher_setting(77 settings: *mut nix_fetchers_settings,78 setting: *const c_char,79 value: *const c_char,80 );81 }82}8384#[derive(Debug, PartialEq, Eq)]85pub enum NixType {86 Thunk,87 Int,88 Float,89 Bool,90 String,91 Path,92 Null,93 Attrs,94 List,95 Function,96 External,97}98impl NixType {99 fn from_int(c: c_uint) -> Self {100 match c {101 0 => Self::Thunk,102 1 => Self::Int,103 2 => Self::Float,104 3 => Self::Bool,105 4 => Self::String,106 5 => Self::Path,107 6 => Self::Null,108 7 => Self::Attrs,109 8 => Self::List,110 9 => Self::Function,111 10 => Self::External,112 _ => unreachable!("unknown nix type: {c}"),113 }114 }115}116117enum FunctorKind {118 Function,119 Functor,120}121122#[derive(Debug)]123#[repr(i32)]124pub enum NixErrorKind {125 Unknown = err_NIX_ERR_UNKNOWN,126 Overflow = err_NIX_ERR_OVERFLOW,127 Key = err_NIX_ERR_KEY,128 Generic = err_NIX_ERR_NIX_ERROR,129}130impl NixErrorKind {131 fn from_int(v: c_int) -> Option<Self> {132 Some(match v {133 0 => return None,134 nix_raw::err_NIX_ERR_UNKNOWN => Self::Unknown,135 nix_raw::err_NIX_ERR_OVERFLOW => Self::Overflow,136 nix_raw::err_NIX_ERR_KEY => Self::Key,137 nix_raw::err_NIX_ERR_NIX_ERROR => Self::Generic,138 _ => {139 debug_assert!(false, "unexpected nix error kind: {v}");140 Self::Unknown141 }142 })143 }144}145146pub fn gc_now() {147 unsafe { gc_now_raw() };148}149150pub fn gc_register_my_thread() {151 assert_eq!(unsafe { GC_thread_is_registered() }, 0);152153 let mut sb = GC_stack_base {154 mem_base: null_mut(),155 };156 let r = unsafe { GC_get_stack_base(&mut sb) };157 if r as u32 != GC_SUCCESS {158 panic!("failed to get thread stack base");159 }160 unsafe { GC_register_my_thread(&sb) };161}162pub fn gc_unregister_my_thread() {163 assert_eq!(unsafe { GC_thread_is_registered() }, 1);164165 unsafe { GC_unregister_my_thread() };166}167168pub struct ThreadRegisterGuard {}169impl ThreadRegisterGuard {170 #[allow(clippy::new_without_default)]171 pub fn new() -> Self {172 gc_register_my_thread();173 Self {}174 }175}176impl Drop for ThreadRegisterGuard {177 fn drop(&mut self) {178 gc_unregister_my_thread();179 }180}181182#[repr(transparent)]183pub struct NixContext(*mut c_context);184impl NixContext {185 pub fn set_err_raw(&mut self, err: NixErrorKind, msg: &CStr) {186 unsafe { set_err_msg(self.0, err as c_int, msg.as_ptr()) };187 }188 pub fn set_err(&mut self, err: anyhow::Error) {189 let mut fmt = format!("{err:?}").replace("\0", "\\0");190 self.set_err_raw(191 NixErrorKind::Generic,192 &CString::new(fmt).expect("NUL bytes were just replaced"),193 );194 }195 pub fn new() -> Self {196 let ctx = unsafe { c_context_create() };197 Self(ctx)198 }199 fn error_kind(&self) -> Option<NixErrorKind> {200 let code = unsafe { err_code(self.0) };201 NixErrorKind::from_int(code)202 }203 fn error<'t>(&self) -> Option<(Cow<'t, str>, Option<Box<ErrorInfoBuilder>>)> {204 if let NixErrorKind::Generic = self.error_kind()? {205 let ei = unsafe { logging::nix_logging_cxx::extract_error_info(self.0) };206 let mut err_out = String::new();207 unsafe {208 err_info_msg(209 null_mut(),210 self.0,211 Some(copy_nix_str),212 (&raw mut err_out).cast(),213 )214 };215 return Some((Cow::Owned(err_out), Some(ei)));216 };217218 219 220 let str = unsafe { err_msg(null_mut(), self.0, null_mut()) };221 Some((unsafe { CStr::from_ptr(str) }.to_string_lossy(), None))222 }223 fn clean_err(&mut self) {224 unsafe {225 clear_err(self.0);226 }227 }228229 fn bail_if_error(&self) -> Result<()> {230 if let Some((err, stack)) = self.error() {231 let mut e = Err(anyhow!("{err}"));232 if let Some(stack) = stack {233 for ele in stack.stack_frames {234 e = e.with_context(|| {235 if ele.pos.is_empty() {236 ele.msg237 } else {238 format!("{} at {}", ele.msg, ele.pos)239 }240 })241 }242 }243 return e.context("<nix frames>");244 };245 Ok(())246 }247248 fn run_in_context<T>(&mut self, f: impl FnOnce(*mut c_context) -> T) -> Result<T> {249 self.clean_err();250 let o = f(self.0);251 self.bail_if_error()?;252 self.clean_err();253 Ok(o)254 }255}256257impl Default for NixContext {258 fn default() -> Self {259 Self::new()260 }261}262impl Drop for NixContext {263 fn drop(&mut self) {264 unsafe {265 c_context_free(self.0);266 }267 }268}269struct GlobalState {270 271 #[allow(dead_code)]272 store: Store,273 state: EvalState,274}275impl GlobalState {276 fn new() -> Result<Self> {277 let mut ctx = NixContext::new();278 let store = ctx279 .run_in_context(|c| unsafe { store_open(c, c"auto".as_ptr(), null_mut()) })280 .map(Store)?;281282 let builder = ctx.run_in_context(|c| unsafe { eval_state_builder_new(c, store.0) })?;283 ctx.run_in_context(|c| unsafe { eval_state_builder_load(c, builder) })?;284 ctx.run_in_context(|c| unsafe {285 eval_state_builder_set_eval_setting(286 c,287 builder,288 c"lazy-trees".as_ptr(),289 c"true".as_ptr(),290 )291 })?;292 ctx.run_in_context(|c| unsafe {293 eval_state_builder_set_eval_setting(294 c,295 builder,296 c"lazy-locks".as_ptr(),297 c"true".as_ptr(),298 )299 })?;300 let state = ctx301 .run_in_context(|c| unsafe { eval_state_build(c, builder) })302 .map(EvalState)?;303304 Ok(Self { store, state })305 }306}307308struct ThreadState {309 ctx: NixContext,310}311impl ThreadState {312 fn new() -> Result<Self> {313 let ctx = NixContext::new();314315 Ok(Self { ctx })316 }317}318319static GLOBAL_STATE: LazyLock<GlobalState> =320 LazyLock::new(|| GlobalState::new().expect("global state init shouldn't fail"));321322thread_local! {323 static THREAD_STATE: RefCell<ThreadState> = RefCell::new(ThreadState::new().expect("thread state init shouldn't fail"));324}325pub(crate) fn with_default_context<T>(326 f: impl FnOnce(*mut c_context, *mut c_eval_state) -> T,327) -> Result<T> {328 let global = &GLOBAL_STATE.state;329 let (ctx, state) = THREAD_STATE.with_borrow_mut(|w| (w.ctx.0, global.0));330 let mut ctx = NixContext(ctx);331 let v = ctx.run_in_context(|c| f(c, state));332 333 std::mem::forget(ctx);334 v335}336337338339pub(crate) fn with_store_context<T>(340 f: impl FnOnce(*mut c_context, *mut c_store, *mut c_eval_state) -> T,341) -> Result<T> {342 let global = &GLOBAL_STATE;343 let (ctx, store, state) =344 THREAD_STATE.with_borrow_mut(|w| (w.ctx.0, global.store.0, global.state.0));345 let mut ctx = NixContext(ctx);346 let v = ctx.run_in_context(|c| f(c, store, state));347 std::mem::forget(ctx);348 v349}350351pub fn set_setting(s: &CStr, v: &CStr) -> Result<()> {352 with_default_context(|c, _| unsafe { setting_set(c, s.as_ptr(), v.as_ptr()) }).map(|_| ())353}354355pub struct FetchSettings(*mut fetchers_settings);356impl FetchSettings {357 pub fn new() -> Self {358 Self::try_new().expect("allocation should not fail")359 }360 fn try_new() -> Result<Self> {361 with_default_context(|c, _| unsafe { fetchers_settings_new(c) }).map(Self)362 }363 pub fn set(&mut self, setting: &CStr, value: &CStr) {364 unsafe {365 set_fetcher_setting(self.0.cast(), setting.as_ptr(), value.as_ptr());366 };367 }368}369unsafe impl Send for FetchSettings {}370unsafe impl Sync for FetchSettings {}371372impl Default for FetchSettings {373 fn default() -> Self {374 Self::new()375 }376}377378impl Drop for FetchSettings {379 fn drop(&mut self) {380 unsafe { fetchers_settings_free(self.0) };381 }382}383pub struct FlakeSettings(*mut flake_settings);384impl FlakeSettings {385 pub fn new() -> Result<Self> {386 with_default_context(|c, _| unsafe { flake_settings_new(c) }).map(Self)387 }388}389unsafe impl Send for FlakeSettings {}390unsafe impl Sync for FlakeSettings {}391impl Drop for FlakeSettings {392 fn drop(&mut self) {393 unsafe {394 flake_settings_free(self.0);395 }396 }397}398399pub struct FlakeReferenceParseFlags(*mut flake_reference_parse_flags);400impl FlakeReferenceParseFlags {401 pub fn new(settings: &FlakeSettings) -> Result<Self> {402 with_default_context(|c, _| unsafe { flake_reference_parse_flags_new(c, settings.0) })403 .map(Self)404 }405 pub fn set_base_dir(&mut self, dir: &str) -> Result<()> {406 with_default_context(|c, _| {407 unsafe {408 flake_reference_parse_flags_set_base_directory(409 c,410 self.0,411 dir.as_ptr().cast(),412 dir.len(),413 )414 };415 })416 }417}418impl Drop for FlakeReferenceParseFlags {419 fn drop(&mut self) {420 unsafe {421 flake_reference_parse_flags_free(self.0);422 }423 }424}425pub struct FlakeLockFlags(*mut flake_lock_flags);426impl FlakeLockFlags {427 pub fn new(settings: &FlakeSettings) -> Result<Self> {428 let o = with_default_context(|c, _| unsafe { flake_lock_flags_new(c, settings.0) })429 .map(Self)?;430 431432 Ok(o)433 }434}435impl Drop for FlakeLockFlags {436 fn drop(&mut self) {437 unsafe {438 flake_lock_flags_free(self.0);439 }440 }441}442443pub(crate) unsafe extern "C" fn copy_nix_str(444 start: *const c_char,445 n: c_uint,446 user_data: *mut c_void,447) {448 let s = unsafe { slice::from_raw_parts(start.cast::<u8>(), n as usize) };449 let s = std::str::from_utf8(s).expect("c string has invalid utf-8");450 unsafe { *user_data.cast::<String>() = s.to_owned() };451}452453struct Store(*mut c_store);454unsafe impl Send for Store {}455unsafe impl Sync for Store {}456457impl Store {458 fn parse_path(&self, path: &CStr) -> Result<StorePath> {459 with_default_context(|c, _| {460 StorePath(unsafe { store_parse_path(c, self.0, path.as_ptr()) })461 })462 }463}464465#[repr(transparent)]466pub struct EvalState(*mut c_eval_state);467unsafe impl Send for EvalState {}468unsafe impl Sync for EvalState {}469470impl Drop for EvalState {471 fn drop(&mut self) {472 unsafe {473 state_free(self.0);474 }475 }476}477478pub struct FlakeReference(*mut flake_reference);479impl FlakeReference {480 #[instrument(name = "new-flake-reference", skip(flake, parse, fetch))]481 pub fn new(482 s: &str,483 flake: &FlakeSettings,484 parse: &FlakeReferenceParseFlags,485 fetch: &FetchSettings,486 ) -> Result<(Self, String)> {487 let mut out = null_mut();488 let mut fragment = String::new();489 490 with_default_context(|c, _| unsafe {491 flake_reference_and_fragment_from_string(492 c,493 fetch.0,494 flake.0,495 parse.0,496 s.as_ptr().cast(),497 s.len(),498 &mut out,499 Some(copy_nix_str),500 (&raw mut fragment).cast(),501 )502 })?;503 assert!(!out.is_null());504505 Ok((Self(out), fragment))506 }507 #[instrument(name = "lock-flake", skip(self, fetch, flake, lock))]508 pub fn lock(509 &mut self,510 fetch: &FetchSettings,511 flake: &FlakeSettings,512 lock: &FlakeLockFlags,513 ) -> Result<LockedFlake> {514 with_default_context(|c, es| unsafe { flake_lock(c, fetch.0, flake.0, es, lock.0, self.0) })515 .map(LockedFlake)516 }517}518unsafe impl Send for FlakeReference {}519unsafe impl Sync for FlakeReference {}520521pub struct LockedFlake(*mut locked_flake);522impl LockedFlake {523 pub fn get_attrs(&self, settings: &mut FlakeSettings) -> Result<Value> {524 with_default_context(|c, es| unsafe {525 locked_flake_get_output_attrs(c, settings.0, es, self.0)526 })527 .map(Value)528 }529}530unsafe impl Send for LockedFlake {}531unsafe impl Sync for LockedFlake {}532impl Drop for LockedFlake {533 fn drop(&mut self) {534 unsafe {535 locked_flake_free(self.0);536 };537 }538}539540type FieldName = [u8; 64];541fn init_field_name(v: &str) -> FieldName {542 let mut f = [0; 64];543 assert!(v.len() < 64, "max field name is 63 chars");544 assert!(545 v.bytes().all(|v| v != 0),546 "nul bytes are unsupported in field name"547 );548 f[0..v.len()].copy_from_slice(v.as_bytes());549 f550}551552pub struct RealisedString(*mut realised_string);553impl fmt::Debug for RealisedString {554 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {555 self.as_str().fmt(f)556 }557}558559impl RealisedString {560 pub fn as_str(&self) -> &str {561 let len = unsafe { realised_string_get_buffer_size(self.0) };562 let data: *const u8 = unsafe { realised_string_get_buffer_start(self.0) }.cast();563 let data = unsafe { slice::from_raw_parts(data, len) };564 std::str::from_utf8(data).expect("non-utf8 strings not supported")565 }566 pub fn path_count(&self) -> usize {567 unsafe { realised_string_get_store_path_count(self.0) }568 }569 pub fn path(&self, i: usize) -> String {570 assert!(i < self.path_count());571 let path = unsafe { realised_string_get_store_path(self.0, i) };572 let mut err_out = String::new();573 unsafe { store_path_name(path, Some(copy_nix_str), (&raw mut err_out).cast()) };574 err_out575 }576}577578unsafe impl Send for RealisedString {}579impl Drop for RealisedString {580 fn drop(&mut self) {581 unsafe { realised_string_free(self.0) }582 }583}584585#[repr(transparent)]586pub struct Value(*mut value);587588unsafe impl Send for Value {}589unsafe impl Sync for Value {}590591pub trait AsFieldName {592 fn as_field_name<T>(&self, v: impl FnOnce(FieldName) -> Result<T>) -> Result<T>;593 fn to_field_name(&self) -> Result<String>;594}595impl AsFieldName for Value {596 fn as_field_name<T>(&self, v: impl FnOnce(FieldName) -> Result<T>) -> Result<T> {597 let f = self.to_string()?;598 v(init_field_name(&f))599 }600 fn to_field_name(&self) -> Result<String> {601 self.to_string()602 }603}604impl<E> AsFieldName for E605where606 E: AsRef<str>,607{608 fn as_field_name<T>(&self, v: impl FnOnce(FieldName) -> Result<T>) -> Result<T> {609 let f = self.as_ref();610 v(init_field_name(f))611 }612 fn to_field_name(&self) -> Result<String> {613 Ok(self.as_ref().to_owned())614 }615}616617struct AttrsBuilder(*mut c_bindings_builder);618impl AttrsBuilder {619 fn new(capacity: usize) -> Self {620 with_default_context(|c, es| unsafe { make_bindings_builder(c, es, capacity) })621 .map(Self)622 .expect("alloc should not fail")623 }624 fn insert(&mut self, k: &impl AsFieldName, v: Value) {625 k.as_field_name(|name| {626 with_default_context(|c, _| unsafe {627 bindings_builder_insert(c, self.0, name.as_ptr().cast(), v.0);628 629 })630 })631 .expect("builder insert shouldn't fail");632 }633}634impl Drop for AttrsBuilder {635 fn drop(&mut self) {636 unsafe { bindings_builder_free(self.0) };637 }638}639640struct ListBuilder(*mut c_list_builder, c_uint);641impl ListBuilder {642 fn new(capacity: usize) -> Self {643 with_default_context(|c, es| unsafe { make_list_builder(c, es, capacity) })644 .map(|l| Self(l, 0))645 .expect("alloc should not fail")646 }647}648impl ListBuilder {649 fn push(&mut self, v: Value) {650 with_default_context(|c, _| unsafe {651 list_builder_insert(652 c,653 self.0,654 {655 let v = self.1;656 self.1 += 1;657 v658 },659 v.0,660 )661 })662 .expect("list insert shouldn't fail");663 }664}665impl Drop for ListBuilder {666 fn drop(&mut self) {667 unsafe { list_builder_free(self.0) };668 }669}670671impl Value {672 pub fn new_primop(v: NativeFn) -> Self {673 let out = Self::new_uninit();674 with_default_context(|c, _| unsafe { init_primop(c, out.0, v.0) })675 .expect("primop initialization should not fail");676 out677 }678 pub fn new_attrs(v: HashMap<&str, Value>) -> Self {679 let out = Self::new_uninit();680 let mut b = AttrsBuilder::new(v.len());681 for (k, v) in v {682 b.insert(&k, v);683 }684 with_default_context(|c, _| unsafe { make_attrs(c, out.0, b.0) })685 .expect("attrs initialization should not fail");686687 out688 }689 fn new_list<T: Into<Self>>(v: Vec<T>) -> Self {690 let out = Self::new_uninit();691 let mut b = ListBuilder::new(v.len());692 for v in v {693 b.push(v.into());694 }695 with_default_context(|c, _| unsafe { make_list(c, b.0, out.0) })696 .expect("list initialization should not fail");697698 out699 }700 fn new_uninit() -> Self {701 let out = with_default_context(|c, es| unsafe { alloc_value(c, es) })702 .expect("value allocation should not fail");703 Self(out)704 }705 pub fn new_str(v: &str) -> Self {706 let s = CString::new(v).expect("string should not contain NULs");707 let out = Self::new_uninit();708 709 with_default_context(|c, _| unsafe { init_string(c, out.0, s.as_ptr()) })710 .expect("string initialization should not fail");711 out712 }713 pub fn new_int(i: i64) -> Self {714 let out = Self::new_uninit();715 with_default_context(|c, _| unsafe { init_int(c, out.0, i) })716 .expect("int initialization should not fail");717 out718 }719 pub fn new_bool(v: bool) -> Self {720 let out = Self::new_uninit();721 with_default_context(|c, _| unsafe { init_bool(c, out.0, v) })722 .expect("bool initialization should not fail");723 out724 }725 726 727 728 729 730 pub fn type_of(&self) -> NixType {731 let ty = with_default_context(|c, _| unsafe { get_type(c, self.0) })732 .expect("get_type should not fail");733 NixType::from_int(ty)734 }735 fn builtin_to_string(&self) -> Result<Self> {736 let builtin = Self::eval("builtins.toString")?;737 builtin.call(self.clone())738 }739 fn force(&mut self, s: *mut nix_raw::EvalState) -> Result<()> {740 with_default_context(|c, _| unsafe { value_force(c, s, self.0) })?;741 Ok(())742 }743 pub fn to_string(&self) -> Result<String> {744 let ty = self.type_of();745 if !matches!(ty, NixType::String) {746 bail!("unexpected type: {ty:?}, expected string");747 }748 let mut str_out = String::new();749 with_default_context(|c, _| unsafe {750 get_string(c, self.0, Some(copy_nix_str), (&raw mut str_out).cast())751 })?;752753 Ok(str_out)754 }755 pub fn to_realised_string(&self) -> Result<RealisedString> {756 with_default_context(|c, es| unsafe { string_realise(c, es, self.0, false) })757 .map(RealisedString)758759 760 761 762 763 764 765 766 }767768 pub fn has_field(&self, field: &str) -> Result<bool> {769 if !matches!(self.type_of(), NixType::Attrs) {770 bail!("invalid type: expected attrs");771 }772773 let f = init_field_name(field);774 with_default_context(|c, es| unsafe { has_attr_byname(c, self.0, es, f.as_ptr().cast()) })775 }776 777 778 779 pub fn list_fields(&self) -> Result<Vec<String>> {780 if !matches!(self.type_of(), NixType::Attrs) {781 bail!("invalid type: expected attrs");782 }783784 let len = with_default_context(|c, _| unsafe { get_attrs_size(c, self.0) })?;785 let mut out = Vec::with_capacity(len as usize);786787 for i in 0..len {788 let name =789 with_default_context(|c, es| unsafe { get_attr_name_byidx(c, self.0, es, i) })?;790 let c = unsafe { CStr::from_ptr(name) };791 out.push(c.to_str().expect("nix field names are utf-8").to_owned());792 }793 Ok(out)794 }795 pub fn get_elem(&self, v: usize) -> Result<Self> {796 if !matches!(self.type_of(), NixType::List) {797 bail!("invalid type: expected list");798 }799 let len = with_default_context(|c, _| unsafe { get_list_size(c, self.0) })? as usize;800 if v >= len {801 bail!("oob list get: {v} >= {len}");802 }803804 with_default_context(|c, es| unsafe { get_list_byidx(c, self.0, es, v as u32) }).map(Self)805 }806 pub fn attrs_update(self, other: Value ) -> Result<Self> {807 let attrs_update_fn = Self::eval("a: b: a // b")?;808809 attrs_update_fn810 .call(self)?811 .call(other)812 .context("attrs update")813 }814 pub fn get_field(&self, name: impl AsFieldName) -> Result<Self> {815 if !matches!(self.type_of(), NixType::Attrs) {816 bail!("invalid type: expected attrs");817 }818819 name.as_field_name(|name| {820 with_default_context(|c, es| unsafe {821 get_attr_byname(c, self.0, es, name.as_ptr().cast())822 })823 .map(Self)824 })825 .with_context(|| format!("getting field {:?}", name.to_field_name()))826 }827 pub fn call(&self, v: Value) -> Result<Self> {828 let kind = self829 .functor_kind()830 .ok_or_else(|| anyhow!("can only call function or functor"))?;831832 let function = match kind {833 FunctorKind::Function => self.clone(),834 FunctorKind::Functor => {835 let f = self836 .get_field("__functor")837 .context("getting functor value")?;838 assert_eq!(839 f.type_of(),840 NixType::Function,841 "invalid functor encountered"842 );843 f844 }845 };846847 let out = Value::new_uninit();848 with_default_context(|c, es| unsafe { value_call(c, es, function.0, v.0, out.0) })?;849850 Ok(out)851 }852 pub fn eval(v: &str) -> Result<Self> {853 let s = CString::new(v).expect("expression shouldn't have internal NULs");854 let out = Self::new_uninit();855 with_default_context(|c, es| unsafe {856 expr_eval_from_string(c, es, s.as_ptr(), c"/root".as_ptr(), out.0)857 })?;858 Ok(out)859 }860 #[instrument(name = "build", skip(self), fields(output))]861 pub fn build(&self, output: &str) -> Result<PathBuf> {862 if !self.is_derivation() {863 bail!("expected derivation to build")864 }865 let output_name = self866 .get_field("outputName")867 .context("getting output name field")?868 .to_string()?;869 let v = if output_name != output {870 let out = self.get_field(output).context("getting target output")?;871 if !out.is_derivation() {872 bail!("unknown output: {output}");873 }874 out875 } else {876 self.clone()877 };878879 let drv_path = v880 .get_field("drvPath")881 .context("getting drvPath")?882 .to_string()?;883 let graph = drv::DrvGraph::resolve(&drv_path)?;884 let _guard = logging::register_build_graph(&Span::current(), &graph);885886 887 let s = v.builtin_to_string()?;888 let rs = s.to_realised_string()?;889 let out_path = rs.as_str().to_owned();890 Ok(PathBuf::from(out_path))891 }892 pub fn as_json<T: DeserializeOwned>(&self) -> Result<T> {893 let to_json = Self::eval("builtins.toJSON")?;894 let s = to_json.call(self.clone())?.to_string()?;895 Ok(serde_json::from_str(&s)?)896 }897 pub fn serialized<T: Serialize>(v: &T) -> Result<Self> {898 Self::eval(&nixlike::serialize(v)?)899 }900901 902 903 904 905 906907 fn is_derivation(&self) -> bool {908 if !matches!(self.type_of(), NixType::Attrs) {909 return false;910 }911 let Some(ty) = self.get_field("type").ok() else {912 return false;913 };914 matches!(ty.to_string().as_deref(), Ok("derivation"))915 }916 fn functor_kind(&self) -> Option<FunctorKind> {917 match self.type_of() {918 NixType::Attrs => self919 .has_field("__functor")920 .expect("has_field shouldn't fail for attrs")921 .then_some(FunctorKind::Functor),922 NixType::Function => Some(FunctorKind::Function),923 _ => None,924 }925 }926 pub fn is_function(&self) -> bool {927 self.functor_kind().is_some()928 }929 pub fn is_null(&self) -> bool {930 matches!(self.type_of(), NixType::Null)931 }932 pub fn is_string(&self) -> bool {933 matches!(self.type_of(), NixType::String)934 }935 pub fn is_attrs(&self) -> bool {936 matches!(self.type_of(), NixType::Attrs)937 }938}939940impl From<String> for Value {941 fn from(value: String) -> Self {942 Value::new_str(&value)943 }944}945impl From<bool> for Value {946 fn from(value: bool) -> Self {947 Value::new_bool(value)948 }949}950impl From<&str> for Value {951 fn from(value: &str) -> Self {952 Value::new_str(value)953 }954}955impl<T> From<Vec<T>> for Value956where957 T: Into<Value>,958{959 fn from(value: Vec<T>) -> Self {960 Value::new_list(value)961 }962}963964impl Clone for Value {965 fn clone(&self) -> Self {966 with_default_context(|c, _| unsafe { value_incref(c, self.0) })967 .expect("value incref should not fail");968 Self(self.0)969 }970}971impl Drop for Value {972 fn drop(&mut self) {973 with_default_context(|c, _| unsafe { value_decref(c, self.0) })974 .expect("value drop should not fail");975 }976}977978static TOKIO_FOR_NIX: OnceLock<Arc<tokio::runtime::Runtime>> = OnceLock::new();979980pub fn init_libraries() {981 unsafe { GC_allow_register_threads() };982983 let mut ctx = NixContext::new();984 ctx.run_in_context(|c| unsafe { libutil_init(c) })985 .expect("util init should not fail");986 ctx.run_in_context(|c| unsafe { libstore_init(c) })987 .expect("store init should not fail");988 ctx.run_in_context(|c| unsafe { libexpr_init(c) })989 .expect("expr init should not fail");990991 nix_logging_cxx::apply_tracing_logger();992}993994pub fn init_tokio_for_nix(tokio: Arc<tokio::runtime::Runtime>) {995 TOKIO_FOR_NIX996 .set(tokio)997 .expect("tokio for nix should only be initialized once");998}9991000pub fn await_in_nix<F: Send + 'static>(f: impl Future<Output = F> + Send + 'static) -> F {1001 1002 let runtime = TOKIO_FOR_NIX1003 .get()1004 .expect("init_tokio_for_nix was not called");1005 std::thread::spawn(move || runtime.block_on(f))1006 .join()1007 .expect("await_in_nix inner thread panicked")1008}10091010unsafe extern "C" fn nix_primop_closure_adapter<const N: usize>(1011 user_data: *mut c_void,1012 mut context: *mut c_context,1013 state: *mut nix_raw::EvalState,1014 args: *mut *mut value,1015 ret: *mut value,1016) {1017 let user_closure: &UserClosure<N> = unsafe { &*user_data.cast_const().cast() };1018 let args: [&Value; N] = array::from_fn(|i| {1019 let v: &mut Value = unsafe { &mut *args.add(i).cast() };1020 v as &Value1021 });1022 let ctx: &mut NixContext = unsafe { transmute(&mut context) };10231024 let state: &EvalState = unsafe { std::mem::transmute(&state) };10251026 match user_closure(state, args) {1027 Ok(v) => {1028 unsafe { copy_value(context, ret, v.0) };1029 }1030 Err(e) => {1031 ctx.set_err(e);1032 }1033 }1034}10351036type UserClosure<const N: usize> = Box<dyn Fn(&EvalState, [&Value; N]) -> Result<Value>>;10371038pub struct NativeFn(*mut PrimOp);1039impl NativeFn {1040 pub fn new<const N: usize>(1041 name: &'static CStr,1042 doc: &'static CStr,1043 args: [&'static CStr; N],1044 f: impl Fn(&EvalState, [&Value; N]) -> Result<Value> + 'static,1045 ) -> Self {1046 1047 let closure: Box<UserClosure<N>> = Box::new(Box::new(f));1048 let f: PrimOpFun = Some(nix_primop_closure_adapter::<N>);1049 let mut args = args.into_iter().map(|v| v.as_ptr()).collect_vec();1050 args.push(null());1051 let args = args.as_mut_ptr();1052 let primop = unsafe {1053 alloc_primop(1054 null_mut(),1055 f,1056 N as i32,1057 name.as_ptr(),1058 args,1059 doc.as_ptr(),1060 Box::into_raw(closure).cast(),1061 )1062 };10631064 assert!(!primop.is_null(), "primop allocation should not fail");10651066 Self(primop)1067 }1068 pub fn register(self) {1069 unsafe { register_primop(null_mut(), self.0) };1070 }1071}10721073struct StorePath(*mut c_store_path);1074impl StorePath {}10751076impl Drop for StorePath {1077 fn drop(&mut self) {1078 unsafe { store_path_free(self.0) }1079 }1080}10811082#[test_log::test]1083fn test_native() -> Result<()> {1084 init_libraries();1085 NativeFn::new(1086 c"__uppercaseSuffix2",1087 c"make string uppercase and add suffix",1088 [c"str", c"suffix"],1089 |_, [str, suffix]: [&Value; 2]| {1090 let str = str.to_string()?;1091 let suffix = suffix.to_string()?;1092 Ok(Value::new_str(&format!("{}{suffix}", str.to_uppercase())))1093 },1094 )1095 .register();10961097 let mut fetch_settings = FetchSettings::new();1098 fetch_settings.set(c"warn-dirty", c"false");10991100 let manifest = format!("git+file://{}/../../", env!("CARGO_MANIFEST_DIR"));1101 let flake = FlakeSettings::new()?;1102 let parse = FlakeReferenceParseFlags::new(&flake)?;1103 let (mut r, _) = FlakeReference::new(&manifest, &flake, &parse, &fetch_settings)?;1104 let lock = FlakeLockFlags::new(&flake)?;1105 let locked = r.lock(&fetch_settings, &flake, &lock)?;1106 let attrs = locked.get_attrs(&mut FlakeSettings::new()?)?;11071108 let builtins = Value::eval("builtins")?;1109 assert_eq!(builtins.type_of(), NixType::Attrs);11101111 assert_eq!(attrs.type_of(), NixType::Attrs);1112 let test_data = nix_go!(attrs.testData);11131114 let test_string: String = nix_go_json!(test_data.testString);1115 assert_eq!(test_string, "hello");11161117 let s = nix_go!(attrs.packages["x86_64-linux"].fleet.drvPath);1118 let s = CString::new(s.to_string()?).expect("path str is cstring");11191120 let uppercase_suffix = Value::new_primop(NativeFn::new(1121 c"uppercase_suffix",1122 c"make string uppercase and add suffix",1123 [c"str", c"suffix"],1124 |es, [str, suffix]: [&Value; 2]| {1125 let str = str.to_string()?;1126 let suffix = suffix.to_string()?;1127 Ok(Value::new_str(&format!("{}{suffix}", str.to_uppercase())))1128 },1129 ));11301131 let test_result: String = nix_go_json!(test_data.testPrimop(uppercase_suffix));1132 assert_eq!(test_result, "PREFIX_BODY_SUFFIX");1133 let test_result: String = nix_go_json!(builtins.uppercaseSuffix2("test")("suffix"));1134 assert_eq!(test_result, "TESTsuffix");11351136 let drv_path =1137 nix_go!(attrs.packages["x86_64-linux"]["fleet-install-secrets"].drvPath).to_string()?;1138 let graph = drv::DrvGraph::resolve(&drv_path)?;1139 eprintln!(1140 "fleet-install-secrets dependency graph: {} nodes",1141 graph.nodes.len()1142 );1143 for (path, node) in &graph.nodes {1144 if !node.input_drvs.is_empty() {1145 eprintln!(" {} ({} deps)", node.name, node.input_drvs.len());1146 }1147 }11481149 Ok(())1150}1151115211531154115511561157115811591160116111621163116411651166116711681169