diff --git a/Cargo.lock b/Cargo.lock index 9af7062..eec43ca 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -72,6 +72,22 @@ dependencies = [ "libloading", ] +[[package]] +name = "ctor" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "424e0138278faeb2b401f174ad17e715c829512d74f3d1e81eb43365c2e0590e" +dependencies = [ + "ctor-proc-macro", + "dtor", +] + +[[package]] +name = "ctor-proc-macro" +version = "0.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52560adf09603e58c9a7ee1fe1dcb95a16927b17c127f0ac02d6e768a0e25bc1" + [[package]] name = "doxygen-bindgen" version = "0.1.3" @@ -81,6 +97,21 @@ dependencies = [ "yap", ] +[[package]] +name = "dtor" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "404d02eeb088a82cfd873006cb713fe411306c7d182c344905e101fb1167d301" +dependencies = [ + "dtor-proc-macro", +] + +[[package]] +name = "dtor-proc-macro" +version = "0.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f678cf4a922c215c63e0de95eb1ff08a958a81d47e485cf9da1e27bf6305cfa5" + [[package]] name = "either" version = "1.15.0" @@ -190,6 +221,7 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" name = "nixide" version = "0.1.0" dependencies = [ + "ctor", "libc", "nixide-sys", "serial_test", diff --git a/nixide-sys/Cargo.toml b/nixide-sys/Cargo.toml index 0be064c..2cc0980 100644 --- a/nixide-sys/Cargo.toml +++ b/nixide-sys/Cargo.toml @@ -19,14 +19,16 @@ targets = [ "x86_64-unknown-linux-gnu" ] [lib] path = "lib.rs" +# NOTE: `[features]` have a 1-1 correspondence to the +# NOTE: shared libraries produced by the Nix C API. [features] -default = ["util"] -expr = [] -fetchers = [] -flakes = [] -store = [] -util = [] -gc = [] +default = ["nix-util-c"] +nix-util-c = [] +nix-store-c = [] +nix-expr-c = [] +nix-fetchers-c = [] +nix-flake-c = [] +nix-main-c = [] [build-dependencies] bindgen = { default-features = false, features = [ "logging", "runtime" ], version = "0.72.1" } diff --git a/nixide-sys/build.rs b/nixide-sys/build.rs index 6631264..0641cf8 100644 --- a/nixide-sys/build.rs +++ b/nixide-sys/build.rs @@ -20,17 +20,26 @@ impl ParseCallbacks for DoxygenCallbacks { fn main() { // Invalidate the built crate whenever the wrapper changes - println!("cargo:rerun-if-changed=include/wrapper.h"); + println!("cargo:rerun-if-changed=include/nix-util.h"); + println!("cargo:rerun-if-changed=include/nix-store.h"); + println!("cargo:rerun-if-changed=include/nix-expr.h"); + println!("cargo:rerun-if-changed=include/nix-fetchers.h"); + println!("cargo:rerun-if-changed=include/nix-flake.h"); + println!("cargo:rerun-if-changed=include/nix-main.h"); - // Use pkg-config to find nix-store include and link paths - // This NEEDS to be included, or otherwise `nix_api_store.h` cannot - // be found. let libs = [ - "nix-main-c", - "nix-expr-c", - "nix-store-c", + #[cfg(feature = "nix-util-c")] "nix-util-c", + #[cfg(feature = "nix-store-c")] + "nix-store-c", + #[cfg(feature = "nix-expr-c")] + "nix-expr-c", + #[cfg(feature = "nix-fetchers-c")] + "nix-fetchers-c", + #[cfg(feature = "nix-flake-c")] "nix-flake-c", + #[cfg(feature = "nix-main-c")] + "nix-main-c", ]; let lib_args: Vec = libs @@ -50,17 +59,43 @@ fn main() { .flatten() .collect(); - let bindings = bindgen::Builder::default() + let mut builder = bindgen::Builder::default() .clang_args(lib_args) - // The input header we would like to generate bindings for - .header("include/wrapper.h") // Invalidate the built crate when an included header file changes .parse_callbacks(Box::new(bindgen::CargoCallbacks::new())) // Add `doxygen_bindgen` callbacks .parse_callbacks(Box::new(DoxygenCallbacks)) // Format generated bindings with rustfmt .formatter(bindgen::Formatter::Rustfmt) - .rustfmt_configuration_file(std::fs::canonicalize(".rustfmt.toml").ok()) + .rustfmt_configuration_file(std::fs::canonicalize(".rustfmt.toml").ok()); + + // The input headers we would like to generate bindings for + #[cfg(feature = "nix-util-c")] + { + builder = builder.header("include/nix-util.h") + } + #[cfg(feature = "nix-store-c")] + { + builder = builder.header("include/nix-store.h") + } + #[cfg(feature = "nix-expr-c")] + { + builder = builder.header("include/nix-expr.h") + } + #[cfg(feature = "nix-fetchers-c")] + { + builder = builder.header("include/nix-fetchers.h") + } + #[cfg(feature = "nix-flake-c")] + { + builder = builder.header("include/nix-flake.h") + } + #[cfg(feature = "nix-main-c")] + { + builder = builder.header("include/nix-main.h") + } + + let bindings = builder // Finish the builder and generate the bindings .generate() // Unwrap the Result and panic on failure diff --git a/nixide-sys/include/nix-expr.h b/nixide-sys/include/nix-expr.h new file mode 100644 index 0000000..b20d63b --- /dev/null +++ b/nixide-sys/include/nix-expr.h @@ -0,0 +1,10 @@ +// Nix C API for the Nix expressions evaluator. +#include + +// Nix C API for value manipulation. +// +#include + +// Nix C API for external values. +// +#include diff --git a/nixide-sys/include/nix-fetchers.h b/nixide-sys/include/nix-fetchers.h new file mode 100644 index 0000000..4e80a28 --- /dev/null +++ b/nixide-sys/include/nix-fetchers.h @@ -0,0 +1,3 @@ +// Nix C API for fetcher operations. +// +#include diff --git a/nixide-sys/include/nix-flake.h b/nixide-sys/include/nix-flake.h new file mode 100644 index 0000000..e07cfab --- /dev/null +++ b/nixide-sys/include/nix-flake.h @@ -0,0 +1,3 @@ +// Nix C API for flake support. +// +#include diff --git a/nixide-sys/include/nix-main.h b/nixide-sys/include/nix-main.h new file mode 100644 index 0000000..7815125 --- /dev/null +++ b/nixide-sys/include/nix-main.h @@ -0,0 +1,3 @@ +// Nix C API for CLI support. +// +#include diff --git a/nixide-sys/include/nix-store.h b/nixide-sys/include/nix-store.h new file mode 100644 index 0000000..239bde5 --- /dev/null +++ b/nixide-sys/include/nix-store.h @@ -0,0 +1,3 @@ +// Nix C API for store operations. +// +#include diff --git a/nixide-sys/include/nix-util.h b/nixide-sys/include/nix-util.h new file mode 100644 index 0000000..8d60eed --- /dev/null +++ b/nixide-sys/include/nix-util.h @@ -0,0 +1,7 @@ +// Nix C API for utilities. +// +// Most notably containing functions for handling +// the `nix_c_context` structure, which is used throughout +// the Nix C APIs for error handling. +// +#include diff --git a/nixide-sys/include/wrapper.h b/nixide-sys/include/wrapper.h deleted file mode 100644 index 30dd94e..0000000 --- a/nixide-sys/include/wrapper.h +++ /dev/null @@ -1,23 +0,0 @@ -// Pure C API for store operations -#include - -// Pure C API for error handling -#include - -// Pure C API for the Nix evaluator -#include - -// Pure C API for external values -#include - -// Pure C API for value manipulation -#include - -// Pure C API for fetcher operations -#include - -// Pure C API for flake support -#include - -// Pure C API for main/CLI support -#include diff --git a/nixide/Cargo.toml b/nixide/Cargo.toml index 7a1b4e4..eab4e9b 100644 --- a/nixide/Cargo.toml +++ b/nixide/Cargo.toml @@ -15,18 +15,16 @@ edition = "2024" path = "src/lib.rs" [features] -default = ["util"] -expr = [] -fetchers = [] -flakes = [] -store = [] -util = [] -gc = [] +default = [] +store = ["nixide-sys/nix-store-c"] +expr = ["store", "nixide-sys/nix-expr-c"] +flake = ["store", "nixide-sys/nix-flake-c", "nixide-sys/nix-fetchers-c"] [dependencies] libc = "0.2.183" stdext = "0.3.3" -nixide-sys = { path = "../nixide-sys", version = "0.1.0" } +ctor = "0.6.3" +nixide-sys = { path = "../nixide-sys", version = "0.1.0", features = ["nix-util-c", "nix-main-c"]} [dev-dependencies] serial_test = "3.4.0" diff --git a/nixide/src/errors/context.rs b/nixide/src/errors/context.rs index 0f53046..39b1ea6 100644 --- a/nixide/src/errors/context.rs +++ b/nixide/src/errors/context.rs @@ -180,11 +180,36 @@ impl ErrorContext { /// `nix_clear_err` only modifies the `last_err_code`, it does not /// clear all attributes of a `nix_c_context` struct. Hence all uses /// of `nix_c_context` must be careful to check the `last_err_code` regularly. + /// pub fn clear(&mut self) { unsafe { sys::nix_clear_err(self.as_ptr()); } } + /// This function never fails. + /// Nixide will always guarantee `context != nullptr`. + /// + /// # Nix C++ API Internals + /// + /// ```cpp + /// nix_err nix_set_err_msg(nix_c_context * context, nix_err err, const char * msg) + /// { + /// if (context == nullptr) { + /// // todo last_err_code + /// throw nix::Error("Nix C api error: %s", msg); + /// } + /// context->last_err_code = err; + /// context->last_err = msg; + /// return err; + /// } + /// ``` + /// + pub fn set_err(&self, err: NixError, msg: &str) { + let ptr = unsafe { self.as_ptr() }; + assert!(!ptr.is_null(), ""); + + sys::nix_set_err_msg(ptr, err.into()) + } /// Returns [None] if [self.code] is [sys::nix_err_NIX_OK], and [Some] otherwise. /// diff --git a/nixide/src/lib.rs b/nixide/src/lib.rs index c8f3b58..dd127c6 100644 --- a/nixide/src/lib.rs +++ b/nixide/src/lib.rs @@ -1,31 +1,93 @@ // #![warn(missing_docs)] -// #[allow(unused_extern_crates)] pub extern crate libc; pub extern crate nixide_sys as sys; pub(crate) mod errors; -mod expr; -// mod flake; mod stdext; -mod store; pub(crate) mod util; mod verbosity; mod version; +#[cfg(feature = "expr")] +mod expr; +#[cfg(feature = "store")] +mod store; + +#[cfg(feature = "flake")] +mod flake; + pub use errors::{NixError, NixideError, NixideResult}; -pub use expr::{EvalState, EvalStateBuilder, Value, ValueType}; -pub use store::{Store, StorePath}; pub use verbosity::NixVerbosity; pub use version::NixVersion; -/// Sets the verbosity level +#[cfg(feature = "expr")] +pub use expr::{EvalState, EvalStateBuilder, Value, ValueType}; +#[cfg(feature = "store")] +pub use store::{Store, StorePath}; + +use ctor::ctor; +use util::wrappers::AsInnerPtr as _; + +pub(crate) static mut INIT_LIBUTIL_STATUS: Option> = None; +#[cfg(feature = "store")] +pub(crate) static mut INIT_LIBSTORE_STATUS: Option> = None; +#[cfg(feature = "expr")] +pub(crate) static mut INIT_LIBEXPR_STATUS: Option> = None; + +/// # Warning /// -/// # Arguments -/// -/// * `context` - additional error context, used as an output -/// * `level` - verbosity level -pub fn set_verbosity() { - // nix_err nix_set_verbosity(nix_c_context * context, nix_verbosity level); - // XXX: TODO: (implement Context first) +/// > Rust's philosophy is that nothing happens before or after main and [ctor](https://github.com/mmastrac/rust-ctor) +/// > explicitly subverts that. The code that runs in `ctor` functions +/// > should be careful to limit itself to libc functions and code +/// > that does not rely on Rust's stdlib services. +/// > - Excerpt from the [github:mmastrac/rust-ctor README.md](https://github.com/mmastrac/rust-ctor?tab=readme-ov-file#warnings) +#[ctor] +fn init_libutil() { + unsafe { + INIT_LIBUTIL_STATUS = Some(util::wrap::nix_fn!(|ctx: &errors::ErrorContext| unsafe { + sys::nix_libutil_init(ctx.as_ptr()); + })); + } +} + +/// # TODO +/// **Only run this if the "store" feature flag was enabled** +/// +/// # Warning +/// +/// > Rust's philosophy is that nothing happens before or after main and [ctor](https://github.com/mmastrac/rust-ctor) +/// > explicitly subverts that. The code that runs in `ctor` functions +/// > should be careful to limit itself to libc functions and code +/// > that does not rely on Rust's stdlib services. +/// > - Excerpt from the [github:mmastrac/rust-ctor README.md](https://github.com/mmastrac/rust-ctor?tab=readme-ov-file#warnings) +#[ctor] +#[cfg(feature = "store")] +fn init_libstore() { + // XXX: TODO: how do I support `sys::nix_libstore_init_no_load_config(context)`? + unsafe { + INIT_LIBSTORE_STATUS = Some(util::wrap::nix_fn!(|ctx: &errors::ErrorContext| unsafe { + sys::nix_libutil_init(ctx.as_ptr()); + })); + } +} + +/// # TODO +/// **Only run this if the "expr" feature flag was enabled** +// +/// # Warning +/// +/// > Rust's philosophy is that nothing happens before or after main and [ctor](https://github.com/mmastrac/rust-ctor) +/// > explicitly subverts that. The code that runs in `ctor` functions +/// > should be careful to limit itself to libc functions and code +/// > that does not rely on Rust's stdlib services. +/// > - Excerpt from the [github:mmastrac/rust-ctor README.md](https://github.com/mmastrac/rust-ctor?tab=readme-ov-file#warnings) +#[ctor] +#[cfg(feature = "expr")] +fn init_libexpr() { + unsafe { + INIT_LIBEXPR_STATUS = Some(util::wrap::nix_fn!(|ctx: &errors::ErrorContext| unsafe { + sys::nix_libexpr_init(ctx.as_ptr()); + })); + } } diff --git a/nixide/src/nix_settings.rs b/nixide/src/nix_settings.rs new file mode 100644 index 0000000..e69de29 diff --git a/nixide/src/stdext/cchar_ptr_ext.rs b/nixide/src/stdext/cchar_ptr_ext.rs index 5d506a9..733ed98 100644 --- a/nixide/src/stdext/cchar_ptr_ext.rs +++ b/nixide/src/stdext/cchar_ptr_ext.rs @@ -5,6 +5,31 @@ use std::str::from_utf8; use crate::errors::new_nixide_error; use crate::NixideResult; +pub trait AsCPtr { + fn as_c_ptr(&self) -> NixideResult<*const T>; + + fn into_c_ptr(self) -> NixideResult<*mut T>; +} + +impl AsCPtr for T +where + T: AsRef, +{ + fn as_c_ptr(&self) -> NixideResult<*const c_char> { + match CStr::from_bytes_until_nul(self.as_ref().as_bytes()) { + Ok(s) => Ok(s.as_ptr()), + Err(_) => Err(new_nixide_error!(StringNulByte)), + } + } + + fn into_c_ptr(self) -> NixideResult<*mut c_char> { + match CStr::from_bytes_until_nul(self.as_ref().as_bytes()) { + Ok(s) => Ok(s.as_ptr().cast_mut()), + Err(_) => Err(new_nixide_error!(StringNulByte)), + } + } +} + pub trait CCharPtrExt { fn to_utf8_string(self) -> NixideResult; diff --git a/nixide/src/verbosity.rs b/nixide/src/verbosity.rs index fb1918b..a740027 100644 --- a/nixide/src/verbosity.rs +++ b/nixide/src/verbosity.rs @@ -1,4 +1,7 @@ +use crate::errors::ErrorContext; use crate::sys; +use crate::util::wrappers::AsInnerPtr as _; +use crate::util::{panic_issue, panic_issue_call_failed, wrap}; /// Verbosity level /// @@ -28,7 +31,46 @@ impl From for NixVerbosity { sys::nix_verbosity_NIX_LVL_CHATTY => NixVerbosity::Chatty, sys::nix_verbosity_NIX_LVL_DEBUG => NixVerbosity::Debug, sys::nix_verbosity_NIX_LVL_VOMIT => NixVerbosity::Vomit, - _ => panic!("nixide encountered unknown `nix_verbosity` value, please submit this as an issue at https://github.com/cry128/nixide"), + value => panic_issue!( + "nixide encountered unknown `nix_verbosity` value ({})", + value + ), } } } + +impl Into for NixVerbosity { + fn into(self) -> sys::nix_verbosity { + self as sys::nix_verbosity + } +} + +/// Sets the verbosity level. +/// +/// **This function should never fail!** +/// A panic would indicate a bug in nixide itself. +/// +/// # Nix C++ API Internals +/// +/// ```cpp +/// nix_err nix_set_verbosity(nix_c_context * context, nix_verbosity level) +/// { +/// if (context) +/// context->last_err_code = NIX_OK; +/// if (level > NIX_LVL_VOMIT || level < NIX_LVL_ERROR) +/// return nix_set_err_msg(context, NIX_ERR_UNKNOWN, "Invalid verbosity level"); +/// try { +/// nix::verbosity = static_cast(level); +/// } catch (...) { +/// return nix_context_error(context); +/// } +/// return NIX_OK; +/// } +/// ``` +/// +pub fn set_verbosity(level: NixVerbosity) { + wrap::nix_fn!(|ctx: &ErrorContext| unsafe { + sys::nix_set_verbosity(ctx.as_ptr(), level.into()); + }) + .unwrap_or_else(|err| panic_issue_call_failed!("{}", err)) +}