difftreelog
refactor nix-eval macro-support
in: trunk
3 files changed
cmds/repl-plugin-unstable/src/lib.rsdiffbeforeafterboth--- a/cmds/repl-plugin-unstable/src/lib.rs
+++ b/cmds/repl-plugin-unstable/src/lib.rs
@@ -1,5 +1,6 @@
use fleet_base::primops::init_primops;
+/// SAFETY: expected plugin dynamic library entry point
#[unsafe(no_mangle)]
fn nix_plugin_entry() {
init_primops();
crates/nix-eval/src/lib.rsdiffbeforeafterboth1use 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;49pub mod util;5051#[allow(52 non_upper_case_globals,53 non_camel_case_types,54 non_snake_case,55 dead_code56)]57mod nix_raw {58 include!(concat!(env!("OUT_DIR"), "/bindings.rs"));59}60#[cxx::bridge]61pub mod nix_cxx {62 unsafe extern "C++" {63 type nix_fetchers_settings;64 include!("nix-eval/src/lib.hh");6566 #[allow(clippy::missing_safety_doc)]67 unsafe fn set_fetcher_setting(68 settings: *mut nix_fetchers_settings,69 setting: *const c_char,70 value: *const c_char,71 );72 }73}7475#[derive(Debug, PartialEq, Eq)]76pub enum NixType {77 Thunk,78 Int,79 Float,80 Bool,81 String,82 Path,83 Null,84 Attrs,85 List,86 Function,87 External,88}89impl NixType {90 fn from_int(c: c_uint) -> Self {91 match c {92 0 => Self::Thunk,93 1 => Self::Int,94 2 => Self::Float,95 3 => Self::Bool,96 4 => Self::String,97 5 => Self::Path,98 6 => Self::Null,99 7 => Self::Attrs,100 8 => Self::List,101 9 => Self::Function,102 10 => Self::External,103 _ => unreachable!("unknown nix type: {c}"),104 }105 }106}107108enum FunctorKind {109 Function,110 Functor,111}112113#[derive(Debug)]114#[repr(i32)]115pub enum NixErrorKind {116 Unknown = err_NIX_ERR_UNKNOWN,117 Overflow = err_NIX_ERR_OVERFLOW,118 Key = err_NIX_ERR_KEY,119 Generic = err_NIX_ERR_NIX_ERROR,120}121impl NixErrorKind {122 fn from_int(v: c_int) -> Option<Self> {123 Some(match v {124 0 => return None,125 nix_raw::err_NIX_ERR_UNKNOWN => Self::Unknown,126 nix_raw::err_NIX_ERR_OVERFLOW => Self::Overflow,127 nix_raw::err_NIX_ERR_KEY => Self::Key,128 nix_raw::err_NIX_ERR_NIX_ERROR => Self::Generic,129 _ => {130 debug_assert!(false, "unexpected nix error kind: {v}");131 Self::Unknown132 }133 })134 }135}136137pub fn gc_now() {138 unsafe { gc_now_raw() };139}140141pub fn gc_register_my_thread() {142 assert_eq!(unsafe { GC_thread_is_registered() }, 0);143144 let mut sb = GC_stack_base {145 mem_base: null_mut(),146 };147 let r = unsafe { GC_get_stack_base(&mut sb) };148 if r as u32 != GC_SUCCESS {149 panic!("failed to get thread stack base");150 }151 unsafe { GC_register_my_thread(&sb) };152}153pub fn gc_unregister_my_thread() {154 assert_eq!(unsafe { GC_thread_is_registered() }, 1);155156 unsafe { GC_unregister_my_thread() };157}158159pub struct ThreadRegisterGuard {}160impl ThreadRegisterGuard {161 #[allow(clippy::new_without_default)]162 pub fn new() -> Self {163 gc_register_my_thread();164 Self {}165 }166}167impl Drop for ThreadRegisterGuard {168 fn drop(&mut self) {169 gc_unregister_my_thread();170 }171}172173#[repr(transparent)]174pub struct NixContext(*mut c_context);175impl NixContext {176 pub fn set_err_raw(&mut self, err: NixErrorKind, msg: &CStr) {177 unsafe { set_err_msg(self.0, err as c_int, msg.as_ptr()) };178 }179 pub fn set_err(&mut self, err: anyhow::Error) {180 let mut fmt = format!("{err:?}").replace("\0", "\\0");181 self.set_err_raw(182 NixErrorKind::Generic,183 &CString::new(fmt).expect("NUL bytes were just replaced"),184 );185 }186 pub fn new() -> Self {187 let ctx = unsafe { c_context_create() };188 Self(ctx)189 }190 fn error_kind(&self) -> Option<NixErrorKind> {191 let code = unsafe { err_code(self.0) };192 NixErrorKind::from_int(code)193 }194 fn error<'t>(&self) -> Option<(Cow<'t, str>, Option<Box<ErrorInfoBuilder>>)> {195 if let NixErrorKind::Generic = self.error_kind()? {196 let ei = unsafe { logging::nix_logging_cxx::extract_error_info(self.0) };197 let mut err_out = String::new();198 unsafe {199 err_info_msg(200 null_mut(),201 self.0,202 Some(copy_nix_str),203 (&raw mut err_out).cast(),204 )205 };206 return Some((Cow::Owned(err_out), Some(ei)));207 };208209 // TODO: Can throw error (resulting in panic) if unable to retrieve error. Should be able to resolve by passing context as a first argument,210 // but it looks ugly211 let str = unsafe { err_msg(null_mut(), self.0, null_mut()) };212 Some((unsafe { CStr::from_ptr(str) }.to_string_lossy(), None))213 }214 fn clean_err(&mut self) {215 unsafe {216 clear_err(self.0);217 }218 }219220 fn bail_if_error(&self) -> Result<()> {221 if let Some((err, stack)) = self.error() {222 let mut e = Err(anyhow!("{err}"));223 if let Some(stack) = stack {224 for ele in stack.stack_frames {225 e = e.with_context(|| {226 if ele.pos.is_empty() {227 ele.msg228 } else {229 format!("{} at {}", ele.msg, ele.pos)230 }231 })232 }233 }234 return e.context("<nix frames>");235 };236 Ok(())237 }238239 fn run_in_context<T>(&mut self, f: impl FnOnce(*mut c_context) -> T) -> Result<T> {240 self.clean_err();241 let o = f(self.0);242 self.bail_if_error()?;243 self.clean_err();244 Ok(o)245 }246}247248impl Default for NixContext {249 fn default() -> Self {250 Self::new()251 }252}253impl Drop for NixContext {254 fn drop(&mut self) {255 unsafe {256 c_context_free(self.0);257 }258 }259}260struct GlobalState {261 // Store should be valid as long as EvalState is valid262 #[allow(dead_code)]263 store: Store,264 state: EvalState,265}266impl GlobalState {267 fn new() -> Result<Self> {268 let mut ctx = NixContext::new();269 let store = ctx270 .run_in_context(|c| unsafe { store_open(c, c"auto".as_ptr(), null_mut()) })271 .map(Store)?;272273 let builder = ctx.run_in_context(|c| unsafe { eval_state_builder_new(c, store.0) })?;274 ctx.run_in_context(|c| unsafe { eval_state_builder_load(c, builder) })?;275 ctx.run_in_context(|c| unsafe {276 eval_state_builder_set_eval_setting(277 c,278 builder,279 c"lazy-trees".as_ptr(),280 c"true".as_ptr(),281 )282 })?;283 ctx.run_in_context(|c| unsafe {284 eval_state_builder_set_eval_setting(285 c,286 builder,287 c"lazy-locks".as_ptr(),288 c"true".as_ptr(),289 )290 })?;291 let state = ctx292 .run_in_context(|c| unsafe { eval_state_build(c, builder) })293 .map(EvalState)?;294295 Ok(Self { store, state })296 }297}298299struct ThreadState {300 ctx: NixContext,301}302impl ThreadState {303 fn new() -> Result<Self> {304 let ctx = NixContext::new();305306 Ok(Self { ctx })307 }308}309310static GLOBAL_STATE: LazyLock<GlobalState> =311 LazyLock::new(|| GlobalState::new().expect("global state init shouldn't fail"));312313thread_local! {314 static THREAD_STATE: RefCell<ThreadState> = RefCell::new(ThreadState::new().expect("thread state init shouldn't fail"));315}316fn with_default_context<T>(f: impl FnOnce(*mut c_context, *mut c_eval_state) -> T) -> Result<T> {317 let global = &GLOBAL_STATE.state;318 let (ctx, state) = THREAD_STATE.with_borrow_mut(|w| (w.ctx.0, global.0));319 let mut ctx = NixContext(ctx);320 let v = ctx.run_in_context(|c| f(c, state));321 // It is reused for thread322 std::mem::forget(ctx);323 v324}325326pub fn set_setting(s: &CStr, v: &CStr) -> Result<()> {327 with_default_context(|c, _| unsafe { setting_set(c, s.as_ptr(), v.as_ptr()) }).map(|_| ())328}329330pub struct FetchSettings(*mut fetchers_settings);331impl FetchSettings {332 pub fn new() -> Self {333 Self::try_new().expect("allocation should not fail")334 }335 fn try_new() -> Result<Self> {336 with_default_context(|c, _| unsafe { fetchers_settings_new(c) }).map(Self)337 }338 pub fn set(&mut self, setting: &CStr, value: &CStr) {339 unsafe {340 set_fetcher_setting(self.0.cast(), setting.as_ptr(), value.as_ptr());341 };342 }343}344unsafe impl Send for FetchSettings {}345unsafe impl Sync for FetchSettings {}346347impl Default for FetchSettings {348 fn default() -> Self {349 Self::new()350 }351}352353impl Drop for FetchSettings {354 fn drop(&mut self) {355 unsafe { fetchers_settings_free(self.0) };356 }357}358pub struct FlakeSettings(*mut flake_settings);359impl FlakeSettings {360 pub fn new() -> Result<Self> {361 with_default_context(|c, _| unsafe { flake_settings_new(c) }).map(Self)362 }363}364unsafe impl Send for FlakeSettings {}365unsafe impl Sync for FlakeSettings {}366impl Drop for FlakeSettings {367 fn drop(&mut self) {368 unsafe {369 flake_settings_free(self.0);370 }371 }372}373374pub struct FlakeReferenceParseFlags(*mut flake_reference_parse_flags);375impl FlakeReferenceParseFlags {376 pub fn new(settings: &FlakeSettings) -> Result<Self> {377 with_default_context(|c, _| unsafe { flake_reference_parse_flags_new(c, settings.0) })378 .map(Self)379 }380 pub fn set_base_dir(&mut self, dir: &str) -> Result<()> {381 with_default_context(|c, _| {382 unsafe {383 flake_reference_parse_flags_set_base_directory(384 c,385 self.0,386 dir.as_ptr().cast(),387 dir.len(),388 )389 };390 })391 }392}393impl Drop for FlakeReferenceParseFlags {394 fn drop(&mut self) {395 unsafe {396 flake_reference_parse_flags_free(self.0);397 }398 }399}400pub struct FlakeLockFlags(*mut flake_lock_flags);401impl FlakeLockFlags {402 pub fn new(settings: &FlakeSettings) -> Result<Self> {403 let o = with_default_context(|c, _| unsafe { flake_lock_flags_new(c, settings.0) })404 .map(Self)?;405 // with_default_context(|c, _| unsafe { flake_lock_flags_set_mode_virtual(c, o.0) })?;406407 Ok(o)408 }409}410impl Drop for FlakeLockFlags {411 fn drop(&mut self) {412 unsafe {413 flake_lock_flags_free(self.0);414 }415 }416}417418unsafe extern "C" fn copy_nix_str(start: *const c_char, n: c_uint, user_data: *mut c_void) {419 let s = unsafe { slice::from_raw_parts(start.cast::<u8>(), n as usize) };420 let s = std::str::from_utf8(s).expect("c string has invalid utf-8");421 unsafe { *user_data.cast::<String>() = s.to_owned() };422}423424struct Store(*mut c_store);425unsafe impl Send for Store {}426unsafe impl Sync for Store {}427428impl Store {429 fn parse_path(&self, path: &CStr) -> Result<StorePath> {430 with_default_context(|c, _| {431 StorePath(unsafe { store_parse_path(c, self.0, path.as_ptr()) })432 })433 }434}435436#[repr(transparent)]437pub struct EvalState(*mut c_eval_state);438unsafe impl Send for EvalState {}439unsafe impl Sync for EvalState {}440441impl Drop for EvalState {442 fn drop(&mut self) {443 unsafe {444 state_free(self.0);445 }446 }447}448449pub struct FlakeReference(*mut flake_reference);450impl FlakeReference {451 #[instrument(name = "new-flake-reference", skip(flake, parse, fetch))]452 pub fn new(453 s: &str,454 flake: &FlakeSettings,455 parse: &FlakeReferenceParseFlags,456 fetch: &FetchSettings,457 ) -> Result<(Self, String)> {458 let mut out = null_mut();459 let mut fragment = String::new();460 // let fetch_settings = fetcher_settings;461 with_default_context(|c, _| unsafe {462 flake_reference_and_fragment_from_string(463 c,464 fetch.0,465 flake.0,466 parse.0,467 s.as_ptr().cast(),468 s.len(),469 &mut out,470 Some(copy_nix_str),471 (&raw mut fragment).cast(),472 )473 })?;474 assert!(!out.is_null());475476 Ok((Self(out), fragment))477 }478 #[instrument(name = "lock-flake", skip(self, fetch, flake, lock))]479 pub fn lock(480 &mut self,481 fetch: &FetchSettings,482 flake: &FlakeSettings,483 lock: &FlakeLockFlags,484 ) -> Result<LockedFlake> {485 with_default_context(|c, es| unsafe { flake_lock(c, fetch.0, flake.0, es, lock.0, self.0) })486 .map(LockedFlake)487 }488}489unsafe impl Send for FlakeReference {}490unsafe impl Sync for FlakeReference {}491492pub struct LockedFlake(*mut locked_flake);493impl LockedFlake {494 pub fn get_attrs(&self, settings: &mut FlakeSettings) -> Result<Value> {495 with_default_context(|c, es| unsafe {496 locked_flake_get_output_attrs(c, settings.0, es, self.0)497 })498 .map(Value)499 }500}501unsafe impl Send for LockedFlake {}502unsafe impl Sync for LockedFlake {}503impl Drop for LockedFlake {504 fn drop(&mut self) {505 unsafe {506 locked_flake_free(self.0);507 };508 }509}510511type FieldName = [u8; 64];512fn init_field_name(v: &str) -> FieldName {513 let mut f = [0; 64];514 assert!(v.len() < 64, "max field name is 63 chars");515 assert!(516 v.bytes().all(|v| v != 0),517 "nul bytes are unsupported in field name"518 );519 f[0..v.len()].copy_from_slice(v.as_bytes());520 f521}522523pub struct RealisedString(*mut realised_string);524impl fmt::Debug for RealisedString {525 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {526 self.as_str().fmt(f)527 }528}529530impl RealisedString {531 pub fn as_str(&self) -> &str {532 let len = unsafe { realised_string_get_buffer_size(self.0) };533 let data: *const u8 = unsafe { realised_string_get_buffer_start(self.0) }.cast();534 let data = unsafe { slice::from_raw_parts(data, len) };535 std::str::from_utf8(data).expect("non-utf8 strings not supported")536 }537 pub fn path_count(&self) -> usize {538 unsafe { realised_string_get_store_path_count(self.0) }539 }540 pub fn path(&self, i: usize) -> String {541 assert!(i < self.path_count());542 let path = unsafe { realised_string_get_store_path(self.0, i) };543 let mut err_out = String::new();544 unsafe { store_path_name(path, Some(copy_nix_str), (&raw mut err_out).cast()) };545 err_out546 }547}548549unsafe impl Send for RealisedString {}550impl Drop for RealisedString {551 fn drop(&mut self) {552 unsafe { realised_string_free(self.0) }553 }554}555556#[repr(transparent)]557pub struct Value(*mut value);558559unsafe impl Send for Value {}560unsafe impl Sync for Value {}561562pub trait AsFieldName {563 fn as_field_name<T>(&self, v: impl FnOnce(FieldName) -> Result<T>) -> Result<T>;564 fn to_field_name(&self) -> Result<String>;565}566impl AsFieldName for Value {567 fn as_field_name<T>(&self, v: impl FnOnce(FieldName) -> Result<T>) -> Result<T> {568 let f = self.to_string()?;569 v(init_field_name(&f))570 }571 fn to_field_name(&self) -> Result<String> {572 self.to_string()573 }574}575impl<E> AsFieldName for E576where577 E: AsRef<str>,578{579 fn as_field_name<T>(&self, v: impl FnOnce(FieldName) -> Result<T>) -> Result<T> {580 let f = self.as_ref();581 v(init_field_name(f))582 }583 fn to_field_name(&self) -> Result<String> {584 Ok(self.as_ref().to_owned())585 }586}587588struct AttrsBuilder(*mut c_bindings_builder);589impl AttrsBuilder {590 fn new(capacity: usize) -> Self {591 with_default_context(|c, es| unsafe { make_bindings_builder(c, es, capacity) })592 .map(Self)593 .expect("alloc should not fail")594 }595 fn insert(&mut self, k: &impl AsFieldName, v: Value) {596 k.as_field_name(|name| {597 with_default_context(|c, _| unsafe {598 bindings_builder_insert(c, self.0, name.as_ptr().cast(), v.0);599 // bindings_builder_insert doesn't do incref600 })601 })602 .expect("builder insert shouldn't fail");603 }604}605impl Drop for AttrsBuilder {606 fn drop(&mut self) {607 unsafe { bindings_builder_free(self.0) };608 }609}610611struct ListBuilder(*mut c_list_builder, c_uint);612impl ListBuilder {613 fn new(capacity: usize) -> Self {614 with_default_context(|c, es| unsafe { make_list_builder(c, es, capacity) })615 .map(|l| Self(l, 0))616 .expect("alloc should not fail")617 }618}619impl ListBuilder {620 fn push(&mut self, v: Value) {621 with_default_context(|c, _| unsafe {622 list_builder_insert(623 c,624 self.0,625 {626 let v = self.1;627 self.1 += 1;628 v629 },630 v.0,631 )632 })633 .expect("list insert shouldn't fail");634 }635}636impl Drop for ListBuilder {637 fn drop(&mut self) {638 unsafe { list_builder_free(self.0) };639 }640}641642impl Value {643 pub fn new_primop(v: NativeFn) -> Self {644 let out = Self::new_uninit();645 with_default_context(|c, _| unsafe { init_primop(c, out.0, v.0) })646 .expect("primop initialization should not fail");647 out648 }649 pub fn new_attrs(v: HashMap<&str, Value>) -> Self {650 let out = Self::new_uninit();651 let mut b = AttrsBuilder::new(v.len());652 for (k, v) in v {653 b.insert(&k, v);654 }655 with_default_context(|c, _| unsafe { make_attrs(c, out.0, b.0) })656 .expect("attrs initialization should not fail");657658 out659 }660 fn new_list<T: Into<Self>>(v: Vec<T>) -> Self {661 let out = Self::new_uninit();662 let mut b = ListBuilder::new(v.len());663 for v in v {664 b.push(v.into());665 }666 with_default_context(|c, _| unsafe { make_list(c, b.0, out.0) })667 .expect("list initialization should not fail");668669 out670 }671 fn new_uninit() -> Self {672 let out = with_default_context(|c, es| unsafe { alloc_value(c, es) })673 .expect("value allocation should not fail");674 Self(out)675 }676 pub fn new_str(v: &str) -> Self {677 let s = CString::new(v).expect("string should not contain NULs");678 let out = Self::new_uninit();679 // String is copied, `s` is free to be dropped680 with_default_context(|c, _| unsafe { init_string(c, out.0, s.as_ptr()) })681 .expect("string initialization should not fail");682 out683 }684 pub fn new_int(i: i64) -> Self {685 let out = Self::new_uninit();686 with_default_context(|c, _| unsafe { init_int(c, out.0, i) })687 .expect("int initialization should not fail");688 out689 }690 pub fn new_bool(v: bool) -> Self {691 let out = Self::new_uninit();692 with_default_context(|c, _| unsafe { init_bool(c, out.0, v) })693 .expect("bool initialization should not fail");694 out695 }696 // TODO: As far as I can see, there is no way to get Thunks from nix public C api, so this function is useless697 // fn force(&mut self, st: &mut EvalState) -> Result<()> {698 // with_default_context(|c, _| unsafe { value_force(c, st.0, self.0) })?;699 // Ok(())700 // }701 pub fn type_of(&self) -> NixType {702 let ty = with_default_context(|c, _| unsafe { get_type(c, self.0) })703 .expect("get_type should not fail");704 NixType::from_int(ty)705 }706 fn builtin_to_string(&self) -> Result<Self> {707 let builtin = Self::eval("builtins.toString")?;708 builtin.call(self.clone())709 }710 fn force(&mut self, s: *mut nix_raw::EvalState) -> Result<()> {711 with_default_context(|c, _| unsafe { value_force(c, s, self.0) })?;712 Ok(())713 }714 pub fn to_string(&self) -> Result<String> {715 let ty = self.type_of();716 if !matches!(ty, NixType::String) {717 bail!("unexpected type: {ty:?}, expected string");718 }719 let mut str_out = String::new();720 with_default_context(|c, _| unsafe {721 get_string(c, self.0, Some(copy_nix_str), (&raw mut str_out).cast())722 })?;723724 Ok(str_out)725 }726 pub fn to_realised_string(&self) -> Result<RealisedString> {727 with_default_context(|c, es| unsafe { string_realise(c, es, self.0, false) })728 .map(RealisedString)729730 // let store_paths = unsafe { nix_raw::realised_string_get_store_path_count(str) };731 // for i in 0..store_paths {732 // let store_path = unsafe { nix_raw::realised_string_get_store_path(str, i) };733 // nix_raw::store_path_name(store_path, callback, user_data);734 // }735 // dbg!(store_paths);736 // todo!();737 }738739 pub fn has_field(&self, field: &str) -> Result<bool> {740 if !matches!(self.type_of(), NixType::Attrs) {741 bail!("invalid type: expected attrs");742 }743744 let f = init_field_name(field);745 with_default_context(|c, es| unsafe { has_attr_byname(c, self.0, es, f.as_ptr().cast()) })746 }747 // pub fn derivation_path(&self) {748 // nix_raw::real749 // }750 pub fn list_fields(&self) -> Result<Vec<String>> {751 if !matches!(self.type_of(), NixType::Attrs) {752 bail!("invalid type: expected attrs");753 }754755 let len = with_default_context(|c, _| unsafe { get_attrs_size(c, self.0) })?;756 let mut out = Vec::with_capacity(len as usize);757758 for i in 0..len {759 let name =760 with_default_context(|c, es| unsafe { get_attr_name_byidx(c, self.0, es, i) })?;761 let c = unsafe { CStr::from_ptr(name) };762 out.push(c.to_str().expect("nix field names are utf-8").to_owned());763 }764 Ok(out)765 }766 pub fn get_elem(&self, v: usize) -> Result<Self> {767 if !matches!(self.type_of(), NixType::List) {768 bail!("invalid type: expected list");769 }770 let len = with_default_context(|c, _| unsafe { get_list_size(c, self.0) })? as usize;771 if v >= len {772 bail!("oob list get: {v} >= {len}");773 }774775 with_default_context(|c, es| unsafe { get_list_byidx(c, self.0, es, v as u32) }).map(Self)776 }777 pub fn attrs_update(self, other: Value /*, ignore_errors: bool*/) -> Result<Self> {778 let attrs_update_fn = Self::eval("a: b: a // b")?;779780 attrs_update_fn781 .call(self)?782 .call(other)783 .context("attrs update")784 }785 pub fn get_field(&self, name: impl AsFieldName) -> Result<Self> {786 if !matches!(self.type_of(), NixType::Attrs) {787 bail!("invalid type: expected attrs");788 }789790 name.as_field_name(|name| {791 with_default_context(|c, es| unsafe {792 get_attr_byname(c, self.0, es, name.as_ptr().cast())793 })794 .map(Self)795 })796 .with_context(|| format!("getting field {:?}", name.to_field_name()))797 }798 pub fn call(&self, v: Value) -> Result<Self> {799 let kind = self800 .functor_kind()801 .ok_or_else(|| anyhow!("can only call function or functor"))?;802803 let function = match kind {804 FunctorKind::Function => self.clone(),805 FunctorKind::Functor => {806 let f = self807 .get_field("__functor")808 .context("getting functor value")?;809 assert_eq!(810 f.type_of(),811 NixType::Function,812 "invalid functor encountered"813 );814 f815 }816 };817818 let out = Value::new_uninit();819 with_default_context(|c, es| unsafe { value_call(c, es, function.0, v.0, out.0) })?;820821 Ok(out)822 }823 pub fn eval(v: &str) -> Result<Self> {824 let s = CString::new(v).expect("expression shouldn't have internal NULs");825 let out = Self::new_uninit();826 with_default_context(|c, es| unsafe {827 expr_eval_from_string(c, es, s.as_ptr(), c"/root".as_ptr(), out.0)828 })?;829 Ok(out)830 }831 pub fn build(&self, output: &str) -> Result<PathBuf> {832 if !self.is_derivation() {833 bail!("expected derivation to build")834 }835 let output_name = self836 .get_field("outputName")837 .context("getting output name field")?838 .to_string()?;839 let v = if output_name != output {840 let out = self.get_field(output).context("getting target output")?;841 if !out.is_derivation() {842 bail!("unknown output: {output}");843 }844 out845 } else {846 self.clone()847 };848 // to_string here blocks until the path is built849 let s = v.builtin_to_string()?;850 let rs = s.to_realised_string()?;851 let drv_path = rs.as_str().to_owned();852 Ok(PathBuf::from(drv_path))853 }854 pub fn as_json<T: DeserializeOwned>(&self) -> Result<T> {855 let to_json = Self::eval("builtins.toJSON")?;856 let s = to_json.call(self.clone())?.to_string()?;857 Ok(serde_json::from_str(&s)?)858 }859 pub fn serialized<T: Serialize>(v: &T) -> Result<Self> {860 Self::eval(&nixlike::serialize(v)?)861 }862863 // Convert to string/evaluate derivations/etc864 // fn to_string_weak(&self) -> Result<String> {865 // // TODO: For now, it works exactly like to_string, see the comment for fn force()866 // self.to_string()867 // }868869 fn is_derivation(&self) -> bool {870 if !matches!(self.type_of(), NixType::Attrs) {871 return false;872 }873 let Some(ty) = self.get_field("type").ok() else {874 return false;875 };876 matches!(ty.to_string().as_deref(), Ok("derivation"))877 }878 fn functor_kind(&self) -> Option<FunctorKind> {879 match self.type_of() {880 NixType::Attrs => self881 .has_field("__functor")882 .expect("has_field shouldn't fail for attrs")883 .then_some(FunctorKind::Functor),884 NixType::Function => Some(FunctorKind::Function),885 _ => None,886 }887 }888 pub fn is_function(&self) -> bool {889 self.functor_kind().is_some()890 }891 pub fn is_null(&self) -> bool {892 matches!(self.type_of(), NixType::Null)893 }894 pub fn is_string(&self) -> bool {895 matches!(self.type_of(), NixType::String)896 }897 pub fn is_attrs(&self) -> bool {898 matches!(self.type_of(), NixType::Attrs)899 }900}901902impl From<String> for Value {903 fn from(value: String) -> Self {904 Value::new_str(&value)905 }906}907impl From<bool> for Value {908 fn from(value: bool) -> Self {909 Value::new_bool(value)910 }911}912impl From<&str> for Value {913 fn from(value: &str) -> Self {914 Value::new_str(value)915 }916}917impl<T> From<Vec<T>> for Value918where919 T: Into<Value>,920{921 fn from(value: Vec<T>) -> Self {922 Value::new_list(value)923 }924}925926impl Clone for Value {927 fn clone(&self) -> Self {928 with_default_context(|c, _| unsafe { value_incref(c, self.0) })929 .expect("value incref should not fail");930 Self(self.0)931 }932}933impl Drop for Value {934 fn drop(&mut self) {935 with_default_context(|c, _| unsafe { value_decref(c, self.0) })936 .expect("value drop should not fail");937 }938}939940static TOKIO_FOR_NIX: OnceLock<Arc<tokio::runtime::Runtime>> = OnceLock::new();941942pub fn init_libraries() {943 unsafe { GC_allow_register_threads() };944945 let mut ctx = NixContext::new();946 ctx.run_in_context(|c| unsafe { libutil_init(c) })947 .expect("util init should not fail");948 ctx.run_in_context(|c| unsafe { libstore_init(c) })949 .expect("store init should not fail");950 ctx.run_in_context(|c| unsafe { libexpr_init(c) })951 .expect("expr init should not fail");952953 nix_logging_cxx::apply_tracing_logger();954}955956pub fn init_tokio_for_nix(tokio: Arc<tokio::runtime::Runtime>) {957 TOKIO_FOR_NIX958 .set(tokio)959 .expect("tokio for nix should only be initialized once");960}961962pub fn await_in_nix<F: Send + 'static>(f: impl Future<Output = F> + Send + 'static) -> F {963 // It should be possible to do Handle::current(), but some of the planned features don't work well with that964 let runtime = TOKIO_FOR_NIX965 .get()966 .expect("init_tokio_for_nix was not called");967 std::thread::spawn(move || runtime.block_on(f))968 .join()969 .expect("await_in_nix inner thread panicked")970}971972unsafe extern "C" fn nix_primop_closure_adapter<const N: usize>(973 user_data: *mut c_void,974 mut context: *mut c_context,975 state: *mut nix_raw::EvalState,976 args: *mut *mut value,977 ret: *mut value,978) {979 let user_closure: &UserClosure<N> = unsafe { &*user_data.cast_const().cast() };980 let args: [&Value; N] = array::from_fn(|i| {981 let v: &mut Value = unsafe { &mut *args.add(i).cast() };982 v as &Value983 });984 let ctx: &mut NixContext = unsafe { transmute(&mut context) };985986 let state: &EvalState = unsafe { std::mem::transmute(&state) };987988 match user_closure(state, args) {989 Ok(v) => {990 unsafe { copy_value(context, ret, v.0) };991 }992 Err(e) => {993 ctx.set_err(e);994 }995 }996}997998type UserClosure<const N: usize> = Box<dyn Fn(&EvalState, [&Value; N]) -> Result<Value>>;9991000pub struct NativeFn(*mut PrimOp);1001impl NativeFn {1002 pub fn new<const N: usize>(1003 name: &'static CStr,1004 doc: &'static CStr,1005 args: [&'static CStr; N],1006 f: impl Fn(&EvalState, [&Value; N]) -> Result<Value> + 'static,1007 ) -> Self {1008 // Double-boxing to make it thin pointer, as vtable gets outside of first Box1009 let closure: Box<UserClosure<N>> = Box::new(Box::new(f));1010 let f: PrimOpFun = Some(nix_primop_closure_adapter::<N>);1011 let mut args = args.into_iter().map(|v| v.as_ptr()).collect_vec();1012 args.push(null());1013 let args = args.as_mut_ptr();1014 let primop = unsafe {1015 alloc_primop(1016 null_mut(),1017 f,1018 N as i32,1019 name.as_ptr(),1020 args,1021 doc.as_ptr(),1022 Box::into_raw(closure).cast(),1023 )1024 };10251026 assert!(!primop.is_null(), "primop allocation should not fail");10271028 Self(primop)1029 }1030 pub fn register(self) {1031 unsafe { register_primop(null_mut(), self.0) };1032 }1033}10341035struct StorePath(*mut c_store_path);1036impl StorePath {}10371038impl Drop for StorePath {1039 fn drop(&mut self) {1040 unsafe { store_path_free(self.0) }1041 }1042}10431044#[test_log::test]1045fn test_native() -> Result<()> {1046 init_libraries();1047 NativeFn::new(1048 c"__uppercaseSuffix2",1049 c"make string uppercase and add suffix",1050 [c"str", c"suffix"],1051 |_, [str, suffix]: [&Value; 2]| {1052 let str = str.to_string()?;1053 let suffix = suffix.to_string()?;1054 Ok(Value::new_str(&format!("{}{suffix}", str.to_uppercase())))1055 },1056 )1057 .register();10581059 let mut fetch_settings = FetchSettings::new();1060 fetch_settings.set(c"warn-dirty", c"false");10611062 let manifest = format!("git+file://{}/../../", env!("CARGO_MANIFEST_DIR"));1063 let flake = FlakeSettings::new()?;1064 let parse = FlakeReferenceParseFlags::new(&flake)?;1065 let (mut r, _) = FlakeReference::new(&manifest, &flake, &parse, &fetch_settings)?;1066 let lock = FlakeLockFlags::new(&flake)?;1067 let locked = r.lock(&fetch_settings, &flake, &lock)?;1068 let attrs = locked.get_attrs(&mut FlakeSettings::new()?)?;10691070 let builtins = Value::eval("builtins")?;1071 assert_eq!(builtins.type_of(), NixType::Attrs);10721073 assert_eq!(attrs.type_of(), NixType::Attrs);1074 let test_data = nix_go!(attrs.testData);10751076 let test_string: String = nix_go_json!(test_data.testString);1077 assert_eq!(test_string, "hello");10781079 let s = nix_go!(attrs.packages["x86_64-linux"].fleet.drvPath);1080 let s = CString::new(s.to_string()?).expect("path str is cstring");10811082 let uppercase_suffix = Value::new_primop(NativeFn::new(1083 c"uppercase_suffix",1084 c"make string uppercase and add suffix",1085 [c"str", c"suffix"],1086 |es, [str, suffix]: [&Value; 2]| {1087 let str = str.to_string()?;1088 let suffix = suffix.to_string()?;1089 Ok(Value::new_str(&format!("{}{suffix}", str.to_uppercase())))1090 },1091 ));10921093 let test_result: String = nix_go_json!(test_data.testPrimop(uppercase_suffix));1094 assert_eq!(test_result, "PREFIX_BODY_SUFFIX");1095 let test_result: String = nix_go_json!(builtins.uppercaseSuffix2("test")("suffix"));1096 assert_eq!(test_result, "TESTsuffix");10971098 let nix_ctx = NixContext::new();1099 let store = GLOBAL_STATE.store.parse_path(s.as_c_str())?;11001101 // nix_raw::store_get_fs_closure(1);11021103 Ok(())1104}11051106// pub struct GcAlloc;1107// unsafe impl GlobalAlloc for GcAlloc {1108// unsafe fn alloc(&self, l: Layout) -> *mut u8 {1109// let ptr = unsafe { GC_malloc(l.size()) };1110// ptr.cast()1111// }1112// unsafe fn dealloc(&self, ptr: *mut u8, _: Layout) {1113// // unsafe { GC_free(ptr.cast()) };1114// }1115//1116// unsafe fn realloc(&self, ptr: *mut u8, _: Layout, new_size: usize) -> *mut u8 {1117// let ptr = unsafe { GC_realloc(ptr.cast(), new_size) };1118// ptr.cast()1119// }1120// }1121//1122// #[global_allocator]1123// static GC: GcAlloc = GcAlloc;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;crates/nix-eval/src/macros.rsdiffbeforeafterboth--- a/crates/nix-eval/src/macros.rs
+++ b/crates/nix-eval/src/macros.rs
@@ -18,12 +18,12 @@
(@obj($o:ident)) => {{}};
(Obj { }) => {{
use $crate::{nix_expr_inner};
- let out = std::collections::hash_map::HashMap::new();
+ let out = $crate::__macro_support::HashMap::new();
Value::new_attrs(out)
}};
(Obj { $($tt:tt)+ }) => {{
use $crate::{nix_expr_inner};
- let mut out = std::collections::hash_map::HashMap::new();
+ let mut out = $crate::__macro_support::HashMap::new();
nix_expr_inner!(@obj(out) $($tt)*);
Value::new_attrs(out)
}};
@@ -68,25 +68,25 @@
#[macro_export]
macro_rules! nix_go {
(@o($o:expr, $path:expr) . $var:ident $($tt:tt)*) => {{
- nix_go!(@o(tokio::task::block_in_place(|| $o.get_field(stringify!($var))).context(concat!("getting nested ", $path))?, $path) $($tt)*)
+ nix_go!(@o($crate::__macro_support::block_in_place(|| $o.get_field(stringify!($var))).context(concat!("getting nested ", $path))?, $path) $($tt)*)
}};
(@o($o:expr, $path:expr) [ $v:expr ] $($tt:tt)*) => {{
- nix_go!(@o(tokio::task::block_in_place(|| $o.get_field($v)).context(concat!("getting nested ", $path))?, $path) $($tt)*)
+ nix_go!(@o($crate::__macro_support::block_in_place(|| $o.get_field($v)).context(concat!("getting nested ", $path))?, $path) $($tt)*)
}};
(@o($o:expr, $path:expr) ($($var:tt)*) $($tt:tt)*) => {
- nix_go!(@o(tokio::task::block_in_place(|| $o.call($crate::nix_expr_inner!($($var)+))).context(concat!("getting nested ", $path))?, $path) $($tt)*)
+ nix_go!(@o($crate::__macro_support::block_in_place(|| $o.call($crate::nix_expr_inner!($($var)+))).context(concat!("getting nested ", $path))?, $path) $($tt)*)
};
(@o($o:expr, $path:expr)) => {$o};
($field:ident $($tt:tt)+) => {{
use $crate::nix_go;
- use ::anyhow::Context;
+ use $crate::__macro_support::Context;
let out = $field.clone();
- nix_go!(@o(out, ::std::stringify!($($tt)*)) $($tt)*)
+ nix_go!(@o(out, stringify!($($tt)*)) $($tt)*)
}}
}
#[macro_export]
macro_rules! nix_go_json {
($($tt:tt)*) => {{
- tokio::task::block_in_place(|| $crate::nix_go!($($tt)*).as_json())?
+ $crate::__macro_support::block_in_place(|| $crate::nix_go!($($tt)*).as_json())?
}};
}