git.delta.rocks / jrsonnet / refs/commits / d5b0f6a487f6

difftreelog

source

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