git.delta.rocks / jrsonnet / refs/commits / 521a65806d80

difftreelog

source

crates/nix-eval/src/lib.rs23.4 KiBsourcehistory
1//! This whole library should be replaced with either binding to nix libexpr,2//! or with tvix (once it is able to build NixOS).3//!4//! Current api is awful, little effort was put into this implementation.56use std::borrow::Cow;7use std::cell::RefCell;8use std::ffi::{CStr, CString, c_char, c_int, c_uint, c_void};9use std::fmt;10use std::ptr::null_mut;11use std::sync::LazyLock;12use std::{collections::HashMap, path::PathBuf};1314use anyhow::{Context, bail};15use serde::Serialize;16use serde::de::DeserializeOwned;1718pub use anyhow::Result;1920use self::logging::nix_logging_cxx;21use self::nix_cxx::set_fetcher_setting;22use self::nix_raw::{23	alloc_value, c_context, c_context_create, err_code, err_info_msg, eval_state_build,24	eval_state_builder_new, expr_eval_from_string, fetchers_settings, fetchers_settings_free,25	fetchers_settings_new, flake_lock, flake_lock_flags, flake_lock_flags_free,26	flake_lock_flags_new, flake_reference_parse_flags, flake_reference_parse_flags_free,27	flake_reference_parse_flags_new, flake_reference_parse_flags_set_base_directory,28	flake_settings, flake_settings_free, flake_settings_new, init_bool, init_int, init_string,29	locked_flake_free, locked_flake_get_output_attrs, set_err_msg, setting_set, state_free,30	value_decref, value_force, value_incref,31};3233mod value;34// Contains macros helpers35pub mod logging;36#[doc(hidden)]37pub mod macros;38pub mod util;3940#[allow(non_upper_case_globals, non_camel_case_types, non_snake_case)]41mod nix_raw {42	include!(concat!(env!("OUT_DIR"), "/bindings.rs"));43}44#[cxx::bridge]45pub mod nix_cxx {46	unsafe extern "C++" {47		type nix_fetchers_settings;48		include!("nix-eval/src/lib.hh");4950		unsafe fn set_fetcher_setting(51			settings: *mut nix_fetchers_settings,52			setting: *const c_char,53			value: *const c_char,54		);55	}56}5758#[derive(Debug, PartialEq, Eq)]59pub enum NixType {60	Thunk,61	Int,62	Float,63	Bool,64	String,65	Path,66	Null,67	Attrs,68	List,69	Function,70	External,71}72impl NixType {73	fn from_int(c: c_uint) -> Self {74		match c {75			0 => Self::Thunk,76			1 => Self::Int,77			2 => Self::Float,78			3 => Self::Bool,79			4 => Self::String,80			5 => Self::Path,81			6 => Self::Null,82			7 => Self::Attrs,83			8 => Self::List,84			9 => Self::Function,85			10 => Self::External,86			_ => unreachable!("unknown nix type: {c}"),87		}88	}89}9091#[derive(Debug)]92#[repr(i32)]93enum NixErrorKind {94	Unknown = 1,95	Overflow = 2,96	Key = 3,97	Generic = 4,98}99impl NixErrorKind {100	fn from_int(v: c_int) -> Option<Self> {101		Some(match v {102			0 => return None,103			-1 => Self::Unknown,104			-2 => Self::Overflow,105			-3 => Self::Key,106			-4 => Self::Generic,107			_ => {108				debug_assert!(false, "unexpected nix error kind: {v}");109				Self::Unknown110			}111		})112	}113}114115pub fn gc_register_my_thread() {116	assert_eq!(unsafe { nix_raw::GC_thread_is_registered() }, 0);117118	let mut sb = nix_raw::GC_stack_base {119		mem_base: null_mut(),120	};121	let r = unsafe { nix_raw::GC_get_stack_base(&mut sb) };122	if r as u32 != nix_raw::GC_SUCCESS {123		panic!("failed to get thread stack base");124	}125	unsafe { nix_raw::GC_register_my_thread(&sb) };126}127pub fn gc_unregister_my_thread() {128	assert_eq!(unsafe { nix_raw::GC_thread_is_registered() }, 1);129130	unsafe { nix_raw::GC_unregister_my_thread() };131}132133struct ThreadRegisterGuard {}134impl ThreadRegisterGuard {135	fn new() -> Self {136		gc_register_my_thread();137		Self {}138	}139}140impl Drop for ThreadRegisterGuard {141	fn drop(&mut self) {142		gc_unregister_my_thread();143	}144}145146struct NixContext(*mut c_context);147impl NixContext {148	fn set_err(&mut self, err: NixErrorKind, msg: &CStr) {149		unsafe { set_err_msg(self.0, err as c_int, msg.as_ptr()) };150	}151	fn new() -> Self {152		let ctx = unsafe { c_context_create() };153		Self(ctx)154	}155	fn error_kind(&self) -> Option<NixErrorKind> {156		let code = unsafe { err_code(self.0) };157		NixErrorKind::from_int(code)158	}159	fn error<'t>(&self) -> Option<Cow<'t, str>> {160		if let NixErrorKind::Generic = self.error_kind()? {161			let mut err_out = String::new();162			unsafe {163				err_info_msg(164					null_mut(),165					self.0,166					Some(copy_nix_str),167					(&raw mut err_out).cast(),168				)169			};170			return Some(Cow::Owned(err_out));171		};172173		// TODO: Can throw error (resulting in panic) if unable to retrieve error. Should be able to resolve by passing context as a first argument,174		// but it looks ugly175		let str = unsafe { nix_raw::err_msg(null_mut(), self.0, null_mut()) };176		Some(unsafe { CStr::from_ptr(str) }.to_string_lossy())177178		// TODO: There is also nix_err_info_msg, but I don't understand when it should be used179		// Some(match self.error_kind()? {180		// 	NixErrorKind::Generic => {181		// 	}182		// })183	}184	fn clean_err(&mut self) {185		unsafe {186			nix_raw::clear_err(self.0);187		}188	}189190	fn bail_if_error(&self) -> Result<()> {191		if let Some(err) = self.error() {192			bail!("{err}");193		};194		Ok(())195	}196197	fn run_in_context<T>(&mut self, f: impl FnOnce(*mut c_context) -> T) -> Result<T> {198		self.clean_err();199		let o = f(self.0);200		self.bail_if_error()?;201		self.clean_err();202		Ok(o)203	}204}205impl Drop for NixContext {206	fn drop(&mut self) {207		unsafe {208			nix_raw::c_context_free(self.0);209		}210	}211}212struct GlobalState {213	store: Store,214	state: EvalState,215}216impl GlobalState {217	fn new() -> Result<Self> {218		let mut ctx = NixContext::new();219		let store = ctx220			.run_in_context(|c| unsafe { nix_raw::store_open(c, c"daemon".as_ptr(), null_mut()) })221			.map(Store)?;222223		let builder = ctx.run_in_context(|c| unsafe { eval_state_builder_new(c, store.0) })?;224		ctx.run_in_context(|c| {225			unsafe {226				nix_raw::eval_state_builder_set_eval_setting(227					c,228					builder,229					c"lazy-trees".as_ptr(),230					c"true".as_ptr(),231				)232			}233			// eval_s234		})?;235		let state = ctx236			.run_in_context(|c| unsafe { eval_state_build(c, builder) })237			.map(EvalState)?;238239		Ok(Self { store, state })240	}241}242243struct ThreadState {244	ctx: NixContext,245}246impl ThreadState {247	fn new() -> Result<Self> {248		let ctx = NixContext::new();249250		Ok(Self { ctx })251	}252}253254static GLOBAL_STATE: LazyLock<GlobalState> =255	LazyLock::new(|| GlobalState::new().expect("global state init shouldn't fail"));256257thread_local! {258	static THREAD_STATE: RefCell<ThreadState> = RefCell::new(ThreadState::new().expect("thread state init shouldn't fail"));259}260fn with_default_context<T>(261	f: impl FnOnce(*mut c_context, *mut nix_raw::EvalState) -> T,262) -> Result<T> {263	let global = &GLOBAL_STATE.state;264	let (ctx, state) = THREAD_STATE.with_borrow_mut(|w| (w.ctx.0, global.0));265	let mut ctx = NixContext(ctx);266	let v = ctx.run_in_context(|c| f(c, state));267	// It is reused for thread268	std::mem::forget(ctx);269	v270}271272fn set_setting(s: &CStr, v: &CStr) -> Result<()> {273	with_default_context(|c, _| unsafe { setting_set(c, s.as_ptr(), v.as_ptr()) }).map(|_| ())274}275276pub struct FetchSettings(*mut fetchers_settings);277impl FetchSettings {278	pub fn new() -> Self {279		Self::try_new().expect("allocation should not fail")280	}281	fn try_new() -> Result<Self> {282		with_default_context(|c, _| unsafe { fetchers_settings_new(c) }).map(Self)283	}284	pub fn set(&mut self, setting: &CStr, value: &CStr) {285		unsafe {286			set_fetcher_setting(self.0.cast(), setting.as_ptr(), value.as_ptr());287		};288	}289}290unsafe impl Send for FetchSettings {}291unsafe impl Sync for FetchSettings {}292impl Drop for FetchSettings {293	fn drop(&mut self) {294		unsafe { fetchers_settings_free(self.0) };295	}296}297pub struct FlakeSettings(*mut flake_settings);298impl FlakeSettings {299	pub fn new() -> Result<Self> {300		with_default_context(|c, _| unsafe { flake_settings_new(c) }).map(Self)301	}302}303unsafe impl Send for FlakeSettings {}304unsafe impl Sync for FlakeSettings {}305impl Drop for FlakeSettings {306	fn drop(&mut self) {307		unsafe {308			flake_settings_free(self.0);309		}310	}311}312313struct FlakeReferenceParseFlags(*mut flake_reference_parse_flags);314impl FlakeReferenceParseFlags {315	fn new(settings: &mut FlakeSettings) -> Result<Self> {316		with_default_context(|c, _| unsafe { flake_reference_parse_flags_new(c, settings.0) })317			.map(Self)318	}319	fn set_base_dir(&mut self, dir: &str) -> Result<()> {320		with_default_context(|c, _| {321			unsafe {322				flake_reference_parse_flags_set_base_directory(323					c,324					self.0,325					dir.as_ptr().cast(),326					dir.len(),327				)328			};329		})330	}331}332impl Drop for FlakeReferenceParseFlags {333	fn drop(&mut self) {334		unsafe {335			flake_reference_parse_flags_free(self.0);336		}337	}338}339struct FlakeLockFlags(*mut flake_lock_flags);340impl FlakeLockFlags {341	fn new(settings: &mut FlakeSettings) -> Result<Self> {342		with_default_context(|c, _| unsafe { flake_lock_flags_new(c, settings.0) }).map(Self)343	}344}345impl Drop for FlakeLockFlags {346	fn drop(&mut self) {347		unsafe {348			flake_lock_flags_free(self.0);349		}350	}351}352353unsafe extern "C" fn copy_nix_str(start: *const c_char, n: c_uint, user_data: *mut c_void) {354	let s = unsafe { std::slice::from_raw_parts(start.cast::<u8>(), n as usize) };355	let s = std::str::from_utf8(s).expect("c string has invalid utf-8");356	unsafe { *user_data.cast::<String>() = s.to_owned() };357}358359struct Store(*mut nix_raw::Store);360unsafe impl Send for Store {}361unsafe impl Sync for Store {}362363struct EvalState(*mut nix_raw::EvalState);364impl EvalState {365	// TODO: store ownership366	fn new_raw(store: *mut nix_raw::Store) -> Result<Self> {367		let builder =368			with_default_context(|c, _| unsafe { nix_raw::eval_state_builder_new(c, store) })?;369370		with_default_context(|c, _| unsafe { eval_state_build(c, builder) }).map(Self)371372		// with_default_context(|c| state_create(c))373	}374}375unsafe impl Send for EvalState {}376unsafe impl Sync for EvalState {}377impl Drop for EvalState {378	fn drop(&mut self) {379		unsafe {380			state_free(self.0);381		}382	}383}384385pub struct FlakeReference(*mut nix_raw::flake_reference);386impl FlakeReference {387	pub fn new(s: &str, fetch: &FetchSettings) -> Result<(Self, String)> {388		let mut flake_settings = FlakeSettings::new()?;389		let mut parse_flags = FlakeReferenceParseFlags::new(&mut flake_settings)?;390391		// parse_flags.set_base_dir("/home/lach/build/fleet")?;392393		let mut out = null_mut();394		let mut fragment = String::new();395		// let fetch_settings = fetcher_settings;396		with_default_context(|c, _| unsafe {397			nix_raw::flake_reference_and_fragment_from_string(398				c,399				fetch.0,400				flake_settings.0,401				parse_flags.0,402				s.as_ptr().cast(),403				s.len(),404				&mut out,405				Some(copy_nix_str),406				(&raw mut fragment).cast(),407			)408		})?;409		assert!(!out.is_null());410411		Ok((Self(out), fragment))412	}413	pub fn lock(&mut self, fetch: &FetchSettings) -> Result<LockedFlake> {414		let mut settings = FlakeSettings::new()?;415		let lock_flags = FlakeLockFlags::new(&mut settings)?;416		with_default_context(|c, es| unsafe {417			flake_lock(c, fetch.0, settings.0, es, lock_flags.0, self.0)418		})419		.map(LockedFlake)420	}421}422unsafe impl Send for FlakeReference {}423unsafe impl Sync for FlakeReference {}424425pub struct LockedFlake(*mut nix_raw::locked_flake);426impl LockedFlake {427	pub fn get_attrs(&self, settings: &mut FlakeSettings) -> Result<Value> {428		with_default_context(|c, es| unsafe {429			locked_flake_get_output_attrs(c, settings.0, es, self.0)430		})431		.map(Value)432	}433}434unsafe impl Send for LockedFlake {}435unsafe impl Sync for LockedFlake {}436impl Drop for LockedFlake {437	fn drop(&mut self) {438		unsafe {439			locked_flake_free(self.0);440		};441	}442}443444type FieldName = [u8; 32];445fn init_field_name(v: &str) -> FieldName {446	let mut f = [0; 32];447	assert!(v.len() < 32, "max field name is 31 char");448	assert!(449		v.bytes().all(|v| v != 0),450		"nul bytes are unsupported in field name"451	);452	f[0..v.len()].copy_from_slice(v.as_bytes());453	f454}455456pub struct RealisedString(*mut nix_raw::realised_string);457impl fmt::Debug for RealisedString {458	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {459		self.as_str().fmt(f)460	}461}462463impl RealisedString {464	fn as_str(&self) -> &str {465		let len = unsafe { nix_raw::realised_string_get_buffer_size(self.0) };466		let data: *const u8 = unsafe { nix_raw::realised_string_get_buffer_start(self.0) }.cast();467		let data = unsafe { std::slice::from_raw_parts(data, len) };468		std::str::from_utf8(data).expect("non-utf8 strings not supported")469	}470	fn path_count(&self) -> usize {471		unsafe { nix_raw::realised_string_get_store_path_count(self.0) }472	}473	fn path(&self, i: usize) -> String {474		assert!(i < self.path_count());475		let path = unsafe { nix_raw::realised_string_get_store_path(self.0, i) };476		let mut err_out = String::new();477		unsafe { nix_raw::store_path_name(path, Some(copy_nix_str), (&raw mut err_out).cast()) };478		err_out479	}480}481482unsafe impl Send for RealisedString {}483impl Drop for RealisedString {484	fn drop(&mut self) {485		with_default_context(|c, _| unsafe { nix_raw::realised_string_free(self.0) })486			.expect("string free should not fail")487	}488}489490pub struct Value(*mut nix_raw::value);491492unsafe impl Send for Value {}493unsafe impl Sync for Value {}494495pub trait AsFieldName {496	fn as_field_name<T>(&self, v: impl FnOnce(FieldName) -> Result<T>) -> Result<T>;497	fn to_field_name(&self) -> Result<String>;498}499impl AsFieldName for Value {500	fn as_field_name<T>(&self, v: impl FnOnce(FieldName) -> Result<T>) -> Result<T> {501		let f = self.to_string()?;502		v(init_field_name(&f))503	}504	fn to_field_name(&self) -> Result<String> {505		self.to_string()506	}507}508impl<E> AsFieldName for E509where510	E: AsRef<str>,511{512	fn as_field_name<T>(&self, v: impl FnOnce(FieldName) -> Result<T>) -> Result<T> {513		let f = self.as_ref();514		v(init_field_name(f))515	}516	fn to_field_name(&self) -> Result<String> {517		Ok(self.as_ref().to_owned())518	}519}520521struct AttrsBuilder(*mut nix_raw::BindingsBuilder);522impl AttrsBuilder {523	fn new(capacity: usize) -> Self {524		with_default_context(|c, es| unsafe { nix_raw::make_bindings_builder(c, es, capacity) })525			.map(Self)526			.expect("alloc should not fail")527	}528	fn insert(&mut self, k: &impl AsFieldName, v: Value) {529		k.as_field_name(|name| {530			with_default_context(|c, _| unsafe {531				nix_raw::bindings_builder_insert(c, self.0, name.as_ptr().cast(), v.0)532			})533		})534		.expect("builder insert shouldn't fail");535	}536}537impl Drop for AttrsBuilder {538	fn drop(&mut self) {539		unsafe { nix_raw::bindings_builder_free(self.0) };540	}541}542543impl Value {544	pub fn new_attrs(v: HashMap<&str, Value>) -> Result<Self> {545		let out = Self::new_uninit()?;546		let mut b = AttrsBuilder::new(v.len());547		for (k, v) in v {548			b.insert(&k, v);549		}550		with_default_context(|c, _| unsafe { nix_raw::make_attrs(c, out.0, b.0) })?;551		Ok(out)552	}553	fn new_list<T: Into<Self>>(v: Vec<T>) -> Result<Self> {554		todo!()555	}556	fn new_uninit() -> Result<Self> {557		let out = with_default_context(|c, es| unsafe { alloc_value(c, es) })?;558		Ok(Self(out))559	}560	fn new_str(v: &str) -> Result<Self> {561		let s = CString::new(v).expect("string should not contain NULs");562		let uninit = Self::new_uninit()?;563		// String is copied, `s` is free to be dropped564		with_default_context(|c, _| unsafe { init_string(c, uninit.0, s.as_ptr()) })?;565		Ok(uninit)566	}567	fn new_int(i: i64) -> Result<Self> {568		let uninit = Self::new_uninit()?;569		with_default_context(|c, _| unsafe { init_int(c, uninit.0, i) })?;570		Ok(uninit)571	}572	fn new_bool(v: bool) -> Result<Self> {573		let uninit = Self::new_uninit()?;574		with_default_context(|c, _| unsafe { init_bool(c, uninit.0, v) })?;575		Ok(uninit)576	}577	fn force(&mut self, st: &mut EvalState) -> Result<()> {578		with_default_context(|c, _| unsafe { value_force(c, st.0, self.0) })?;579		Ok(())580	}581	pub fn type_of(&self) -> Result<NixType> {582		let ty = with_default_context(|c, _| unsafe { nix_raw::get_type(c, self.0) })?;583		Ok(NixType::from_int(ty))584	}585	pub fn to_string(&self) -> Result<String> {586		Ok(self.to_realised_string()?.as_str().to_owned())587	}588	pub fn to_realised_string(&self) -> Result<RealisedString> {589		with_default_context(|c, es| unsafe { nix_raw::string_realise(c, es, self.0, false) })590			.map(RealisedString)591592		// let store_paths = unsafe { nix_raw::realised_string_get_store_path_count(str) };593		// for i in 0..store_paths {594		// 	let store_path = unsafe { nix_raw::realised_string_get_store_path(str, i) };595		// 	nix_raw::store_path_name(store_path, callback, user_data);596		// }597		// dbg!(store_paths);598		// todo!();599	}600601	pub fn has_field(&self, field: &str) -> Result<bool> {602		let f = init_field_name(field);603		with_default_context(|c, es| unsafe {604			nix_raw::has_attr_byname(c, self.0, es, f.as_ptr().cast())605		})606	}607	// pub fn derivation_path(&self) {608	// 	nix_raw::real609	// }610	pub fn list_fields(&self) -> Result<Vec<String>> {611		if !matches!(self.type_of()?, NixType::Attrs) {612			bail!("invalid type: expected attrs");613		}614615		let len = with_default_context(|c, _| unsafe { nix_raw::get_attrs_size(c, self.0) })?;616		let mut out = Vec::with_capacity(len as usize);617618		for i in 0..len {619			let name = with_default_context(|c, es| unsafe {620				nix_raw::get_attr_name_byidx(c, self.0, es, i)621			})?;622			let c = unsafe { CStr::from_ptr(name) };623			out.push(c.to_str().expect("nix field names are utf-8").to_owned());624		}625		Ok(out)626	}627	pub fn get_elem(&self, v: usize) -> Result<Self> {628		if !matches!(self.type_of()?, NixType::List) {629			bail!("invalid type: expected list");630		}631		let len =632			with_default_context(|c, _| unsafe { nix_raw::get_list_size(c, self.0) })? as usize;633		if v >= len {634			bail!("oob list get: {v} >= {len}");635		}636637		with_default_context(|c, es| unsafe { nix_raw::get_list_byidx(c, self.0, es, v as u32) })638			.map(Self)639	}640	pub fn attrs_update(self, other: Value) -> Result<Self> {641		let a_fields = self.list_fields()?;642		let b_fields = other.list_fields()?;643		match (a_fields.len(), b_fields.len()) {644			(_, 0) => return Ok(self),645			(0, _) => return Ok(other),646			_ => {}647		}648		let mut out = HashMap::new();649		for f in a_fields.iter() {650			if b_fields.contains(f) {651				break;652			}653			out.insert(f.as_str(), self.get_field(f)?);654		}655		if out.is_empty() {656			// All fields from lhs are overriden by rhs657			return Ok(other);658		}659		for f in b_fields.iter() {660			out.insert(f.as_str(), other.get_field(f)?);661		}662		Self::new_attrs(out)663	}664	pub fn get_field(&self, name: impl AsFieldName) -> Result<Self> {665		if !matches!(self.type_of()?, NixType::Attrs) {666			bail!("invalid type: expected attrs");667		}668669		name.as_field_name(|name| {670			with_default_context(|c, es| unsafe {671				nix_raw::get_attr_byname(c, self.0, es, name.as_ptr().cast())672			})673			.map(Self)674		})675		.with_context(|| format!("getting field {:?}", name.to_field_name()))676	}677	pub fn call(&self, v: Value) -> Result<Self> {678		if !matches!(self.type_of()?, NixType::Function) {679			// TODO: Functors680			bail!("invalid type: expected function");681		}682683		let out = Value::new_uninit()?;684		with_default_context(|c, es| unsafe { nix_raw::value_call(c, es, self.0, v.0, out.0) })?;685686		Ok(out)687	}688	pub fn eval(v: &str) -> Result<Self> {689		let s = CString::new(v).expect("expression shouldn't have internal NULs");690		let out = Self::new_uninit()?;691		with_default_context(|c, es| unsafe {692			expr_eval_from_string(c, es, s.as_ptr(), c"/homeless-shelter".as_ptr(), out.0)693		})?;694		Ok(out)695	}696	pub async fn build(&self, output: &str) -> Result<PathBuf> {697		if !self.is_derivation() {698			bail!("expected derivation to build")699		}700		let output_name = self.get_field("outputName")?.to_string()?;701		let v = if output_name != output {702			let out = self.get_field(output)?;703			if !out.is_derivation() {704				bail!("unknown output: {output}");705			}706			out707		} else {708			self.clone()709		};710		// to_string here blocks until the path is built711		let drv_path = tokio::task::spawn_blocking(move || v.get_field("outPath")?.to_string())712			.await713			.expect("should not fail")?;714		Ok(PathBuf::from(drv_path))715	}716	pub fn as_json<T: DeserializeOwned>(&self) -> Result<T> {717		let to_json = Self::eval("builtins.toJSON")?;718		let s = to_json.call(self.clone())?.to_string()?;719		Ok(serde_json::from_str(&s)?)720	}721	pub fn serialized<T: Serialize>(v: &T) -> Result<Self> {722		Self::eval(&nixlike::serialize(v)?)723	}724725	// Convert to string/evaluate derivations/etc726	fn to_string_weak(&self) -> Result<String> {727		// TODO728		self.to_string()729	}730731	fn is_derivation(&self) -> bool {732		if !matches!(self.type_of(), Ok(NixType::Attrs)) {733			return false;734		}735		let Some(ty) = self.get_field("type").ok() else {736			return false;737		};738		matches!(ty.to_string().as_deref(), Ok("derivation"))739	}740}741742impl From<String> for Value {743	fn from(value: String) -> Self {744		Value::new_str(&value).expect("todo: TryFrom")745	}746}747impl From<bool> for Value {748	fn from(value: bool) -> Self {749		Value::new_bool(value).expect("todo: TryFrom")750	}751}752impl From<&str> for Value {753	fn from(value: &str) -> Self {754		Value::new_str(&value).expect("todo: TryFrom")755	}756}757impl<T> From<Vec<T>> for Value758where759	T: Into<Value>,760{761	fn from(value: Vec<T>) -> Self {762		Value::new_list(value).expect("todo: TryFrom")763	}764}765766impl Clone for Value {767	fn clone(&self) -> Self {768		with_default_context(|c, _| unsafe { value_incref(c, self.0) })769			.expect("value incref should not fail");770		Self(self.0)771	}772}773impl Drop for Value {774	fn drop(&mut self) {775		with_default_context(|c, _| unsafe { value_decref(c, self.0) })776			.expect("value drop should not fail");777	}778}779780pub fn init_libraries() {781	unsafe { nix_raw::GC_allow_register_threads() };782783	let mut ctx = NixContext::new();784	ctx.run_in_context(|c| unsafe { nix_raw::libutil_init(c) })785		.expect("util init should not fail");786	ctx.run_in_context(|c| unsafe { nix_raw::libstore_init(c) })787		.expect("store init should not fail");788	ctx.run_in_context(|c| unsafe { nix_raw::libexpr_init(c) })789		.expect("expr init should not fail");790791	nix_logging_cxx::apply_tracing_logger();792}793794#[test_log::test]795fn test_native() -> Result<()> {796	let mut fetch_settings = FetchSettings::new();797	fetch_settings.set(c"warn-dirty", c"false");798	//799800	let (mut r, _) = FlakeReference::new("/home/lach/build/fleet", &fetch_settings)?;801	let locked = r.lock(&fetch_settings)?;802	let attrs = locked.get_attrs(&mut FlakeSettings::new()?)?;803804	let builtins = Value::eval("builtins")?;805	dbg!(builtins.type_of()?);806807	dbg!(attrs.type_of()?);808	dbg!(attrs.list_fields()?);809	dbg!(810		attrs811			.get_field("packages")?812			.get_field("x86_64-linux")?813			.get_field("fleet")?814			.get_field("outPath")?815			.to_string()816	);817818	Ok(())819}820821// struct NixBuildTask(Value, oneshot::Sender<Result<HashMap<String, PathBuf>>>);822//823// #[derive(Clone)]824// pub struct NixBuildBatch {825// 	tx: mpsc::UnboundedSender<NixBuildTask>,826// }827//828// #[instrument(skip(values))]829// async fn build_multiple(name: String, values: Vec<Value>) -> Result<()> {830// 	let builtins = Value::eval("builtins")?;831// 	let drv = nix_go!(builtins.derivation(Obj {832// 		// FIXME: pass system from localSystem or fleet args833// 		// system,834// 		name,835// 		builder: "/bin/sh",836// 		// we want nothing from this derivation, it is only used to perform multiple builds at once.837// 		args: vec!["-c", "echo > $out"],838// 		preferLocalBuild: true,839// 		allowSubstitutes: false,840// 		buildInputs: values,841// 	}));842// 	drv.build()?;843// 	Ok(())844// }845//846// impl NixBuildBatch {847// 	fn new(name: String) -> Self {848// 		let (tx, mut rx) = mpsc::unbounded_channel::<NixBuildTask>();849//850// 		tokio::task::spawn(async move {851// 			let mut deps = vec![];852// 			let mut build_data = vec![];853// 			while let Some(task) = rx.recv().await {854// 				build_data.push(task.0.clone());855// 				deps.push(task);856// 			}857// 			if deps.is_empty() {858// 				return;859// 			}860// 			match build_multiple(name, build_data).await {861// 				Ok(_) => {862// 					for NixBuildTask(v, o) in deps {863// 						let _ = o.send(v.build());864// 					}865// 				}866// 				Err(e) => {867// 					for NixBuildTask(v, o) in deps {868// 						let s = v.to_string_weak();869// 						let s = match s {870// 							Ok(s) => s,871// 							Err(e) => {872// 								let _ = o.send(Err(e));873// 								continue;874// 							}875// 						};876// 						if PathBuf::from(s).exists() {877// 							let _ = o.send(v.build());878// 						} else {879// 							let _ = o.send(Err(e.clone()));880// 						}881// 					}882// 				}883// 			};884// 		});885// 		Self { tx }886// 	}887// 	pub async fn submit(self, task: Value) -> Result<HashMap<String, PathBuf>> {888// 		let Self { tx: task_tx } = self;889// 		let (tx, rx) = oneshot::channel();890// 		let _ = task_tx.send(NixBuildTask(task, tx));891// 		drop(task_tx);892// 		rx.await.expect("shoudn't be cancelled here")893// 	}894// }