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> =311 LazyLock::new(|| GlobalState::new().expect("global state init shouldn't fail"));312313thread_local! {314 static THREAD_STATE: RefCell<ThreadState> = RefCell::new(ThreadState::new().expect("thread state init shouldn't fail"));315}316fn with_default_context<T>(f: impl FnOnce(*mut c_context, *mut c_eval_state) -> T) -> Result<T> {317 let global = &GLOBAL_STATE.state;318 let (ctx, state) = THREAD_STATE.with_borrow_mut(|w| (w.ctx.0, global.0));319 let mut ctx = NixContext(ctx);320 let v = ctx.run_in_context(|c| f(c, state));321 322 std::mem::forget(ctx);323 v324}325326pub fn set_setting(s: &CStr, v: &CStr) -> Result<()> {327 with_default_context(|c, _| unsafe { setting_set(c, s.as_ptr(), v.as_ptr()) }).map(|_| ())328}329330pub struct FetchSettings(*mut fetchers_settings);331impl FetchSettings {332 pub fn new() -> Self {333 Self::try_new().expect("allocation should not fail")334 }335 fn try_new() -> Result<Self> {336 with_default_context(|c, _| unsafe { fetchers_settings_new(c) }).map(Self)337 }338 pub fn set(&mut self, setting: &CStr, value: &CStr) {339 unsafe {340 set_fetcher_setting(self.0.cast(), setting.as_ptr(), value.as_ptr());341 };342 }343}344unsafe impl Send for FetchSettings {}345unsafe impl Sync for FetchSettings {}346347impl Default for FetchSettings {348 fn default() -> Self {349 Self::new()350 }351}352353impl Drop for FetchSettings {354 fn drop(&mut self) {355 unsafe { fetchers_settings_free(self.0) };356 }357}358pub struct FlakeSettings(*mut flake_settings);359impl FlakeSettings {360 pub fn new() -> Result<Self> {361 with_default_context(|c, _| unsafe { flake_settings_new(c) }).map(Self)362 }363}364unsafe impl Send for FlakeSettings {}365unsafe impl Sync for FlakeSettings {}366impl Drop for FlakeSettings {367 fn drop(&mut self) {368 unsafe {369 flake_settings_free(self.0);370 }371 }372}373374pub struct FlakeReferenceParseFlags(*mut flake_reference_parse_flags);375impl FlakeReferenceParseFlags {376 pub fn new(settings: &FlakeSettings) -> Result<Self> {377 with_default_context(|c, _| unsafe { flake_reference_parse_flags_new(c, settings.0) })378 .map(Self)379 }380 pub fn set_base_dir(&mut self, dir: &str) -> Result<()> {381 with_default_context(|c, _| {382 unsafe {383 flake_reference_parse_flags_set_base_directory(384 c,385 self.0,386 dir.as_ptr().cast(),387 dir.len(),388 )389 };390 })391 }392}393impl Drop for FlakeReferenceParseFlags {394 fn drop(&mut self) {395 unsafe {396 flake_reference_parse_flags_free(self.0);397 }398 }399}400pub struct FlakeLockFlags(*mut flake_lock_flags);401impl FlakeLockFlags {402 pub fn new(settings: &FlakeSettings) -> Result<Self> {403 let o = with_default_context(|c, _| unsafe { flake_lock_flags_new(c, settings.0) })404 .map(Self)?;405 406407 Ok(o)408 }409}410impl Drop for FlakeLockFlags {411 fn drop(&mut self) {412 unsafe {413 flake_lock_flags_free(self.0);414 }415 }416}417418unsafe extern "C" fn copy_nix_str(start: *const c_char, n: c_uint, user_data: *mut c_void) {419 let s = unsafe { slice::from_raw_parts(start.cast::<u8>(), n as usize) };420 let s = std::str::from_utf8(s).expect("c string has invalid utf-8");421 unsafe { *user_data.cast::<String>() = s.to_owned() };422}423424struct Store(*mut c_store);425unsafe impl Send for Store {}426unsafe impl Sync for Store {}427428impl Store {429 fn parse_path(&self, path: &CStr) -> Result<StorePath> {430 with_default_context(|c, _| {431 StorePath(unsafe { store_parse_path(c, self.0, path.as_ptr()) })432 })433 }434}435436#[repr(transparent)]437pub struct EvalState(*mut c_eval_state);438unsafe impl Send for EvalState {}439unsafe impl Sync for EvalState {}440441impl Drop for EvalState {442 fn drop(&mut self) {443 unsafe {444 state_free(self.0);445 }446 }447}448449pub struct FlakeReference(*mut flake_reference);450impl FlakeReference {451 #[instrument(name = "new-flake-reference", skip(flake, parse, fetch))]452 pub fn new(453 s: &str,454 flake: &FlakeSettings,455 parse: &FlakeReferenceParseFlags,456 fetch: &FetchSettings,457 ) -> Result<(Self, String)> {458 let mut out = null_mut();459 let mut fragment = String::new();460 461 with_default_context(|c, _| unsafe {462 flake_reference_and_fragment_from_string(463 c,464 fetch.0,465 flake.0,466 parse.0,467 s.as_ptr().cast(),468 s.len(),469 &mut out,470 Some(copy_nix_str),471 (&raw mut fragment).cast(),472 )473 })?;474 assert!(!out.is_null());475476 Ok((Self(out), fragment))477 }478 #[instrument(name = "lock-flake", skip(self, fetch, flake, lock))]479 pub fn lock(480 &mut self,481 fetch: &FetchSettings,482 flake: &FlakeSettings,483 lock: &FlakeLockFlags,484 ) -> Result<LockedFlake> {485 with_default_context(|c, es| unsafe { flake_lock(c, fetch.0, flake.0, es, lock.0, self.0) })486 .map(LockedFlake)487 }488}489unsafe impl Send for FlakeReference {}490unsafe impl Sync for FlakeReference {}491492pub struct LockedFlake(*mut locked_flake);493impl LockedFlake {494 pub fn get_attrs(&self, settings: &mut FlakeSettings) -> Result<Value> {495 with_default_context(|c, es| unsafe {496 locked_flake_get_output_attrs(c, settings.0, es, self.0)497 })498 .map(Value)499 }500}501unsafe impl Send for LockedFlake {}502unsafe impl Sync for LockedFlake {}503impl Drop for LockedFlake {504 fn drop(&mut self) {505 unsafe {506 locked_flake_free(self.0);507 };508 }509}510511type FieldName = [u8; 64];512fn init_field_name(v: &str) -> FieldName {513 let mut f = [0; 64];514 assert!(v.len() < 64, "max field name is 63 chars");515 assert!(516 v.bytes().all(|v| v != 0),517 "nul bytes are unsupported in field name"518 );519 f[0..v.len()].copy_from_slice(v.as_bytes());520 f521}522523pub struct RealisedString(*mut realised_string);524impl fmt::Debug for RealisedString {525 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {526 self.as_str().fmt(f)527 }528}529530impl RealisedString {531 pub fn as_str(&self) -> &str {532 let len = unsafe { realised_string_get_buffer_size(self.0) };533 let data: *const u8 = unsafe { realised_string_get_buffer_start(self.0) }.cast();534 let data = unsafe { slice::from_raw_parts(data, len) };535 std::str::from_utf8(data).expect("non-utf8 strings not supported")536 }537 pub fn path_count(&self) -> usize {538 unsafe { realised_string_get_store_path_count(self.0) }539 }540 pub fn path(&self, i: usize) -> String {541 assert!(i < self.path_count());542 let path = unsafe { realised_string_get_store_path(self.0, i) };543 let mut err_out = String::new();544 unsafe { store_path_name(path, Some(copy_nix_str), (&raw mut err_out).cast()) };545 err_out546 }547}548549unsafe impl Send for RealisedString {}550impl Drop for RealisedString {551 fn drop(&mut self) {552 unsafe { realised_string_free(self.0) }553 }554}555556#[repr(transparent)]557pub struct Value(*mut value);558559unsafe impl Send for Value {}560unsafe impl Sync for Value {}561562pub trait AsFieldName {563 fn as_field_name<T>(&self, v: impl FnOnce(FieldName) -> Result<T>) -> Result<T>;564 fn to_field_name(&self) -> Result<String>;565}566impl AsFieldName for Value {567 fn as_field_name<T>(&self, v: impl FnOnce(FieldName) -> Result<T>) -> Result<T> {568 let f = self.to_string()?;569 v(init_field_name(&f))570 }571 fn to_field_name(&self) -> Result<String> {572 self.to_string()573 }574}575impl<E> AsFieldName for E576where577 E: AsRef<str>,578{579 fn as_field_name<T>(&self, v: impl FnOnce(FieldName) -> Result<T>) -> Result<T> {580 let f = self.as_ref();581 v(init_field_name(f))582 }583 fn to_field_name(&self) -> Result<String> {584 Ok(self.as_ref().to_owned())585 }586}587588struct AttrsBuilder(*mut c_bindings_builder);589impl AttrsBuilder {590 fn new(capacity: usize) -> Self {591 with_default_context(|c, es| unsafe { make_bindings_builder(c, es, capacity) })592 .map(Self)593 .expect("alloc should not fail")594 }595 fn insert(&mut self, k: &impl AsFieldName, v: Value) {596 k.as_field_name(|name| {597 with_default_context(|c, _| unsafe {598 bindings_builder_insert(c, self.0, name.as_ptr().cast(), v.0);599 600 })601 })602 .expect("builder insert shouldn't fail");603 }604}605impl Drop for AttrsBuilder {606 fn drop(&mut self) {607 unsafe { bindings_builder_free(self.0) };608 }609}610611struct ListBuilder(*mut c_list_builder, c_uint);612impl ListBuilder {613 fn new(capacity: usize) -> Self {614 with_default_context(|c, es| unsafe { make_list_builder(c, es, capacity) })615 .map(|l| Self(l, 0))616 .expect("alloc should not fail")617 }618}619impl ListBuilder {620 fn push(&mut self, v: Value) {621 with_default_context(|c, _| unsafe {622 list_builder_insert(623 c,624 self.0,625 {626 let v = self.1;627 self.1 += 1;628 v629 },630 v.0,631 )632 })633 .expect("list insert shouldn't fail");634 }635}636impl Drop for ListBuilder {637 fn drop(&mut self) {638 unsafe { list_builder_free(self.0) };639 }640}641642impl Value {643 pub fn new_primop(v: NativeFn) -> Self {644 let out = Self::new_uninit();645 with_default_context(|c, _| unsafe { init_primop(c, out.0, v.0) })646 .expect("primop initialization should not fail");647 out648 }649 pub fn new_attrs(v: HashMap<&str, Value>) -> Self {650 let out = Self::new_uninit();651 let mut b = AttrsBuilder::new(v.len());652 for (k, v) in v {653 b.insert(&k, v);654 }655 with_default_context(|c, _| unsafe { make_attrs(c, out.0, b.0) })656 .expect("attrs initialization should not fail");657658 out659 }660 fn new_list<T: Into<Self>>(v: Vec<T>) -> Self {661 let out = Self::new_uninit();662 let mut b = ListBuilder::new(v.len());663 for v in v {664 b.push(v.into());665 }666 with_default_context(|c, _| unsafe { make_list(c, b.0, out.0) })667 .expect("list initialization should not fail");668669 out670 }671 fn new_uninit() -> Self {672 let out = with_default_context(|c, es| unsafe { alloc_value(c, es) })673 .expect("value allocation should not fail");674 Self(out)675 }676 pub fn new_str(v: &str) -> Self {677 let s = CString::new(v).expect("string should not contain NULs");678 let out = Self::new_uninit();679 680 with_default_context(|c, _| unsafe { init_string(c, out.0, s.as_ptr()) })681 .expect("string initialization should not fail");682 out683 }684 pub fn new_int(i: i64) -> Self {685 let out = Self::new_uninit();686 with_default_context(|c, _| unsafe { init_int(c, out.0, i) })687 .expect("int initialization should not fail");688 out689 }690 pub fn new_bool(v: bool) -> Self {691 let out = Self::new_uninit();692 with_default_context(|c, _| unsafe { init_bool(c, out.0, v) })693 .expect("bool initialization should not fail");694 out695 }696 697 698 699 700 701 pub fn type_of(&self) -> NixType {702 let ty = with_default_context(|c, _| unsafe { get_type(c, self.0) })703 .expect("get_type should not fail");704 NixType::from_int(ty)705 }706 fn builtin_to_string(&self) -> Result<Self> {707 let builtin = Self::eval("builtins.toString")?;708 builtin.call(self.clone())709 }710 fn force(&mut self, s: *mut nix_raw::EvalState) -> Result<()> {711 with_default_context(|c, _| unsafe { value_force(c, s, self.0) })?;712 Ok(())713 }714 pub fn to_string(&self) -> Result<String> {715 let ty = self.type_of();716 if !matches!(ty, NixType::String) {717 bail!("unexpected type: {ty:?}, expected string");718 }719 let mut str_out = String::new();720 with_default_context(|c, _| unsafe {721 get_string(c, self.0, Some(copy_nix_str), (&raw mut str_out).cast())722 })?;723724 Ok(str_out)725 }726 pub fn to_realised_string(&self) -> Result<RealisedString> {727 with_default_context(|c, es| unsafe { string_realise(c, es, self.0, false) })728 .map(RealisedString)729730 731 732 733 734 735 736 737 }738739 pub fn has_field(&self, field: &str) -> Result<bool> {740 if !matches!(self.type_of(), NixType::Attrs) {741 bail!("invalid type: expected attrs");742 }743744 let f = init_field_name(field);745 with_default_context(|c, es| unsafe { has_attr_byname(c, self.0, es, f.as_ptr().cast()) })746 }747 748 749 750 pub fn list_fields(&self) -> Result<Vec<String>> {751 if !matches!(self.type_of(), NixType::Attrs) {752 bail!("invalid type: expected attrs");753 }754755 let len = with_default_context(|c, _| unsafe { get_attrs_size(c, self.0) })?;756 let mut out = Vec::with_capacity(len as usize);757758 for i in 0..len {759 let name =760 with_default_context(|c, es| unsafe { get_attr_name_byidx(c, self.0, es, i) })?;761 let c = unsafe { CStr::from_ptr(name) };762 out.push(c.to_str().expect("nix field names are utf-8").to_owned());763 }764 Ok(out)765 }766 pub fn get_elem(&self, v: usize) -> Result<Self> {767 if !matches!(self.type_of(), NixType::List) {768 bail!("invalid type: expected list");769 }770 let len = with_default_context(|c, _| unsafe { get_list_size(c, self.0) })? as usize;771 if v >= len {772 bail!("oob list get: {v} >= {len}");773 }774775 with_default_context(|c, es| unsafe { get_list_byidx(c, self.0, es, v as u32) }).map(Self)776 }777 pub fn attrs_update(self, other: Value ) -> Result<Self> {778 let attrs_update_fn = Self::eval("a: b: a // b")?;779780 attrs_update_fn781 .call(self)?782 .call(other)783 .context("attrs update")784 }785 pub fn get_field(&self, name: impl AsFieldName) -> Result<Self> {786 if !matches!(self.type_of(), NixType::Attrs) {787 bail!("invalid type: expected attrs");788 }789790 name.as_field_name(|name| {791 with_default_context(|c, es| unsafe {792 get_attr_byname(c, self.0, es, name.as_ptr().cast())793 })794 .map(Self)795 })796 .with_context(|| format!("getting field {:?}", name.to_field_name()))797 }798 pub fn call(&self, v: Value) -> Result<Self> {799 let kind = self800 .functor_kind()801 .ok_or_else(|| anyhow!("can only call function or functor"))?;802803 let function = match kind {804 FunctorKind::Function => self.clone(),805 FunctorKind::Functor => {806 let f = self807 .get_field("__functor")808 .context("getting functor value")?;809 assert_eq!(810 f.type_of(),811 NixType::Function,812 "invalid functor encountered"813 );814 f815 }816 };817818 let out = Value::new_uninit();819 with_default_context(|c, es| unsafe { value_call(c, es, function.0, v.0, out.0) })?;820821 Ok(out)822 }823 pub fn eval(v: &str) -> Result<Self> {824 let s = CString::new(v).expect("expression shouldn't have internal NULs");825 let out = Self::new_uninit();826 with_default_context(|c, es| unsafe {827 expr_eval_from_string(c, es, s.as_ptr(), c"/root".as_ptr(), out.0)828 })?;829 Ok(out)830 }831 pub fn build(&self, output: &str) -> Result<PathBuf> {832 if !self.is_derivation() {833 bail!("expected derivation to build")834 }835 let output_name = self836 .get_field("outputName")837 .context("getting output name field")?838 .to_string()?;839 let v = if output_name != output {840 let out = self.get_field(output).context("getting target output")?;841 if !out.is_derivation() {842 bail!("unknown output: {output}");843 }844 out845 } else {846 self.clone()847 };848 849 let s = v.builtin_to_string()?;850 let rs = s.to_realised_string()?;851 let drv_path = rs.as_str().to_owned();852 Ok(PathBuf::from(drv_path))853 }854 pub fn as_json<T: DeserializeOwned>(&self) -> Result<T> {855 let to_json = Self::eval("builtins.toJSON")?;856 let s = to_json.call(self.clone())?.to_string()?;857 Ok(serde_json::from_str(&s)?)858 }859 pub fn serialized<T: Serialize>(v: &T) -> Result<Self> {860 Self::eval(&nixlike::serialize(v)?)861 }862863 864 865 866 867 868869 fn is_derivation(&self) -> bool {870 if !matches!(self.type_of(), NixType::Attrs) {871 return false;872 }873 let Some(ty) = self.get_field("type").ok() else {874 return false;875 };876 matches!(ty.to_string().as_deref(), Ok("derivation"))877 }878 fn functor_kind(&self) -> Option<FunctorKind> {879 match self.type_of() {880 NixType::Attrs => self881 .has_field("__functor")882 .expect("has_field shouldn't fail for attrs")883 .then_some(FunctorKind::Functor),884 NixType::Function => Some(FunctorKind::Function),885 _ => None,886 }887 }888 pub fn is_function(&self) -> bool {889 self.functor_kind().is_some()890 }891 pub fn is_null(&self) -> bool {892 matches!(self.type_of(), NixType::Null)893 }894 pub fn is_string(&self) -> bool {895 matches!(self.type_of(), NixType::String)896 }897 pub fn is_attrs(&self) -> bool {898 matches!(self.type_of(), NixType::Attrs)899 }900}901902impl From<String> for Value {903 fn from(value: String) -> Self {904 Value::new_str(&value)905 }906}907impl From<bool> for Value {908 fn from(value: bool) -> Self {909 Value::new_bool(value)910 }911}912impl From<&str> for Value {913 fn from(value: &str) -> Self {914 Value::new_str(value)915 }916}917impl<T> From<Vec<T>> for Value918where919 T: Into<Value>,920{921 fn from(value: Vec<T>) -> Self {922 Value::new_list(value)923 }924}925926impl Clone for Value {927 fn clone(&self) -> Self {928 with_default_context(|c, _| unsafe { value_incref(c, self.0) })929 .expect("value incref should not fail");930 Self(self.0)931 }932}933impl Drop for Value {934 fn drop(&mut self) {935 with_default_context(|c, _| unsafe { value_decref(c, self.0) })936 .expect("value drop should not fail");937 }938}939940static TOKIO_FOR_NIX: OnceLock<Arc<tokio::runtime::Runtime>> = OnceLock::new();941942pub fn init_libraries() {943 unsafe { GC_allow_register_threads() };944945 let mut ctx = NixContext::new();946 ctx.run_in_context(|c| unsafe { libutil_init(c) })947 .expect("util init should not fail");948 ctx.run_in_context(|c| unsafe { libstore_init(c) })949 .expect("store init should not fail");950 ctx.run_in_context(|c| unsafe { libexpr_init(c) })951 .expect("expr init should not fail");952953 nix_logging_cxx::apply_tracing_logger();954}955956pub fn init_tokio_for_nix(tokio: Arc<tokio::runtime::Runtime>) {957 TOKIO_FOR_NIX958 .set(tokio)959 .expect("tokio for nix should only be initialized once");960}961962pub fn await_in_nix<F: Send + 'static>(f: impl Future<Output = F> + Send + 'static) -> F {963 964 let runtime = TOKIO_FOR_NIX965 .get()966 .expect("init_tokio_for_nix was not called");967 std::thread::spawn(move || runtime.block_on(f))968 .join()969 .expect("await_in_nix inner thread panicked")970}971972unsafe extern "C" fn nix_primop_closure_adapter<const N: usize>(973 user_data: *mut c_void,974 mut context: *mut c_context,975 state: *mut nix_raw::EvalState,976 args: *mut *mut value,977 ret: *mut value,978) {979 let user_closure: &UserClosure<N> = unsafe { &*user_data.cast_const().cast() };980 let args: [&Value; N] = array::from_fn(|i| {981 let v: &mut Value = unsafe { &mut *args.add(i).cast() };982 v as &Value983 });984 let ctx: &mut NixContext = unsafe { transmute(&mut context) };985986 let state: &EvalState = unsafe { std::mem::transmute(&state) };987988 match user_closure(state, args) {989 Ok(v) => {990 unsafe { copy_value(context, ret, v.0) };991 }992 Err(e) => {993 ctx.set_err(e);994 }995 }996}997998type UserClosure<const N: usize> = Box<dyn Fn(&EvalState, [&Value; N]) -> Result<Value>>;9991000pub struct NativeFn(*mut PrimOp);1001impl NativeFn {1002 pub fn new<const N: usize>(1003 name: &'static CStr,1004 doc: &'static CStr,1005 args: [&'static CStr; N],1006 f: impl Fn(&EvalState, [&Value; N]) -> Result<Value> + 'static,1007 ) -> Self {1008 1009 let closure: Box<UserClosure<N>> = Box::new(Box::new(f));1010 let f: PrimOpFun = Some(nix_primop_closure_adapter::<N>);1011 let mut args = args.into_iter().map(|v| v.as_ptr()).collect_vec();1012 args.push(null());1013 let args = args.as_mut_ptr();1014 let primop = unsafe {1015 alloc_primop(1016 null_mut(),1017 f,1018 N as i32,1019 name.as_ptr(),1020 args,1021 doc.as_ptr(),1022 Box::into_raw(closure).cast(),1023 )1024 };10251026 assert!(!primop.is_null(), "primop allocation should not fail");10271028 Self(primop)1029 }1030 pub fn register(self) {1031 unsafe { register_primop(null_mut(), self.0) };1032 }1033}10341035struct StorePath(*mut c_store_path);1036impl StorePath {}10371038impl Drop for StorePath {1039 fn drop(&mut self) {1040 unsafe { store_path_free(self.0) }1041 }1042}10431044#[test_log::test]1045fn test_native() -> Result<()> {1046 init_libraries();1047 NativeFn::new(1048 c"__uppercaseSuffix2",1049 c"make string uppercase and add suffix",1050 [c"str", c"suffix"],1051 |_, [str, suffix]: [&Value; 2]| {1052 let str = str.to_string()?;1053 let suffix = suffix.to_string()?;1054 Ok(Value::new_str(&format!("{}{suffix}", str.to_uppercase())))1055 },1056 )1057 .register();10581059 let mut fetch_settings = FetchSettings::new();1060 fetch_settings.set(c"warn-dirty", c"false");10611062 let manifest = format!("git+file://{}/../../", env!("CARGO_MANIFEST_DIR"));1063 let flake = FlakeSettings::new()?;1064 let parse = FlakeReferenceParseFlags::new(&flake)?;1065 let (mut r, _) = FlakeReference::new(&manifest, &flake, &parse, &fetch_settings)?;1066 let lock = FlakeLockFlags::new(&flake)?;1067 let locked = r.lock(&fetch_settings, &flake, &lock)?;1068 let attrs = locked.get_attrs(&mut FlakeSettings::new()?)?;10691070 let builtins = Value::eval("builtins")?;1071 assert_eq!(builtins.type_of(), NixType::Attrs);10721073 assert_eq!(attrs.type_of(), NixType::Attrs);1074 let test_data = nix_go!(attrs.testData);10751076 let test_string: String = nix_go_json!(test_data.testString);1077 assert_eq!(test_string, "hello");10781079 let s = nix_go!(attrs.packages["x86_64-linux"].fleet.drvPath);1080 let s = CString::new(s.to_string()?).expect("path str is cstring");10811082 let uppercase_suffix = Value::new_primop(NativeFn::new(1083 c"uppercase_suffix",1084 c"make string uppercase and add suffix",1085 [c"str", c"suffix"],1086 |es, [str, suffix]: [&Value; 2]| {1087 let str = str.to_string()?;1088 let suffix = suffix.to_string()?;1089 Ok(Value::new_str(&format!("{}{suffix}", str.to_uppercase())))1090 },1091 ));10921093 let test_result: String = nix_go_json!(test_data.testPrimop(uppercase_suffix));1094 assert_eq!(test_result, "PREFIX_BODY_SUFFIX");1095 let test_result: String = nix_go_json!(builtins.uppercaseSuffix2("test")("suffix"));1096 assert_eq!(test_result, "TESTsuffix");10971098 let nix_ctx = NixContext::new();1099 let store = GLOBAL_STATE.store.parse_path(s.as_c_str())?;11001101 11021103 Ok(())1104}1105110611071108110911101111111211131114111511161117111811191120112111221123