git.delta.rocks / jrsonnet / refs/heads / trunk

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,30	fetchers_settings_free, fetchers_settings_new, flake_lock, flake_lock_flags,31	flake_lock_flags_free, flake_lock_flags_new, flake_reference,32	flake_reference_and_fragment_from_string, flake_reference_parse_flags,33	flake_reference_parse_flags_free, flake_reference_parse_flags_new,34	flake_reference_parse_flags_set_base_directory, flake_settings, flake_settings_free,35	flake_settings_new, gc_now as gc_now_raw, get_attr_byname, get_attr_name_byidx, get_attrs_size,36	get_list_byidx, get_list_size, get_string, get_type, has_attr_byname, init_bool, init_int,37	init_primop, init_string, libexpr_init, libstore_init, libutil_init, list_builder_free,38	list_builder_insert, locked_flake, locked_flake_free, locked_flake_get_output_attrs,39	make_attrs, make_bindings_builder, make_list, make_list_builder, realised_string,40	realised_string_free, realised_string_get_buffer_size, realised_string_get_buffer_start,41	realised_string_get_store_path, realised_string_get_store_path_count, register_primop,42	set_err_msg, setting_set, state_free, store_open, store_parse_path, store_path_free,43	store_path_name, string_realise, value, value_call, value_decref, value_force, value_incref,44};4546// Contains macros helpers47pub mod drv;48pub mod logging;49#[doc(hidden)]50pub mod macros;5152#[doc(hidden)]53pub mod __macro_support {54	pub use std::collections::hash_map::HashMap;5556	pub use anyhow::Context;57	pub use tokio::task::block_in_place;58}59pub mod util;6061#[allow(62	non_upper_case_globals,63	non_camel_case_types,64	non_snake_case,65	dead_code66)]67mod nix_raw {68	include!(concat!(env!("OUT_DIR"), "/bindings.rs"));69}70#[cxx::bridge]71pub mod nix_cxx {72	unsafe extern "C++" {73		type nix_fetchers_settings;74		include!("nix-eval/src/lib.hh");7576		#[allow(clippy::missing_safety_doc)]77		unsafe fn set_fetcher_setting(78			settings: *mut nix_fetchers_settings,79			setting: *const c_char,80			value: *const c_char,81		);82	}83}8485#[derive(Debug, PartialEq, Eq)]86pub enum NixType {87	Thunk,88	Int,89	Float,90	Bool,91	String,92	Path,93	Null,94	Attrs,95	List,96	Function,97	External,98}99impl NixType {100	fn from_int(c: c_uint) -> Self {101		match c {102			0 => Self::Thunk,103			1 => Self::Int,104			2 => Self::Float,105			3 => Self::Bool,106			4 => Self::String,107			5 => Self::Path,108			6 => Self::Null,109			7 => Self::Attrs,110			8 => Self::List,111			9 => Self::Function,112			10 => Self::External,113			_ => unreachable!("unknown nix type: {c}"),114		}115	}116}117118enum FunctorKind {119	Function,120	Functor,121}122123#[derive(Debug)]124#[repr(i32)]125pub enum NixErrorKind {126	Unknown = err_NIX_ERR_UNKNOWN,127	Overflow = err_NIX_ERR_OVERFLOW,128	Key = err_NIX_ERR_KEY,129	Generic = err_NIX_ERR_NIX_ERROR,130}131impl NixErrorKind {132	fn from_int(v: c_int) -> Option<Self> {133		Some(match v {134			0 => return None,135			nix_raw::err_NIX_ERR_UNKNOWN => Self::Unknown,136			nix_raw::err_NIX_ERR_OVERFLOW => Self::Overflow,137			nix_raw::err_NIX_ERR_KEY => Self::Key,138			nix_raw::err_NIX_ERR_NIX_ERROR => Self::Generic,139			_ => {140				debug_assert!(false, "unexpected nix error kind: {v}");141				Self::Unknown142			}143		})144	}145}146147pub fn gc_now() {148	unsafe { gc_now_raw() };149}150151pub fn gc_register_my_thread() {152	assert_eq!(unsafe { GC_thread_is_registered() }, 0);153154	let mut sb = GC_stack_base {155		mem_base: null_mut(),156	};157	let r = unsafe { GC_get_stack_base(&mut sb) };158	if r as u32 != GC_SUCCESS {159		panic!("failed to get thread stack base");160	}161	unsafe { GC_register_my_thread(&sb) };162}163pub fn gc_unregister_my_thread() {164	assert_eq!(unsafe { GC_thread_is_registered() }, 1);165166	unsafe { GC_unregister_my_thread() };167}168169pub struct ThreadRegisterGuard {}170impl ThreadRegisterGuard {171	#[allow(clippy::new_without_default)]172	pub fn new() -> Self {173		gc_register_my_thread();174		Self {}175	}176}177impl Drop for ThreadRegisterGuard {178	fn drop(&mut self) {179		gc_unregister_my_thread();180	}181}182183#[repr(transparent)]184pub struct NixContext(*mut c_context);185impl NixContext {186	pub fn set_err_raw(&mut self, err: NixErrorKind, msg: &CStr) {187		unsafe { set_err_msg(self.0, err as c_int, msg.as_ptr()) };188	}189	pub fn set_err(&mut self, err: anyhow::Error) {190		let mut fmt = format!("{err:?}").replace("\0", "\\0");191		self.set_err_raw(192			NixErrorKind::Generic,193			&CString::new(fmt).expect("NUL bytes were just replaced"),194		);195	}196	pub fn new() -> Self {197		let ctx = unsafe { c_context_create() };198		Self(ctx)199	}200	fn error_kind(&self) -> Option<NixErrorKind> {201		let code = unsafe { err_code(self.0) };202		NixErrorKind::from_int(code)203	}204	fn error<'t>(&self) -> Option<(Cow<'t, str>, Option<Box<ErrorInfoBuilder>>)> {205		if let NixErrorKind::Generic = self.error_kind()? {206			let ei = unsafe { logging::nix_logging_cxx::extract_error_info(self.0) };207			let mut err_out = String::new();208			unsafe {209				err_info_msg(210					null_mut(),211					self.0,212					Some(copy_nix_str),213					(&raw mut err_out).cast(),214				)215			};216			return Some((Cow::Owned(err_out), Some(ei)));217		};218219		// TODO: Can throw error (resulting in panic) if unable to retrieve error. Should be able to resolve by passing context as a first argument,220		// but it looks ugly221		let str = unsafe { err_msg(null_mut(), self.0, null_mut()) };222		Some((unsafe { CStr::from_ptr(str) }.to_string_lossy(), None))223	}224	fn clean_err(&mut self) {225		unsafe {226			clear_err(self.0);227		}228	}229230	fn bail_if_error(&self) -> Result<()> {231		if let Some((err, stack)) = self.error() {232			let mut e = Err(anyhow!("{err}"));233			if let Some(stack) = stack {234				for ele in stack.stack_frames {235					e = e.with_context(|| {236						if ele.pos.is_empty() {237							ele.msg238						} else {239							format!("{} at {}", ele.msg, ele.pos)240						}241					})242				}243			}244			return e.context("<nix frames>");245		};246		Ok(())247	}248249	fn run_in_context<T>(&mut self, f: impl FnOnce(*mut c_context) -> T) -> Result<T> {250		self.clean_err();251		let o = f(self.0);252		self.bail_if_error()?;253		self.clean_err();254		Ok(o)255	}256}257258impl Default for NixContext {259	fn default() -> Self {260		Self::new()261	}262}263impl Drop for NixContext {264	fn drop(&mut self) {265		unsafe {266			c_context_free(self.0);267		}268	}269}270struct GlobalState {271	// Store should be valid as long as EvalState is valid272	#[allow(dead_code)]273	store: Store,274	state: EvalState,275}276impl GlobalState {277	fn new() -> Result<Self> {278		let mut ctx = NixContext::new();279		let store = ctx280			.run_in_context(|c| unsafe { store_open(c, c"auto".as_ptr(), null_mut()) })281			.map(Store)?;282283		let builder = ctx.run_in_context(|c| unsafe { eval_state_builder_new(c, store.0) })?;284		ctx.run_in_context(|c| unsafe { eval_state_builder_load(c, builder) })?;285		ctx.run_in_context(|c| unsafe {286			eval_state_builder_set_eval_setting(287				c,288				builder,289				c"lazy-trees".as_ptr(),290				c"true".as_ptr(),291			)292		})?;293		ctx.run_in_context(|c| unsafe {294			eval_state_builder_set_eval_setting(295				c,296				builder,297				c"lazy-locks".as_ptr(),298				c"true".as_ptr(),299			)300		})?;301		let state = ctx302			.run_in_context(|c| unsafe { eval_state_build(c, builder) })303			.map(EvalState)?;304305		Ok(Self { store, state })306	}307}308309struct ThreadState {310	ctx: NixContext,311}312impl ThreadState {313	fn new() -> Result<Self> {314		let ctx = NixContext::new();315316		Ok(Self { ctx })317	}318}319320static GLOBAL_STATE: LazyLock<GlobalState> =321	LazyLock::new(|| GlobalState::new().expect("global state init shouldn't fail"));322323thread_local! {324	static THREAD_STATE: RefCell<ThreadState> = RefCell::new(ThreadState::new().expect("thread state init shouldn't fail"));325}326pub(crate) fn with_default_context<T>(f: impl FnOnce(*mut c_context, *mut c_eval_state) -> T) -> Result<T> {327	let global = &GLOBAL_STATE.state;328	let (ctx, state) = THREAD_STATE.with_borrow_mut(|w| (w.ctx.0, global.0));329	let mut ctx = NixContext(ctx);330	let v = ctx.run_in_context(|c| f(c, state));331	// It is reused for thread332	std::mem::forget(ctx);333	v334}335336/// Same as with_default_context, but also passes store...337/// Yep, this code is garbage and needs to be refactored.338pub(crate) fn with_store_context<T>(339	f: impl FnOnce(*mut c_context, *mut c_store, *mut c_eval_state) -> T,340) -> Result<T> {341	let global = &GLOBAL_STATE;342	let (ctx, store, state) =343		THREAD_STATE.with_borrow_mut(|w| (w.ctx.0, global.store.0, global.state.0));344	let mut ctx = NixContext(ctx);345	let v = ctx.run_in_context(|c| f(c, store, state));346	std::mem::forget(ctx);347	v348}349350pub fn set_setting(s: &CStr, v: &CStr) -> Result<()> {351	with_default_context(|c, _| unsafe { setting_set(c, s.as_ptr(), v.as_ptr()) }).map(|_| ())352}353354pub struct FetchSettings(*mut fetchers_settings);355impl FetchSettings {356	pub fn new() -> Self {357		Self::try_new().expect("allocation should not fail")358	}359	fn try_new() -> Result<Self> {360		with_default_context(|c, _| unsafe { fetchers_settings_new(c) }).map(Self)361	}362	pub fn set(&mut self, setting: &CStr, value: &CStr) {363		unsafe {364			set_fetcher_setting(self.0.cast(), setting.as_ptr(), value.as_ptr());365		};366	}367}368unsafe impl Send for FetchSettings {}369unsafe impl Sync for FetchSettings {}370371impl Default for FetchSettings {372	fn default() -> Self {373		Self::new()374	}375}376377impl Drop for FetchSettings {378	fn drop(&mut self) {379		unsafe { fetchers_settings_free(self.0) };380	}381}382pub struct FlakeSettings(*mut flake_settings);383impl FlakeSettings {384	pub fn new() -> Result<Self> {385		with_default_context(|c, _| unsafe { flake_settings_new(c) }).map(Self)386	}387}388unsafe impl Send for FlakeSettings {}389unsafe impl Sync for FlakeSettings {}390impl Drop for FlakeSettings {391	fn drop(&mut self) {392		unsafe {393			flake_settings_free(self.0);394		}395	}396}397398pub struct FlakeReferenceParseFlags(*mut flake_reference_parse_flags);399impl FlakeReferenceParseFlags {400	pub fn new(settings: &FlakeSettings) -> Result<Self> {401		with_default_context(|c, _| unsafe { flake_reference_parse_flags_new(c, settings.0) })402			.map(Self)403	}404	pub fn set_base_dir(&mut self, dir: &str) -> Result<()> {405		with_default_context(|c, _| {406			unsafe {407				flake_reference_parse_flags_set_base_directory(408					c,409					self.0,410					dir.as_ptr().cast(),411					dir.len(),412				)413			};414		})415	}416}417impl Drop for FlakeReferenceParseFlags {418	fn drop(&mut self) {419		unsafe {420			flake_reference_parse_flags_free(self.0);421		}422	}423}424pub struct FlakeLockFlags(*mut flake_lock_flags);425impl FlakeLockFlags {426	pub fn new(settings: &FlakeSettings) -> Result<Self> {427		let o = with_default_context(|c, _| unsafe { flake_lock_flags_new(c, settings.0) })428			.map(Self)?;429		// with_default_context(|c, _| unsafe { flake_lock_flags_set_mode_virtual(c, o.0) })?;430431		Ok(o)432	}433}434impl Drop for FlakeLockFlags {435	fn drop(&mut self) {436		unsafe {437			flake_lock_flags_free(self.0);438		}439	}440}441442pub(crate) unsafe extern "C" fn copy_nix_str(start: *const c_char, n: c_uint, user_data: *mut c_void) {443	let s = unsafe { slice::from_raw_parts(start.cast::<u8>(), n as usize) };444	let s = std::str::from_utf8(s).expect("c string has invalid utf-8");445	unsafe { *user_data.cast::<String>() = s.to_owned() };446}447448struct Store(*mut c_store);449unsafe impl Send for Store {}450unsafe impl Sync for Store {}451452impl Store {453	fn parse_path(&self, path: &CStr) -> Result<StorePath> {454		with_default_context(|c, _| {455			StorePath(unsafe { store_parse_path(c, self.0, path.as_ptr()) })456		})457	}458}459460#[repr(transparent)]461pub struct EvalState(*mut c_eval_state);462unsafe impl Send for EvalState {}463unsafe impl Sync for EvalState {}464465impl Drop for EvalState {466	fn drop(&mut self) {467		unsafe {468			state_free(self.0);469		}470	}471}472473pub struct FlakeReference(*mut flake_reference);474impl FlakeReference {475	#[instrument(name = "new-flake-reference", skip(flake, parse, fetch))]476	pub fn new(477		s: &str,478		flake: &FlakeSettings,479		parse: &FlakeReferenceParseFlags,480		fetch: &FetchSettings,481	) -> Result<(Self, String)> {482		let mut out = null_mut();483		let mut fragment = String::new();484		// let fetch_settings = fetcher_settings;485		with_default_context(|c, _| unsafe {486			flake_reference_and_fragment_from_string(487				c,488				fetch.0,489				flake.0,490				parse.0,491				s.as_ptr().cast(),492				s.len(),493				&mut out,494				Some(copy_nix_str),495				(&raw mut fragment).cast(),496			)497		})?;498		assert!(!out.is_null());499500		Ok((Self(out), fragment))501	}502	#[instrument(name = "lock-flake", skip(self, fetch, flake, lock))]503	pub fn lock(504		&mut self,505		fetch: &FetchSettings,506		flake: &FlakeSettings,507		lock: &FlakeLockFlags,508	) -> Result<LockedFlake> {509		with_default_context(|c, es| unsafe { flake_lock(c, fetch.0, flake.0, es, lock.0, self.0) })510			.map(LockedFlake)511	}512}513unsafe impl Send for FlakeReference {}514unsafe impl Sync for FlakeReference {}515516pub struct LockedFlake(*mut locked_flake);517impl LockedFlake {518	pub fn get_attrs(&self, settings: &mut FlakeSettings) -> Result<Value> {519		with_default_context(|c, es| unsafe {520			locked_flake_get_output_attrs(c, settings.0, es, self.0)521		})522		.map(Value)523	}524}525unsafe impl Send for LockedFlake {}526unsafe impl Sync for LockedFlake {}527impl Drop for LockedFlake {528	fn drop(&mut self) {529		unsafe {530			locked_flake_free(self.0);531		};532	}533}534535type FieldName = [u8; 64];536fn init_field_name(v: &str) -> FieldName {537	let mut f = [0; 64];538	assert!(v.len() < 64, "max field name is 63 chars");539	assert!(540		v.bytes().all(|v| v != 0),541		"nul bytes are unsupported in field name"542	);543	f[0..v.len()].copy_from_slice(v.as_bytes());544	f545}546547pub struct RealisedString(*mut realised_string);548impl fmt::Debug for RealisedString {549	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {550		self.as_str().fmt(f)551	}552}553554impl RealisedString {555	pub fn as_str(&self) -> &str {556		let len = unsafe { realised_string_get_buffer_size(self.0) };557		let data: *const u8 = unsafe { realised_string_get_buffer_start(self.0) }.cast();558		let data = unsafe { slice::from_raw_parts(data, len) };559		std::str::from_utf8(data).expect("non-utf8 strings not supported")560	}561	pub fn path_count(&self) -> usize {562		unsafe { realised_string_get_store_path_count(self.0) }563	}564	pub fn path(&self, i: usize) -> String {565		assert!(i < self.path_count());566		let path = unsafe { realised_string_get_store_path(self.0, i) };567		let mut err_out = String::new();568		unsafe { store_path_name(path, Some(copy_nix_str), (&raw mut err_out).cast()) };569		err_out570	}571}572573unsafe impl Send for RealisedString {}574impl Drop for RealisedString {575	fn drop(&mut self) {576		unsafe { realised_string_free(self.0) }577	}578}579580#[repr(transparent)]581pub struct Value(*mut value);582583unsafe impl Send for Value {}584unsafe impl Sync for Value {}585586pub trait AsFieldName {587	fn as_field_name<T>(&self, v: impl FnOnce(FieldName) -> Result<T>) -> Result<T>;588	fn to_field_name(&self) -> Result<String>;589}590impl AsFieldName for Value {591	fn as_field_name<T>(&self, v: impl FnOnce(FieldName) -> Result<T>) -> Result<T> {592		let f = self.to_string()?;593		v(init_field_name(&f))594	}595	fn to_field_name(&self) -> Result<String> {596		self.to_string()597	}598}599impl<E> AsFieldName for E600where601	E: AsRef<str>,602{603	fn as_field_name<T>(&self, v: impl FnOnce(FieldName) -> Result<T>) -> Result<T> {604		let f = self.as_ref();605		v(init_field_name(f))606	}607	fn to_field_name(&self) -> Result<String> {608		Ok(self.as_ref().to_owned())609	}610}611612struct AttrsBuilder(*mut c_bindings_builder);613impl AttrsBuilder {614	fn new(capacity: usize) -> Self {615		with_default_context(|c, es| unsafe { make_bindings_builder(c, es, capacity) })616			.map(Self)617			.expect("alloc should not fail")618	}619	fn insert(&mut self, k: &impl AsFieldName, v: Value) {620		k.as_field_name(|name| {621			with_default_context(|c, _| unsafe {622				bindings_builder_insert(c, self.0, name.as_ptr().cast(), v.0);623				// bindings_builder_insert doesn't do incref624			})625		})626		.expect("builder insert shouldn't fail");627	}628}629impl Drop for AttrsBuilder {630	fn drop(&mut self) {631		unsafe { bindings_builder_free(self.0) };632	}633}634635struct ListBuilder(*mut c_list_builder, c_uint);636impl ListBuilder {637	fn new(capacity: usize) -> Self {638		with_default_context(|c, es| unsafe { make_list_builder(c, es, capacity) })639			.map(|l| Self(l, 0))640			.expect("alloc should not fail")641	}642}643impl ListBuilder {644	fn push(&mut self, v: Value) {645		with_default_context(|c, _| unsafe {646			list_builder_insert(647				c,648				self.0,649				{650					let v = self.1;651					self.1 += 1;652					v653				},654				v.0,655			)656		})657		.expect("list insert shouldn't fail");658	}659}660impl Drop for ListBuilder {661	fn drop(&mut self) {662		unsafe { list_builder_free(self.0) };663	}664}665666impl Value {667	pub fn new_primop(v: NativeFn) -> Self {668		let out = Self::new_uninit();669		with_default_context(|c, _| unsafe { init_primop(c, out.0, v.0) })670			.expect("primop initialization should not fail");671		out672	}673	pub fn new_attrs(v: HashMap<&str, Value>) -> Self {674		let out = Self::new_uninit();675		let mut b = AttrsBuilder::new(v.len());676		for (k, v) in v {677			b.insert(&k, v);678		}679		with_default_context(|c, _| unsafe { make_attrs(c, out.0, b.0) })680			.expect("attrs initialization should not fail");681682		out683	}684	fn new_list<T: Into<Self>>(v: Vec<T>) -> Self {685		let out = Self::new_uninit();686		let mut b = ListBuilder::new(v.len());687		for v in v {688			b.push(v.into());689		}690		with_default_context(|c, _| unsafe { make_list(c, b.0, out.0) })691			.expect("list initialization should not fail");692693		out694	}695	fn new_uninit() -> Self {696		let out = with_default_context(|c, es| unsafe { alloc_value(c, es) })697			.expect("value allocation should not fail");698		Self(out)699	}700	pub fn new_str(v: &str) -> Self {701		let s = CString::new(v).expect("string should not contain NULs");702		let out = Self::new_uninit();703		// String is copied, `s` is free to be dropped704		with_default_context(|c, _| unsafe { init_string(c, out.0, s.as_ptr()) })705			.expect("string initialization should not fail");706		out707	}708	pub fn new_int(i: i64) -> Self {709		let out = Self::new_uninit();710		with_default_context(|c, _| unsafe { init_int(c, out.0, i) })711			.expect("int initialization should not fail");712		out713	}714	pub fn new_bool(v: bool) -> Self {715		let out = Self::new_uninit();716		with_default_context(|c, _| unsafe { init_bool(c, out.0, v) })717			.expect("bool initialization should not fail");718		out719	}720	// TODO: As far as I can see, there is no way to get Thunks from nix public C api, so this function is useless721	// fn force(&mut self, st: &mut EvalState) -> Result<()> {722	// 	with_default_context(|c, _| unsafe { value_force(c, st.0, self.0) })?;723	// 	Ok(())724	// }725	pub fn type_of(&self) -> NixType {726		let ty = with_default_context(|c, _| unsafe { get_type(c, self.0) })727			.expect("get_type should not fail");728		NixType::from_int(ty)729	}730	fn builtin_to_string(&self) -> Result<Self> {731		let builtin = Self::eval("builtins.toString")?;732		builtin.call(self.clone())733	}734	fn force(&mut self, s: *mut nix_raw::EvalState) -> Result<()> {735		with_default_context(|c, _| unsafe { value_force(c, s, self.0) })?;736		Ok(())737	}738	pub fn to_string(&self) -> Result<String> {739		let ty = self.type_of();740		if !matches!(ty, NixType::String) {741			bail!("unexpected type: {ty:?}, expected string");742		}743		let mut str_out = String::new();744		with_default_context(|c, _| unsafe {745			get_string(c, self.0, Some(copy_nix_str), (&raw mut str_out).cast())746		})?;747748		Ok(str_out)749	}750	pub fn to_realised_string(&self) -> Result<RealisedString> {751		with_default_context(|c, es| unsafe { string_realise(c, es, self.0, false) })752			.map(RealisedString)753754		// let store_paths = unsafe { nix_raw::realised_string_get_store_path_count(str) };755		// for i in 0..store_paths {756		// 	let store_path = unsafe { nix_raw::realised_string_get_store_path(str, i) };757		// 	nix_raw::store_path_name(store_path, callback, user_data);758		// }759		// dbg!(store_paths);760		// todo!();761	}762763	pub fn has_field(&self, field: &str) -> Result<bool> {764		if !matches!(self.type_of(), NixType::Attrs) {765			bail!("invalid type: expected attrs");766		}767768		let f = init_field_name(field);769		with_default_context(|c, es| unsafe { has_attr_byname(c, self.0, es, f.as_ptr().cast()) })770	}771	// pub fn derivation_path(&self) {772	// 	nix_raw::real773	// }774	pub fn list_fields(&self) -> Result<Vec<String>> {775		if !matches!(self.type_of(), NixType::Attrs) {776			bail!("invalid type: expected attrs");777		}778779		let len = with_default_context(|c, _| unsafe { get_attrs_size(c, self.0) })?;780		let mut out = Vec::with_capacity(len as usize);781782		for i in 0..len {783			let name =784				with_default_context(|c, es| unsafe { get_attr_name_byidx(c, self.0, es, i) })?;785			let c = unsafe { CStr::from_ptr(name) };786			out.push(c.to_str().expect("nix field names are utf-8").to_owned());787		}788		Ok(out)789	}790	pub fn get_elem(&self, v: usize) -> Result<Self> {791		if !matches!(self.type_of(), NixType::List) {792			bail!("invalid type: expected list");793		}794		let len = with_default_context(|c, _| unsafe { get_list_size(c, self.0) })? as usize;795		if v >= len {796			bail!("oob list get: {v} >= {len}");797		}798799		with_default_context(|c, es| unsafe { get_list_byidx(c, self.0, es, v as u32) }).map(Self)800	}801	pub fn attrs_update(self, other: Value /*, ignore_errors: bool*/) -> Result<Self> {802		let attrs_update_fn = Self::eval("a: b: a // b")?;803804		attrs_update_fn805			.call(self)?806			.call(other)807			.context("attrs update")808	}809	pub fn get_field(&self, name: impl AsFieldName) -> Result<Self> {810		if !matches!(self.type_of(), NixType::Attrs) {811			bail!("invalid type: expected attrs");812		}813814		name.as_field_name(|name| {815			with_default_context(|c, es| unsafe {816				get_attr_byname(c, self.0, es, name.as_ptr().cast())817			})818			.map(Self)819		})820		.with_context(|| format!("getting field {:?}", name.to_field_name()))821	}822	pub fn call(&self, v: Value) -> Result<Self> {823		let kind = self824			.functor_kind()825			.ok_or_else(|| anyhow!("can only call function or functor"))?;826827		let function = match kind {828			FunctorKind::Function => self.clone(),829			FunctorKind::Functor => {830				let f = self831					.get_field("__functor")832					.context("getting functor value")?;833				assert_eq!(834					f.type_of(),835					NixType::Function,836					"invalid functor encountered"837				);838				f839			}840		};841842		let out = Value::new_uninit();843		with_default_context(|c, es| unsafe { value_call(c, es, function.0, v.0, out.0) })?;844845		Ok(out)846	}847	pub fn eval(v: &str) -> Result<Self> {848		let s = CString::new(v).expect("expression shouldn't have internal NULs");849		let out = Self::new_uninit();850		with_default_context(|c, es| unsafe {851			expr_eval_from_string(c, es, s.as_ptr(), c"/root".as_ptr(), out.0)852		})?;853		Ok(out)854	}855	#[instrument(name = "build", skip(self), fields(output))]856	pub fn build(&self, output: &str) -> Result<PathBuf> {857		if !self.is_derivation() {858			bail!("expected derivation to build")859		}860		let output_name = self861			.get_field("outputName")862			.context("getting output name field")?863			.to_string()?;864		let v = if output_name != output {865			let out = self.get_field(output).context("getting target output")?;866			if !out.is_derivation() {867				bail!("unknown output: {output}");868			}869			out870		} else {871			self.clone()872		};873874		let drv_path = v875			.get_field("drvPath")876			.context("getting drvPath")?877			.to_string()?;878		let graph = drv::DrvGraph::resolve(&drv_path)?;879		let _guard = logging::register_build_graph(&Span::current(), &graph);880881		// to_string here blocks until the path is built882		let s = v.builtin_to_string()?;883		let rs = s.to_realised_string()?;884		let out_path = rs.as_str().to_owned();885		Ok(PathBuf::from(out_path))886	}887	pub fn as_json<T: DeserializeOwned>(&self) -> Result<T> {888		let to_json = Self::eval("builtins.toJSON")?;889		let s = to_json.call(self.clone())?.to_string()?;890		Ok(serde_json::from_str(&s)?)891	}892	pub fn serialized<T: Serialize>(v: &T) -> Result<Self> {893		Self::eval(&nixlike::serialize(v)?)894	}895896	// Convert to string/evaluate derivations/etc897	// fn to_string_weak(&self) -> Result<String> {898	// 	// TODO: For now, it works exactly like to_string, see the comment for fn force()899	// 	self.to_string()900	// }901902	fn is_derivation(&self) -> bool {903		if !matches!(self.type_of(), NixType::Attrs) {904			return false;905		}906		let Some(ty) = self.get_field("type").ok() else {907			return false;908		};909		matches!(ty.to_string().as_deref(), Ok("derivation"))910	}911	fn functor_kind(&self) -> Option<FunctorKind> {912		match self.type_of() {913			NixType::Attrs => self914				.has_field("__functor")915				.expect("has_field shouldn't fail for attrs")916				.then_some(FunctorKind::Functor),917			NixType::Function => Some(FunctorKind::Function),918			_ => None,919		}920	}921	pub fn is_function(&self) -> bool {922		self.functor_kind().is_some()923	}924	pub fn is_null(&self) -> bool {925		matches!(self.type_of(), NixType::Null)926	}927	pub fn is_string(&self) -> bool {928		matches!(self.type_of(), NixType::String)929	}930	pub fn is_attrs(&self) -> bool {931		matches!(self.type_of(), NixType::Attrs)932	}933}934935impl From<String> for Value {936	fn from(value: String) -> Self {937		Value::new_str(&value)938	}939}940impl From<bool> for Value {941	fn from(value: bool) -> Self {942		Value::new_bool(value)943	}944}945impl From<&str> for Value {946	fn from(value: &str) -> Self {947		Value::new_str(value)948	}949}950impl<T> From<Vec<T>> for Value951where952	T: Into<Value>,953{954	fn from(value: Vec<T>) -> Self {955		Value::new_list(value)956	}957}958959impl Clone for Value {960	fn clone(&self) -> Self {961		with_default_context(|c, _| unsafe { value_incref(c, self.0) })962			.expect("value incref should not fail");963		Self(self.0)964	}965}966impl Drop for Value {967	fn drop(&mut self) {968		with_default_context(|c, _| unsafe { value_decref(c, self.0) })969			.expect("value drop should not fail");970	}971}972973static TOKIO_FOR_NIX: OnceLock<Arc<tokio::runtime::Runtime>> = OnceLock::new();974975pub fn init_libraries() {976	unsafe { GC_allow_register_threads() };977978	let mut ctx = NixContext::new();979	ctx.run_in_context(|c| unsafe { libutil_init(c) })980		.expect("util init should not fail");981	ctx.run_in_context(|c| unsafe { libstore_init(c) })982		.expect("store init should not fail");983	ctx.run_in_context(|c| unsafe { libexpr_init(c) })984		.expect("expr init should not fail");985986	nix_logging_cxx::apply_tracing_logger();987}988989pub fn init_tokio_for_nix(tokio: Arc<tokio::runtime::Runtime>) {990	TOKIO_FOR_NIX991		.set(tokio)992		.expect("tokio for nix should only be initialized once");993}994995pub fn await_in_nix<F: Send + 'static>(f: impl Future<Output = F> + Send + 'static) -> F {996	// It should be possible to do Handle::current(), but some of the planned features don't work well with that997	let runtime = TOKIO_FOR_NIX998		.get()999		.expect("init_tokio_for_nix was not called");1000	std::thread::spawn(move || runtime.block_on(f))1001		.join()1002		.expect("await_in_nix inner thread panicked")1003}10041005unsafe extern "C" fn nix_primop_closure_adapter<const N: usize>(1006	user_data: *mut c_void,1007	mut context: *mut c_context,1008	state: *mut nix_raw::EvalState,1009	args: *mut *mut value,1010	ret: *mut value,1011) {1012	let user_closure: &UserClosure<N> = unsafe { &*user_data.cast_const().cast() };1013	let args: [&Value; N] = array::from_fn(|i| {1014		let v: &mut Value = unsafe { &mut *args.add(i).cast() };1015		v as &Value1016	});1017	let ctx: &mut NixContext = unsafe { transmute(&mut context) };10181019	let state: &EvalState = unsafe { std::mem::transmute(&state) };10201021	match user_closure(state, args) {1022		Ok(v) => {1023			unsafe { copy_value(context, ret, v.0) };1024		}1025		Err(e) => {1026			ctx.set_err(e);1027		}1028	}1029}10301031type UserClosure<const N: usize> = Box<dyn Fn(&EvalState, [&Value; N]) -> Result<Value>>;10321033pub struct NativeFn(*mut PrimOp);1034impl NativeFn {1035	pub fn new<const N: usize>(1036		name: &'static CStr,1037		doc: &'static CStr,1038		args: [&'static CStr; N],1039		f: impl Fn(&EvalState, [&Value; N]) -> Result<Value> + 'static,1040	) -> Self {1041		// Double-boxing to make it thin pointer, as vtable gets outside of first Box1042		let closure: Box<UserClosure<N>> = Box::new(Box::new(f));1043		let f: PrimOpFun = Some(nix_primop_closure_adapter::<N>);1044		let mut args = args.into_iter().map(|v| v.as_ptr()).collect_vec();1045		args.push(null());1046		let args = args.as_mut_ptr();1047		let primop = unsafe {1048			alloc_primop(1049				null_mut(),1050				f,1051				N as i32,1052				name.as_ptr(),1053				args,1054				doc.as_ptr(),1055				Box::into_raw(closure).cast(),1056			)1057		};10581059		assert!(!primop.is_null(), "primop allocation should not fail");10601061		Self(primop)1062	}1063	pub fn register(self) {1064		unsafe { register_primop(null_mut(), self.0) };1065	}1066}10671068struct StorePath(*mut c_store_path);1069impl StorePath {}10701071impl Drop for StorePath {1072	fn drop(&mut self) {1073		unsafe { store_path_free(self.0) }1074	}1075}10761077#[test_log::test]1078fn test_native() -> Result<()> {1079	init_libraries();1080	NativeFn::new(1081		c"__uppercaseSuffix2",1082		c"make string uppercase and add suffix",1083		[c"str", c"suffix"],1084		|_, [str, suffix]: [&Value; 2]| {1085			let str = str.to_string()?;1086			let suffix = suffix.to_string()?;1087			Ok(Value::new_str(&format!("{}{suffix}", str.to_uppercase())))1088		},1089	)1090	.register();10911092	let mut fetch_settings = FetchSettings::new();1093	fetch_settings.set(c"warn-dirty", c"false");10941095	let manifest = format!("git+file://{}/../../", env!("CARGO_MANIFEST_DIR"));1096	let flake = FlakeSettings::new()?;1097	let parse = FlakeReferenceParseFlags::new(&flake)?;1098	let (mut r, _) = FlakeReference::new(&manifest, &flake, &parse, &fetch_settings)?;1099	let lock = FlakeLockFlags::new(&flake)?;1100	let locked = r.lock(&fetch_settings, &flake, &lock)?;1101	let attrs = locked.get_attrs(&mut FlakeSettings::new()?)?;11021103	let builtins = Value::eval("builtins")?;1104	assert_eq!(builtins.type_of(), NixType::Attrs);11051106	assert_eq!(attrs.type_of(), NixType::Attrs);1107	let test_data = nix_go!(attrs.testData);11081109	let test_string: String = nix_go_json!(test_data.testString);1110	assert_eq!(test_string, "hello");11111112	let s = nix_go!(attrs.packages["x86_64-linux"].fleet.drvPath);1113	let s = CString::new(s.to_string()?).expect("path str is cstring");11141115	let uppercase_suffix = Value::new_primop(NativeFn::new(1116		c"uppercase_suffix",1117		c"make string uppercase and add suffix",1118		[c"str", c"suffix"],1119		|es, [str, suffix]: [&Value; 2]| {1120			let str = str.to_string()?;1121			let suffix = suffix.to_string()?;1122			Ok(Value::new_str(&format!("{}{suffix}", str.to_uppercase())))1123		},1124	));11251126	let test_result: String = nix_go_json!(test_data.testPrimop(uppercase_suffix));1127	assert_eq!(test_result, "PREFIX_BODY_SUFFIX");1128	let test_result: String = nix_go_json!(builtins.uppercaseSuffix2("test")("suffix"));1129	assert_eq!(test_result, "TESTsuffix");11301131	let drv_path = nix_go!(attrs.packages["x86_64-linux"]["fleet-install-secrets"].drvPath)1132		.to_string()?;1133	let graph = drv::DrvGraph::resolve(&drv_path)?;1134	eprintln!(1135		"fleet-install-secrets dependency graph: {} nodes",1136		graph.nodes.len()1137	);1138	for (path, node) in &graph.nodes {1139		if !node.input_drvs.is_empty() {1140			eprintln!("  {} ({} deps)", node.name, node.input_drvs.len());1141		}1142	}11431144	Ok(())1145}11461147// pub struct GcAlloc;1148// unsafe impl GlobalAlloc for GcAlloc {1149// 	unsafe fn alloc(&self, l: Layout) -> *mut u8 {1150// 		let ptr = unsafe { GC_malloc(l.size()) };1151// 		ptr.cast()1152// 	}1153// 	unsafe fn dealloc(&self, ptr: *mut u8, _: Layout) {1154// 		// unsafe { GC_free(ptr.cast()) };1155// 	}1156//1157// 	unsafe fn realloc(&self, ptr: *mut u8, _: Layout, new_size: usize) -> *mut u8 {1158// 		let ptr = unsafe { GC_realloc(ptr.cast(), new_size) };1159// 		ptr.cast()1160// 	}1161// }1162//1163// #[global_allocator]1164// static GC: GcAlloc = GcAlloc;