git.delta.rocks / fleet / refs/commits / 7bbada6c128c

difftreelog

source

crates/nix-eval/src/lib.rs31.2 KiBsourcehistory
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};4445// Contains macros helpers46pub 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		// TODO: Can throw error (resulting in panic) if unable to retrieve error. Should be able to resolve by passing context as a first argument,219		// but it looks ugly220		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	// Store should be valid as long as EvalState is valid271	#[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	// It is reused for thread333	std::mem::forget(ctx);334	v335}336337/// Same as with_default_context, but also passes store...338/// Yep, this code is garbage and needs to be refactored.339pub(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		// with_default_context(|c, _| unsafe { flake_lock_flags_set_mode_virtual(c, o.0) })?;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		// let fetch_settings = fetcher_settings;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				// bindings_builder_insert doesn't do incref629			})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		// String is copied, `s` is free to be dropped709		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	// TODO: As far as I can see, there is no way to get Thunks from nix public C api, so this function is useless726	// fn force(&mut self, st: &mut EvalState) -> Result<()> {727	// 	with_default_context(|c, _| unsafe { value_force(c, st.0, self.0) })?;728	// 	Ok(())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		// let store_paths = unsafe { nix_raw::realised_string_get_store_path_count(str) };760		// for i in 0..store_paths {761		// 	let store_path = unsafe { nix_raw::realised_string_get_store_path(str, i) };762		// 	nix_raw::store_path_name(store_path, callback, user_data);763		// }764		// dbg!(store_paths);765		// todo!();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	// pub fn derivation_path(&self) {777	// 	nix_raw::real778	// }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 /*, ignore_errors: bool*/) -> 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		// to_string here blocks until the path is built887		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	// Convert to string/evaluate derivations/etc902	// fn to_string_weak(&self) -> Result<String> {903	// 	// TODO: For now, it works exactly like to_string, see the comment for fn force()904	// 	self.to_string()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	// It should be possible to do Handle::current(), but some of the planned features don't work well with that1002	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		// Double-boxing to make it thin pointer, as vtable gets outside of first Box1047		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}11511152// pub struct GcAlloc;1153// unsafe impl GlobalAlloc for GcAlloc {1154// 	unsafe fn alloc(&self, l: Layout) -> *mut u8 {1155// 		let ptr = unsafe { GC_malloc(l.size()) };1156// 		ptr.cast()1157// 	}1158// 	unsafe fn dealloc(&self, ptr: *mut u8, _: Layout) {1159// 		// unsafe { GC_free(ptr.cast()) };1160// 	}1161//1162// 	unsafe fn realloc(&self, ptr: *mut u8, _: Layout, new_size: usize) -> *mut u8 {1163// 		let ptr = unsafe { GC_realloc(ptr.cast(), new_size) };1164// 		ptr.cast()1165// 	}1166// }1167//1168// #[global_allocator]1169// static GC: GcAlloc = GcAlloc;