difftreelog
feat basic primop interface
in: trunk
3 files changed
crates/nix-eval/src/lib.rsdiffbeforeafterboth1use std::borrow::Cow;1use std::borrow::Cow;2use std::cell::RefCell;2use std::cell::RefCell;3use std::ffi::{CStr, CString, c_char, c_int, c_uint, c_void};3use std::ffi::{CStr, CString, c_char, c_int, c_uint, c_void};4use std::ptr::null_mut;4use std::ptr::{null, null_mut};5use std::sync::LazyLock;5use std::sync::LazyLock;6use std::{array, fmt, slice};6use std::{collections::HashMap, path::PathBuf};7use std::{collections::HashMap, path::PathBuf};7use std::{fmt, slice};889use anyhow::{Context, anyhow, bail};9use anyhow::{Context, anyhow, bail};10use itertools::Itertools;10use serde::Serialize;11use serde::Serialize;11use serde::de::DeserializeOwned;12use serde::de::DeserializeOwned;121318use self::nix_raw::{19use self::nix_raw::{19 BindingsBuilder as c_bindings_builder, EvalState as c_eval_state, GC_SUCCESS,20 BindingsBuilder as c_bindings_builder, EvalState as c_eval_state, GC_SUCCESS,20 GC_allow_register_threads, GC_get_stack_base, GC_register_my_thread, GC_stack_base,21 GC_allow_register_threads, GC_get_stack_base, GC_register_my_thread, GC_stack_base,21 GC_thread_is_registered, GC_unregister_my_thread, ListBuilder as c_list_builder,22 GC_thread_is_registered, GC_unregister_my_thread, ListBuilder as c_list_builder, PrimOp,22 Store as c_store, StorePath as c_store_path, alloc_value, bindings_builder_free,23 PrimOpFun, Store as c_store, StorePath as c_store_path, alloc_primop, alloc_value,23 bindings_builder_insert, c_context, c_context_create, c_context_free, clear_err, err_code,24 bindings_builder_free, bindings_builder_insert, c_context, c_context_create, c_context_free,24 err_info_msg, err_msg, eval_state_build, eval_state_builder_load, eval_state_builder_new,25 clear_err, copy_value, err_code, err_info_msg, err_msg, eval_state_build,25 eval_state_builder_set_eval_setting, expr_eval_from_string, fetchers_settings,26 eval_state_builder_load, eval_state_builder_new, eval_state_builder_set_eval_setting,26 fetchers_settings_free, fetchers_settings_new, flake_lock, flake_lock_flags,27 expr_eval_from_string, fetchers_settings, fetchers_settings_free, fetchers_settings_new,27 flake_lock_flags_free, flake_lock_flags_new, flake_reference,28 flake_lock, flake_lock_flags, flake_lock_flags_free, flake_lock_flags_new, flake_reference,30 flake_reference_parse_flags_set_base_directory, flake_settings, flake_settings_free,31 flake_reference_parse_flags_set_base_directory, flake_settings, flake_settings_free,31 flake_settings_new, gc_now as gc_now_raw, get_attr_byname, get_attr_name_byidx, get_attrs_size,32 flake_settings_new, gc_now as gc_now_raw, get_attr_byname, get_attr_name_byidx, get_attrs_size,32 get_list_byidx, get_list_size, get_string, get_type, has_attr_byname, init_bool, init_int,33 get_list_byidx, get_list_size, get_string, get_type, has_attr_byname, init_bool, init_int,33 init_string, libexpr_init, libstore_init, libutil_init, list_builder_free, list_builder_insert,34 init_primop, init_string, libexpr_init, libstore_init, libutil_init, list_builder_free,34 locked_flake, locked_flake_free, locked_flake_get_output_attrs, make_attrs,35 list_builder_insert, locked_flake, locked_flake_free, locked_flake_get_output_attrs,35 make_bindings_builder, make_list, make_list_builder, realised_string, realised_string_free,36 make_attrs, make_bindings_builder, make_list, make_list_builder, realised_string,36 realised_string_get_buffer_size, realised_string_get_buffer_start,37 realised_string_free, realised_string_get_buffer_size, realised_string_get_buffer_start,37 realised_string_get_store_path, realised_string_get_store_path_count, set_err_msg, setting_set,38 realised_string_get_store_path, realised_string_get_store_path_count, register_primop,38 state_free, store_open, store_parse_path, store_path_free, store_path_name, string_realise,39 set_err_msg, setting_set, state_free, store_copy_closure, store_get_fs_closure, store_open,39 value, value_call, value_decref, value_incref,40 store_parse_path, store_path_free, store_path_name, string_realise, value, value_call,41 value_decref, value_incref,40};42};166 }168 }167}169}168170171#[repr(transparent)]169pub struct NixContext(*mut c_context);172pub struct NixContext(*mut c_context);170impl NixContext {173impl NixContext {171 pub fn set_err(&mut self, err: NixErrorKind, msg: &CStr) {174 pub fn set_err(&mut self, err: NixErrorKind, msg: &CStr) {540 }543 }541}544}542545546#[repr(transparent)]543pub struct Value(*mut value);547pub struct Value(*mut value);544548545unsafe impl Send for Value {}549unsafe impl Send for Value {}626}630}627631628impl Value {632impl Value {633 pub fn new_primop(v: NativeFn) -> Self {634 let out = Self::new_uninit();635 with_default_context(|c, _| unsafe { init_primop(c, out.0, v.0) })636 .expect("primop initialization should not fail");637 out638 }629 pub fn new_attrs(v: HashMap<&str, Value>) -> Self {639 pub fn new_attrs(v: HashMap<&str, Value>) -> Self {630 let out = Self::new_uninit();640 let out = Self::new_uninit();631 let mut b = AttrsBuilder::new(v.len());641 let mut b = AttrsBuilder::new(v.len());913 nix_logging_cxx::apply_tracing_logger();923 nix_logging_cxx::apply_tracing_logger();914}924}925926unsafe extern "C" fn nix_primop_closure_adapter<const N: usize>(927 user_data: *mut c_void,928 context: *mut c_context,929 state: *mut nix_raw::EvalState,930 args: *mut *mut value,931 ret: *mut value,932) {933 let user_closure: &UserClosure<N> = unsafe { &*user_data.cast_const().cast() };934 let args: [&Value; N] = array::from_fn(|i| {935 let v: &Value = unsafe { &*args.add(i).cast_const().cast() };936 v937 });938 let ctx: &mut NixContext = unsafe { &mut *context.cast() };939940 match user_closure(args) {941 Ok(v) => {942 unsafe { copy_value(context, ret, v.0) };943 }944 Err(e) => {945 ctx.set_err(946 NixErrorKind::Unknown,947 &CString::new(e.to_string()).expect("error should not contain internal nuls"),948 );949 }950 }951}952953type UserClosure<const N: usize> = Box<dyn Fn([&Value; N]) -> Result<Value>>;954955struct NativeFn(*mut PrimOp);956impl NativeFn {957 pub fn new<const N: usize>(958 name: &'static CStr,959 doc: &'static CStr,960 args: [&'static CStr; N],961 f: impl Fn([&Value; N]) -> Result<Value> + 'static,962 ) -> Self {963 // Double-boxing to make it thin pointer, as vtable gets outside of first Box964 let closure: Box<UserClosure<N>> = Box::new(Box::new(f));965 let f: PrimOpFun = Some(nix_primop_closure_adapter::<N>);966 let mut args = args.into_iter().map(|v| v.as_ptr()).collect_vec();967 args.push(null());968 let args = args.as_mut_ptr();969 let primop = with_default_context(|c, _| unsafe {970 alloc_primop(971 c,972 f,973 N as i32,974 name.as_ptr(),975 args,976 doc.as_ptr(),977 Box::into_raw(closure).cast(),978 )979 })980 .expect("primop allocation should not fail");981982 Self(primop)983 }984 pub fn register(self) {985 with_default_context(|c, _| unsafe { register_primop(c, self.0) })986 .expect("primop registration should not fail");987 }988}915989916struct StorePath(*mut c_store_path);990struct StorePath(*mut c_store_path);917impl StorePath {}991impl StorePath {}949 let s = nix_go!(attrs.packages["x86_64-linux"].fleet.drvPath);1023 let s = nix_go!(attrs.packages["x86_64-linux"].fleet.drvPath);950 let s = CString::new(s.to_string()?).expect("path str is cstring");1024 let s = CString::new(s.to_string()?).expect("path str is cstring");10251026 let uppercase_suffix = Value::new_primop(NativeFn::new(1027 c"uppercase_suffix",1028 c"make string uppercase and add suffix",1029 [c"str", c"suffix"],1030 |[str, suffix]: [&Value; 2]| {1031 let str = str.to_string()?;1032 let suffix = suffix.to_string()?;1033 Ok(Value::new_str(&format!("{}{suffix}", str.to_uppercase())))1034 },1035 ));10361037 let test_result: String = nix_go_json!(test_data.testPrimop(uppercase_suffix));1038 assert_eq!(test_result, "PREFIX_BODY_SUFFIX");9511039952 let nix_ctx = NixContext::new();1040 let nix_ctx = NixContext::new();953 let store = GLOBAL_STATE.store.parse_path(s.as_c_str())?;1041 let store = GLOBAL_STATE.store.parse_path(s.as_c_str())?;flake.lockdiffbeforeafterboth2 "nodes": {2 "nodes": {3 "crane": {3 "crane": {4 "locked": {4 "locked": {5 "lastModified": 1762538466,5 "lastModified": 1763511871,6 "narHash": "sha256-8zrIPl6J+wLm9MH5ksHcW7BUHo7jSNOu0/hA0ohOOaM=",7 "owner": "ipetkov",6 "owner": "ipetkov",8 "repo": "crane",7 "repo": "crane",9 "rev": "0cea393fffb39575c46b7a0318386467272182fe",8 "rev": "099f9014bc8d0cd6e445470ea1df0fd691d5a548",10 "type": "github"9 "type": "github"11 },10 },12 "original": {11 "original": {38 ]37 ]39 },38 },40 "locked": {39 "locked": {41 "lastModified": 1762810396,40 "lastModified": 1763759067,42 "narHash": "sha256-dxFVgQPG+R72dkhXTtqUm7KpxElw3u6E+YlQ2WaDgt8=",43 "owner": "hercules-ci",41 "owner": "hercules-ci",44 "repo": "flake-parts",42 "repo": "flake-parts",45 "rev": "0bdadb1b265fb4143a75bd1ec7d8c915898a9923",43 "rev": "2cccadc7357c0ba201788ae99c4dfa90728ef5e0",46 "type": "github"44 "type": "github"47 },45 },48 "original": {46 "original": {51 "type": "github"49 "type": "github"52 }50 }53 },51 },54 "flake-parts_2": {55 "inputs": {56 "nixpkgs-lib": [57 "nix",58 "nixpkgs"59 ]60 },61 "locked": {62 "lastModified": 1748821116,63 "narHash": "sha256-F82+gS044J1APL0n4hH50GYdPRv/5JWm34oCJYmVKdE=",64 "rev": "49f0870db23e8c1ca0b5259734a02cd9e1e371a1",65 "revCount": 377,66 "type": "tarball",67 "url": "https://api.flakehub.com/f/pinned/hercules-ci/flake-parts/0.1.377%2Brev-49f0870db23e8c1ca0b5259734a02cd9e1e371a1/01972f28-554a-73f8-91f4-d488cc502f08/source.tar.gz"68 },69 "original": {70 "type": "tarball",71 "url": "https://flakehub.com/f/hercules-ci/flake-parts/0.1"72 }73 },74 "fleet-tf": {52 "fleet-tf": {75 "inputs": {53 "inputs": {76 "flake-parts": [54 "flake-parts": [85 },63 },86 "locked": {64 "locked": {87 "lastModified": 1759080490,65 "lastModified": 1759080490,88 "narHash": "sha256-6eog70ItEoiusftwCp1vjY/7kA1+BDTUuRwg4KmszUs=",89 "owner": "CertainLach",66 "owner": "CertainLach",90 "repo": "fleet-tf",67 "repo": "fleet-tf",91 "rev": "878bd8c23933d628bf750378bbe527b841901c3d",68 "rev": "878bd8c23933d628bf750378bbe527b841901c3d",123 },100 },124 "nix": {101 "nix": {125 "inputs": {102 "inputs": {126 "flake-parts": "flake-parts_2",103 "flake-parts": [104 "flake-parts"105 ],127 "git-hooks-nix": "git-hooks-nix",106 "git-hooks-nix": "git-hooks-nix",128 "nixpkgs": "nixpkgs",107 "nixpkgs": [108 "nixpkgs"109 ],129 "nixpkgs-23-11": "nixpkgs-23-11",110 "nixpkgs-23-11": "nixpkgs-23-11",130 "nixpkgs-regression": "nixpkgs-regression"111 "nixpkgs-regression": "nixpkgs-regression"131 },112 },132 "locked": {113 "locked": {133 "lastModified": 1762216145,114 "lastModified": 1763844027,134 "narHash": "sha256-ff2SX5zKVsOAvN84AT+81TmWob5B5dxlkMcFexPZHAA=",135 "owner": "deltarocks",115 "owner": "deltarocks",136 "repo": "nix",116 "repo": "nix",137 "rev": "dece43d5bae876aa475033d5a5a389b478b1644e",117 "rev": "6b07ab20f1a65bd4aa54ec1418ff6f179dde64ac",138 "type": "github"118 "type": "github"139 },119 },140 "original": {120 "original": {146 },126 },147 "nixpkgs": {127 "nixpkgs": {148 "locked": {128 "locked": {149 "lastModified": 1755922037,129 "lastModified": 1763844746,150 "narHash": "sha256-wY1+2JPH0ZZC4BQefoZw/k+3+DowFyfOxv17CN/idKs=",130 "owner": "nixos",151 "rev": "b1b3291469652d5a2edb0becc4ef0246fff97a7c",131 "repo": "nixpkgs",152 "revCount": 808723,132 "rev": "d20f025296df84f7d97dee7c1a069bea8b17c8f2",153 "type": "tarball",133 "type": "github"154 "url": "https://api.flakehub.com/f/pinned/NixOS/nixpkgs/0.2505.808723%2Brev-b1b3291469652d5a2edb0becc4ef0246fff97a7c/0198daf7-011a-7703-95d7-57146e794342/source.tar.gz"155 },134 },156 "original": {135 "original": {157 "type": "tarball",136 "owner": "nixos",158 "url": "https://flakehub.com/f/NixOS/nixpkgs/0.2505"137 "ref": "release-25.05",138 "repo": "nixpkgs",139 "type": "github"159 }140 }160 },141 },161 "nixpkgs-23-11": {142 "nixpkgs-23-11": {190 "type": "github"171 "type": "github"191 }172 }192 },173 },193 "nixpkgs_2": {194 "locked": {195 "lastModified": 1762881507,196 "narHash": "sha256-P59mF2fC4/ijf5ZoCQA+wcaN6b0OgKKA3BQZpfPqHtY=",197 "owner": "nixos",198 "repo": "nixpkgs",199 "rev": "2bbfb713196ae4cf5428530e8387cadcf95d3e2f",200 "type": "github"201 },202 "original": {203 "owner": "nixos",204 "ref": "release-25.05",205 "repo": "nixpkgs",206 "type": "github"207 }208 },209 "root": {174 "root": {210 "inputs": {175 "inputs": {211 "crane": "crane",176 "crane": "crane",212 "flake-parts": "flake-parts",177 "flake-parts": "flake-parts",213 "fleet-tf": "fleet-tf",178 "fleet-tf": "fleet-tf",214 "nix": "nix",179 "nix": "nix",215 "nixpkgs": "nixpkgs_2",180 "nixpkgs": "nixpkgs",216 "rust-overlay": "rust-overlay",181 "rust-overlay": "rust-overlay",217 "shelly": "shelly",182 "shelly": "shelly",218 "treefmt-nix": "treefmt-nix"183 "treefmt-nix": "treefmt-nix"225 ]190 ]226 },191 },227 "locked": {192 "locked": {228 "lastModified": 1762828736,193 "lastModified": 1763778964,229 "narHash": "sha256-RxtFHWZpKwVcWHhx88E2NhWuBbgYVqIoIDynGs5FoJs=",230 "owner": "oxalica",194 "owner": "oxalica",231 "repo": "rust-overlay",195 "repo": "rust-overlay",232 "rev": "8d5baa5628f6dbd7ce6beca3c299bae27755204c",196 "rev": "7f3aa46dfa230ec2a4ca9281186a24771650ccd1",233 "type": "github"197 "type": "github"234 },198 },235 "original": {199 "original": {241 "shelly": {205 "shelly": {242 "locked": {206 "locked": {243 "lastModified": 1756323923,207 "lastModified": 1756323923,244 "narHash": "sha256-sKUaQrgnYVBmG5cGmGoFYXc61g74ufWpPYGPoJia58k=",245 "owner": "CertainLach",208 "owner": "CertainLach",246 "repo": "shelly",209 "repo": "shelly",247 "rev": "b5dd29a500db04f54a9f1c2bf81cdd84df8b0cd7",210 "rev": "b5dd29a500db04f54a9f1c2bf81cdd84df8b0cd7",260 ]223 ]261 },224 },262 "locked": {225 "locked": {263 "lastModified": 1762410071,226 "lastModified": 1762938485,264 "narHash": "sha256-aF5fvoZeoXNPxT0bejFUBXeUjXfHLSL7g+mjR/p5TEg=",265 "owner": "numtide",227 "owner": "numtide",266 "repo": "treefmt-nix",228 "repo": "treefmt-nix",267 "rev": "97a30861b13c3731a84e09405414398fbf3e109f",229 "rev": "5b4ee75aeefd1e2d5a1cc43cf6ba65eba75e83e4",268 "type": "github"230 "type": "github"269 },231 },270 "original": {232 "original": {flake.nixdiffbeforeafterboth25 };25 };26 # DeterminateSystem's nix fork is controversial, but I don't mind it,26 # DeterminateSystem's nix fork is controversial, but I don't mind it,27 # and it has lazy-trees support which is useful for fleet.27 # and it has lazy-trees support which is useful for fleet.28 nix.url = "github:deltarocks/nix/fleet";28 nix = {29 url = "github:deltarocks/nix/fleet";30 inputs.nixpkgs.follows = "nixpkgs";31 inputs.flake-parts.follows = "flake-parts";32 };29 };33 };30 outputs =34 outputs =31 inputs:35 inputs:56 v = "Hello";60 v = "Hello";57 };61 };58 testString = "hello";62 testString = "hello";63 testPrimop = op: "PREFIX_" + (op "body" "_SUFFIX");59 };64 };606561 # To be used with https://github.com/NixOS/nix/pull/889266 # To be used with https://github.com/NixOS/nix/pull/8892