git.delta.rocks / jrsonnet / refs/commits / 771ccbb734d6

difftreelog

feat ensure lazy trees are enabled for everyone

vrozkvrmYaroslav Bolyukin2025-09-04parent: #d91d16d.patch.diff
in: trunk

4 files changed

modifiedcrates/fleet-base/src/opts.rsdiffbeforeafterboth
--- a/crates/fleet-base/src/opts.rs
+++ b/crates/fleet-base/src/opts.rs
@@ -7,7 +7,10 @@
 };
 
 use anyhow::{Context, Result, bail};
-use nix_eval::{FetchSettings, FlakeReference, FlakeSettings, Value, nix_go, util::assert_warn};
+use nix_eval::{
+	FetchSettings, FlakeReference, FlakeReferenceParseFlags, FlakeSettings, Value, nix_go,
+	util::assert_warn,
+};
 use nom::{
 	Parser,
 	bytes::complete::take_while1,
@@ -213,16 +216,21 @@
 		let mut fetch_settings = FetchSettings::new();
 		fetch_settings.set(c"warn-dirty", c"false");
 
-		// TODO: use correct directory, not cwd
+		let mut flake_settings = FlakeSettings::new()?;
+		let mut parse = FlakeReferenceParseFlags::new(&flake_settings)?;
+		// For some reason, lazy trees not being used when there is no base dir set
+		parse.set_base_dir("/")?;
+
 		let (mut flake, _) = FlakeReference::new(
 			directory
 				.to_str()
 				.ok_or_else(|| anyhow::anyhow!("fleet dir should have utf-8 path"))?,
+			&flake_settings,
+			&parse,
 			&fetch_settings,
 		)?;
 		let flake = flake.lock(&fetch_settings)?;
 
-		let mut flake_settings = FlakeSettings::new()?;
 		let flake = flake.get_attrs(&mut flake_settings)?;
 
 		let builtins_field = Value::eval("builtins")?;
modifiedcrates/nix-eval/src/lib.rsdiffbeforeafterboth
before · crates/nix-eval/src/lib.rs
1use std::borrow::Cow;2use std::cell::RefCell;3use std::ffi::{CStr, CString, c_char, c_int, c_uint, c_void};4use std::fmt;5use std::ptr::null_mut;6use std::sync::LazyLock;7use std::{collections::HashMap, path::PathBuf};89use anyhow::{Context, anyhow, bail};10use serde::Serialize;11use serde::de::DeserializeOwned;1213pub use anyhow::Result;1415use self::logging::nix_logging_cxx;16use self::nix_cxx::set_fetcher_setting;17use self::nix_raw::{18	alloc_value, c_context, c_context_create, err_code, err_info_msg, eval_state_build,19	eval_state_builder_new, expr_eval_from_string, fetchers_settings, fetchers_settings_free,20	fetchers_settings_new, flake_lock, flake_lock_flags, flake_lock_flags_free,21	flake_lock_flags_new, flake_reference_parse_flags, flake_reference_parse_flags_free,22	flake_reference_parse_flags_new, flake_reference_parse_flags_set_base_directory,23	flake_settings, flake_settings_free, flake_settings_new, init_bool, init_int, init_string,24	locked_flake_free, locked_flake_get_output_attrs, set_err_msg, setting_set, state_free,25	value_decref, value_incref,26};2728// Contains macros helpers29pub mod logging;30#[doc(hidden)]31pub mod macros;32pub mod util;3334#[allow(35	non_upper_case_globals,36	non_camel_case_types,37	non_snake_case,38	dead_code39)]40mod nix_raw {41	include!(concat!(env!("OUT_DIR"), "/bindings.rs"));42}43#[cxx::bridge]44pub mod nix_cxx {45	unsafe extern "C++" {46		type nix_fetchers_settings;47		include!("nix-eval/src/lib.hh");4849		unsafe fn set_fetcher_setting(50			settings: *mut nix_fetchers_settings,51			setting: *const c_char,52			value: *const c_char,53		);54	}55}5657#[derive(Debug, PartialEq, Eq)]58pub enum NixType {59	Thunk,60	Int,61	Float,62	Bool,63	String,64	Path,65	Null,66	Attrs,67	List,68	Function,69	External,70}71impl NixType {72	fn from_int(c: c_uint) -> Self {73		match c {74			0 => Self::Thunk,75			1 => Self::Int,76			2 => Self::Float,77			3 => Self::Bool,78			4 => Self::String,79			5 => Self::Path,80			6 => Self::Null,81			7 => Self::Attrs,82			8 => Self::List,83			9 => Self::Function,84			10 => Self::External,85			_ => unreachable!("unknown nix type: {c}"),86		}87	}88}8990enum FunctorKind {91	Function,92	Functor,93}9495#[derive(Debug)]96#[repr(i32)]97enum NixErrorKind {98	Unknown = 1,99	Overflow = 2,100	Key = 3,101	Generic = 4,102}103impl NixErrorKind {104	fn from_int(v: c_int) -> Option<Self> {105		Some(match v {106			0 => return None,107			-1 => Self::Unknown,108			-2 => Self::Overflow,109			-3 => Self::Key,110			-4 => Self::Generic,111			_ => {112				debug_assert!(false, "unexpected nix error kind: {v}");113				Self::Unknown114			}115		})116	}117}118119pub fn gc_register_my_thread() {120	assert_eq!(unsafe { nix_raw::GC_thread_is_registered() }, 0);121122	let mut sb = nix_raw::GC_stack_base {123		mem_base: null_mut(),124	};125	let r = unsafe { nix_raw::GC_get_stack_base(&mut sb) };126	if r as u32 != nix_raw::GC_SUCCESS {127		panic!("failed to get thread stack base");128	}129	unsafe { nix_raw::GC_register_my_thread(&sb) };130}131pub fn gc_unregister_my_thread() {132	assert_eq!(unsafe { nix_raw::GC_thread_is_registered() }, 1);133134	unsafe { nix_raw::GC_unregister_my_thread() };135}136137pub struct ThreadRegisterGuard {}138impl ThreadRegisterGuard {139	pub fn new() -> Self {140		gc_register_my_thread();141		Self {}142	}143}144impl Drop for ThreadRegisterGuard {145	fn drop(&mut self) {146		gc_unregister_my_thread();147	}148}149150pub struct NixContext(*mut c_context);151impl NixContext {152	pub fn set_err(&mut self, err: NixErrorKind, msg: &CStr) {153		unsafe { set_err_msg(self.0, err as c_int, msg.as_ptr()) };154	}155	pub fn new() -> Self {156		let ctx = unsafe { c_context_create() };157		Self(ctx)158	}159	fn error_kind(&self) -> Option<NixErrorKind> {160		let code = unsafe { err_code(self.0) };161		NixErrorKind::from_int(code)162	}163	fn error<'t>(&self) -> Option<Cow<'t, str>> {164		if let NixErrorKind::Generic = self.error_kind()? {165			let mut err_out = String::new();166			unsafe {167				err_info_msg(168					null_mut(),169					self.0,170					Some(copy_nix_str),171					(&raw mut err_out).cast(),172				)173			};174			return Some(Cow::Owned(err_out));175		};176177		// TODO: Can throw error (resulting in panic) if unable to retrieve error. Should be able to resolve by passing context as a first argument,178		// but it looks ugly179		let str = unsafe { nix_raw::err_msg(null_mut(), self.0, null_mut()) };180		Some(unsafe { CStr::from_ptr(str) }.to_string_lossy())181	}182	fn clean_err(&mut self) {183		unsafe {184			nix_raw::clear_err(self.0);185		}186	}187188	fn bail_if_error(&self) -> Result<()> {189		if let Some(err) = self.error() {190			bail!("{err}");191		};192		Ok(())193	}194195	fn run_in_context<T>(&mut self, f: impl FnOnce(*mut c_context) -> T) -> Result<T> {196		self.clean_err();197		let o = f(self.0);198		self.bail_if_error()?;199		self.clean_err();200		Ok(o)201	}202}203impl Drop for NixContext {204	fn drop(&mut self) {205		unsafe {206			nix_raw::c_context_free(self.0);207		}208	}209}210struct GlobalState {211	// Store should be valid as long as EvalState is valid212	#[allow(dead_code)]213	store: Store,214	state: EvalState,215}216impl GlobalState {217	fn new() -> Result<Self> {218		let mut ctx = NixContext::new();219		let store = ctx220			.run_in_context(|c| unsafe { nix_raw::store_open(c, c"daemon".as_ptr(), null_mut()) })221			.map(Store)?;222223		let builder = ctx.run_in_context(|c| unsafe { eval_state_builder_new(c, store.0) })?;224		ctx.run_in_context(|c| {225			unsafe {226				nix_raw::eval_state_builder_set_eval_setting(227					c,228					builder,229					c"lazy-trees".as_ptr(),230					c"true".as_ptr(),231				)232			}233			// eval_s234		})?;235		let state = ctx236			.run_in_context(|c| unsafe { eval_state_build(c, builder) })237			.map(EvalState)?;238239		Ok(Self { store, state })240	}241}242243struct ThreadState {244	ctx: NixContext,245}246impl ThreadState {247	fn new() -> Result<Self> {248		let ctx = NixContext::new();249250		Ok(Self { ctx })251	}252}253254static GLOBAL_STATE: LazyLock<GlobalState> =255	LazyLock::new(|| GlobalState::new().expect("global state init shouldn't fail"));256257thread_local! {258	static THREAD_STATE: RefCell<ThreadState> = RefCell::new(ThreadState::new().expect("thread state init shouldn't fail"));259}260fn with_default_context<T>(261	f: impl FnOnce(*mut c_context, *mut nix_raw::EvalState) -> T,262) -> Result<T> {263	let global = &GLOBAL_STATE.state;264	let (ctx, state) = THREAD_STATE.with_borrow_mut(|w| (w.ctx.0, global.0));265	let mut ctx = NixContext(ctx);266	let v = ctx.run_in_context(|c| f(c, state));267	// It is reused for thread268	std::mem::forget(ctx);269	v270}271272pub fn set_setting(s: &CStr, v: &CStr) -> Result<()> {273	with_default_context(|c, _| unsafe { setting_set(c, s.as_ptr(), v.as_ptr()) }).map(|_| ())274}275276pub struct FetchSettings(*mut fetchers_settings);277impl FetchSettings {278	pub fn new() -> Self {279		Self::try_new().expect("allocation should not fail")280	}281	fn try_new() -> Result<Self> {282		with_default_context(|c, _| unsafe { fetchers_settings_new(c) }).map(Self)283	}284	pub fn set(&mut self, setting: &CStr, value: &CStr) {285		unsafe {286			set_fetcher_setting(self.0.cast(), setting.as_ptr(), value.as_ptr());287		};288	}289}290unsafe impl Send for FetchSettings {}291unsafe impl Sync for FetchSettings {}292293impl Default for FetchSettings {294	fn default() -> Self {295		Self::new()296	}297}298299impl Drop for FetchSettings {300	fn drop(&mut self) {301		unsafe { fetchers_settings_free(self.0) };302	}303}304pub struct FlakeSettings(*mut flake_settings);305impl FlakeSettings {306	pub fn new() -> Result<Self> {307		with_default_context(|c, _| unsafe { flake_settings_new(c) }).map(Self)308	}309}310unsafe impl Send for FlakeSettings {}311unsafe impl Sync for FlakeSettings {}312impl Drop for FlakeSettings {313	fn drop(&mut self) {314		unsafe {315			flake_settings_free(self.0);316		}317	}318}319320pub struct FlakeReferenceParseFlags(*mut flake_reference_parse_flags);321impl FlakeReferenceParseFlags {322	pub fn new(settings: &mut FlakeSettings) -> Result<Self> {323		with_default_context(|c, _| unsafe { flake_reference_parse_flags_new(c, settings.0) })324			.map(Self)325	}326	pub fn set_base_dir(&mut self, dir: &str) -> Result<()> {327		with_default_context(|c, _| {328			unsafe {329				flake_reference_parse_flags_set_base_directory(330					c,331					self.0,332					dir.as_ptr().cast(),333					dir.len(),334				)335			};336		})337	}338}339impl Drop for FlakeReferenceParseFlags {340	fn drop(&mut self) {341		unsafe {342			flake_reference_parse_flags_free(self.0);343		}344	}345}346struct FlakeLockFlags(*mut flake_lock_flags);347impl FlakeLockFlags {348	fn new(settings: &mut FlakeSettings) -> Result<Self> {349		with_default_context(|c, _| unsafe { flake_lock_flags_new(c, settings.0) }).map(Self)350	}351}352impl Drop for FlakeLockFlags {353	fn drop(&mut self) {354		unsafe {355			flake_lock_flags_free(self.0);356		}357	}358}359360unsafe extern "C" fn copy_nix_str(start: *const c_char, n: c_uint, user_data: *mut c_void) {361	let s = unsafe { std::slice::from_raw_parts(start.cast::<u8>(), n as usize) };362	let s = std::str::from_utf8(s).expect("c string has invalid utf-8");363	unsafe { *user_data.cast::<String>() = s.to_owned() };364}365366struct Store(*mut nix_raw::Store);367unsafe impl Send for Store {}368unsafe impl Sync for Store {}369370struct EvalState(*mut nix_raw::EvalState);371unsafe impl Send for EvalState {}372unsafe impl Sync for EvalState {}373374impl Drop for EvalState {375	fn drop(&mut self) {376		unsafe {377			state_free(self.0);378		}379	}380}381382pub struct FlakeReference(*mut nix_raw::flake_reference);383impl FlakeReference {384	pub fn new(s: &str, fetch: &FetchSettings) -> Result<(Self, String)> {385		let mut flake_settings = FlakeSettings::new()?;386		let parse_flags = FlakeReferenceParseFlags::new(&mut flake_settings)?;387388		let mut out = null_mut();389		let mut fragment = String::new();390		// let fetch_settings = fetcher_settings;391		with_default_context(|c, _| unsafe {392			nix_raw::flake_reference_and_fragment_from_string(393				c,394				fetch.0,395				flake_settings.0,396				parse_flags.0,397				s.as_ptr().cast(),398				s.len(),399				&mut out,400				Some(copy_nix_str),401				(&raw mut fragment).cast(),402			)403		})?;404		assert!(!out.is_null());405406		Ok((Self(out), fragment))407	}408	pub fn lock(&mut self, fetch: &FetchSettings) -> Result<LockedFlake> {409		let mut settings = FlakeSettings::new()?;410		let lock_flags = FlakeLockFlags::new(&mut settings)?;411		with_default_context(|c, es| unsafe {412			flake_lock(c, fetch.0, settings.0, es, lock_flags.0, self.0)413		})414		.map(LockedFlake)415	}416}417unsafe impl Send for FlakeReference {}418unsafe impl Sync for FlakeReference {}419420pub struct LockedFlake(*mut nix_raw::locked_flake);421impl LockedFlake {422	pub fn get_attrs(&self, settings: &mut FlakeSettings) -> Result<Value> {423		with_default_context(|c, es| unsafe {424			locked_flake_get_output_attrs(c, settings.0, es, self.0)425		})426		.map(Value)427	}428}429unsafe impl Send for LockedFlake {}430unsafe impl Sync for LockedFlake {}431impl Drop for LockedFlake {432	fn drop(&mut self) {433		unsafe {434			locked_flake_free(self.0);435		};436	}437}438439type FieldName = [u8; 32];440fn init_field_name(v: &str) -> FieldName {441	let mut f = [0; 32];442	assert!(v.len() < 32, "max field name is 31 char");443	assert!(444		v.bytes().all(|v| v != 0),445		"nul bytes are unsupported in field name"446	);447	f[0..v.len()].copy_from_slice(v.as_bytes());448	f449}450451pub struct RealisedString(*mut nix_raw::realised_string);452impl fmt::Debug for RealisedString {453	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {454		self.as_str().fmt(f)455	}456}457458impl RealisedString {459	pub fn as_str(&self) -> &str {460		let len = unsafe { nix_raw::realised_string_get_buffer_size(self.0) };461		let data: *const u8 = unsafe { nix_raw::realised_string_get_buffer_start(self.0) }.cast();462		let data = unsafe { std::slice::from_raw_parts(data, len) };463		std::str::from_utf8(data).expect("non-utf8 strings not supported")464	}465	pub fn path_count(&self) -> usize {466		unsafe { nix_raw::realised_string_get_store_path_count(self.0) }467	}468	pub fn path(&self, i: usize) -> String {469		assert!(i < self.path_count());470		let path = unsafe { nix_raw::realised_string_get_store_path(self.0, i) };471		let mut err_out = String::new();472		unsafe { nix_raw::store_path_name(path, Some(copy_nix_str), (&raw mut err_out).cast()) };473		err_out474	}475}476477unsafe impl Send for RealisedString {}478impl Drop for RealisedString {479	fn drop(&mut self) {480		unsafe { nix_raw::realised_string_free(self.0) }481	}482}483484pub struct Value(*mut nix_raw::value);485486unsafe impl Send for Value {}487unsafe impl Sync for Value {}488489pub trait AsFieldName {490	fn as_field_name<T>(&self, v: impl FnOnce(FieldName) -> Result<T>) -> Result<T>;491	fn to_field_name(&self) -> Result<String>;492}493impl AsFieldName for Value {494	fn as_field_name<T>(&self, v: impl FnOnce(FieldName) -> Result<T>) -> Result<T> {495		let f = self.to_string()?;496		v(init_field_name(&f))497	}498	fn to_field_name(&self) -> Result<String> {499		self.to_string()500	}501}502impl<E> AsFieldName for E503where504	E: AsRef<str>,505{506	fn as_field_name<T>(&self, v: impl FnOnce(FieldName) -> Result<T>) -> Result<T> {507		let f = self.as_ref();508		v(init_field_name(f))509	}510	fn to_field_name(&self) -> Result<String> {511		Ok(self.as_ref().to_owned())512	}513}514515struct AttrsBuilder(*mut nix_raw::BindingsBuilder);516impl AttrsBuilder {517	fn new(capacity: usize) -> Self {518		with_default_context(|c, es| unsafe { nix_raw::make_bindings_builder(c, es, capacity) })519			.map(Self)520			.expect("alloc should not fail")521	}522	fn insert(&mut self, k: &impl AsFieldName, v: Value) {523		k.as_field_name(|name| {524			with_default_context(|c, _| unsafe {525				nix_raw::bindings_builder_insert(c, self.0, name.as_ptr().cast(), v.0)526			})527		})528		.expect("builder insert shouldn't fail");529	}530}531impl Drop for AttrsBuilder {532	fn drop(&mut self) {533		unsafe { nix_raw::bindings_builder_free(self.0) };534	}535}536537impl Value {538	pub fn new_attrs(v: HashMap<&str, Value>) -> Self {539		let out = Self::new_uninit();540		let mut b = AttrsBuilder::new(v.len());541		for (k, v) in v {542			b.insert(&k, v);543		}544		with_default_context(|c, _| unsafe { nix_raw::make_attrs(c, out.0, b.0) })545			.expect("attrs initialization should not fail");546		out547	}548	fn new_list<T: Into<Self>>(v: Vec<T>) -> Self {549		todo!()550	}551	fn new_uninit() -> Self {552		let out = with_default_context(|c, es| unsafe { alloc_value(c, es) })553			.expect("value allocation should not fail");554		Self(out)555	}556	pub fn new_str(v: &str) -> Self {557		let s = CString::new(v).expect("string should not contain NULs");558		let out = Self::new_uninit();559		// String is copied, `s` is free to be dropped560		with_default_context(|c, _| unsafe { init_string(c, out.0, s.as_ptr()) })561			.expect("string initialization should not fail");562		out563	}564	pub fn new_int(i: i64) -> Self {565		let out = Self::new_uninit();566		with_default_context(|c, _| unsafe { init_int(c, out.0, i) })567			.expect("int initialization should not fail");568		out569	}570	pub fn new_bool(v: bool) -> Self {571		let out = Self::new_uninit();572		with_default_context(|c, _| unsafe { init_bool(c, out.0, v) })573			.expect("bool initialization should not fail");574		out575	}576	// TODO: As far as I can see, there is no way to get Thunks from nix public C api, so this function is useless577	// fn force(&mut self, st: &mut EvalState) -> Result<()> {578	// 	with_default_context(|c, _| unsafe { value_force(c, st.0, self.0) })?;579	// 	Ok(())580	// }581	pub fn type_of(&self) -> NixType {582		let ty = with_default_context(|c, _| unsafe { nix_raw::get_type(c, self.0) })583			.expect("get_type should not fail");584		NixType::from_int(ty)585	}586	pub fn to_string(&self) -> Result<String> {587		Ok(self.to_realised_string()?.as_str().to_owned())588	}589	pub fn to_realised_string(&self) -> Result<RealisedString> {590		with_default_context(|c, es| unsafe { nix_raw::string_realise(c, es, self.0, false) })591			.map(RealisedString)592593		// let store_paths = unsafe { nix_raw::realised_string_get_store_path_count(str) };594		// for i in 0..store_paths {595		// 	let store_path = unsafe { nix_raw::realised_string_get_store_path(str, i) };596		// 	nix_raw::store_path_name(store_path, callback, user_data);597		// }598		// dbg!(store_paths);599		// todo!();600	}601602	pub fn has_field(&self, field: &str) -> Result<bool> {603		let f = init_field_name(field);604		with_default_context(|c, es| unsafe {605			nix_raw::has_attr_byname(c, self.0, es, f.as_ptr().cast())606		})607	}608	// pub fn derivation_path(&self) {609	// 	nix_raw::real610	// }611	pub fn list_fields(&self) -> Result<Vec<String>> {612		if !matches!(self.type_of(), NixType::Attrs) {613			bail!("invalid type: expected attrs");614		}615616		let len = with_default_context(|c, _| unsafe { nix_raw::get_attrs_size(c, self.0) })?;617		let mut out = Vec::with_capacity(len as usize);618619		for i in 0..len {620			let name = with_default_context(|c, es| unsafe {621				nix_raw::get_attr_name_byidx(c, self.0, es, i)622			})?;623			let c = unsafe { CStr::from_ptr(name) };624			out.push(c.to_str().expect("nix field names are utf-8").to_owned());625		}626		Ok(out)627	}628	pub fn get_elem(&self, v: usize) -> Result<Self> {629		if !matches!(self.type_of(), NixType::List) {630			bail!("invalid type: expected list");631		}632		let len =633			with_default_context(|c, _| unsafe { nix_raw::get_list_size(c, self.0) })? as usize;634		if v >= len {635			bail!("oob list get: {v} >= {len}");636		}637638		with_default_context(|c, es| unsafe { nix_raw::get_list_byidx(c, self.0, es, v as u32) })639			.map(Self)640	}641	pub fn attrs_update(self, other: Value) -> Result<Self> {642		let a_fields = self.list_fields()?;643		let b_fields = other.list_fields()?;644		match (a_fields.len(), b_fields.len()) {645			(_, 0) => return Ok(self),646			(0, _) => return Ok(other),647			_ => {}648		}649		let mut out = HashMap::new();650		for f in a_fields.iter() {651			if b_fields.contains(f) {652				break;653			}654			out.insert(f.as_str(), self.get_field(f)?);655		}656		if out.is_empty() {657			// All fields from lhs are overriden by rhs658			return Ok(other);659		}660		for f in b_fields.iter() {661			out.insert(f.as_str(), other.get_field(f)?);662		}663		Ok(Self::new_attrs(out))664	}665	pub fn get_field(&self, name: impl AsFieldName) -> Result<Self> {666		if !matches!(self.type_of(), NixType::Attrs) {667			bail!("invalid type: expected attrs");668		}669670		name.as_field_name(|name| {671			with_default_context(|c, es| unsafe {672				nix_raw::get_attr_byname(c, self.0, es, name.as_ptr().cast())673			})674			.map(Self)675		})676		.with_context(|| format!("getting field {:?}", name.to_field_name()))677	}678	pub fn call(&self, v: Value) -> Result<Self> {679		let kind = self680			.functor_kind()681			.ok_or_else(|| anyhow!("can only call function or functor"))?;682683		let function = match kind {684			FunctorKind::Function => self.clone(),685			FunctorKind::Functor => {686				let f = self.get_field("__functor")?;687				assert_eq!(688					f.type_of(),689					NixType::Function,690					"invalid functor encountered"691				);692				f693			}694		};695696		let out = Value::new_uninit();697		with_default_context(|c, es| unsafe {698			nix_raw::value_call(c, es, function.0, v.0, out.0)699		})?;700701		Ok(out)702	}703	pub fn eval(v: &str) -> Result<Self> {704		let s = CString::new(v).expect("expression shouldn't have internal NULs");705		let out = Self::new_uninit();706		with_default_context(|c, es| unsafe {707			expr_eval_from_string(c, es, s.as_ptr(), c"/homeless-shelter".as_ptr(), out.0)708		})?;709		Ok(out)710	}711	pub async fn build(&self, output: &str) -> Result<PathBuf> {712		if !self.is_derivation() {713			bail!("expected derivation to build")714		}715		let output_name = self.get_field("outputName")?.to_string()?;716		let v = if output_name != output {717			let out = self.get_field(output)?;718			if !out.is_derivation() {719				bail!("unknown output: {output}");720			}721			out722		} else {723			self.clone()724		};725		// to_string here blocks until the path is built726		let drv_path = tokio::task::spawn_blocking(move || v.get_field("outPath")?.to_string())727			.await728			.expect("should not fail")?;729		Ok(PathBuf::from(drv_path))730	}731	pub fn as_json<T: DeserializeOwned>(&self) -> Result<T> {732		let to_json = Self::eval("builtins.toJSON")?;733		let s = to_json.call(self.clone())?.to_string()?;734		Ok(serde_json::from_str(&s)?)735	}736	pub fn serialized<T: Serialize>(v: &T) -> Result<Self> {737		Self::eval(&nixlike::serialize(v)?)738	}739740	// Convert to string/evaluate derivations/etc741	// fn to_string_weak(&self) -> Result<String> {742	// 	// TODO: For now, it works exactly like to_string, see the comment for fn force()743	// 	self.to_string()744	// }745746	fn is_derivation(&self) -> bool {747		if !matches!(self.type_of(), NixType::Attrs) {748			return false;749		}750		let Some(ty) = self.get_field("type").ok() else {751			return false;752		};753		matches!(ty.to_string().as_deref(), Ok("derivation"))754	}755	fn functor_kind(&self) -> Option<FunctorKind> {756		match self.type_of() {757			NixType::Attrs => self758				.has_field("__functor")759				.expect("has_field shouldn't fail for attrs")760				.then_some(FunctorKind::Functor),761			NixType::Function => Some(FunctorKind::Function),762			_ => None,763		}764	}765	pub fn is_function(&self) -> bool {766		self.functor_kind().is_some()767	}768}769770impl From<String> for Value {771	fn from(value: String) -> Self {772		Value::new_str(&value)773	}774}775impl From<bool> for Value {776	fn from(value: bool) -> Self {777		Value::new_bool(value)778	}779}780impl From<&str> for Value {781	fn from(value: &str) -> Self {782		Value::new_str(value)783	}784}785impl<T> From<Vec<T>> for Value786where787	T: Into<Value>,788{789	fn from(value: Vec<T>) -> Self {790		Value::new_list(value)791	}792}793794impl Clone for Value {795	fn clone(&self) -> Self {796		with_default_context(|c, _| unsafe { value_incref(c, self.0) })797			.expect("value incref should not fail");798		Self(self.0)799	}800}801impl Drop for Value {802	fn drop(&mut self) {803		with_default_context(|c, _| unsafe { value_decref(c, self.0) })804			.expect("value drop should not fail");805	}806}807808pub fn init_libraries() {809	unsafe { nix_raw::GC_allow_register_threads() };810811	let mut ctx = NixContext::new();812	ctx.run_in_context(|c| unsafe { nix_raw::libutil_init(c) })813		.expect("util init should not fail");814	ctx.run_in_context(|c| unsafe { nix_raw::libstore_init(c) })815		.expect("store init should not fail");816	ctx.run_in_context(|c| unsafe { nix_raw::libexpr_init(c) })817		.expect("expr init should not fail");818819	nix_logging_cxx::apply_tracing_logger();820}821822#[test_log::test]823fn test_native() -> Result<()> {824	init_libraries();825826	let mut fetch_settings = FetchSettings::new();827	fetch_settings.set(c"warn-dirty", c"false");828829	let manifest = format!("{}/../../", env!("CARGO_MANIFEST_DIR"));830	let (mut r, _) = FlakeReference::new(&manifest, &fetch_settings)?;831	let locked = r.lock(&fetch_settings)?;832	let attrs = locked.get_attrs(&mut FlakeSettings::new()?)?;833834	let builtins = Value::eval("builtins")?;835	assert_eq!(builtins.type_of(), NixType::Attrs);836837	assert_eq!(attrs.type_of(), NixType::Attrs);838	let test_data = nix_go!(attrs.testData);839840	let test_string: String = nix_go_json!(test_data.testString);841	assert_eq!(test_string, "hello");842843	Ok(())844}
after · crates/nix-eval/src/lib.rs
1use std::borrow::Cow;2use std::cell::RefCell;3use std::ffi::{CStr, CString, c_char, c_int, c_uint, c_void};4use std::fmt;5use std::ptr::null_mut;6use std::sync::LazyLock;7use std::{collections::HashMap, path::PathBuf};89use anyhow::{Context, anyhow, bail};10use serde::Serialize;11use serde::de::DeserializeOwned;1213pub use anyhow::Result;14use tracing::instrument;1516use self::logging::nix_logging_cxx;17use self::nix_cxx::set_fetcher_setting;18use self::nix_raw::{19	alloc_value, c_context, c_context_create, err_code, err_info_msg, eval_state_build,20	eval_state_builder_new, expr_eval_from_string, fetchers_settings, fetchers_settings_free,21	fetchers_settings_new, flake_lock, flake_lock_flags, flake_lock_flags_free,22	flake_lock_flags_new, flake_lock_flags_set_mode_virtual, flake_reference_parse_flags,23	flake_reference_parse_flags_free, flake_reference_parse_flags_new,24	flake_reference_parse_flags_set_base_directory, flake_settings, flake_settings_free,25	flake_settings_new, init_bool, init_int, init_string, locked_flake_free,26	locked_flake_get_output_attrs, set_err_msg, setting_set, state_free, value_decref,27	value_incref,28};2930// Contains macros helpers31pub mod logging;32#[doc(hidden)]33pub mod macros;34pub mod util;3536#[allow(37	non_upper_case_globals,38	non_camel_case_types,39	non_snake_case,40	dead_code41)]42mod nix_raw {43	include!(concat!(env!("OUT_DIR"), "/bindings.rs"));44}45#[cxx::bridge]46pub mod nix_cxx {47	unsafe extern "C++" {48		type nix_fetchers_settings;49		include!("nix-eval/src/lib.hh");5051		unsafe fn set_fetcher_setting(52			settings: *mut nix_fetchers_settings,53			setting: *const c_char,54			value: *const c_char,55		);56	}57}5859#[derive(Debug, PartialEq, Eq)]60pub enum NixType {61	Thunk,62	Int,63	Float,64	Bool,65	String,66	Path,67	Null,68	Attrs,69	List,70	Function,71	External,72}73impl NixType {74	fn from_int(c: c_uint) -> Self {75		match c {76			0 => Self::Thunk,77			1 => Self::Int,78			2 => Self::Float,79			3 => Self::Bool,80			4 => Self::String,81			5 => Self::Path,82			6 => Self::Null,83			7 => Self::Attrs,84			8 => Self::List,85			9 => Self::Function,86			10 => Self::External,87			_ => unreachable!("unknown nix type: {c}"),88		}89	}90}9192enum FunctorKind {93	Function,94	Functor,95}9697#[derive(Debug)]98#[repr(i32)]99pub enum NixErrorKind {100	Unknown = 1,101	Overflow = 2,102	Key = 3,103	Generic = 4,104}105impl NixErrorKind {106	fn from_int(v: c_int) -> Option<Self> {107		Some(match v {108			0 => return None,109			-1 => Self::Unknown,110			-2 => Self::Overflow,111			-3 => Self::Key,112			-4 => Self::Generic,113			_ => {114				debug_assert!(false, "unexpected nix error kind: {v}");115				Self::Unknown116			}117		})118	}119}120121pub fn gc_register_my_thread() {122	assert_eq!(unsafe { nix_raw::GC_thread_is_registered() }, 0);123124	let mut sb = nix_raw::GC_stack_base {125		mem_base: null_mut(),126	};127	let r = unsafe { nix_raw::GC_get_stack_base(&mut sb) };128	if r as u32 != nix_raw::GC_SUCCESS {129		panic!("failed to get thread stack base");130	}131	unsafe { nix_raw::GC_register_my_thread(&sb) };132}133pub fn gc_unregister_my_thread() {134	assert_eq!(unsafe { nix_raw::GC_thread_is_registered() }, 1);135136	unsafe { nix_raw::GC_unregister_my_thread() };137}138139pub struct ThreadRegisterGuard {}140impl ThreadRegisterGuard {141	#[allow(clippy::new_without_default)]142	pub fn new() -> Self {143		gc_register_my_thread();144		Self {}145	}146}147impl Drop for ThreadRegisterGuard {148	fn drop(&mut self) {149		gc_unregister_my_thread();150	}151}152153pub struct NixContext(*mut c_context);154impl NixContext {155	pub fn set_err(&mut self, err: NixErrorKind, msg: &CStr) {156		unsafe { set_err_msg(self.0, err as c_int, msg.as_ptr()) };157	}158	pub fn new() -> Self {159		let ctx = unsafe { c_context_create() };160		Self(ctx)161	}162	fn error_kind(&self) -> Option<NixErrorKind> {163		let code = unsafe { err_code(self.0) };164		NixErrorKind::from_int(code)165	}166	fn error<'t>(&self) -> Option<Cow<'t, str>> {167		if let NixErrorKind::Generic = self.error_kind()? {168			let mut err_out = String::new();169			unsafe {170				err_info_msg(171					null_mut(),172					self.0,173					Some(copy_nix_str),174					(&raw mut err_out).cast(),175				)176			};177			return Some(Cow::Owned(err_out));178		};179180		// TODO: Can throw error (resulting in panic) if unable to retrieve error. Should be able to resolve by passing context as a first argument,181		// but it looks ugly182		let str = unsafe { nix_raw::err_msg(null_mut(), self.0, null_mut()) };183		Some(unsafe { CStr::from_ptr(str) }.to_string_lossy())184	}185	fn clean_err(&mut self) {186		unsafe {187			nix_raw::clear_err(self.0);188		}189	}190191	fn bail_if_error(&self) -> Result<()> {192		if let Some(err) = self.error() {193			bail!("{err}");194		};195		Ok(())196	}197198	fn run_in_context<T>(&mut self, f: impl FnOnce(*mut c_context) -> T) -> Result<T> {199		self.clean_err();200		let o = f(self.0);201		self.bail_if_error()?;202		self.clean_err();203		Ok(o)204	}205}206207impl Default for NixContext {208	fn default() -> Self {209		Self::new()210	}211}212impl Drop for NixContext {213	fn drop(&mut self) {214		unsafe {215			nix_raw::c_context_free(self.0);216		}217	}218}219struct GlobalState {220	// Store should be valid as long as EvalState is valid221	#[allow(dead_code)]222	store: Store,223	state: EvalState,224}225impl GlobalState {226	fn new() -> Result<Self> {227		let mut ctx = NixContext::new();228		let store = ctx229			.run_in_context(|c| unsafe { nix_raw::store_open(c, c"daemon".as_ptr(), null_mut()) })230			.map(Store)?;231232		let builder = ctx.run_in_context(|c| unsafe { eval_state_builder_new(c, store.0) })?;233		ctx.run_in_context(|c| {234			unsafe {235				nix_raw::eval_state_builder_set_eval_setting(236					c,237					builder,238					c"lazy-trees".as_ptr(),239					c"true".as_ptr(),240				)241			}242			// eval_s243		})?;244		ctx.run_in_context(|c| {245			unsafe {246				nix_raw::eval_state_builder_set_eval_setting(247					c,248					builder,249					c"lazy-locks".as_ptr(),250					c"true".as_ptr(),251				)252			}253			// eval_s254		})?;255		let state = ctx256			.run_in_context(|c| unsafe { eval_state_build(c, builder) })257			.map(EvalState)?;258259		Ok(Self { store, state })260	}261}262263struct ThreadState {264	ctx: NixContext,265}266impl ThreadState {267	fn new() -> Result<Self> {268		let ctx = NixContext::new();269270		Ok(Self { ctx })271	}272}273274static GLOBAL_STATE: LazyLock<GlobalState> =275	LazyLock::new(|| GlobalState::new().expect("global state init shouldn't fail"));276277thread_local! {278	static THREAD_STATE: RefCell<ThreadState> = RefCell::new(ThreadState::new().expect("thread state init shouldn't fail"));279}280fn with_default_context<T>(281	f: impl FnOnce(*mut c_context, *mut nix_raw::EvalState) -> T,282) -> Result<T> {283	let global = &GLOBAL_STATE.state;284	let (ctx, state) = THREAD_STATE.with_borrow_mut(|w| (w.ctx.0, global.0));285	let mut ctx = NixContext(ctx);286	let v = ctx.run_in_context(|c| f(c, state));287	// It is reused for thread288	std::mem::forget(ctx);289	v290}291292pub fn set_setting(s: &CStr, v: &CStr) -> Result<()> {293	with_default_context(|c, _| unsafe { setting_set(c, s.as_ptr(), v.as_ptr()) }).map(|_| ())294}295296pub struct FetchSettings(*mut fetchers_settings);297impl FetchSettings {298	pub fn new() -> Self {299		Self::try_new().expect("allocation should not fail")300	}301	fn try_new() -> Result<Self> {302		with_default_context(|c, _| unsafe { fetchers_settings_new(c) }).map(Self)303	}304	pub fn set(&mut self, setting: &CStr, value: &CStr) {305		unsafe {306			set_fetcher_setting(self.0.cast(), setting.as_ptr(), value.as_ptr());307		};308	}309}310unsafe impl Send for FetchSettings {}311unsafe impl Sync for FetchSettings {}312313impl Default for FetchSettings {314	fn default() -> Self {315		Self::new()316	}317}318319impl Drop for FetchSettings {320	fn drop(&mut self) {321		unsafe { fetchers_settings_free(self.0) };322	}323}324pub struct FlakeSettings(*mut flake_settings);325impl FlakeSettings {326	pub fn new() -> Result<Self> {327		with_default_context(|c, _| unsafe { flake_settings_new(c) }).map(Self)328	}329}330unsafe impl Send for FlakeSettings {}331unsafe impl Sync for FlakeSettings {}332impl Drop for FlakeSettings {333	fn drop(&mut self) {334		unsafe {335			flake_settings_free(self.0);336		}337	}338}339340pub struct FlakeReferenceParseFlags(*mut flake_reference_parse_flags);341impl FlakeReferenceParseFlags {342	pub fn new(settings: &FlakeSettings) -> Result<Self> {343		with_default_context(|c, _| unsafe { flake_reference_parse_flags_new(c, settings.0) })344			.map(Self)345	}346	pub fn set_base_dir(&mut self, dir: &str) -> Result<()> {347		with_default_context(|c, _| {348			unsafe {349				flake_reference_parse_flags_set_base_directory(350					c,351					self.0,352					dir.as_ptr().cast(),353					dir.len(),354				)355			};356		})357	}358}359impl Drop for FlakeReferenceParseFlags {360	fn drop(&mut self) {361		unsafe {362			flake_reference_parse_flags_free(self.0);363		}364	}365}366struct FlakeLockFlags(*mut flake_lock_flags);367impl FlakeLockFlags {368	fn new(settings: &mut FlakeSettings) -> Result<Self> {369		let o = with_default_context(|c, _| unsafe { flake_lock_flags_new(c, settings.0) })370			.map(Self)?;371		with_default_context(|c, _| unsafe { flake_lock_flags_set_mode_virtual(c, o.0) })?;372373		Ok(o)374	}375}376impl Drop for FlakeLockFlags {377	fn drop(&mut self) {378		unsafe {379			flake_lock_flags_free(self.0);380		}381	}382}383384unsafe extern "C" fn copy_nix_str(start: *const c_char, n: c_uint, user_data: *mut c_void) {385	let s = unsafe { std::slice::from_raw_parts(start.cast::<u8>(), n as usize) };386	let s = std::str::from_utf8(s).expect("c string has invalid utf-8");387	unsafe { *user_data.cast::<String>() = s.to_owned() };388}389390struct Store(*mut nix_raw::Store);391unsafe impl Send for Store {}392unsafe impl Sync for Store {}393394struct EvalState(*mut nix_raw::EvalState);395unsafe impl Send for EvalState {}396unsafe impl Sync for EvalState {}397398impl Drop for EvalState {399	fn drop(&mut self) {400		unsafe {401			state_free(self.0);402		}403	}404}405406pub struct FlakeReference(*mut nix_raw::flake_reference);407impl FlakeReference {408	#[instrument(name = "new-flake-reference", skip(flake, parse, fetch))]409	pub fn new(410		s: &str,411		flake: &FlakeSettings,412		parse: &FlakeReferenceParseFlags,413		fetch: &FetchSettings,414	) -> Result<(Self, String)> {415		let mut out = null_mut();416		let mut fragment = String::new();417		// let fetch_settings = fetcher_settings;418		with_default_context(|c, _| unsafe {419			nix_raw::flake_reference_and_fragment_from_string(420				c,421				fetch.0,422				flake.0,423				parse.0,424				s.as_ptr().cast(),425				s.len(),426				&mut out,427				Some(copy_nix_str),428				(&raw mut fragment).cast(),429			)430		})?;431		assert!(!out.is_null());432433		Ok((Self(out), fragment))434	}435	#[instrument(name = "lock-flake", skip(self, fetch))]436	pub fn lock(&mut self, fetch: &FetchSettings) -> Result<LockedFlake> {437		let mut settings = FlakeSettings::new()?;438		let lock_flags = FlakeLockFlags::new(&mut settings)?;439		with_default_context(|c, es| unsafe {440			flake_lock(c, fetch.0, settings.0, es, lock_flags.0, self.0)441		})442		.map(LockedFlake)443	}444}445unsafe impl Send for FlakeReference {}446unsafe impl Sync for FlakeReference {}447448pub struct LockedFlake(*mut nix_raw::locked_flake);449impl LockedFlake {450	pub fn get_attrs(&self, settings: &mut FlakeSettings) -> Result<Value> {451		with_default_context(|c, es| unsafe {452			locked_flake_get_output_attrs(c, settings.0, es, self.0)453		})454		.map(Value)455	}456}457unsafe impl Send for LockedFlake {}458unsafe impl Sync for LockedFlake {}459impl Drop for LockedFlake {460	fn drop(&mut self) {461		unsafe {462			locked_flake_free(self.0);463		};464	}465}466467type FieldName = [u8; 32];468fn init_field_name(v: &str) -> FieldName {469	let mut f = [0; 32];470	assert!(v.len() < 32, "max field name is 31 char");471	assert!(472		v.bytes().all(|v| v != 0),473		"nul bytes are unsupported in field name"474	);475	f[0..v.len()].copy_from_slice(v.as_bytes());476	f477}478479pub struct RealisedString(*mut nix_raw::realised_string);480impl fmt::Debug for RealisedString {481	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {482		self.as_str().fmt(f)483	}484}485486impl RealisedString {487	pub fn as_str(&self) -> &str {488		let len = unsafe { nix_raw::realised_string_get_buffer_size(self.0) };489		let data: *const u8 = unsafe { nix_raw::realised_string_get_buffer_start(self.0) }.cast();490		let data = unsafe { std::slice::from_raw_parts(data, len) };491		std::str::from_utf8(data).expect("non-utf8 strings not supported")492	}493	pub fn path_count(&self) -> usize {494		unsafe { nix_raw::realised_string_get_store_path_count(self.0) }495	}496	pub fn path(&self, i: usize) -> String {497		assert!(i < self.path_count());498		let path = unsafe { nix_raw::realised_string_get_store_path(self.0, i) };499		let mut err_out = String::new();500		unsafe { nix_raw::store_path_name(path, Some(copy_nix_str), (&raw mut err_out).cast()) };501		err_out502	}503}504505unsafe impl Send for RealisedString {}506impl Drop for RealisedString {507	fn drop(&mut self) {508		unsafe { nix_raw::realised_string_free(self.0) }509	}510}511512pub struct Value(*mut nix_raw::value);513514unsafe impl Send for Value {}515unsafe impl Sync for Value {}516517pub trait AsFieldName {518	fn as_field_name<T>(&self, v: impl FnOnce(FieldName) -> Result<T>) -> Result<T>;519	fn to_field_name(&self) -> Result<String>;520}521impl AsFieldName for Value {522	fn as_field_name<T>(&self, v: impl FnOnce(FieldName) -> Result<T>) -> Result<T> {523		let f = self.to_string()?;524		v(init_field_name(&f))525	}526	fn to_field_name(&self) -> Result<String> {527		self.to_string()528	}529}530impl<E> AsFieldName for E531where532	E: AsRef<str>,533{534	fn as_field_name<T>(&self, v: impl FnOnce(FieldName) -> Result<T>) -> Result<T> {535		let f = self.as_ref();536		v(init_field_name(f))537	}538	fn to_field_name(&self) -> Result<String> {539		Ok(self.as_ref().to_owned())540	}541}542543struct AttrsBuilder(*mut nix_raw::BindingsBuilder);544impl AttrsBuilder {545	fn new(capacity: usize) -> Self {546		with_default_context(|c, es| unsafe { nix_raw::make_bindings_builder(c, es, capacity) })547			.map(Self)548			.expect("alloc should not fail")549	}550	fn insert(&mut self, k: &impl AsFieldName, v: Value) {551		k.as_field_name(|name| {552			with_default_context(|c, _| unsafe {553				nix_raw::bindings_builder_insert(c, self.0, name.as_ptr().cast(), v.0)554			})555		})556		.expect("builder insert shouldn't fail");557	}558}559impl Drop for AttrsBuilder {560	fn drop(&mut self) {561		unsafe { nix_raw::bindings_builder_free(self.0) };562	}563}564565impl Value {566	pub fn new_attrs(v: HashMap<&str, Value>) -> Self {567		let out = Self::new_uninit();568		let mut b = AttrsBuilder::new(v.len());569		for (k, v) in v {570			b.insert(&k, v);571		}572		with_default_context(|c, _| unsafe { nix_raw::make_attrs(c, out.0, b.0) })573			.expect("attrs initialization should not fail");574		out575	}576	fn new_list<T: Into<Self>>(v: Vec<T>) -> Self {577		todo!()578	}579	fn new_uninit() -> Self {580		let out = with_default_context(|c, es| unsafe { alloc_value(c, es) })581			.expect("value allocation should not fail");582		Self(out)583	}584	pub fn new_str(v: &str) -> Self {585		let s = CString::new(v).expect("string should not contain NULs");586		let out = Self::new_uninit();587		// String is copied, `s` is free to be dropped588		with_default_context(|c, _| unsafe { init_string(c, out.0, s.as_ptr()) })589			.expect("string initialization should not fail");590		out591	}592	pub fn new_int(i: i64) -> Self {593		let out = Self::new_uninit();594		with_default_context(|c, _| unsafe { init_int(c, out.0, i) })595			.expect("int initialization should not fail");596		out597	}598	pub fn new_bool(v: bool) -> Self {599		let out = Self::new_uninit();600		with_default_context(|c, _| unsafe { init_bool(c, out.0, v) })601			.expect("bool initialization should not fail");602		out603	}604	// TODO: As far as I can see, there is no way to get Thunks from nix public C api, so this function is useless605	// fn force(&mut self, st: &mut EvalState) -> Result<()> {606	// 	with_default_context(|c, _| unsafe { value_force(c, st.0, self.0) })?;607	// 	Ok(())608	// }609	pub fn type_of(&self) -> NixType {610		let ty = with_default_context(|c, _| unsafe { nix_raw::get_type(c, self.0) })611			.expect("get_type should not fail");612		NixType::from_int(ty)613	}614	pub fn to_string(&self) -> Result<String> {615		Ok(self.to_realised_string()?.as_str().to_owned())616	}617	pub fn to_realised_string(&self) -> Result<RealisedString> {618		with_default_context(|c, es| unsafe { nix_raw::string_realise(c, es, self.0, false) })619			.map(RealisedString)620621		// let store_paths = unsafe { nix_raw::realised_string_get_store_path_count(str) };622		// for i in 0..store_paths {623		// 	let store_path = unsafe { nix_raw::realised_string_get_store_path(str, i) };624		// 	nix_raw::store_path_name(store_path, callback, user_data);625		// }626		// dbg!(store_paths);627		// todo!();628	}629630	pub fn has_field(&self, field: &str) -> Result<bool> {631		let f = init_field_name(field);632		with_default_context(|c, es| unsafe {633			nix_raw::has_attr_byname(c, self.0, es, f.as_ptr().cast())634		})635	}636	// pub fn derivation_path(&self) {637	// 	nix_raw::real638	// }639	pub fn list_fields(&self) -> Result<Vec<String>> {640		if !matches!(self.type_of(), NixType::Attrs) {641			bail!("invalid type: expected attrs");642		}643644		let len = with_default_context(|c, _| unsafe { nix_raw::get_attrs_size(c, self.0) })?;645		let mut out = Vec::with_capacity(len as usize);646647		for i in 0..len {648			let name = with_default_context(|c, es| unsafe {649				nix_raw::get_attr_name_byidx(c, self.0, es, i)650			})?;651			let c = unsafe { CStr::from_ptr(name) };652			out.push(c.to_str().expect("nix field names are utf-8").to_owned());653		}654		Ok(out)655	}656	pub fn get_elem(&self, v: usize) -> Result<Self> {657		if !matches!(self.type_of(), NixType::List) {658			bail!("invalid type: expected list");659		}660		let len =661			with_default_context(|c, _| unsafe { nix_raw::get_list_size(c, self.0) })? as usize;662		if v >= len {663			bail!("oob list get: {v} >= {len}");664		}665666		with_default_context(|c, es| unsafe { nix_raw::get_list_byidx(c, self.0, es, v as u32) })667			.map(Self)668	}669	pub fn attrs_update(self, other: Value) -> Result<Self> {670		let a_fields = self.list_fields()?;671		let b_fields = other.list_fields()?;672		match (a_fields.len(), b_fields.len()) {673			(_, 0) => return Ok(self),674			(0, _) => return Ok(other),675			_ => {}676		}677		let mut out = HashMap::new();678		for f in a_fields.iter() {679			if b_fields.contains(f) {680				break;681			}682			out.insert(f.as_str(), self.get_field(f)?);683		}684		if out.is_empty() {685			// All fields from lhs are overriden by rhs686			return Ok(other);687		}688		for f in b_fields.iter() {689			out.insert(f.as_str(), other.get_field(f)?);690		}691		Ok(Self::new_attrs(out))692	}693	pub fn get_field(&self, name: impl AsFieldName) -> Result<Self> {694		if !matches!(self.type_of(), NixType::Attrs) {695			bail!("invalid type: expected attrs");696		}697698		name.as_field_name(|name| {699			with_default_context(|c, es| unsafe {700				nix_raw::get_attr_byname(c, self.0, es, name.as_ptr().cast())701			})702			.map(Self)703		})704		.with_context(|| format!("getting field {:?}", name.to_field_name()))705	}706	pub fn call(&self, v: Value) -> Result<Self> {707		let kind = self708			.functor_kind()709			.ok_or_else(|| anyhow!("can only call function or functor"))?;710711		let function = match kind {712			FunctorKind::Function => self.clone(),713			FunctorKind::Functor => {714				let f = self.get_field("__functor")?;715				assert_eq!(716					f.type_of(),717					NixType::Function,718					"invalid functor encountered"719				);720				f721			}722		};723724		let out = Value::new_uninit();725		with_default_context(|c, es| unsafe {726			nix_raw::value_call(c, es, function.0, v.0, out.0)727		})?;728729		Ok(out)730	}731	pub fn eval(v: &str) -> Result<Self> {732		let s = CString::new(v).expect("expression shouldn't have internal NULs");733		let out = Self::new_uninit();734		with_default_context(|c, es| unsafe {735			expr_eval_from_string(c, es, s.as_ptr(), c"/homeless-shelter".as_ptr(), out.0)736		})?;737		Ok(out)738	}739	pub async fn build(&self, output: &str) -> Result<PathBuf> {740		if !self.is_derivation() {741			bail!("expected derivation to build")742		}743		let output_name = self.get_field("outputName")?.to_string()?;744		let v = if output_name != output {745			let out = self.get_field(output)?;746			if !out.is_derivation() {747				bail!("unknown output: {output}");748			}749			out750		} else {751			self.clone()752		};753		// to_string here blocks until the path is built754		let drv_path = tokio::task::spawn_blocking(move || v.get_field("outPath")?.to_string())755			.await756			.expect("should not fail")?;757		Ok(PathBuf::from(drv_path))758	}759	pub fn as_json<T: DeserializeOwned>(&self) -> Result<T> {760		let to_json = Self::eval("builtins.toJSON")?;761		let s = to_json.call(self.clone())?.to_string()?;762		Ok(serde_json::from_str(&s)?)763	}764	pub fn serialized<T: Serialize>(v: &T) -> Result<Self> {765		Self::eval(&nixlike::serialize(v)?)766	}767768	// Convert to string/evaluate derivations/etc769	// fn to_string_weak(&self) -> Result<String> {770	// 	// TODO: For now, it works exactly like to_string, see the comment for fn force()771	// 	self.to_string()772	// }773774	fn is_derivation(&self) -> bool {775		if !matches!(self.type_of(), NixType::Attrs) {776			return false;777		}778		let Some(ty) = self.get_field("type").ok() else {779			return false;780		};781		matches!(ty.to_string().as_deref(), Ok("derivation"))782	}783	fn functor_kind(&self) -> Option<FunctorKind> {784		match self.type_of() {785			NixType::Attrs => self786				.has_field("__functor")787				.expect("has_field shouldn't fail for attrs")788				.then_some(FunctorKind::Functor),789			NixType::Function => Some(FunctorKind::Function),790			_ => None,791		}792	}793	pub fn is_function(&self) -> bool {794		self.functor_kind().is_some()795	}796}797798impl From<String> for Value {799	fn from(value: String) -> Self {800		Value::new_str(&value)801	}802}803impl From<bool> for Value {804	fn from(value: bool) -> Self {805		Value::new_bool(value)806	}807}808impl From<&str> for Value {809	fn from(value: &str) -> Self {810		Value::new_str(value)811	}812}813impl<T> From<Vec<T>> for Value814where815	T: Into<Value>,816{817	fn from(value: Vec<T>) -> Self {818		Value::new_list(value)819	}820}821822impl Clone for Value {823	fn clone(&self) -> Self {824		with_default_context(|c, _| unsafe { value_incref(c, self.0) })825			.expect("value incref should not fail");826		Self(self.0)827	}828}829impl Drop for Value {830	fn drop(&mut self) {831		with_default_context(|c, _| unsafe { value_decref(c, self.0) })832			.expect("value drop should not fail");833	}834}835836pub fn init_libraries() {837	unsafe { nix_raw::GC_allow_register_threads() };838839	let mut ctx = NixContext::new();840	ctx.run_in_context(|c| unsafe { nix_raw::libutil_init(c) })841		.expect("util init should not fail");842	ctx.run_in_context(|c| unsafe { nix_raw::libstore_init(c) })843		.expect("store init should not fail");844	ctx.run_in_context(|c| unsafe { nix_raw::libexpr_init(c) })845		.expect("expr init should not fail");846847	nix_logging_cxx::apply_tracing_logger();848}849850#[test_log::test]851fn test_native() -> Result<()> {852	init_libraries();853854	let mut fetch_settings = FetchSettings::new();855	fetch_settings.set(c"warn-dirty", c"false");856857	let manifest = format!("{}/../../", env!("CARGO_MANIFEST_DIR"));858	let (mut r, _) = FlakeReference::new(&manifest, &fetch_settings)?;859	let locked = r.lock(&fetch_settings)?;860	let attrs = locked.get_attrs(&mut FlakeSettings::new()?)?;861862	let builtins = Value::eval("builtins")?;863	assert_eq!(builtins.type_of(), NixType::Attrs);864865	assert_eq!(attrs.type_of(), NixType::Attrs);866	let test_data = nix_go!(attrs.testData);867868	let test_string: String = nix_go_json!(test_data.testString);869	assert_eq!(test_string, "hello");870871	Ok(())872}
modifiedcrates/nix-eval/src/logging.ccdiffbeforeafterboth
--- a/crates/nix-eval/src/logging.cc
+++ b/crates/nix-eval/src/logging.cc
@@ -8,19 +8,6 @@
   TracingLogger() {}
 
   bool isVerbose() override { return true; }
-  // void addFields(nlohmann::json & json, const Fields & fields)
-  //    {
-  //        if (fields.empty())
-  //            return;
-  //        auto & arr = json["fields"] = nlohmann::json::array();
-  //        for (auto & f : fields)
-  //            if (f.type == Logger::Field::tInt)
-  //                arr.push_back(f.i);
-  //            else if (f.type == Logger::Field::tString)
-  //                arr.push_back(f.s);
-  //            else
-  //                unreachable();
-  //    }
   void log(Verbosity lvl, std::string_view s) override {
     rust::Str str(s.data(), s.size());
     emit_log(lvl, str);
@@ -60,16 +47,18 @@
   };
 
   void writeToStdout(std::string_view s) override {
-    printf("writeToStdout() called\n");
+    emit_warn("writeToStdout() called, but unsupported");
   }
   void warn(const std::string &msg) override { emit_warn(msg); }
 
   virtual std::optional<char> ask(std::string_view s) {
-    printf("ask() called\n");
+    emit_warn("ask() called, but unsupported");
     return {};
   }
 };
 
 extern "C" {
-void apply_tracing_logger() { logger = std::make_unique<TracingLogger>(); }
+void apply_tracing_logger() {
+  logger = std::make_unique<TracingLogger>();
+}
 }
modifiedflake.lockdiffbeforeafterboth
--- a/flake.lock
+++ b/flake.lock
@@ -105,7 +105,6 @@
       },
       "locked": {
         "lastModified": 1757000273,
-        "narHash": "sha256-9AKhwsSlegWnNFy8++OMNctrxJUUIE7nG4s4ZHmFPic=",
         "owner": "deltarocks",
         "repo": "nix",
         "rev": "eba1f549ec21208cf98343f1351a95e2e6eb3fbb",