Compare commits

...

5 commits

Author SHA1 Message Date
eb2a5d0ec2
support nix_register_plugin extension
support nix_register_plugin extension
2026-04-10 12:25:13 +10:00
f76658fcfb
add libnixide-c extensions to FlakeLockFlags 2026-04-10 11:53:44 +10:00
378569ff24
rename flake_reference -> flakeref 2026-04-10 11:52:03 +10:00
9f90e0c323
produce libnixide-c bindings 2026-04-10 11:51:27 +10:00
93d76c002e
move Store logic into StorePath 2026-04-10 10:49:04 +10:00
19 changed files with 520 additions and 322 deletions

View file

@ -1,5 +1,6 @@
use std::env;
use std::fs;
use std::iter;
use std::path::PathBuf;
use bindgen::RustEdition;
@ -76,36 +77,41 @@ impl ParseCallbacks for BindfmtCallbacks {
}
}
const LIBS: &[&'static str] = &[
const FEATURES: &[&'static str] = &[
#[cfg(feature = "nix-util-c")]
"nix-util-c",
"util",
#[cfg(feature = "nix-store-c")]
"nix-store-c",
"store",
#[cfg(feature = "nix-expr-c")]
"nix-expr-c",
"expr",
#[cfg(feature = "nix-fetchers-c")]
"nix-fetchers-c",
"fetchers",
#[cfg(feature = "nix-flake-c")]
"nix-flake-c",
"flake",
#[cfg(feature = "nix-main-c")]
"nix-main-c",
"main",
];
fn main() {
let libs: Vec<pkg_config::Library> = LIBS
let libs: Vec<pkg_config::Library> = FEATURES
.iter()
.map(|&name| {
.map(|&feature| {
let name = &format!("nix-{feature}-c");
pkg_config::probe_library(name).expect(&format!("Unable to find .pc file for {}", name))
})
.collect();
#[allow(unused)]
let include_paths: Vec<PathBuf> = libs
.clone()
.into_iter()
.map(|lib| lib.include_paths)
.flatten()
.chain(iter::once(fs::canonicalize("./libnixide-c").unwrap()))
.unique()
.collect();
#[allow(unused)]
let link_paths: Vec<PathBuf> = libs
.clone()
.into_iter()
@ -114,20 +120,7 @@ fn main() {
.unique()
.collect();
// ::DEBUG:DEBUG::
// for path in link_paths {
// println!("cargo::rustc-link-lib={}", path.display());
// }
// ::DEBUG:DEBUG::
let clang_args: Vec<String> = vec!["-x", "c++", "-std=c++23"]
.into_iter()
.map(|s: &str| s.to_owned())
.chain(include_paths.iter().map(|p| format!("-I{}", p.display())))
.collect();
dbg!(&clang_args);
// build the libnixide-c extension
cc::Build::new()
// .cargo_output(true)
// .cargo_warnings(true)
@ -135,15 +128,19 @@ fn main() {
// .cargo_debug(cfg!(debug_assertions))
.cpp(true)
.std("c++23") // libnix compiles against `-std=c++23`
.cpp_link_stdlib("stdc++") // use libstdc++
.flags(["-fconcepts-diagnostics-depth=2"])
.file("libnixide-c/nixide_api_flake.cc")
.file("libnixide-c/nixide_api_fetchers.cc")
// .files(LIBS.iter().map(|s| (*s).strip_prefix("nix-").unwrap().strip_suffix("-c").unwrap()))
.cpp_link_stdlib("c++") // libstdc++ for GNU, c++ for Clang
.opt_level((!cfg!(debug_assertions)) as u32 * 3)
.files(FEATURES.iter().map(|&feature| format!("libnixide-c/nixide_api_{feature}.cc")))
.includes(&include_paths)
.compile("nixide");
.compile("nixide-c"); // libnixide-c
let clang_args: Vec<String> = vec!["-x", "c++", "-std=c++23"]
.into_iter()
.map(|s: &str| s.to_owned())
.chain(include_paths.iter().map(|p| format!("-I{}", p.display())))
.collect();
dbg!(&clang_args);
let mut builder = bindgen::Builder::default()
.rust_edition(RustEdition::Edition2024)
.clang_args(clang_args)
@ -156,8 +153,8 @@ fn main() {
.rustfmt_configuration_file(std::fs::canonicalize("rustfmt.toml").ok())
// Control allow/block listing
.allowlist_recursively(true)
// .allowlist_file(r".*nix_api_[a-z]+(_internal)?\.h")
.allowlist_file(r".*nix_api_[a-z]+(/[a-z_]+)?\.h")
.allowlist_file(r".*nixide_api_[a-z]+(/[a-z_]+)?\.h")
// .layout_tests(false) // DEBUG
.use_core() // use ::core instead of ::std
@ -181,10 +178,10 @@ fn main() {
.raw_line("/** These bindings were auto-generated for the Nixide project (https://github.com/cry128/nixide) */");
// Register the input headers we would like to generate bindings for
builder = LIBS
builder = FEATURES
.iter()
.map(|lib| {
let path = format!("include/{}.h", lib.strip_suffix("-c").unwrap());
.map(|&feature| {
let path = format!("include/nix-{feature}.h");
assert!(fs::exists(&path).unwrap());
// Invalidate the built crate if the binding headers change
println!("cargo::rerun-if-changed={path}");

View file

@ -2,8 +2,8 @@
#define NIXIDE_EXPR
// Nix C API for the Nix expressions evaluator.
//
#include <nix_api_expr.h>
// #include <nix_api_expr_internal.h>
// Nix C API for value manipulation.
//
@ -13,4 +13,8 @@
//
#include <nix_api_external.h>
// Nixide C API extensions for the Nix expressions evaluator.
//
#include <nixide_api_expr.h>
#endif

View file

@ -1,10 +1,12 @@
#ifndef NIXIDE_FETCHERS
#define NIXIDE_FETCHERS
// #include <nix_api_fetchers_internal.hh>
// Nix C API for fetcher operations.
//
#include <nix_api_fetchers.h>
// Nixide C API extensions for fetcher operations.
//
#include <nixide_api_fetchers.h>
#endif

View file

@ -1,10 +1,12 @@
#ifndef NIXIDE_FLAKE
#define NIXIDE_FLAKE
// #include <nix_api_flake_internal.hh>
// Nix C API for flake support.
//
#include <nix_api_flake.h>
// Nixide C API extensions for flake support.
//
#include <nixide_api_flake.h>
#endif

View file

@ -5,4 +5,8 @@
//
#include <nix_api_main.h>
// Nixide C API extensions for CLI support.
//
#include <nixide_api_main.h>
#endif

View file

@ -4,9 +4,17 @@
// Nix C API for store operations.
//
#include <nix_api_store.h>
// #include <nix_api_store_internal.h>
// Nix C API for derivation operations that don't require a store.
//
#include <nix_api_store/derivation.h>
// Nix C API for store path operations that don't require a store.
//
#include <nix_api_store/store_path.h>
// Nixide C API extensions for store operations.
//
#include <nixide_api_store.h>
#endif

View file

@ -8,6 +8,9 @@
// the Nix C APIs for error handling.
//
#include <nix_api_util.h>
// #include <nix_api_util_internal.h>
// Nixide C API extensions for utilities.
//
#include <nixide_api_util.h>
#endif

View file

@ -7,7 +7,7 @@
extern "C" {
nix_err nixide_register_plugin(nix_c_context * context, char * plugin)
nix_err nix_register_plugin(nix_c_context * context, char * plugin)
{
if (context)
context->last_err_code = NIX_OK;

View file

@ -8,7 +8,7 @@ extern "C" {
#endif
// NOTE: all plugins should be registered BEFORE `nix_init_plugins` is run
nix_err nixide_register_plugin(nix_c_context * context, char * plugin);
nix_err nix_register_plugin(nix_c_context * context, char * plugin);
#ifdef __cplusplus
} // extern "C"

View file

@ -78,7 +78,7 @@ impl<'a> RealisedString<'a> {
path: Self::parse_path(inner.as_ptr(), state.store_ref().clone()),
children: LazyArray::new(
size,
Box::new(|_| StorePath::fake_path(state.store_ref().clone()).unwrap()),
Box::new(|_| StorePath::fake_path(state.store_ref().clone())),
),
})
}
@ -98,7 +98,7 @@ impl<'a> RealisedString<'a> {
err
)
});
StorePath::parse(store, &path_str).unwrap_or_else(|err| {
StorePath::new(store, &path_str).unwrap_or_else(|err| {
panic_issue_call_failed!(
"`sys::nix_realised_string_get_buffer_(start|size)` invalid store path ({})",
err

View file

@ -1,3 +1,4 @@
use std::ffi::c_char;
use std::ptr::NonNull;
use super::{FlakeRef, FlakeSettings};
@ -77,6 +78,60 @@ impl FlakeLockFlags {
Ok(FlakeLockFlags { inner })
}
/// Adds an input override to the lock file that will be produced.
/// The [LockedFlake::lock] operation will not write to the lock file.
///
/// # Warning
///
/// Calling this function will implicitly set the [FlakeLockMode] to
/// [FlakeLockMode::Virtual] if `self.mode` is not [FlakeLockMode::Check].
///
/// # Arguments
///
/// * `path` - The input name/path to override (must not be empty)
/// * `flakeref` - The flake reference to use as the override
///
#[allow(unused)]
pub fn override_input(self, input: &str, flakeref: &FlakeRef) -> NixideResult<Self> {
// XXX: TODO: should `input` be wrapped as `format!("inputs.{input}")`?
let input_path = input.as_c_ptr()?;
wrap::nix_fn!(|ctx: &ErrorContext| unsafe {
sys::nix_flake_lock_flags_add_input_override(
ctx.as_ptr(),
self.as_ptr(),
input_path,
flakeref.as_ptr(),
);
})?;
Ok(self)
}
/// Adds an input update to the lock file that will be produced.
/// Meaning the current pinned version is ignored, and updated
/// to the latest version the fetcher can resolve.
///
/// This is equivalent to running `nix flake update ${input}`.
///
/// # Nix C API Internals
///
/// This binding is **not provided by the Nix C API.**
/// It is instead **exposed by the Nixide C API extensions.**
///
#[allow(unused)]
pub fn update_input(self, input: &str) -> NixideResult<Self> {
// XXX: TODO: should `input` be wrapped as `format!("inputs.{input}")`?
let input_path = input.as_c_ptr()?;
wrap::nix_fn!(|ctx: &ErrorContext| unsafe {
sys::nix_flake_lock_flags_add_input_update(ctx.as_ptr(), self.as_ptr(), input_path);
})?;
Ok(self)
}
#[allow(unused)]
pub fn set_mode(self, mode: FlakeLockMode) -> NixideResult<Self> {
wrap::nix_fn!(|ctx: &ErrorContext| {
match mode {
@ -90,33 +145,176 @@ impl FlakeLockFlags {
sys::nix_flake_lock_flags_set_mode_check(ctx.as_ptr(), self.as_ptr())
},
};
});
})?;
Ok(self)
}
/// Adds an input override to the lock file that will be produced.
/// The [LockedFlake::lock] operation will not write to the lock file.
/// # Nix C API Internals
///
/// # Warning
/// This binding is **not provided by the Nix C API.**
/// It is instead **exposed by the Nixide C API extensions.**
///
/// Calling this function will implicitly set the [FlakeLockMode] to
/// [FlakeLockMode::Virtual] if `self.mode` is not [FlakeLockMode::Check].
#[allow(unused)]
pub fn recreate_lock_file(self, value: bool) -> Self {
wrap::nix_fn!(|ctx: &ErrorContext| unsafe {
sys::nix_flake_lock_flags_set_recreate_lock_file(ctx.as_ptr(), self.as_ptr(), value)
})
.unwrap();
self
}
/// # Nix C API Internals
///
/// # Arguments
/// This binding is **not provided by the Nix C API.**
/// It is instead **exposed by the Nixide C API extensions.**
///
/// * `path` - The input name/path to override (must not be empty)
/// * `flakeref` - The flake reference to use as the override
pub fn override_input(&mut self, path: &str, flakeref: &FlakeRef) -> NixideResult<()> {
let input_path = path.as_c_ptr()?;
#[allow(unused)]
pub fn update_lock_file(self, value: bool) -> Self {
wrap::nix_fn!(|ctx: &ErrorContext| unsafe {
sys::nix_flake_lock_flags_set_update_lock_file(ctx.as_ptr(), self.as_ptr(), value)
})
.unwrap();
self
}
/// # Nix C API Internals
///
/// This binding is **not provided by the Nix C API.**
/// It is instead **exposed by the Nixide C API extensions.**
///
#[allow(unused)]
pub fn write_lock_file(self, value: bool) -> Self {
wrap::nix_fn!(|ctx: &ErrorContext| unsafe {
sys::nix_flake_lock_flags_set_write_lock_file(ctx.as_ptr(), self.as_ptr(), value)
})
.unwrap();
self
}
/// # Nix C API Internals
///
/// This binding is **not provided by the Nix C API.**
/// It is instead **exposed by the Nixide C API extensions.**
///
#[allow(unused)]
pub fn commit_lock_file(self, value: bool) -> Self {
wrap::nix_fn!(|ctx: &ErrorContext| unsafe {
sys::nix_flake_lock_flags_set_commit_lock_file(ctx.as_ptr(), self.as_ptr(), value)
})
.unwrap();
self
}
/// # Nix C API Internals
///
/// This binding is **not provided by the Nix C API.**
/// It is instead **exposed by the Nixide C API extensions.**
///
#[allow(unused)]
pub fn allow_unlocked(self, value: bool) -> Self {
wrap::nix_fn!(|ctx: &ErrorContext| unsafe {
sys::nix_flake_lock_flags_set_allow_unlocked(ctx.as_ptr(), self.as_ptr(), value)
})
.unwrap();
self
}
/// # Nix C API Internals
///
/// This binding is **not provided by the Nix C API.**
/// It is instead **exposed by the Nixide C API extensions.**
///
#[allow(unused)]
pub fn fail_on_unlocked(self, value: bool) -> Self {
wrap::nix_fn!(|ctx: &ErrorContext| unsafe {
sys::nix_flake_lock_flags_set_fail_on_unlocked(ctx.as_ptr(), self.as_ptr(), value)
})
.unwrap();
self
}
/// # Nix C API Internals
///
/// This binding is **not provided by the Nix C API.**
/// It is instead **exposed by the Nixide C API extensions.**
///
#[allow(unused)]
pub fn use_registries(self, value: bool) -> Self {
wrap::nix_fn!(|ctx: &ErrorContext| unsafe {
sys::nix_flake_lock_flags_set_use_registries(ctx.as_ptr(), self.as_ptr(), value)
})
.unwrap();
self
}
/// # Nix C API Internals
///
/// This binding is **not provided by the Nix C API.**
/// It is instead **exposed by the Nixide C API extensions.**
///
#[allow(unused)]
pub fn apply_nix_config(self, value: bool) -> Self {
wrap::nix_fn!(|ctx: &ErrorContext| unsafe {
sys::nix_flake_lock_flags_set_apply_nix_config(ctx.as_ptr(), self.as_ptr(), value)
})
.unwrap();
self
}
/// # Errors
/// Fails if the given `path` contains a NUL byte.
///
/// # Nix C API Internals
///
/// This binding is **not provided by the Nix C API.**
/// It is instead **exposed by the Nixide C API extensions.**
///
#[allow(unused)]
pub fn input_lock_file_path(self, path: &str) -> NixideResult<Self> {
let path_ptr = path.as_c_ptr()? as *mut c_char;
wrap::nix_fn!(|ctx: &ErrorContext| unsafe {
sys::nix_flake_lock_flags_add_input_override(
sys::nix_flake_lock_flags_set_reference_lock_file_path(
ctx.as_ptr(),
self.as_ptr(),
input_path,
flakeref.as_ptr(),
);
path_ptr,
)
})
.unwrap();
Ok(self)
}
/// # Errors
/// Fails if the given `path` contains a NUL byte.
///
/// # Nix C API Internals
///
/// This binding is **not provided by the Nix C API.**
/// It is instead **exposed by the Nixide C API extensions.**
///
#[allow(unused)]
pub fn output_lock_file_path(self, path: &str) -> NixideResult<Self> {
let path_ptr = path.as_c_ptr()? as *mut c_char;
wrap::nix_fn!(|ctx: &ErrorContext| unsafe {
sys::nix_flake_lock_flags_set_output_lock_file_path(
ctx.as_ptr(),
self.as_ptr(),
path_ptr,
)
})
.unwrap();
Ok(self)
}
}

View file

@ -1,104 +0,0 @@
use std::ffi::{c_char, c_void};
use std::ptr::{NonNull, null_mut};
use super::{FetchersSettings, FlakeRefParseFlags, FlakeSettings};
use crate::NixideError;
use crate::errors::{ErrorContext, new_nixide_error};
use crate::sys;
use crate::util::wrap;
use crate::util::wrappers::AsInnerPtr;
pub struct FlakeRef {
inner: NonNull<sys::NixFlakeReference>,
fragment: String,
fetch_settings: FetchersSettings,
flake_settings: FlakeSettings,
}
// impl Clone for FlakeReference {
// fn clone(&self) -> Self {
// wrap::nix_fn!(|ctx: &ErrorContext| unsafe {
// sys::nix_gc_incref(ctx.as_ptr(), self.as_ptr() as *mut c_void);
// })
// .unwrap();
//
// Self {
// inner: self.inner.clone(),
// fragment: self.fragment.clone(),
// }
// }
// }
impl Drop for FlakeRef {
fn drop(&mut self) {
unsafe {
sys::nix_flake_reference_free(self.as_ptr());
}
}
}
impl AsInnerPtr<sys::NixFlakeReference> for FlakeRef {
#[inline]
unsafe fn as_ptr(&self) -> *mut sys::NixFlakeReference {
self.inner.as_ptr()
}
#[inline]
unsafe fn as_ref(&self) -> &sys::NixFlakeReference {
unsafe { self.inner.as_ref() }
}
#[inline]
unsafe fn as_mut(&mut self) -> &mut sys::NixFlakeReference {
unsafe { self.inner.as_mut() }
}
}
impl FlakeRef {
/// Parse a flake reference from a string.
/// The string must be a valid flake reference, such as `github:owner/repo`.
/// It may also be suffixed with a `#` and a fragment, such as `github:owner/repo#something`,
/// in which case, the returned string will contain the fragment.
pub fn parse<S: AsRef<str>>(reference: S) -> Result<FlakeRef, NixideError> {
let fetch_settings = FetchersSettings::new()?;
let flake_settings = FlakeSettings::new()?;
let parse_flags = FlakeRefParseFlags::new(&flake_settings)?;
let mut ptr: *mut sys::NixFlakeReference = null_mut();
let fragment = wrap::nix_string_callback!(
|callback, userdata: *mut __UserData, ctx: &ErrorContext| unsafe {
sys::nix_flake_reference_and_fragment_from_string(
ctx.as_ptr(),
fetch_settings.as_ptr(),
flake_settings.as_ptr(),
parse_flags.as_ptr(),
reference.as_ref().as_ptr() as *const c_char,
reference.as_ref().len(),
&mut ptr,
Some(callback),
userdata as *mut c_void,
)
}
)?;
match NonNull::new(ptr) {
Some(inner) => Ok(FlakeRef {
inner,
fragment,
fetch_settings,
flake_settings,
}),
None => Err(new_nixide_error!(NullPtr)),
}
}
// XXX: TODO: is it possible to get the URI string itself? (minus the fragment part?)
/// Get a shared reference to the URI fragment part.
///
#[inline]
#[allow(unused)]
pub fn fragment(&self) -> &str {
&self.fragment
}
}

View file

@ -1,3 +1,104 @@
pub struct FlakeRef {}
use std::ffi::{c_char, c_void};
use std::ptr::{NonNull, null_mut};
impl FlakeRef {}
use super::{FetchersSettings, FlakeRefParseFlags, FlakeSettings};
use crate::NixideError;
use crate::errors::{ErrorContext, new_nixide_error};
use crate::sys;
use crate::util::wrap;
use crate::util::wrappers::AsInnerPtr;
pub struct FlakeRef {
inner: NonNull<sys::NixFlakeReference>,
fragment: String,
fetch_settings: FetchersSettings,
flake_settings: FlakeSettings,
}
// impl Clone for FlakeReference {
// fn clone(&self) -> Self {
// wrap::nix_fn!(|ctx: &ErrorContext| unsafe {
// sys::nix_gc_incref(ctx.as_ptr(), self.as_ptr() as *mut c_void);
// })
// .unwrap();
//
// Self {
// inner: self.inner.clone(),
// fragment: self.fragment.clone(),
// }
// }
// }
impl Drop for FlakeRef {
fn drop(&mut self) {
unsafe {
sys::nix_flake_reference_free(self.as_ptr());
}
}
}
impl AsInnerPtr<sys::NixFlakeReference> for FlakeRef {
#[inline]
unsafe fn as_ptr(&self) -> *mut sys::NixFlakeReference {
self.inner.as_ptr()
}
#[inline]
unsafe fn as_ref(&self) -> &sys::NixFlakeReference {
unsafe { self.inner.as_ref() }
}
#[inline]
unsafe fn as_mut(&mut self) -> &mut sys::NixFlakeReference {
unsafe { self.inner.as_mut() }
}
}
impl FlakeRef {
/// Parse a flake reference from a string.
/// The string must be a valid flake reference, such as `github:owner/repo`.
/// It may also be suffixed with a `#` and a fragment, such as `github:owner/repo#something`,
/// in which case, the returned string will contain the fragment.
pub fn parse<S: AsRef<str>>(reference: S) -> Result<FlakeRef, NixideError> {
let fetch_settings = FetchersSettings::new()?;
let flake_settings = FlakeSettings::new()?;
let parse_flags = FlakeRefParseFlags::new(&flake_settings)?;
let mut ptr: *mut sys::NixFlakeReference = null_mut();
let fragment = wrap::nix_string_callback!(
|callback, userdata: *mut __UserData, ctx: &ErrorContext| unsafe {
sys::nix_flake_reference_and_fragment_from_string(
ctx.as_ptr(),
fetch_settings.as_ptr(),
flake_settings.as_ptr(),
parse_flags.as_ptr(),
reference.as_ref().as_ptr() as *const c_char,
reference.as_ref().len(),
&mut ptr,
Some(callback),
userdata as *mut c_void,
)
}
)?;
match NonNull::new(ptr) {
Some(inner) => Ok(FlakeRef {
inner,
fragment,
fetch_settings,
flake_settings,
}),
None => Err(new_nixide_error!(NullPtr)),
}
}
// XXX: TODO: is it possible to get the URI string itself? (minus the fragment part?)
/// Get a shared reference to the URI fragment part.
///
#[inline]
#[allow(unused)]
pub fn fragment(&self) -> &str {
&self.fragment
}
}

View file

@ -1,13 +1,13 @@
mod fetchers_settings;
mod flake_lock_flags;
mod flake_reference;
mod flake_reference_parse_flags;
mod flake_settings;
mod flakeref;
mod flakeref_parse_flags;
mod locked_flake;
use fetchers_settings::FetchersSettings;
use flake_lock_flags::{FlakeLockFlags, FlakeLockMode};
use flake_reference::FlakeRef;
use flake_reference_parse_flags::FlakeRefParseFlags;
pub use flake_settings::FlakeSettings;
use flakeref::FlakeRef;
use flakeref_parse_flags::FlakeRefParseFlags;
pub use locked_flake::LockedFlake;

View file

@ -1,5 +1,6 @@
use crate::NixideResult;
use crate::errors::ErrorContext;
use crate::stdext::AsCPtr as _;
use crate::util::wrap;
use crate::util::wrappers::AsInnerPtr as _;
@ -8,3 +9,10 @@ pub fn load_plugins() -> NixideResult<()> {
sys::nix_init_plugins(ctx.as_ptr());
})
}
pub fn register_plugin<S: AsRef<str>>(path: S) -> NixideResult<()> {
let path_ptr = path.as_ref().into_c_ptr()?;
wrap::nix_fn!(|ctx: &ErrorContext| unsafe {
sys::nix_register_plugin(ctx.as_ptr(), path_ptr);
})
}

View file

@ -15,10 +15,10 @@ use std::rc::Rc;
use crate::NixideResult;
use crate::errors::ErrorContext;
use crate::stdext::{AsCPtr as _, CCharPtrExt as _};
use crate::stdext::AsCPtr as _;
use crate::sys;
use crate::util::wrap;
use crate::util::wrappers::AsInnerPtr;
use crate::util::{panic_issue_call_failed, wrap};
/// Nix store for managing packages and derivations.
///
@ -28,19 +28,6 @@ pub struct Store {
inner: NonNull<sys::Store>,
}
// impl Clone for Store {
// fn clone(&self) -> Self {
// let inner = self.inner.clone();
//
// wrap::nix_fn!(|ctx: &ErrorContext| unsafe {
// sys::nix_gc_incref(ctx.as_ptr(), self.as_ptr() as *mut c_void);
// })
// .unwrap();
//
// Self { inner }
// }
// }
impl AsInnerPtr<sys::Store> for Store {
#[inline]
unsafe fn as_ptr(&self) -> *mut sys::Store {
@ -71,17 +58,17 @@ impl Store {
/// Returns an error if the store cannot be opened.
///
pub fn open(uri: &str) -> NixideResult<Rc<RefCell<Self>>> {
Self::open_ptr(uri.as_c_ptr()?)
unsafe { Self::open_ptr(uri.as_c_ptr()?) }
}
/// Opens a connection to the default Nix store.
///
pub fn default() -> NixideResult<Rc<RefCell<Self>>> {
Self::open_ptr(null())
unsafe { Self::open_ptr(null()) }
}
#[inline]
fn open_ptr(uri_ptr: *const c_char) -> NixideResult<Rc<RefCell<Self>>> {
unsafe fn open_ptr(uri_ptr: *const c_char) -> NixideResult<Rc<RefCell<Self>>> {
let inner = wrap::nix_ptr_fn!(|ctx: &ErrorContext| unsafe {
// XXX: TODO: allow args to be parsed instead of just `null_mut`
sys::nix_store_open(ctx.as_ptr(), uri_ptr, null_mut())
@ -90,97 +77,6 @@ impl Store {
Ok(Rc::new(RefCell::new(Store { inner })))
}
/// Realize a store path.
///
/// This builds/downloads the store path and all its dependencies,
/// making them available in the local store.
///
/// # Arguments
///
/// * `path` - The store path to realize
///
/// # Returns
///
/// A vector of (output_name, store_path) tuples for each realized output.
/// For example, a derivation might produce outputs like ("out", path1), ("dev", path2).
///
/// # Errors
///
/// Returns an error if the path cannot be realized.
///
pub fn realise(
&self,
path: &StorePath,
user_callback: fn(&str, &StorePath),
) -> NixideResult<Vec<(String, StorePath)>> {
wrap::nix_callback!(
|; userdata: fn(&str, &StorePath);
output_name_ptr: *const c_char,
output_path_ptr: *const sys::StorePath|
-> Vec<(String, StorePath)> {
// XXX: TODO: test to see if this is ever null ("out" as a default feels unsafe...)
// NOTE: this also ensures `output_name_ptr` isn't null
let output_name = output_name_ptr.to_utf8_string().unwrap_or_else(|err| panic_issue_call_failed!("{}", err));
let inner = wrap::nix_ptr_fn!(|ctx| unsafe {
sys::nix_store_path_clone(output_path_ptr as *mut sys::StorePath)
}).unwrap_or_else(|err| panic_issue_call_failed!("{}", err));
let store_path = StorePath { inner };
let callback = unsafe { (*userdata).inner };
callback(output_name.as_ref(), &store_path);
(unsafe {(*userdata).retval }).append((output_name, store_path));
},
|callback,
state: *mut __UserData,
ctx: &ErrorContext| unsafe {
// register userdata
// WARNING: Using `write` instead of assignment via `=`
// WARNING: to not call `drop` on the old, uninitialized value.
(&raw mut (*state).inner).write(user_callback);
// register return value
(&raw mut (*state).retval).write(Vec::new());
sys::nix_store_realise(
ctx.as_ptr(),
self.as_ptr(),
path.as_ptr(),
(*state).inner_ptr() as *mut c_void,
Some(callback),
);
}
)
}
/// Parse a store path string into a StorePath.
///
/// This is a convenience method that wraps `StorePath::parse()`.
///
/// # Arguments
///
/// * `path` - The store path string (e.g., "/nix/store/...")
///
/// # Errors
///
/// Returns an error if the path cannot be parsed.
///
/// # Example
///
/// ```no_run
/// # use std::sync::Arc;
/// # use nixide::Store;
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
/// let store = Store::open(None)?;
/// let path = store.store_path("/nix/store/...")?;
/// # Ok(())
/// # }
/// ```
///
pub fn store_path(&self, path: &str) -> NixideResult<StorePath> {
StorePath::parse(self, path)
}
/// Get the version of a Nix store
///
/// If the store doesn't have a version (like the dummy store), returns None
@ -198,7 +94,7 @@ impl Store {
)
}
/// Get the URI of a Nix store
/// Get the URI of a Nix store as a String.
///
pub fn uri(&self) -> NixideResult<String> {
wrap::nix_string_callback!(
@ -213,6 +109,8 @@ impl Store {
)
}
/// Get the store directory path of a Nix store.
///
pub fn store_dir(&self) -> NixideResult<PathBuf> {
wrap::nix_pathbuf_callback!(
|callback, userdata: *mut __UserData, ctx: &ErrorContext| unsafe {
@ -225,28 +123,6 @@ impl Store {
}
)
}
pub fn copy_closure_to(&self, dst_store: &Store, store_path: &StorePath) -> NixideResult<()> {
wrap::nix_fn!(|ctx: &ErrorContext| unsafe {
sys::nix_store_copy_closure(
ctx.as_ptr(),
self.as_ptr(),
dst_store.as_ptr(),
store_path.as_ptr(),
);
})
}
pub fn copy_closure_from(&self, src_store: &Store, store_path: &StorePath) -> NixideResult<()> {
wrap::nix_fn!(|ctx: &ErrorContext| unsafe {
sys::nix_store_copy_closure(
ctx.as_ptr(),
src_store.as_ptr(),
self.as_ptr(),
store_path.as_ptr(),
);
})
}
}
impl Drop for Store {

View file

@ -1,5 +1,5 @@
use std::cell::RefCell;
use std::ffi::{CString, c_void};
use std::ffi::{CString, c_char, c_void};
use std::path::PathBuf;
use std::ptr::NonNull;
use std::rc::Rc;
@ -7,6 +7,7 @@ use std::rc::Rc;
use super::Store;
use crate::NixideResult;
use crate::errors::{ErrorContext, new_nixide_error};
use crate::stdext::CCharPtrExt as _;
use crate::sys;
use crate::util::panic_issue_call_failed;
use crate::util::wrap;
@ -18,7 +19,7 @@ use crate::util::wrappers::AsInnerPtr;
///
pub struct StorePath {
inner: NonNull<sys::StorePath>,
store: Rc<RefCell<Store>>,
store_ref: Rc<RefCell<Store>>,
}
impl Clone for StorePath {
@ -30,7 +31,7 @@ impl Clone for StorePath {
StorePath {
inner,
store: self.store.clone(),
store_ref: self.store_ref.clone(),
}
}
}
@ -71,18 +72,33 @@ impl StorePath {
/// # Errors
///
/// Returns an error if the path cannot be parsed.
pub fn parse(store: Rc<RefCell<Store>>, path: &str) -> NixideResult<Self> {
///
/// # Example
///
/// ```no_run
/// use nixide::{Store, StorePath};
///
/// fn main() {
/// let store_ref = Store::default().unwrap();
/// let path = StorePath::new("/nix/store/f7gmvzd74wc1vlxzjdqy0af2381g8wr6-nix-manual-2.31.2-man").unwrap();
/// }
/// ```
///
pub fn new(store: Rc<RefCell<Store>>, path: &str) -> NixideResult<Self> {
let c_path = CString::new(path).or(Err(new_nixide_error!(StringNulByte)))?;
let inner = wrap::nix_ptr_fn!(|ctx: &ErrorContext| unsafe {
sys::nix_store_parse_path(ctx.as_ptr(), store.borrow().as_ptr(), c_path.as_ptr())
})?;
Ok(Self { inner, store })
Ok(Self {
inner,
store_ref: store,
})
}
pub fn fake_path(store: Rc<RefCell<Store>>) -> NixideResult<Self> {
Self::parse(store, "/nix/store/00000000000000000000000000000000-fake")
pub fn fake_path(store: Rc<RefCell<Store>>) -> Self {
Self::new(store, "/nix/store/00000000000000000000000000000000-fake").unwrap()
}
/// Get the name component of the store path.
@ -125,7 +141,7 @@ impl StorePath {
|callback, userdata: *mut __UserData, ctx: &ErrorContext| unsafe {
sys::nix_store_real_path(
ctx.as_ptr(),
self.store.borrow().as_ptr(),
self.store_ref.borrow().as_ptr(),
self.as_ptr(),
Some(callback),
userdata as *mut c_void,
@ -176,11 +192,11 @@ impl StorePath {
store_path: *const sys::StorePath,
| -> () {
let callback: fn(&StorePath) = unsafe { (*userdata).inner.1 };
let store = unsafe { (*userdata).inner.0.clone() };
let store_ref = unsafe { (*userdata).inner.0.clone() };
let path = &StorePath {
inner: NonNull::new(unsafe { store.borrow().as_ptr() } as *mut sys::StorePath).unwrap(),
store,
inner: NonNull::new(unsafe { store_ref.borrow().as_ptr() } as *mut sys::StorePath).unwrap(),
store_ref,
};
callback(&path);
@ -191,11 +207,11 @@ impl StorePath {
// register userdata
// WARNING: Using `write` instead of assignment via `=`
// WARNING: to not call `drop` on the old, uninitialized value.
(&raw mut (*state).inner).write((self.store.clone(), user_callback));
(&raw mut (*state).inner).write((self.store_ref.clone(), user_callback));
sys::nix_store_get_fs_closure(
ctx.as_ptr(),
self.store.borrow().as_ptr(),
self.store_ref.borrow().as_ptr(),
self.as_ptr(),
flip,
include_outputs,
@ -206,7 +222,92 @@ impl StorePath {
)
}
pub fn copy_closure_to(&self, dst_store_ref: Rc<RefCell<Store>>) -> NixideResult<()> {
wrap::nix_fn!(|ctx: &ErrorContext| unsafe {
sys::nix_store_copy_closure(
ctx.as_ptr(),
self.store_ref.borrow().as_ptr(),
dst_store_ref.borrow().as_ptr(),
self.as_ptr(),
);
})
}
pub fn copy_closure_from(&self, src_store_ref: Rc<RefCell<Store>>) -> NixideResult<()> {
wrap::nix_fn!(|ctx: &ErrorContext| unsafe {
sys::nix_store_copy_closure(
ctx.as_ptr(),
src_store_ref.borrow().as_ptr(),
self.store_ref.borrow().as_ptr(),
self.as_ptr(),
);
})
}
/// Realize a store path.
///
/// This builds/downloads the store path and all its dependencies,
/// making them available in the local store.
///
/// # Arguments
///
/// * `path` - The store path to realize
///
/// # Returns
///
/// A vector of (output_name, store_path) tuples for each realized output.
/// For example, a derivation might produce outputs like ("out", path1), ("dev", path2).
///
/// # Errors
///
/// Returns an error if the path cannot be realized.
///
pub fn realise(
&self,
user_callback: fn(&str, &StorePath),
) -> NixideResult<Vec<(String, StorePath)>> {
wrap::nix_callback!(
|; userdata: (Rc<RefCell<Store>>, fn(&str, &StorePath));
output_name_ptr: *const c_char,
output_path_ptr: *const sys::StorePath|
-> Vec<(String, StorePath)> {
// XXX: TODO: test to see if this is ever null ("out" as a default feels unsafe...)
// NOTE: this also ensures `output_name_ptr` isn't null
let output_name = output_name_ptr.to_utf8_string().unwrap_or_else(|err| panic_issue_call_failed!("{}", err));
let inner = wrap::nix_ptr_fn!(|ctx| unsafe {
sys::nix_store_path_clone(output_path_ptr as *mut sys::StorePath)
}).unwrap_or_else(|err| panic_issue_call_failed!("{}", err));
let store_ref = unsafe { (*userdata).inner.0.clone() };
let callback = unsafe { (*userdata).inner.1 };
let store_path = StorePath { inner, store_ref };
callback(output_name.as_ref(), &store_path);
unsafe { (*userdata).retval.push((output_name, store_path)) };
},
|callback,
state: *mut __UserData,
ctx: &ErrorContext| unsafe {
// register userdata
// WARNING: Using `write` instead of assignment via `=`
// WARNING: to not call `drop` on the old, uninitialized value.
(&raw mut (*state).inner).write((self.store_ref.clone(), user_callback));
// register return value
(&raw mut (*state).retval).write(Vec::new());
sys::nix_store_realise(
ctx.as_ptr(),
self.store_ref.borrow().as_ptr(),
self.as_ptr(),
(*state).inner_ptr() as *mut c_void,
Some(callback),
);
}
)
}
// XXX: TODO: nix 2.34.4 adds a LOT here (ie especially around derivations)
// XXX: TODO: it also removes nix_store_path* functions (ie nix_store_path_free)?
// XXX: TODO: why?? try and research this, maybe they didn't mean to??
}

View file

@ -16,11 +16,10 @@ fn test_store_opening() {
fn test_store_path_parse() {
assert!(unsafe { matches!(LIBNIX_INIT_STATUS, Some(Ok(_))) });
let store = Store::default().expect("Failed to open store");
let store_ref = Store::default().expect("Failed to open store");
// Try parsing a well-formed store path
let result = StorePath::fake_path(&store.borrow());
result.expect("idk hopefully this fails");
StorePath::fake_path(store_ref.clone());
}
#[test]
@ -28,11 +27,10 @@ fn test_store_path_parse() {
fn test_store_path_clone() {
assert!(unsafe { matches!(LIBNIX_INIT_STATUS, Some(Ok(_))) });
let store = Store::default().expect("Failed to open store");
let store_ref = Store::default().expect("Failed to open store");
// Try to get a valid store path by parsing
let path =
StorePath::fake_path(&store.borrow()).expect("Failed to create `StorePath::fake_path`");
let path = StorePath::fake_path(store_ref.clone());
let cloned = path.clone();
// Assert that the cloned path has the same name as the original