feat: Add nix-expr library, update nix
(cherry picked from commit bf1ddd1c9137578b86895577d5b80e0c5771b605)
This commit is contained in:
parent
be8aa55a38
commit
6978e91fa9
11 changed files with 399 additions and 9 deletions
6
flake.lock
generated
6
flake.lock
generated
|
|
@ -173,11 +173,11 @@
|
||||||
"nixpkgs-regression": "nixpkgs-regression"
|
"nixpkgs-regression": "nixpkgs-regression"
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1705358697,
|
"lastModified": 1709227987,
|
||||||
"narHash": "sha256-1SjVVJKIXfSoymtIBCx8mPkhw6LpyihrtzMXdFFOH2M=",
|
"narHash": "sha256-ndFevohurD6MQCCBnOdHLDmsJ3vfpF5+cKizXvq5vmw=",
|
||||||
"owner": "tweag",
|
"owner": "tweag",
|
||||||
"repo": "nix",
|
"repo": "nix",
|
||||||
"rev": "eb456d68bd22e1296c2eca30fa06281de0da289e",
|
"rev": "0fd441d0bf6331a1152cdc091724b4648d187f90",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
|
|
||||||
|
|
@ -26,11 +26,8 @@
|
||||||
perSystem = { config, self', inputs', pkgs, ... }: {
|
perSystem = { config, self', inputs', pkgs, ... }: {
|
||||||
|
|
||||||
|
|
||||||
packages.nix = inputs'.nix.packages.nix.overrideAttrs {
|
|
||||||
# checkPhase does not seem to terminate.
|
packages.nix = inputs'.nix.packages.nix;
|
||||||
# TODO: remove override
|
|
||||||
doCheck = false;
|
|
||||||
};
|
|
||||||
|
|
||||||
pre-commit.settings.hooks.nixpkgs-fmt.enable = true;
|
pre-commit.settings.hooks.nixpkgs-fmt.enable = true;
|
||||||
# Temporarily disable rustfmt due to configuration issues
|
# Temporarily disable rustfmt due to configuration issues
|
||||||
|
|
|
||||||
22
rust/Cargo.lock
generated
22
rust/Cargo.lock
generated
|
|
@ -72,6 +72,16 @@ dependencies = [
|
||||||
"libloading",
|
"libloading",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ctor"
|
||||||
|
version = "0.2.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ad291aa74992b9b7a7e88c38acbbf6ad7e107f1d90ee8775b7bc1fc3394f485c"
|
||||||
|
dependencies = [
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "either"
|
name = "either"
|
||||||
version = "1.10.0"
|
version = "1.10.0"
|
||||||
|
|
@ -172,6 +182,18 @@ dependencies = [
|
||||||
"pkg-config",
|
"pkg-config",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "nix-expr"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
|
"ctor",
|
||||||
|
"lazy_static",
|
||||||
|
"nix-c-raw",
|
||||||
|
"nix-store",
|
||||||
|
"nix-util",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "nix-store"
|
name = "nix-store"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
[workspace]
|
[workspace]
|
||||||
members = [
|
members = [
|
||||||
"nix-c-raw",
|
"nix-c-raw",
|
||||||
|
"nix-expr",
|
||||||
"nix-util",
|
"nix-util",
|
||||||
"nix-store",
|
"nix-store",
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -37,6 +37,14 @@ fn c_headers() -> Vec<String> {
|
||||||
args.push(format!("-I{}", path.to_str().unwrap()));
|
args.push(format!("-I{}", path.to_str().unwrap()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for path in pkg_config::probe_library("bdw-gc")
|
||||||
|
.unwrap()
|
||||||
|
.include_paths
|
||||||
|
.iter()
|
||||||
|
{
|
||||||
|
args.push(format!("-I{}", path.to_str().unwrap()));
|
||||||
|
}
|
||||||
|
|
||||||
if let Ok(cflags) = std::env::var("RUST_NIX_C_RAW_EXTRA_CFLAGS") {
|
if let Ok(cflags) = std::env::var("RUST_NIX_C_RAW_EXTRA_CFLAGS") {
|
||||||
for flag in cflags.split_whitespace() {
|
for flag in cflags.split_whitespace() {
|
||||||
args.push(flag.to_string());
|
args.push(flag.to_string());
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,6 @@
|
||||||
#include <nix_api_util.h>
|
#include <nix_api_util.h>
|
||||||
#include <nix_api_store.h>
|
#include <nix_api_store.h>
|
||||||
|
#define GC_THREADS
|
||||||
|
#include <gc/gc.h>
|
||||||
#include <nix_api_expr.h>
|
#include <nix_api_expr.h>
|
||||||
#include <nix_api_value.h>
|
#include <nix_api_value.h>
|
||||||
|
|
|
||||||
14
rust/nix-expr/Cargo.toml
Normal file
14
rust/nix-expr/Cargo.toml
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
[package]
|
||||||
|
name = "nix-expr"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
anyhow = "1.0.79"
|
||||||
|
nix-store = { path = "../nix-store" }
|
||||||
|
nix-util = { path = "../nix-util" }
|
||||||
|
nix-c-raw = { path = "../nix-c-raw" }
|
||||||
|
lazy_static = "1.4.0"
|
||||||
|
ctor = "0.2.7"
|
||||||
265
rust/nix-expr/src/eval_state.rs
Normal file
265
rust/nix-expr/src/eval_state.rs
Normal file
|
|
@ -0,0 +1,265 @@
|
||||||
|
use crate::value::{Value, ValueType};
|
||||||
|
use anyhow::Context as _;
|
||||||
|
use anyhow::{bail, Result};
|
||||||
|
use lazy_static::lazy_static;
|
||||||
|
use nix_c_raw as raw;
|
||||||
|
use nix_store::store::Store;
|
||||||
|
use nix_util::context::Context;
|
||||||
|
use std::ffi::CString;
|
||||||
|
use std::ptr::null_mut;
|
||||||
|
use std::ptr::NonNull;
|
||||||
|
|
||||||
|
lazy_static! {
|
||||||
|
static ref INIT: Result<()> = {
|
||||||
|
unsafe {
|
||||||
|
raw::GC_allow_register_threads();
|
||||||
|
}
|
||||||
|
let context: Context = Context::new();
|
||||||
|
unsafe {
|
||||||
|
raw::nix_libexpr_init(context.ptr());
|
||||||
|
}
|
||||||
|
context.check_err()?;
|
||||||
|
Ok(())
|
||||||
|
};
|
||||||
|
}
|
||||||
|
pub fn init() -> Result<()> {
|
||||||
|
let x = INIT.as_ref();
|
||||||
|
match x {
|
||||||
|
Ok(_) => Ok(()),
|
||||||
|
Err(e) => {
|
||||||
|
// Couldn't just clone the error, so we have to print it here.
|
||||||
|
Err(anyhow::format_err!("nix_libstore_init error: {}", e))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct EvalState {
|
||||||
|
eval_state: NonNull<raw::EvalState>,
|
||||||
|
store: Store,
|
||||||
|
context: Context,
|
||||||
|
}
|
||||||
|
impl EvalState {
|
||||||
|
pub fn new(store: Store) -> Result<Self> {
|
||||||
|
let context = Context::new();
|
||||||
|
|
||||||
|
init()?;
|
||||||
|
|
||||||
|
let eval_state = unsafe {
|
||||||
|
raw::nix_state_create(
|
||||||
|
context.ptr(),
|
||||||
|
/* searchPath */ null_mut(),
|
||||||
|
store.raw_ptr(),
|
||||||
|
)
|
||||||
|
};
|
||||||
|
if eval_state.is_null() {
|
||||||
|
bail!("nix_state_create returned a null pointer");
|
||||||
|
}
|
||||||
|
Ok(EvalState {
|
||||||
|
eval_state: NonNull::new(eval_state).unwrap(),
|
||||||
|
store,
|
||||||
|
context,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
pub fn raw_ptr(&self) -> *mut raw::EvalState {
|
||||||
|
self.eval_state.as_ptr()
|
||||||
|
}
|
||||||
|
pub fn store(&self) -> &Store {
|
||||||
|
&self.store
|
||||||
|
}
|
||||||
|
pub fn eval_from_string(&self, expr: String, path: String) -> Result<Value> {
|
||||||
|
let expr_ptr =
|
||||||
|
CString::new(expr).with_context(|| "eval_from_string: expr contains null byte")?;
|
||||||
|
let path_ptr =
|
||||||
|
CString::new(path).with_context(|| "eval_from_string: path contains null byte")?;
|
||||||
|
let value = self.new_value_uninitialized();
|
||||||
|
unsafe {
|
||||||
|
let ctx_ptr = self.context.ptr();
|
||||||
|
raw::nix_expr_eval_from_string(
|
||||||
|
ctx_ptr,
|
||||||
|
self.raw_ptr(),
|
||||||
|
expr_ptr.as_ptr(),
|
||||||
|
path_ptr.as_ptr(),
|
||||||
|
value.raw_ptr(),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
self.context.check_err()?;
|
||||||
|
Ok(value)
|
||||||
|
}
|
||||||
|
/** Try turn any Value into a Value that isn't a Thunk. */
|
||||||
|
pub fn force(&self, v: &Value) -> Result<()> {
|
||||||
|
unsafe {
|
||||||
|
raw::nix_value_force(self.context.ptr(), self.raw_ptr(), v.raw_ptr());
|
||||||
|
}
|
||||||
|
self.context.check_err()
|
||||||
|
}
|
||||||
|
pub fn value_is_thunk(&self, value: &Value) -> bool {
|
||||||
|
let r = unsafe {
|
||||||
|
raw::nix_get_type(self.context.ptr(), value.raw_ptr()) == raw::ValueType_NIX_TYPE_THUNK
|
||||||
|
};
|
||||||
|
self.context.check_err().unwrap();
|
||||||
|
r
|
||||||
|
}
|
||||||
|
pub fn value_type(&self, value: &Value) -> Result<ValueType> {
|
||||||
|
if self.value_is_thunk(value) {
|
||||||
|
self.force(value)?;
|
||||||
|
}
|
||||||
|
let r = unsafe { raw::nix_get_type(self.context.ptr(), value.raw_ptr()) };
|
||||||
|
Ok(ValueType::from_raw(r))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn new_value_uninitialized(&self) -> Value {
|
||||||
|
let value = unsafe { raw::nix_alloc_value(self.context.ptr(), self.raw_ptr()) };
|
||||||
|
Value::new(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn gc_now() {
|
||||||
|
unsafe {
|
||||||
|
raw::nix_gc_now();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Run a function while making sure that the current thread is registered with the GC. */
|
||||||
|
pub fn gc_registering_current_thread<F, R>(f: F) -> Result<R>
|
||||||
|
where
|
||||||
|
F: FnOnce() -> R,
|
||||||
|
{
|
||||||
|
init()?;
|
||||||
|
if unsafe { raw::GC_thread_is_registered() } != 0 {
|
||||||
|
return Ok(f());
|
||||||
|
} else {
|
||||||
|
gc_register_my_thread().unwrap();
|
||||||
|
let r = f();
|
||||||
|
unsafe {
|
||||||
|
raw::GC_unregister_my_thread();
|
||||||
|
}
|
||||||
|
return Ok(r);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn gc_register_my_thread() -> Result<()> {
|
||||||
|
unsafe {
|
||||||
|
let already_done = raw::GC_thread_is_registered();
|
||||||
|
if already_done != 0 {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
let mut sb: raw::GC_stack_base = raw::GC_stack_base {
|
||||||
|
mem_base: 0 as *mut _,
|
||||||
|
};
|
||||||
|
let r = raw::GC_get_stack_base(&mut sb);
|
||||||
|
if r as u32 != raw::GC_SUCCESS {
|
||||||
|
Err(anyhow::format_err!("GC_get_stack_base failed: {}", r))?;
|
||||||
|
}
|
||||||
|
raw::GC_register_my_thread(&sb);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for EvalState {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
unsafe {
|
||||||
|
raw::nix_state_free(self.raw_ptr());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use ctor::ctor;
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[ctor]
|
||||||
|
fn setup() {
|
||||||
|
init().unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn eval_state_new_and_drop() {
|
||||||
|
gc_registering_current_thread(|| {
|
||||||
|
// very basic test: make sure initialization doesn't crash
|
||||||
|
let store = Store::open("auto").unwrap();
|
||||||
|
let _e = EvalState::new(store).unwrap();
|
||||||
|
})
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn eval_state_eval_from_string() {
|
||||||
|
gc_registering_current_thread(|| {
|
||||||
|
let store = Store::open("auto").unwrap();
|
||||||
|
let es = EvalState::new(store).unwrap();
|
||||||
|
let v = es
|
||||||
|
.eval_from_string("1".to_string(), "<test>".to_string())
|
||||||
|
.unwrap();
|
||||||
|
let v2 = v.clone();
|
||||||
|
es.force(&v).unwrap();
|
||||||
|
let t = es.value_type(&v).unwrap();
|
||||||
|
assert!(t == ValueType::Int);
|
||||||
|
let t2 = es.value_type(&v2).unwrap();
|
||||||
|
assert!(t2 == ValueType::Int);
|
||||||
|
gc_now();
|
||||||
|
})
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn eval_state_value_bool() {
|
||||||
|
gc_registering_current_thread(|| {
|
||||||
|
let store = Store::open("auto").unwrap();
|
||||||
|
let es = EvalState::new(store).unwrap();
|
||||||
|
let v = es
|
||||||
|
.eval_from_string("true".to_string(), "<test>".to_string())
|
||||||
|
.unwrap();
|
||||||
|
es.force(&v).unwrap();
|
||||||
|
let t = es.value_type(&v).unwrap();
|
||||||
|
assert!(t == ValueType::Bool);
|
||||||
|
})
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn eval_state_value_string() {
|
||||||
|
gc_registering_current_thread(|| {
|
||||||
|
let store = Store::open("auto").unwrap();
|
||||||
|
let es = EvalState::new(store).unwrap();
|
||||||
|
let v = es
|
||||||
|
.eval_from_string("\"hello\"".to_string(), "<test>".to_string())
|
||||||
|
.unwrap();
|
||||||
|
es.force(&v).unwrap();
|
||||||
|
let t = es.value_type(&v).unwrap();
|
||||||
|
assert!(t == ValueType::String);
|
||||||
|
})
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn eval_state_value_attrset() {
|
||||||
|
gc_registering_current_thread(|| {
|
||||||
|
let store = Store::open("auto").unwrap();
|
||||||
|
let es = EvalState::new(store).unwrap();
|
||||||
|
let v = es
|
||||||
|
.eval_from_string("{ }".to_string(), "<test>".to_string())
|
||||||
|
.unwrap();
|
||||||
|
es.force(&v).unwrap();
|
||||||
|
let t = es.value_type(&v).unwrap();
|
||||||
|
assert!(t == ValueType::AttrSet);
|
||||||
|
})
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn eval_state_value_list() {
|
||||||
|
gc_registering_current_thread(|| {
|
||||||
|
let store = Store::open("auto").unwrap();
|
||||||
|
let es = EvalState::new(store).unwrap();
|
||||||
|
let v = es
|
||||||
|
.eval_from_string("[ ]".to_string(), "<test>".to_string())
|
||||||
|
.unwrap();
|
||||||
|
es.force(&v).unwrap();
|
||||||
|
let t = es.value_type(&v).unwrap();
|
||||||
|
assert!(t == ValueType::List);
|
||||||
|
})
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
2
rust/nix-expr/src/lib.rs
Normal file
2
rust/nix-expr/src/lib.rs
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
pub mod eval_state;
|
||||||
|
pub mod value;
|
||||||
75
rust/nix-expr/src/value.rs
Normal file
75
rust/nix-expr/src/value.rs
Normal file
|
|
@ -0,0 +1,75 @@
|
||||||
|
use nix_c_raw as raw;
|
||||||
|
use nix_util::context::Context;
|
||||||
|
use std::ptr::NonNull;
|
||||||
|
|
||||||
|
// TODO: test: cloning a thunk does not duplicate the evaluation.
|
||||||
|
|
||||||
|
/** The type of a value (or thunk) */
|
||||||
|
#[derive(Eq, PartialEq)]
|
||||||
|
pub enum ValueType {
|
||||||
|
AttrSet,
|
||||||
|
Bool,
|
||||||
|
External,
|
||||||
|
Float,
|
||||||
|
Function,
|
||||||
|
Int,
|
||||||
|
List,
|
||||||
|
Null,
|
||||||
|
Path,
|
||||||
|
String,
|
||||||
|
Thunk,
|
||||||
|
Unknown,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ValueType {
|
||||||
|
pub(crate) fn from_raw(raw: raw::ValueType) -> ValueType {
|
||||||
|
match raw {
|
||||||
|
raw::ValueType_NIX_TYPE_ATTRS => ValueType::AttrSet,
|
||||||
|
raw::ValueType_NIX_TYPE_BOOL => ValueType::Bool,
|
||||||
|
raw::ValueType_NIX_TYPE_EXTERNAL => ValueType::External,
|
||||||
|
raw::ValueType_NIX_TYPE_FLOAT => ValueType::Float,
|
||||||
|
raw::ValueType_NIX_TYPE_FUNCTION => ValueType::Function,
|
||||||
|
raw::ValueType_NIX_TYPE_INT => ValueType::Int,
|
||||||
|
raw::ValueType_NIX_TYPE_LIST => ValueType::List,
|
||||||
|
raw::ValueType_NIX_TYPE_NULL => ValueType::Null,
|
||||||
|
raw::ValueType_NIX_TYPE_PATH => ValueType::Path,
|
||||||
|
raw::ValueType_NIX_TYPE_STRING => ValueType::String,
|
||||||
|
raw::ValueType_NIX_TYPE_THUNK => ValueType::Thunk,
|
||||||
|
_ => ValueType::Unknown,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* A pointer to a value or thunk, to be used with EvalState methods. */
|
||||||
|
pub struct Value {
|
||||||
|
inner: NonNull<raw::Value>,
|
||||||
|
}
|
||||||
|
impl Value {
|
||||||
|
pub(crate) fn new(inner: *mut raw::Value) -> Self {
|
||||||
|
Value {
|
||||||
|
inner: NonNull::new(inner).unwrap(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub(crate) fn raw_ptr(&self) -> *mut raw::Value {
|
||||||
|
self.inner.as_ptr()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Drop for Value {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
let context = Context::new();
|
||||||
|
unsafe {
|
||||||
|
raw::nix_gc_decref(context.ptr(), self.inner.as_ptr());
|
||||||
|
}
|
||||||
|
// ignore error from context, because drop should not panic
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Clone for Value {
|
||||||
|
fn clone(&self) -> Self {
|
||||||
|
let context = Context::new();
|
||||||
|
unsafe { raw::nix_gc_incref(context.ptr(), self.inner.as_ptr()) };
|
||||||
|
context.check_err().unwrap();
|
||||||
|
Value { inner: self.inner }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tested in eval_state.rs
|
||||||
|
|
@ -29,7 +29,7 @@ impl StoreRef {
|
||||||
impl Drop for StoreRef {
|
impl Drop for StoreRef {
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
unsafe {
|
unsafe {
|
||||||
raw::nix_store_unref(self.inner.as_ptr());
|
raw::nix_store_free(self.inner.as_ptr());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -73,6 +73,10 @@ impl Store {
|
||||||
Ok(store)
|
Ok(store)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn raw_ptr(&self) -> *mut raw::Store {
|
||||||
|
self.inner.ptr()
|
||||||
|
}
|
||||||
|
|
||||||
pub fn get_uri(&self) -> Result<String> {
|
pub fn get_uri(&self) -> Result<String> {
|
||||||
const N: usize = 1024;
|
const N: usize = 1024;
|
||||||
let mut buffer: [MaybeUninit<u8>; N] = unsafe { MaybeUninit::uninit().assume_init() };
|
let mut buffer: [MaybeUninit<u8>; N] = unsafe { MaybeUninit::uninit().assume_init() };
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue