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

difftreelog

feat derivation graph to spans

xykwwsssYaroslav Bolyukin2026-03-12parent: #d5b0f6a.patch.diff
in: trunk

6 files changed

modifiedcmds/fleet/Cargo.tomldiffbeforeafterboth
--- a/cmds/fleet/Cargo.toml
+++ b/cmds/fleet/Cargo.toml
@@ -50,7 +50,7 @@
 tracing-opentelemetry.workspace = true
 
 [features]
-default = []
+default = ["indicatif"]
 # Not quite stable
 indicatif = [
 	"dep:tracing-indicatif",
modifiedcmds/fleet/src/main.rsdiffbeforeafterboth
--- a/cmds/fleet/src/main.rs
+++ b/cmds/fleet/src/main.rs
@@ -120,7 +120,8 @@
 	let indicatif_layer = {
 		use std::time::Duration;
 
-		IndicatifLayer::new().with_progress_style(
+		IndicatifLayer::new().with_max_progress_bars(10, Some(ProgressStyle::default_spinner()))
+			.with_progress_style(
 			ProgressStyle::with_template(
 				"{color_start}{span_child_prefix} {span_name}{{{span_fields}}}{color_end} {wide_msg} {color_start}{download_progress} {elapsed}{color_end}",
 			)
modifiedcrates/nix-eval/build.rsdiffbeforeafterboth
--- a/crates/nix-eval/build.rs
+++ b/crates/nix-eval/build.rs
@@ -18,6 +18,7 @@
 		"nix-util",
 		"nix-util-c",
 		"nix-store",
+		"nix-store-c",
 		"nix-expr",
 		"nix-flake",
 		"nix-fetchers",
@@ -72,6 +73,12 @@
 		.include_paths
 		.into_iter()
 		.chain(
+			pkg_config::probe_library("nix-store-c")
+				.expect("nix-store-c")
+				.include_paths
+				.into_iter(),
+		)
+		.chain(
 			pkg_config::probe_library("nix-flake-c")
 				.expect("nix-flake-c")
 				.include_paths
addedcrates/nix-eval/src/drv.rsdiffbeforeafterboth
--- /dev/null
+++ b/crates/nix-eval/src/drv.rs
@@ -0,0 +1,150 @@
+use std::collections::{HashMap, HashSet, VecDeque};
+use std::ffi::CString;
+
+use anyhow::{Result, bail};
+use serde::Deserialize;
+
+use crate::nix_raw::{derivation_free, derivation_to_json, store_drv_from_store_path};
+use crate::{copy_nix_str, with_store_context};
+
+fn store_dir() -> Result<String> {
+	let mut out = String::new();
+	with_store_context(|c, store, _| unsafe {
+		crate::nix_raw::store_get_storedir(c, store, Some(copy_nix_str), (&raw mut out).cast())
+	})?;
+	Ok(out)
+}
+
+fn to_absolute_store_path(store_dir: &str, path: &str) -> String {
+	if path.starts_with('/') {
+		path.to_owned()
+	} else {
+		format!("{store_dir}/{path}")
+	}
+}
+
+pub struct Derivation(*mut crate::nix_raw::derivation);
+unsafe impl Send for Derivation {}
+
+impl Derivation {
+	pub fn from_path(drv_path: &str) -> Result<Self> {
+		let path_c = CString::new(drv_path)?;
+		let store_path = with_store_context(|c, store, _| unsafe {
+			crate::nix_raw::store_parse_path(c, store, path_c.as_ptr())
+		})?;
+		let drv = with_store_context(|c, store, _| unsafe {
+			store_drv_from_store_path(c, store, store_path)
+		});
+		unsafe { crate::nix_raw::store_path_free(store_path) };
+		let drv = drv?;
+		if drv.is_null() {
+			bail!("failed to read derivation from {drv_path}");
+		}
+		Ok(Self(drv))
+	}
+
+	pub fn to_json_string(&self) -> Result<String> {
+		let mut out = String::new();
+		with_store_context(|c, _, _| unsafe {
+			derivation_to_json(c, self.0, Some(copy_nix_str), (&raw mut out).cast())
+		})?;
+		Ok(out)
+	}
+
+	pub fn parsed(&self) -> Result<DrvParsed> {
+		let s = self.to_json_string()?;
+		Ok(serde_json::from_str(&s)?)
+	}
+}
+
+impl Drop for Derivation {
+	fn drop(&mut self) {
+		unsafe { derivation_free(self.0) };
+	}
+}
+
+#[derive(Debug, Deserialize)]
+pub struct DrvParsed {
+	pub inputs: DrvInputs,
+	pub outputs: HashMap<String, serde_json::Value>,
+}
+
+#[derive(Debug, Deserialize)]
+pub struct DrvInputs {
+	#[serde(default)]
+	pub srcs: Vec<String>,
+	#[serde(default)]
+	pub drvs: HashMap<String, DrvInputEntry>,
+}
+
+#[derive(Debug, Deserialize)]
+pub struct DrvInputEntry {
+	pub outputs: Vec<String>,
+}
+
+#[derive(Debug)]
+pub struct DrvGraph {
+	pub root: String,
+	pub nodes: HashMap<String, DrvNode>,
+}
+
+#[derive(Debug)]
+pub struct DrvNode {
+	pub name: String,
+	pub input_drvs: HashMap<String, Vec<String>>,
+	pub input_srcs: Vec<String>,
+	pub outputs: Vec<String>,
+}
+
+impl DrvGraph {
+	pub fn resolve(drv_path: &str) -> Result<Self> {
+		let sd = store_dir()?;
+		let root = to_absolute_store_path(&sd, drv_path);
+
+		let mut nodes = HashMap::new();
+		let mut queue = VecDeque::new();
+		let mut visited = HashSet::new();
+		queue.push_back(root.clone());
+		visited.insert(root.clone());
+
+		while let Some(path) = queue.pop_front() {
+			let drv = Derivation::from_path(&path)?;
+			let parsed = drv.parsed()?;
+
+			let input_drvs: HashMap<String, Vec<String>> = parsed
+				.inputs
+				.drvs
+				.into_iter()
+				.map(|(k, v)| (to_absolute_store_path(&sd, &k), v.outputs))
+				.collect();
+
+			for dep_path in input_drvs.keys() {
+				if visited.insert(dep_path.clone()) {
+					queue.push_back(dep_path.clone());
+				}
+			}
+
+			nodes.insert(
+				path.clone(),
+				DrvNode {
+					name: extract_drv_name(&path),
+					input_drvs,
+					input_srcs: parsed.inputs.srcs,
+					outputs: parsed.outputs.into_keys().collect(),
+				},
+			);
+		}
+
+		Ok(Self { root, nodes })
+	}
+}
+
+fn extract_drv_name(drv_path: &str) -> String {
+	drv_path
+		.rsplit('/')
+		.next()
+		.and_then(|f| f.strip_suffix(".drv"))
+		.and_then(|f| f.split_once('-').map(|(_, name)| name))
+		.unwrap_or(drv_path)
+		.to_owned()
+}
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::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;
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::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;
modifiedcrates/nix-eval/src/logging.rsdiffbeforeafterboth
--- a/crates/nix-eval/src/logging.rs
+++ b/crates/nix-eval/src/logging.rs
@@ -1,4 +1,4 @@
-use std::collections::HashMap;
+use std::collections::{HashMap, VecDeque};
 use std::fmt::Arguments;
 use std::sync::{LazyLock, Mutex};
 
@@ -285,6 +285,135 @@
 static NIX_SPAN_MAPPING: LazyLock<Mutex<HashMap<u64, Span>>> =
 	LazyLock::new(|| Mutex::new(HashMap::new()));
 
+struct DrvGraphEntry {
+	name: String,
+	parent: Option<String>,
+	span: Option<Span>,
+	refcount: usize,
+}
+
+static DRV_GRAPH: LazyLock<Mutex<HashMap<String, DrvGraphEntry>>> =
+	LazyLock::new(|| Mutex::new(HashMap::new()));
+
+static ACTIVITY_TO_DRV: LazyLock<Mutex<HashMap<u64, String>>> =
+	LazyLock::new(|| Mutex::new(HashMap::new()));
+
+pub struct BuildGraphGuard {
+	paths: Vec<String>,
+}
+
+impl Drop for BuildGraphGuard {
+	fn drop(&mut self) {
+		let mut drv_graph = DRV_GRAPH.lock().expect("not poisoned");
+		for path in &self.paths {
+			if let Some(entry) = drv_graph.get_mut(path) {
+				entry.refcount -= 1;
+				if entry.refcount == 0 {
+					drv_graph.remove(path);
+				}
+			}
+		}
+	}
+}
+
+pub fn register_build_graph(parent: &Span, graph: &crate::drv::DrvGraph) -> BuildGraphGuard {
+	let mut drv_graph = DRV_GRAPH.lock().expect("not poisoned");
+	let mut paths = Vec::new();
+
+	drv_graph
+		.entry(graph.root.clone())
+		.and_modify(|e| e.refcount += 1)
+		.or_insert_with(|| DrvGraphEntry {
+			name: graph.nodes[&graph.root].name.clone(),
+			parent: None,
+			span: Some(parent.clone()),
+			refcount: 1,
+		});
+	paths.push(graph.root.clone());
+
+	let mut queue = VecDeque::new();
+	queue.push_back(graph.root.clone());
+
+	let mut visited = std::collections::HashSet::new();
+	visited.insert(graph.root.clone());
+
+	while let Some(path) = queue.pop_front() {
+		let Some(node) = graph.nodes.get(&path) else {
+			continue;
+		};
+		for dep_path in node.input_drvs.keys() {
+			if !visited.insert(dep_path.clone()) {
+				continue;
+			}
+			let Some(dep_node) = graph.nodes.get(dep_path) else {
+				continue;
+			};
+			if let Some(entry) = drv_graph.get_mut(dep_path) {
+				entry.refcount += 1;
+			} else {
+				drv_graph.insert(dep_path.clone(), DrvGraphEntry {
+					name: dep_node.name.clone(),
+					parent: Some(path.clone()),
+					span: None,
+					refcount: 1,
+				});
+			}
+			paths.push(dep_path.clone());
+			queue.push_back(dep_path.clone());
+		}
+	}
+
+	BuildGraphGuard { paths }
+}
+
+fn ensure_drv_span(drv_path: &str) -> Option<Span> {
+	let mut drv_graph = DRV_GRAPH.lock().expect("not poisoned");
+
+	if let Some(span) = drv_graph.get(drv_path).and_then(|e| e.span.clone()) {
+		return Some(span);
+	}
+
+	let mut chain = vec![];
+	let mut current = drv_path.to_owned();
+	loop {
+		let Some(entry) = drv_graph.get(&current) else {
+			break;
+		};
+		if entry.span.is_some() {
+			chain.push(current);
+			break;
+		}
+		chain.push(current.clone());
+		match &entry.parent {
+			Some(p) => current = p.clone(),
+			None => break,
+		}
+	}
+
+	if chain.is_empty() {
+		return None;
+	}
+
+	for i in (0..chain.len()).rev() {
+		let path = &chain[i];
+		if drv_graph.get(path).unwrap().span.is_some() {
+			continue;
+		}
+		let parent_span = chain
+			.get(i + 1)
+			.and_then(|p| drv_graph.get(p))
+			.and_then(|e| e.span.clone());
+		let name = drv_graph.get(path).unwrap().name.clone();
+		let span = {
+			let _enter = parent_span.as_ref().map(|s| s.enter());
+			info_span!(target: "nix::build", "building", drv = %name)
+		};
+		drv_graph.get_mut(path).unwrap().span = Some(span);
+	}
+
+	drv_graph.get(drv_path).and_then(|e| e.span.clone())
+}
+
 #[derive(Debug)]
 enum FieldValue {
 	Int(i32),
@@ -306,57 +435,33 @@
 		self.fields.push(FieldValue::Str(v.to_string()));
 	}
 	fn emit(&mut self, parent: u64, s: &str) {
-		let mut mapping = NIX_SPAN_MAPPING.lock().expect("not poisoned");
-
-		let parent = mapping.get(&parent);
+		let graph_span = if matches!(self.typ, ActivityType::Build) {
+			self.fields.first().and_then(|f| match f {
+				FieldValue::Str(drv_path) => {
+					let clean = parse_path(drv_path);
+					let span = ensure_drv_span(clean);
+					if span.is_some() {
+						ACTIVITY_TO_DRV
+							.lock()
+							.expect("not poisoned")
+							.insert(self.activity_id, clean.to_owned());
+					}
+					span
+				}
+				_ => None,
+			})
+		} else {
+			None
+		};
 
-		// let meta = spans.alloc_metadata(
-		// 	self.typ.name(),
-		// 	self.verbosity.into(),
-		// 	MetadataKind::Span,
-		// 	"nix activity start",
-		// 	None,
-		// 	None,
-		// 	None,
-		// 	self.typ.fields(),
-		// );
-		//
-		// let mut fields = meta.fields().iter();
-		// let span = if let Some(parent) = parent {
-		// 	let s = Span::new(
-		// 		meta,
-		// 		&match meta.fields().len() {
-		// 			1 => meta.fields().value_set(
-		// 				&<[_; 1]>::try_from([(
-		// 					&fields.next().expect("has field"),
-		// 					Some(&format_args!("Test") as &dyn tracing::Value),
-		// 				)])
-		// 				.expect("valid size"),
-		// 			),
-		// 			_ => unreachable!(),
-		// 		},
-		// 	);
-		// 	s.follows_from(parent);
-		// 	s
-		// } else {
-		// 	Span::new_root(
-		// 		meta,
-		// 		&match meta.fields().len() {
-		// 			1 => meta.fields().value_set(
-		// 				&<[_; 1]>::try_from([(
-		// 					&fields.next().expect("has field"),
-		// 					Some(&format_args!("Test") as &dyn tracing::Value),
-		// 				)])
-		// 				.expect("valid size"),
-		// 			),
-		// 			_ => unreachable!(),
-		// 		},
-		// 	)
-		// };
-		//
-		// let id = span.id().expect("id created");
+		let mut mapping = NIX_SPAN_MAPPING.lock().expect("not poisoned");
 
-		let span = {
+		let span = if let Some(span) = graph_span {
+			#[cfg(feature = "indicatif")]
+			span.pb_start();
+			span
+		} else {
+			let parent = mapping.get(&parent);
 			let _in_parent = parent.map(|p| p.enter());
 			let level: Level = self.verbosity.into();
 			if level == Level::ERROR {
@@ -380,7 +485,7 @@
 			let s = ansi_filter(s);
 			#[cfg(feature = "indicatif")]
 			{
-				span.pb_set_message(s);
+				span.pb_set_message(&s);
 			}
 			let _e = span.enter();
 			let level: Level = self.verbosity.into();
@@ -454,8 +559,15 @@
 	warn!(target: "nix::eval", "{v}")
 }
 fn emit_stop(v: u64) {
-	let mut mapping = NIX_SPAN_MAPPING.lock().expect("not poisoned");
-	mapping.remove(&v);
+	{
+		let mut mapping = NIX_SPAN_MAPPING.lock().expect("not poisoned");
+		mapping.remove(&v);
+	}
+	if let Some(drv_path) = ACTIVITY_TO_DRV.lock().expect("not poisoned").remove(&v) {
+		if let Some(entry) = DRV_GRAPH.lock().expect("not poisoned").get_mut(&drv_path) {
+			entry.span = None;
+		}
+	}
 }
 fn emit_log(lvl: u32, v: &[u8]) {
 	let verbosity = Verbosity::from_int(lvl);