git.delta.rocks / fleet / refs/commits / 615754ca0747

difftreelog

source

crates/nix-eval/src/lib.rs38.7 KiBsourcehistory
1use std::borrow::Cow;2use std::cell::RefCell;3use std::collections::HashMap;4use std::ffi::{CStr, CString, c_char, c_int, c_uint, c_void};5use std::ptr::{null, null_mut};6use std::sync::{Arc, LazyLock, OnceLock};7use std::{array, fmt, slice};89use anyhow::{Context, anyhow, bail};10use camino::{Utf8Path, Utf8PathBuf};11use itertools::Itertools;12use serde::Serialize;13use serde::de::DeserializeOwned;14use std::mem::transmute;1516pub use anyhow::Result;17use tracing::{Span, instrument, warn};1819use self::logging::{ErrorInfoBuilder, nix_logging_cxx};20use self::nix_cxx::set_fetcher_setting;21use self::nix_raw::{22	BindingsBuilder as c_bindings_builder, EvalState as c_eval_state, GC_SUCCESS,23	GC_allow_register_threads, GC_get_stack_base, GC_register_my_thread, GC_stack_base,24	GC_thread_is_registered, GC_unregister_my_thread, ListBuilder as c_list_builder, PrimOp,25	PrimOpFun, Store as c_store, StorePath as c_store_path, alloc_primop, alloc_value,26	bindings_builder_free, bindings_builder_insert, c_context, c_context_create, c_context_free,27	clear_err, copy_value, err_NIX_ERR_KEY, err_NIX_ERR_NIX_ERROR, err_NIX_ERR_OVERFLOW,28	err_NIX_ERR_UNKNOWN, err_NIX_OK, err_code, err_info_msg, err_msg, eval_state_build,29	eval_state_builder_load, eval_state_builder_new, eval_state_builder_set_eval_setting,30	expr_eval_from_string, fetchers_settings, fetchers_settings_free, fetchers_settings_new,31	flake_lock, flake_lock_flags, 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_get, setting_set, state_free, store_copy_closure, store_free, store_open,43	store_parse_path, store_path_free, store_path_name, string_realise, value, value_call,44	value_decref, value_force, value_incref,45};4647// Contains macros helpers48pub mod drv;49pub mod logging;50#[doc(hidden)]51pub mod macros;52pub mod scheduler;5354#[doc(hidden)]55pub mod __macro_support {56	pub use std::collections::hash_map::HashMap;5758	pub use anyhow::Context;59	pub use tokio::task::block_in_place;60}61pub mod util;6263#[allow(64	non_upper_case_globals,65	non_camel_case_types,66	non_snake_case,67	dead_code68)]69mod nix_raw {70	include!(concat!(env!("OUT_DIR"), "/bindings.rs"));71}72#[cxx::bridge]73pub mod nix_cxx {74	struct AddFileToStoreResult {75		error: String,76		store_path: String,77		hash: String,78	}79	struct CxxProfileGeneration {80		id: u64,81		store_path: String,82		creation_time_unix: i64,83		current: bool,84	}85	struct CxxListGenerationsResult {86		error: String,87		generations: Vec<CxxProfileGeneration>,88	}89	struct CxxBuildResult {90		error: String,91		outputs: Vec<String>,92	}93	struct CxxPathInfo {94		error: String,95		nar_hash: String,96		nar_size: u64,97		references: Vec<String>,98		sigs: Vec<String>,99	}100	unsafe extern "C++" {101		type nix_fetchers_settings;102		type Store;103		include!("nix-eval/src/lib.hh");104105		#[allow(clippy::missing_safety_doc)]106		unsafe fn set_fetcher_setting(107			settings: *mut nix_fetchers_settings,108			setting: *const c_char,109			value: *const c_char,110		);111112		#[allow(clippy::missing_safety_doc)]113		unsafe fn switch_profile(store: *mut Store, profile: &str, store_path: &str) -> String;114115		#[allow(clippy::missing_safety_doc)]116		unsafe fn sign_closure(store: *mut Store, store_path: &str, key_file: &str) -> String;117118		#[allow(clippy::missing_safety_doc)]119		unsafe fn add_file_to_store(120			store: *mut Store,121			name: &str,122			path: &str,123		) -> AddFileToStoreResult;124125		fn list_generations(profile_path: &str) -> CxxListGenerationsResult;126127		#[allow(clippy::missing_safety_doc)]128		unsafe fn build_drv_outputs(129			store: *mut Store,130			drv_path: &str,131			output_names_joined: &str,132		) -> CxxBuildResult;133134		#[allow(clippy::missing_safety_doc)]135		unsafe fn substitute_paths(store: *mut Store, paths_joined: &str) -> CxxBuildResult;136137		#[allow(clippy::missing_safety_doc)]138		unsafe fn is_valid_path(store: *mut Store, path: &str) -> bool;139140		#[allow(clippy::missing_safety_doc)]141		unsafe fn query_path_info(store: *mut Store, path: &str) -> CxxPathInfo;142143		#[allow(clippy::missing_safety_doc)]144		unsafe fn compute_closure(store: *mut Store, path: &str) -> CxxBuildResult;145146		#[allow(clippy::missing_safety_doc)]147		unsafe fn nar_from_path(148			store: *mut Store,149			path: &str,150			sink_data: usize,151			sink: fn(usize, &[u8]) -> bool,152		) -> String;153	}154}155156#[derive(Debug, PartialEq, Eq)]157pub enum NixType {158	Thunk,159	Int,160	Float,161	Bool,162	String,163	Path,164	Null,165	Attrs,166	List,167	Function,168	External,169}170impl NixType {171	fn from_int(c: c_uint) -> Self {172		match c {173			0 => Self::Thunk,174			1 => Self::Int,175			2 => Self::Float,176			3 => Self::Bool,177			4 => Self::String,178			5 => Self::Path,179			6 => Self::Null,180			7 => Self::Attrs,181			8 => Self::List,182			9 => Self::Function,183			10 => Self::External,184			_ => unreachable!("unknown nix type: {c}"),185		}186	}187}188189enum FunctorKind {190	Function,191	Functor,192}193194#[derive(Debug)]195#[repr(i32)]196pub enum NixErrorKind {197	Unknown = err_NIX_ERR_UNKNOWN,198	Overflow = err_NIX_ERR_OVERFLOW,199	Key = err_NIX_ERR_KEY,200	Generic = err_NIX_ERR_NIX_ERROR,201}202impl NixErrorKind {203	fn from_int(v: c_int) -> Option<Self> {204		Some(match v {205			0 => return None,206			nix_raw::err_NIX_ERR_UNKNOWN => Self::Unknown,207			nix_raw::err_NIX_ERR_OVERFLOW => Self::Overflow,208			nix_raw::err_NIX_ERR_KEY => Self::Key,209			nix_raw::err_NIX_ERR_NIX_ERROR => Self::Generic,210			_ => {211				debug_assert!(false, "unexpected nix error kind: {v}");212				Self::Unknown213			}214		})215	}216}217218pub fn gc_now() {219	unsafe { gc_now_raw() };220}221222pub fn gc_register_my_thread() {223	assert_eq!(unsafe { GC_thread_is_registered() }, 0);224225	let mut sb = GC_stack_base {226		mem_base: null_mut(),227	};228	let r = unsafe { GC_get_stack_base(&mut sb) };229	if r as u32 != GC_SUCCESS {230		panic!("failed to get thread stack base");231	}232	unsafe { GC_register_my_thread(&sb) };233}234pub fn gc_unregister_my_thread() {235	assert_eq!(unsafe { GC_thread_is_registered() }, 1);236237	unsafe { GC_unregister_my_thread() };238}239240pub struct ThreadRegisterGuard {}241impl ThreadRegisterGuard {242	#[allow(clippy::new_without_default)]243	pub fn new() -> Self {244		gc_register_my_thread();245		Self {}246	}247}248impl Drop for ThreadRegisterGuard {249	fn drop(&mut self) {250		gc_unregister_my_thread();251	}252}253254#[repr(transparent)]255pub struct NixContext(*mut c_context);256impl NixContext {257	pub fn set_err_raw(&mut self, err: NixErrorKind, msg: &CStr) {258		unsafe { set_err_msg(self.0, err as c_int, msg.as_ptr()) };259	}260	pub fn set_err(&mut self, err: anyhow::Error) {261		let fmt = format!("{err:?}").replace("\0", "\\0");262		self.set_err_raw(263			NixErrorKind::Generic,264			&CString::new(fmt).expect("NUL bytes were just replaced"),265		);266	}267	pub fn new() -> Self {268		let ctx = unsafe { c_context_create() };269		Self(ctx)270	}271	fn error_kind(&self) -> Option<NixErrorKind> {272		let code = unsafe { err_code(self.0) };273		NixErrorKind::from_int(code)274	}275	fn error<'t>(&self) -> Option<(Cow<'t, str>, Option<Box<ErrorInfoBuilder>>)> {276		if let NixErrorKind::Generic = self.error_kind()? {277			let ei = unsafe { logging::nix_logging_cxx::extract_error_info(self.0) };278			let mut err_out = String::new();279			unsafe {280				err_info_msg(281					null_mut(),282					self.0,283					Some(copy_nix_str),284					(&raw mut err_out).cast(),285				)286			};287			return Some((Cow::Owned(err_out), Some(ei)));288		};289290		// TODO: Can throw error (resulting in panic) if unable to retrieve error. Should be able to resolve by passing context as a first argument,291		// but it looks ugly292		let str = unsafe { err_msg(null_mut(), self.0, null_mut()) };293		Some((unsafe { CStr::from_ptr(str) }.to_string_lossy(), None))294	}295	fn clean_err(&mut self) {296		unsafe {297			clear_err(self.0);298		}299	}300301	fn bail_if_error(&self) -> Result<()> {302		if let Some((err, stack)) = self.error() {303			let mut e = Err(anyhow!("{err}"));304			if let Some(stack) = stack {305				for ele in stack.stack_frames {306					e = e.with_context(|| {307						if ele.pos.is_empty() {308							ele.msg309						} else {310							format!("{} at {}", ele.msg, ele.pos)311						}312					})313				}314			}315			return e.context("<nix frames>");316		};317		Ok(())318	}319320	fn run_in_context<T>(&mut self, f: impl FnOnce(*mut c_context) -> T) -> Result<T> {321		self.clean_err();322		let o = f(self.0);323		self.bail_if_error()?;324		self.clean_err();325		Ok(o)326	}327}328329impl Default for NixContext {330	fn default() -> Self {331		Self::new()332	}333}334impl Drop for NixContext {335	fn drop(&mut self) {336		unsafe {337			c_context_free(self.0);338		}339	}340}341struct GlobalState {342	// Store should be valid as long as EvalState is valid343	#[allow(dead_code)]344	store: Arc<Store>,345	state: EvalState,346}347impl GlobalState {348	fn new() -> Result<Self> {349		let mut ctx = NixContext::new();350		let store = Arc::new(351			ctx.run_in_context(|c| unsafe { store_open(c, c"auto".as_ptr(), null_mut()) })352				.map(Store)?,353		);354355		let builder = ctx.run_in_context(|c| unsafe { eval_state_builder_new(c, store.0) })?;356		ctx.run_in_context(|c| unsafe { eval_state_builder_load(c, builder) })?;357		ctx.run_in_context(|c| unsafe {358			eval_state_builder_set_eval_setting(359				c,360				builder,361				c"lazy-trees".as_ptr(),362				c"true".as_ptr(),363			)364		})?;365		ctx.run_in_context(|c| unsafe {366			eval_state_builder_set_eval_setting(367				c,368				builder,369				c"lazy-locks".as_ptr(),370				c"true".as_ptr(),371			)372		})?;373		let state = ctx374			.run_in_context(|c| unsafe { eval_state_build(c, builder) })375			.map(EvalState)?;376377		Ok(Self { store, state })378	}379}380381struct ThreadState {382	ctx: NixContext,383}384impl ThreadState {385	fn new() -> Result<Self> {386		let ctx = NixContext::new();387388		Ok(Self { ctx })389	}390}391392static GLOBAL_STATE: LazyLock<GlobalState> =393	LazyLock::new(|| GlobalState::new().expect("global state init shouldn't fail"));394395thread_local! {396	static THREAD_STATE: RefCell<ThreadState> = RefCell::new(ThreadState::new().expect("thread state init shouldn't fail"));397}398pub(crate) fn with_default_context<T>(399	f: impl FnOnce(*mut c_context, *mut c_eval_state) -> T,400) -> Result<T> {401	let global = &GLOBAL_STATE.state;402	let (ctx, state) = THREAD_STATE.with_borrow_mut(|w| (w.ctx.0, global.0));403	let mut ctx = NixContext(ctx);404	let v = ctx.run_in_context(|c| f(c, state));405	// It is reused for thread406	std::mem::forget(ctx);407	v408}409410pub fn set_setting(s: &CStr, v: &CStr) -> Result<()> {411	with_default_context(|c, _| unsafe { setting_set(c, s.as_ptr(), v.as_ptr()) }).map(|_| ())412}413414pub fn get_setting(s: &CStr) -> Result<String> {415	let mut out = String::new();416	with_default_context(|c, _| unsafe {417		setting_get(c, s.as_ptr(), Some(copy_nix_str), (&raw mut out).cast())418	})?;419	Ok(out)420}421422#[derive(Debug)]423pub struct AddedFile {424	pub store_path: Utf8PathBuf,425	pub hash: String,426}427428#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]429pub struct ProfileGeneration {430	pub id: u64,431	pub store_path: Utf8PathBuf,432	pub creation_time_unix: i64,433	pub current: bool,434}435436#[instrument]437pub fn list_generations(profile_path: &str) -> Result<Vec<ProfileGeneration>> {438	let res = nix_cxx::list_generations(profile_path);439	if !res.error.is_empty() {440		bail!(441			"failed to list generations at {profile_path}: {}",442			res.error443		);444	}445	Ok(res446		.generations447		.into_iter()448		.map(|g| ProfileGeneration {449			id: g.id,450			store_path: Utf8PathBuf::from(g.store_path),451			creation_time_unix: g.creation_time_unix,452			current: g.current,453		})454		.collect())455}456457pub struct FetchSettings(*mut fetchers_settings);458impl FetchSettings {459	pub fn new() -> Self {460		Self::try_new().expect("allocation should not fail")461	}462	fn try_new() -> Result<Self> {463		with_default_context(|c, _| unsafe { fetchers_settings_new(c) }).map(Self)464	}465	pub fn set(&mut self, setting: &CStr, value: &CStr) {466		unsafe {467			set_fetcher_setting(self.0.cast(), setting.as_ptr(), value.as_ptr());468		};469	}470}471unsafe impl Send for FetchSettings {}472unsafe impl Sync for FetchSettings {}473474impl Default for FetchSettings {475	fn default() -> Self {476		Self::new()477	}478}479480impl Drop for FetchSettings {481	fn drop(&mut self) {482		unsafe { fetchers_settings_free(self.0) };483	}484}485pub struct FlakeSettings(*mut flake_settings);486impl FlakeSettings {487	pub fn new() -> Result<Self> {488		with_default_context(|c, _| unsafe { flake_settings_new(c) }).map(Self)489	}490}491unsafe impl Send for FlakeSettings {}492unsafe impl Sync for FlakeSettings {}493impl Drop for FlakeSettings {494	fn drop(&mut self) {495		unsafe {496			flake_settings_free(self.0);497		}498	}499}500501pub struct FlakeReferenceParseFlags(*mut flake_reference_parse_flags);502impl FlakeReferenceParseFlags {503	pub fn new(settings: &FlakeSettings) -> Result<Self> {504		with_default_context(|c, _| unsafe { flake_reference_parse_flags_new(c, settings.0) })505			.map(Self)506	}507	pub fn set_base_dir(&mut self, dir: &str) -> Result<()> {508		with_default_context(|c, _| {509			unsafe {510				flake_reference_parse_flags_set_base_directory(511					c,512					self.0,513					dir.as_ptr().cast(),514					dir.len(),515				)516			};517		})518	}519}520impl Drop for FlakeReferenceParseFlags {521	fn drop(&mut self) {522		unsafe {523			flake_reference_parse_flags_free(self.0);524		}525	}526}527pub struct FlakeLockFlags(*mut flake_lock_flags);528impl FlakeLockFlags {529	pub fn new(settings: &FlakeSettings) -> Result<Self> {530		let o = with_default_context(|c, _| unsafe { flake_lock_flags_new(c, settings.0) })531			.map(Self)?;532		// with_default_context(|c, _| unsafe { flake_lock_flags_set_mode_virtual(c, o.0) })?;533534		Ok(o)535	}536}537impl Drop for FlakeLockFlags {538	fn drop(&mut self) {539		unsafe {540			flake_lock_flags_free(self.0);541		}542	}543}544545pub(crate) unsafe extern "C" fn copy_nix_str(546	start: *const c_char,547	n: c_uint,548	user_data: *mut c_void,549) {550	let s = unsafe { slice::from_raw_parts(start.cast::<u8>(), n as usize) };551	let s = std::str::from_utf8(s).expect("c string has invalid utf-8");552	unsafe { *user_data.cast::<String>() = s.to_owned() };553}554555pub struct Store(*mut c_store);556unsafe impl Send for Store {}557unsafe impl Sync for Store {}558559#[derive(Debug, Clone)]560pub struct PathInfo {561	pub nar_hash: String,562	pub nar_size: u64,563	pub references: Vec<Utf8PathBuf>,564	pub sigs: Vec<String>,565}566567pub fn eval_store() -> Arc<Store> {568	GLOBAL_STATE.store.clone()569}570571impl Store {572	pub fn open(uri: &str) -> Result<Self> {573		let uri = CString::new(uri)?;574		let ptr = with_default_context(|c, _| unsafe { store_open(c, uri.as_ptr(), null_mut()) })?;575		if ptr.is_null() {576			bail!("failed to open store");577		}578		Ok(Store(ptr))579	}580581	pub fn parse_path(&self, path: &Utf8Path) -> Result<StorePath> {582		let path = CString::new(path.as_str()).expect("valid cstr");583		with_default_context(|c, _| {584			StorePath(unsafe { store_parse_path(c, self.0, path.as_ptr()) })585		})586	}587588	#[instrument(skip(self))]589	pub fn sign_closure(&self, path: &Utf8Path, key_file: &Utf8Path) -> Result<()> {590		let err = with_default_context(|_, _| unsafe {591			nix_cxx::sign_closure(self.as_ptr().cast(), path.as_str(), key_file.as_str())592		})?593		.to_string();594595		if err.is_empty() {596			Ok(())597		} else {598			bail!("failed to sign {path}: {err}");599		}600	}601602	#[instrument(skip(self, dst))]603	pub fn copy_to(&self, dst: &Store, path: &Utf8Path) -> Result<()> {604		let sp = self605			.parse_path(path)606			.context("failed to parse store path")?;607		let rc = with_default_context(|c, _| unsafe {608			store_copy_closure(c, self.as_ptr(), dst.0, sp.as_ptr())609		})?;610		if rc != err_NIX_OK {611			bail!("store_copy_closure failed (code {rc})");612		}613		Ok(())614	}615616	/// Would only work with local store.617	#[instrument(skip(self))]618	pub fn switch_profile(&self, profile: &str, path: &Utf8Path) -> Result<()> {619		let msg = unsafe { nix_cxx::switch_profile(self.as_ptr().cast(), profile, path.as_str()) };620		if msg.is_empty() {621			Ok(())622		} else {623			bail!("failed to switch profile {profile}: {msg}");624		}625	}626627	#[instrument(skip(self))]628	pub fn add_file(&self, name: &str, path: &Utf8Path) -> Result<AddedFile> {629		let msg = unsafe { nix_cxx::add_file_to_store(self.as_ptr().cast(), name, path.as_str()) };630		if !msg.error.is_empty() {631			bail!("failed to add {path} to store: {}", msg.error)632		}633		Ok(AddedFile {634			store_path: Utf8PathBuf::from(msg.store_path),635			hash: msg.hash,636		})637	}638639	#[instrument(skip(self, paths))]640	pub fn substitute_paths(&self, paths: &[Utf8PathBuf]) -> Result<Vec<Utf8PathBuf>> {641		let joined = paths.into_iter().join("\n");642		let res = unsafe { nix_cxx::substitute_paths(self.as_ptr().cast(), &joined) };643		if !res.error.is_empty() {644			warn!("substitute_paths reported: {}", res.error);645		}646		Ok(res.outputs.into_iter().map(Utf8PathBuf::from).collect())647	}648649	#[instrument(skip(self))]650	pub fn is_valid_path(&self, path: &Utf8Path) -> bool {651		unsafe { nix_cxx::is_valid_path(self.as_ptr().cast(), path.as_str()) }652	}653654	#[instrument(skip(self))]655	pub fn query_path_info(&self, path: &Utf8Path) -> Result<PathInfo> {656		let res = unsafe { nix_cxx::query_path_info(self.as_ptr().cast(), path.as_str()) };657		if !res.error.is_empty() {658			bail!("failed to query path info for {path}: {}", res.error);659		}660		Ok(PathInfo {661			nar_hash: res.nar_hash,662			nar_size: res.nar_size,663			references: res.references.into_iter().map(Utf8PathBuf::from).collect(),664			sigs: res.sigs,665		})666	}667668	#[instrument(skip(self))]669	pub fn compute_closure(&self, path: &Utf8Path) -> Result<Vec<Utf8PathBuf>> {670		let res = unsafe { nix_cxx::compute_closure(self.as_ptr().cast(), path.as_str()) };671		if !res.error.is_empty() {672			bail!("failed to compute closure of {path}: {}", res.error);673		}674		Ok(res.outputs.into_iter().map(Utf8PathBuf::from).collect())675	}676677	#[instrument(skip(self, out))]678	pub fn nar_from_path(&self, path: &Utf8Path, out: &mut dyn std::io::Write) -> Result<()> {679		struct SinkState<'a> {680			out: &'a mut dyn std::io::Write,681			error: Option<std::io::Error>,682		}683		fn sink(state: usize, data: &[u8]) -> bool {684			let state = unsafe { &mut *(state as *mut SinkState) };685			match state.out.write_all(data) {686				Ok(()) => true,687				Err(e) => {688					state.error = Some(e);689					false690				}691			}692		}693		let mut state = SinkState { out, error: None };694		let msg = unsafe {695			nix_cxx::nar_from_path(696				self.as_ptr().cast(),697				path.as_str(),698				(&raw mut state) as usize,699				sink,700			)701		};702		if let Some(e) = state.error {703			return Err(anyhow!(e).context(format!("nar sink failed for {path}")));704		}705		if !msg.is_empty() {706			bail!("failed to dump nar of {path}: {msg}");707		}708		Ok(())709	}710711	#[instrument(skip(self))]712	pub fn build_drv_outputs(713		&self,714		drv_path: &Utf8Path,715		output_names: &[String],716	) -> Result<Vec<String>> {717		let joined = output_names.join("\n");718		let res =719			unsafe { nix_cxx::build_drv_outputs(self.as_ptr().cast(), drv_path.as_str(), &joined) };720		if !res.error.is_empty() {721			bail!("build of {drv_path} failed: {}", res.error);722		}723		Ok(res.outputs)724	}725726	#[instrument(skip(self))]727	pub fn store_dir(&self) -> Result<Utf8PathBuf> {728		let mut out = String::new();729		with_default_context(|c, es| unsafe {730			nix_raw::store_get_storedir(c, self.as_ptr(), Some(copy_nix_str), (&raw mut out).cast())731		})?;732		let p = Utf8PathBuf::from(out);733		assert!(p.is_absolute());734		Ok(p)735	}736737	fn as_ptr(&self) -> *mut c_store {738		self.0739	}740}741impl Drop for Store {742	fn drop(&mut self) {743		unsafe { store_free(self.0) }744	}745}746747#[repr(transparent)]748pub struct EvalState(*mut c_eval_state);749unsafe impl Send for EvalState {}750unsafe impl Sync for EvalState {}751752impl Drop for EvalState {753	fn drop(&mut self) {754		unsafe {755			state_free(self.0);756		}757	}758}759760pub struct FlakeReference(*mut flake_reference);761impl FlakeReference {762	#[instrument(name = "new-flake-reference", skip(flake, parse, fetch))]763	pub fn new(764		s: &str,765		flake: &FlakeSettings,766		parse: &FlakeReferenceParseFlags,767		fetch: &FetchSettings,768	) -> Result<(Self, String)> {769		let mut out = null_mut();770		let mut fragment = String::new();771		// let fetch_settings = fetcher_settings;772		with_default_context(|c, _| unsafe {773			flake_reference_and_fragment_from_string(774				c,775				fetch.0,776				flake.0,777				parse.0,778				s.as_ptr().cast(),779				s.len(),780				&mut out,781				Some(copy_nix_str),782				(&raw mut fragment).cast(),783			)784		})?;785		assert!(!out.is_null());786787		Ok((Self(out), fragment))788	}789	#[instrument(name = "lock-flake", skip(self, fetch, flake, lock))]790	pub fn lock(791		&mut self,792		fetch: &FetchSettings,793		flake: &FlakeSettings,794		lock: &FlakeLockFlags,795	) -> Result<LockedFlake> {796		with_default_context(|c, es| unsafe { flake_lock(c, fetch.0, flake.0, es, lock.0, self.0) })797			.map(LockedFlake)798	}799}800unsafe impl Send for FlakeReference {}801unsafe impl Sync for FlakeReference {}802803pub struct LockedFlake(*mut locked_flake);804impl LockedFlake {805	pub fn get_attrs(&self, settings: &mut FlakeSettings) -> Result<Value> {806		with_default_context(|c, es| unsafe {807			locked_flake_get_output_attrs(c, settings.0, es, self.0)808		})809		.map(Value)810	}811}812unsafe impl Send for LockedFlake {}813unsafe impl Sync for LockedFlake {}814impl Drop for LockedFlake {815	fn drop(&mut self) {816		unsafe {817			locked_flake_free(self.0);818		};819	}820}821822type FieldName = [u8; 64];823fn init_field_name(v: &str) -> FieldName {824	let mut f = [0; 64];825	assert!(v.len() < 64, "max field name is 63 chars");826	assert!(827		v.bytes().all(|v| v != 0),828		"nul bytes are unsupported in field name"829	);830	f[0..v.len()].copy_from_slice(v.as_bytes());831	f832}833834pub struct RealisedString(*mut realised_string);835impl fmt::Debug for RealisedString {836	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {837		self.as_str().fmt(f)838	}839}840841impl RealisedString {842	pub fn as_str(&self) -> &str {843		let len = unsafe { realised_string_get_buffer_size(self.0) };844		let data: *const u8 = unsafe { realised_string_get_buffer_start(self.0) }.cast();845		let data = unsafe { slice::from_raw_parts(data, len) };846		std::str::from_utf8(data).expect("non-utf8 strings not supported")847	}848	pub fn path_count(&self) -> usize {849		unsafe { realised_string_get_store_path_count(self.0) }850	}851	pub fn path(&self, i: usize) -> String {852		assert!(i < self.path_count());853		let path = unsafe { realised_string_get_store_path(self.0, i) };854		let mut err_out = String::new();855		unsafe { store_path_name(path, Some(copy_nix_str), (&raw mut err_out).cast()) };856		err_out857	}858}859860unsafe impl Send for RealisedString {}861impl Drop for RealisedString {862	fn drop(&mut self) {863		unsafe { realised_string_free(self.0) }864	}865}866867#[repr(transparent)]868pub struct Value(*mut value);869870unsafe impl Send for Value {}871unsafe impl Sync for Value {}872873pub trait AsFieldName {874	fn as_field_name<T>(&self, v: impl FnOnce(FieldName) -> Result<T>) -> Result<T>;875	fn to_field_name(&self) -> Result<String>;876}877impl AsFieldName for Value {878	fn as_field_name<T>(&self, v: impl FnOnce(FieldName) -> Result<T>) -> Result<T> {879		let f = self.to_string()?;880		v(init_field_name(&f))881	}882	fn to_field_name(&self) -> Result<String> {883		self.to_string()884	}885}886impl<E> AsFieldName for E887where888	E: AsRef<str>,889{890	fn as_field_name<T>(&self, v: impl FnOnce(FieldName) -> Result<T>) -> Result<T> {891		let f = self.as_ref();892		v(init_field_name(f))893	}894	fn to_field_name(&self) -> Result<String> {895		Ok(self.as_ref().to_owned())896	}897}898899struct AttrsBuilder(*mut c_bindings_builder);900impl AttrsBuilder {901	fn new(capacity: usize) -> Self {902		with_default_context(|c, es| unsafe { make_bindings_builder(c, es, capacity) })903			.map(Self)904			.expect("alloc should not fail")905	}906	fn insert(&mut self, k: &impl AsFieldName, v: Value) {907		k.as_field_name(|name| {908			with_default_context(|c, _| unsafe {909				bindings_builder_insert(c, self.0, name.as_ptr().cast(), v.0);910				// bindings_builder_insert doesn't do incref911			})912		})913		.expect("builder insert shouldn't fail");914	}915}916impl Drop for AttrsBuilder {917	fn drop(&mut self) {918		unsafe { bindings_builder_free(self.0) };919	}920}921922struct ListBuilder(*mut c_list_builder, c_uint);923impl ListBuilder {924	fn new(capacity: usize) -> Self {925		with_default_context(|c, es| unsafe { make_list_builder(c, es, capacity) })926			.map(|l| Self(l, 0))927			.expect("alloc should not fail")928	}929}930impl ListBuilder {931	fn push(&mut self, v: Value) {932		with_default_context(|c, _| unsafe {933			list_builder_insert(934				c,935				self.0,936				{937					let v = self.1;938					self.1 += 1;939					v940				},941				v.0,942			)943		})944		.expect("list insert shouldn't fail");945	}946}947impl Drop for ListBuilder {948	fn drop(&mut self) {949		unsafe { list_builder_free(self.0) };950	}951}952953impl Value {954	pub fn new_primop(v: NativeFn) -> Self {955		let out = Self::new_uninit();956		with_default_context(|c, _| unsafe { init_primop(c, out.0, v.0) })957			.expect("primop initialization should not fail");958		out959	}960	pub fn new_attrs(v: HashMap<&str, Value>) -> Self {961		let out = Self::new_uninit();962		let mut b = AttrsBuilder::new(v.len());963		for (k, v) in v {964			b.insert(&k, v);965		}966		with_default_context(|c, _| unsafe { make_attrs(c, out.0, b.0) })967			.expect("attrs initialization should not fail");968969		out970	}971	fn new_list<T: Into<Self>>(v: Vec<T>) -> Self {972		let out = Self::new_uninit();973		let mut b = ListBuilder::new(v.len());974		for v in v {975			b.push(v.into());976		}977		with_default_context(|c, _| unsafe { make_list(c, b.0, out.0) })978			.expect("list initialization should not fail");979980		out981	}982	fn new_uninit() -> Self {983		let out = with_default_context(|c, es| unsafe { alloc_value(c, es) })984			.expect("value allocation should not fail");985		Self(out)986	}987	pub fn new_str(v: &str) -> Self {988		let s = CString::new(v).expect("string should not contain NULs");989		let out = Self::new_uninit();990		// String is copied, `s` is free to be dropped991		with_default_context(|c, _| unsafe { init_string(c, out.0, s.as_ptr()) })992			.expect("string initialization should not fail");993		out994	}995	pub fn new_int(i: i64) -> Self {996		let out = Self::new_uninit();997		with_default_context(|c, _| unsafe { init_int(c, out.0, i) })998			.expect("int initialization should not fail");999		out1000	}1001	pub fn new_bool(v: bool) -> Self {1002		let out = Self::new_uninit();1003		with_default_context(|c, _| unsafe { init_bool(c, out.0, v) })1004			.expect("bool initialization should not fail");1005		out1006	}1007	// TODO: As far as I can see, there is no way to get Thunks from nix public C api, so this function is useless1008	// fn force(&mut self, st: &mut EvalState) -> Result<()> {1009	// 	with_default_context(|c, _| unsafe { value_force(c, st.0, self.0) })?;1010	// 	Ok(())1011	// }1012	pub fn type_of(&self) -> NixType {1013		let ty = with_default_context(|c, _| unsafe { get_type(c, self.0) })1014			.expect("get_type should not fail");1015		NixType::from_int(ty)1016	}1017	fn builtin_to_string(&self) -> Result<Self> {1018		let builtin = Self::eval("builtins.toString")?;1019		builtin.call(self.clone())1020	}1021	fn force(&mut self, s: *mut nix_raw::EvalState) -> Result<()> {1022		with_default_context(|c, _| unsafe { value_force(c, s, self.0) })?;1023		Ok(())1024	}1025	pub fn to_string(&self) -> Result<String> {1026		let ty = self.type_of();1027		if !matches!(ty, NixType::String) {1028			bail!("unexpected type: {ty:?}, expected string");1029		}1030		let mut str_out = String::new();1031		with_default_context(|c, _| unsafe {1032			get_string(c, self.0, Some(copy_nix_str), (&raw mut str_out).cast())1033		})?;10341035		Ok(str_out)1036	}1037	pub fn to_realised_string(&self) -> Result<RealisedString> {1038		with_default_context(|c, es| unsafe { string_realise(c, es, self.0, false) })1039			.map(RealisedString)10401041		// let store_paths = unsafe { nix_raw::realised_string_get_store_path_count(str) };1042		// for i in 0..store_paths {1043		// 	let store_path = unsafe { nix_raw::realised_string_get_store_path(str, i) };1044		// 	nix_raw::store_path_name(store_path, callback, user_data);1045		// }1046		// dbg!(store_paths);1047		// todo!();1048	}10491050	pub fn has_field(&self, field: &str) -> Result<bool> {1051		if !matches!(self.type_of(), NixType::Attrs) {1052			bail!("invalid type: expected attrs");1053		}10541055		let f = init_field_name(field);1056		with_default_context(|c, es| unsafe { has_attr_byname(c, self.0, es, f.as_ptr().cast()) })1057	}1058	// pub fn derivation_path(&self) {1059	// 	nix_raw::real1060	// }1061	pub fn list_fields(&self) -> Result<Vec<String>> {1062		if !matches!(self.type_of(), NixType::Attrs) {1063			bail!("invalid type: expected attrs");1064		}10651066		let len = with_default_context(|c, _| unsafe { get_attrs_size(c, self.0) })?;1067		let mut out = Vec::with_capacity(len as usize);10681069		for i in 0..len {1070			let name =1071				with_default_context(|c, es| unsafe { get_attr_name_byidx(c, self.0, es, i) })?;1072			let c = unsafe { CStr::from_ptr(name) };1073			out.push(c.to_str().expect("nix field names are utf-8").to_owned());1074		}1075		Ok(out)1076	}1077	pub fn get_elem(&self, v: usize) -> Result<Self> {1078		if !matches!(self.type_of(), NixType::List) {1079			bail!("invalid type: expected list");1080		}1081		let len = with_default_context(|c, _| unsafe { get_list_size(c, self.0) })? as usize;1082		if v >= len {1083			bail!("oob list get: {v} >= {len}");1084		}10851086		with_default_context(|c, es| unsafe { get_list_byidx(c, self.0, es, v as u32) }).map(Self)1087	}1088	pub fn attrs_update(self, other: Value /*, ignore_errors: bool*/) -> Result<Self> {1089		let attrs_update_fn = Self::eval("a: b: a // b")?;10901091		attrs_update_fn1092			.call(self)?1093			.call(other)1094			.context("attrs update")1095	}1096	pub fn get_field(&self, name: impl AsFieldName) -> Result<Self> {1097		if !matches!(self.type_of(), NixType::Attrs) {1098			bail!("invalid type: expected attrs");1099		}11001101		name.as_field_name(|name| {1102			with_default_context(|c, es| unsafe {1103				get_attr_byname(c, self.0, es, name.as_ptr().cast())1104			})1105			.map(Self)1106		})1107		.with_context(|| format!("getting field {:?}", name.to_field_name()))1108	}1109	pub fn call(&self, v: Value) -> Result<Self> {1110		let kind = self1111			.functor_kind()1112			.ok_or_else(|| anyhow!("can only call function or functor"))?;11131114		let function = match kind {1115			FunctorKind::Function => self.clone(),1116			FunctorKind::Functor => {1117				let f = self1118					.get_field("__functor")1119					.context("getting functor value")?;1120				assert_eq!(1121					f.type_of(),1122					NixType::Function,1123					"invalid functor encountered"1124				);1125				f1126			}1127		};11281129		let out = Value::new_uninit();1130		with_default_context(|c, es| unsafe { value_call(c, es, function.0, v.0, out.0) })?;11311132		Ok(out)1133	}1134	pub fn eval(v: &str) -> Result<Self> {1135		let s = CString::new(v).expect("expression shouldn't have internal NULs");1136		let out = Self::new_uninit();1137		with_default_context(|c, es| unsafe {1138			expr_eval_from_string(c, es, s.as_ptr(), c"/root".as_ptr(), out.0)1139		})?;1140		Ok(out)1141	}1142	#[instrument(name = "build", skip(self), fields(output))]1143	pub fn build(&self, output: &str) -> Result<Utf8PathBuf> {1144		if !self.is_derivation() {1145			bail!("expected derivation to build")1146		}1147		let output_name = self1148			.get_field("outputName")1149			.context("getting output name field")?1150			.to_string()?;1151		let v = if output_name != output {1152			let out = self.get_field(output).context("getting target output")?;1153			if !out.is_derivation() {1154				bail!("unknown output: {output}");1155			}1156			out1157		} else {1158			self.clone()1159		};11601161		let drv_path = Utf8PathBuf::from(1162			v.get_field("drvPath")1163				.context("getting drvPath")?1164				.to_string()?,1165		);1166		let graph = Arc::new(drv::DrvGraph::resolve(&eval_store(), &drv_path)?);1167		let _guard = logging::register_build_graph(&Span::current(), &graph);11681169		scheduler::build_graph_sync(graph.clone(), vec![output.to_owned()])?;11701171		let s = v.builtin_to_string()?;1172		let rs = s.to_realised_string()?;1173		let out_path = rs.as_str().to_owned();1174		Ok(Utf8PathBuf::from(out_path))1175	}1176	pub fn as_json<T: DeserializeOwned>(&self) -> Result<T> {1177		let to_json = Self::eval("builtins.toJSON")?;1178		let s = to_json.call(self.clone())?.to_string()?;1179		Ok(serde_json::from_str(&s)?)1180	}1181	pub fn serialized<T: Serialize>(v: &T) -> Result<Self> {1182		Self::eval(&nixlike::serialize(v)?)1183	}11841185	// Convert to string/evaluate derivations/etc1186	// fn to_string_weak(&self) -> Result<String> {1187	// 	// TODO: For now, it works exactly like to_string, see the comment for fn force()1188	// 	self.to_string()1189	// }11901191	fn is_derivation(&self) -> bool {1192		if !matches!(self.type_of(), NixType::Attrs) {1193			return false;1194		}1195		let Some(ty) = self.get_field("type").ok() else {1196			return false;1197		};1198		matches!(ty.to_string().as_deref(), Ok("derivation"))1199	}1200	fn functor_kind(&self) -> Option<FunctorKind> {1201		match self.type_of() {1202			NixType::Attrs => self1203				.has_field("__functor")1204				.expect("has_field shouldn't fail for attrs")1205				.then_some(FunctorKind::Functor),1206			NixType::Function => Some(FunctorKind::Function),1207			_ => None,1208		}1209	}1210	pub fn is_function(&self) -> bool {1211		self.functor_kind().is_some()1212	}1213	pub fn is_null(&self) -> bool {1214		matches!(self.type_of(), NixType::Null)1215	}1216	pub fn is_string(&self) -> bool {1217		matches!(self.type_of(), NixType::String)1218	}1219	pub fn is_attrs(&self) -> bool {1220		matches!(self.type_of(), NixType::Attrs)1221	}1222}12231224impl From<String> for Value {1225	fn from(value: String) -> Self {1226		Value::new_str(&value)1227	}1228}1229impl From<bool> for Value {1230	fn from(value: bool) -> Self {1231		Value::new_bool(value)1232	}1233}1234impl From<&str> for Value {1235	fn from(value: &str) -> Self {1236		Value::new_str(value)1237	}1238}1239impl<T> From<Vec<T>> for Value1240where1241	T: Into<Value>,1242{1243	fn from(value: Vec<T>) -> Self {1244		Value::new_list(value)1245	}1246}12471248impl Clone for Value {1249	fn clone(&self) -> Self {1250		with_default_context(|c, _| unsafe { value_incref(c, self.0) })1251			.expect("value incref should not fail");1252		Self(self.0)1253	}1254}1255impl Drop for Value {1256	fn drop(&mut self) {1257		with_default_context(|c, _| unsafe { value_decref(c, self.0) })1258			.expect("value drop should not fail");1259	}1260}12611262static TOKIO_FOR_NIX: OnceLock<Arc<tokio::runtime::Runtime>> = OnceLock::new();12631264pub fn init_libraries() {1265	unsafe { GC_allow_register_threads() };12661267	let mut ctx = NixContext::new();1268	ctx.run_in_context(|c| unsafe { libutil_init(c) })1269		.expect("util init should not fail");1270	ctx.run_in_context(|c| unsafe { libstore_init(c) })1271		.expect("store init should not fail");1272	ctx.run_in_context(|c| unsafe { libexpr_init(c) })1273		.expect("expr init should not fail");12741275	nix_logging_cxx::apply_tracing_logger();1276}12771278pub fn init_tokio_for_nix(tokio: Arc<tokio::runtime::Runtime>) {1279	TOKIO_FOR_NIX1280		.set(tokio)1281		.expect("tokio for nix should only be initialized once");1282}12831284pub fn await_in_nix<F: Send + 'static>(f: impl Future<Output = F> + Send + 'static) -> F {1285	// It should be possible to do Handle::current(), but some of the planned features don't work well with that1286	let runtime = TOKIO_FOR_NIX1287		.get()1288		.expect("init_tokio_for_nix was not called");1289	std::thread::spawn(move || runtime.block_on(f))1290		.join()1291		.expect("await_in_nix inner thread panicked")1292}12931294unsafe extern "C" fn nix_primop_closure_adapter<const N: usize>(1295	user_data: *mut c_void,1296	mut context: *mut c_context,1297	state: *mut nix_raw::EvalState,1298	args: *mut *mut value,1299	ret: *mut value,1300) {1301	let user_closure: &UserClosure<N> = unsafe { &*user_data.cast_const().cast() };1302	let args: [&Value; N] = array::from_fn(|i| {1303		let v: &mut Value = unsafe { &mut *args.add(i).cast() };1304		v as &Value1305	});1306	let ctx: &mut NixContext = unsafe { transmute(&mut context) };13071308	let state: &EvalState = unsafe { std::mem::transmute(&state) };13091310	match user_closure(state, args) {1311		Ok(v) => {1312			unsafe { copy_value(context, ret, v.0) };1313		}1314		Err(e) => {1315			ctx.set_err(e);1316		}1317	}1318}13191320type UserClosure<const N: usize> = Box<dyn Fn(&EvalState, [&Value; N]) -> Result<Value>>;13211322pub struct NativeFn(*mut PrimOp);1323impl NativeFn {1324	pub fn new<const N: usize>(1325		name: &'static CStr,1326		doc: &'static CStr,1327		args: [&'static CStr; N],1328		f: impl Fn(&EvalState, [&Value; N]) -> Result<Value> + 'static,1329	) -> Self {1330		// Double-boxing to make it thin pointer, as vtable gets outside of first Box1331		let closure: Box<UserClosure<N>> = Box::new(Box::new(f));1332		let f: PrimOpFun = Some(nix_primop_closure_adapter::<N>);1333		let mut args = args.into_iter().map(|v| v.as_ptr()).collect_vec();1334		args.push(null());1335		let args = args.as_mut_ptr();1336		let primop = unsafe {1337			alloc_primop(1338				null_mut(),1339				f,1340				N as i32,1341				name.as_ptr(),1342				args,1343				doc.as_ptr(),1344				Box::into_raw(closure).cast(),1345			)1346		};13471348		assert!(!primop.is_null(), "primop allocation should not fail");13491350		Self(primop)1351	}1352	pub fn register(self) {1353		unsafe { register_primop(null_mut(), self.0) };1354	}1355}13561357pub struct StorePath(*mut c_store_path);1358impl StorePath {1359	fn as_ptr(&self) -> *mut c_store_path {1360		self.01361	}1362}13631364impl Drop for StorePath {1365	fn drop(&mut self) {1366		unsafe { store_path_free(self.0) }1367	}1368}13691370#[test_log::test]1371fn test_native() -> Result<()> {1372	init_libraries();1373	NativeFn::new(1374		c"__uppercaseSuffix2",1375		c"make string uppercase and add suffix",1376		[c"str", c"suffix"],1377		|_, [str, suffix]: [&Value; 2]| {1378			let str = str.to_string()?;1379			let suffix = suffix.to_string()?;1380			Ok(Value::new_str(&format!("{}{suffix}", str.to_uppercase())))1381		},1382	)1383	.register();13841385	let mut fetch_settings = FetchSettings::new();1386	fetch_settings.set(c"warn-dirty", c"false");13871388	let manifest = format!("git+file://{}/../../", env!("CARGO_MANIFEST_DIR"));1389	let flake = FlakeSettings::new()?;1390	let parse = FlakeReferenceParseFlags::new(&flake)?;1391	let (mut r, _) = FlakeReference::new(&manifest, &flake, &parse, &fetch_settings)?;1392	let lock = FlakeLockFlags::new(&flake)?;1393	let locked = r.lock(&fetch_settings, &flake, &lock)?;1394	let attrs = locked.get_attrs(&mut FlakeSettings::new()?)?;13951396	let builtins = Value::eval("builtins")?;1397	assert_eq!(builtins.type_of(), NixType::Attrs);13981399	assert_eq!(attrs.type_of(), NixType::Attrs);1400	let test_data = nix_go!(attrs.testData);14011402	let test_string: String = nix_go_json!(test_data.testString);1403	assert_eq!(test_string, "hello");14041405	let s = nix_go!(attrs.packages["x86_64-linux"].fleet.drvPath);1406	let s = CString::new(s.to_string()?).expect("path str is cstring");14071408	let uppercase_suffix = Value::new_primop(NativeFn::new(1409		c"uppercase_suffix",1410		c"make string uppercase and add suffix",1411		[c"str", c"suffix"],1412		|es, [str, suffix]: [&Value; 2]| {1413			let str = str.to_string()?;1414			let suffix = suffix.to_string()?;1415			Ok(Value::new_str(&format!("{}{suffix}", str.to_uppercase())))1416		},1417	));14181419	let test_result: String = nix_go_json!(test_data.testPrimop(uppercase_suffix));1420	assert_eq!(test_result, "PREFIX_BODY_SUFFIX");1421	let test_result: String = nix_go_json!(builtins.uppercaseSuffix2("test")("suffix"));1422	assert_eq!(test_result, "TESTsuffix");14231424	let drv_path =1425		nix_go!(attrs.packages["x86_64-linux"]["fleet-install-secrets"].drvPath).to_string()?;1426	let graph = drv::DrvGraph::resolve(&drv_path)?;1427	eprintln!(1428		"fleet-install-secrets dependency graph: {} nodes",1429		graph.nodes.len()1430	);1431	for (path, node) in &graph.nodes {1432		if !node.input_drvs.is_empty() {1433			eprintln!("  {} ({} deps)", node.name, node.input_drvs.len());1434		}1435	}14361437	Ok(())1438}14391440// pub struct GcAlloc;1441// unsafe impl GlobalAlloc for GcAlloc {1442// 	unsafe fn alloc(&self, l: Layout) -> *mut u8 {1443// 		let ptr = unsafe { GC_malloc(l.size()) };1444// 		ptr.cast()1445// 	}1446// 	unsafe fn dealloc(&self, ptr: *mut u8, _: Layout) {1447// 		// unsafe { GC_free(ptr.cast()) };1448// 	}1449//1450// 	unsafe fn realloc(&self, ptr: *mut u8, _: Layout, new_size: usize) -> *mut u8 {1451// 		let ptr = unsafe { GC_realloc(ptr.cast(), new_size) };1452// 		ptr.cast()1453// 	}1454// }1455//1456// #[global_allocator]1457// static GC: GcAlloc = GcAlloc;