Compare commits
17 Commits
1815482385
...
42d4188ebc
| Author | SHA1 | Date | |
|---|---|---|---|
| 42d4188ebc | |||
| 959616efbe | |||
| d64bf49302 | |||
| 86599ff46d | |||
| 1d6503754a | |||
| b148d6645a | |||
| 4799da4f41 | |||
| 45a57d9b32 | |||
|
15b159b6f1
|
|||
| ce3142c1e4 | |||
|
73c6cd51ca
|
|||
| c90a0345e7 | |||
| 7de15fa260 | |||
| fbf5fab083 | |||
| cf0e8fff6a | |||
| 1e61578f35 | |||
| 6ee502a4f9 |
@@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
### Added
|
||||
|
||||
- `primop::RecoverableError` for primop errors that should not be memoized in the thunk, allowing retry on next force. Required by Nix >= 2.34 ([release note](https://nix.dev/manual/nix/2.34/release-notes/rl-2.34.html#c-api-changes)) for recoverable errors to remain recoverable, as Nix 2.34 memoizes errors by default.
|
||||
|
||||
## [0.2.0] - 2026-01-13
|
||||
|
||||
### Added
|
||||
|
||||
+3
-5
@@ -7,12 +7,10 @@ echo "Extending BINDGEN_EXTRA_CLANG_ARGS with system include paths..." 2>&1
|
||||
BINDGEN_EXTRA_CLANG_ARGS="${BINDGEN_EXTRA_CLANG_ARGS:-}"
|
||||
export BINDGEN_EXTRA_CLANG_ARGS
|
||||
include_paths=$(
|
||||
echo | $NIX_CC_UNWRAPPED -v -E -x c - 2>&1 \
|
||||
LC_ALL=C $NIX_CC_UNWRAPPED -v -E -x c - </dev/null 2>&1 \
|
||||
| awk '/#include <...> search starts here:/{flag=1;next} \
|
||||
/End of search list./{flag=0} \
|
||||
flag==1 {print $1}'
|
||||
)
|
||||
for path in $include_paths; do
|
||||
echo " - $path" 2>&1
|
||||
BINDGEN_EXTRA_CLANG_ARGS="$BINDGEN_EXTRA_CLANG_ARGS -I$path"
|
||||
done
|
||||
include_args=$(printf '%s\n' "$include_paths" | awk 'NF {printf " -I%s", $1; printf " - %s\n", $1 > "/dev/stderr"}')
|
||||
BINDGEN_EXTRA_CLANG_ARGS="$BINDGEN_EXTRA_CLANG_ARGS$include_args"
|
||||
|
||||
Generated
+15
-15
@@ -24,11 +24,11 @@
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1768135262,
|
||||
"narHash": "sha256-PVvu7OqHBGWN16zSi6tEmPwwHQ4rLPU9Plvs8/1TUBY=",
|
||||
"lastModified": 1769996383,
|
||||
"narHash": "sha256-AnYjnFWgS49RlqX7LrC4uA+sCCDBj0Ry/WOJ5XWAsa0=",
|
||||
"owner": "hercules-ci",
|
||||
"repo": "flake-parts",
|
||||
"rev": "80daad04eddbbf5a4d883996a73f3f542fa437ac",
|
||||
"rev": "57928607ea566b5db3ad13af0e57e921e6b12381",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -63,11 +63,11 @@
|
||||
"nixpkgs": "nixpkgs"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1768476106,
|
||||
"narHash": "sha256-V0YOJRum50gtKgwavsAfwXc9+XAsJCC7386YZx1sWGQ=",
|
||||
"lastModified": 1771131391,
|
||||
"narHash": "sha256-HPBNYf7HiKtBVy7/69vKpLYHX6wTcUxndxmybzDlXP8=",
|
||||
"owner": "hercules-ci",
|
||||
"repo": "hercules-ci-effects",
|
||||
"rev": "c19e263e6e22ec7379d972f19e6a322f943c73fb",
|
||||
"rev": "0b152e0f7c5cc265a529cd63374b80e2771b207b",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -78,11 +78,11 @@
|
||||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1768305791,
|
||||
"narHash": "sha256-AIdl6WAn9aymeaH/NvBj0H9qM+XuAuYbGMZaP0zcXAQ=",
|
||||
"lastModified": 1771008912,
|
||||
"narHash": "sha256-gf2AmWVTs8lEq7z/3ZAsgnZDhWIckkb+ZnAo5RzSxJg=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "1412caf7bf9e660f2f962917c14b1ea1c3bc695e",
|
||||
"rev": "a82ccc39b39b621151d6732718e3e250109076fa",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -99,11 +99,11 @@
|
||||
"nixpkgs": []
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1769069492,
|
||||
"narHash": "sha256-Efs3VUPelRduf3PpfPP2ovEB4CXT7vHf8W+xc49RL/U=",
|
||||
"lastModified": 1772024342,
|
||||
"narHash": "sha256-+eXlIc4/7dE6EcPs9a2DaSY3fTA9AE526hGqkNID3Wg=",
|
||||
"owner": "cachix",
|
||||
"repo": "pre-commit-hooks.nix",
|
||||
"rev": "a1ef738813b15cf8ec759bdff5761b027e3e1d23",
|
||||
"rev": "6e34e97ed9788b17796ee43ccdbaf871a5c2b476",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -124,11 +124,11 @@
|
||||
"nixpkgs": []
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1769691507,
|
||||
"narHash": "sha256-8aAYwyVzSSwIhP2glDhw/G0i5+wOrren3v6WmxkVonM=",
|
||||
"lastModified": 1770228511,
|
||||
"narHash": "sha256-wQ6NJSuFqAEmIg2VMnLdCnUc0b7vslUohqqGGD+Fyxk=",
|
||||
"owner": "numtide",
|
||||
"repo": "treefmt-nix",
|
||||
"rev": "28b19c5844cc6e2257801d43f2772a4b4c050a1b",
|
||||
"rev": "337a4fe074be1042a35086f15481d763b8ddc0e7",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
||||
Generated
+35
-35
@@ -43,15 +43,15 @@
|
||||
"flake-compat": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
"lastModified": 1767039857,
|
||||
"narHash": "sha256-vNpUSpF5Nuw8xvDLj2KCwwksIbjua2LZCqhV1LNRDns=",
|
||||
"owner": "NixOS",
|
||||
"lastModified": 1696426674,
|
||||
"narHash": "sha256-kvjfFW7WAETZlt09AgDn1MrtKzP7t90Vf7vypd3OL1U=",
|
||||
"owner": "edolstra",
|
||||
"repo": "flake-compat",
|
||||
"rev": "5edf11c44bc78a0d334f6334cdaf7d60d732daab",
|
||||
"rev": "0f9255e01c2351cc7d116c072cb317785dd33b33",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NixOS",
|
||||
"owner": "edolstra",
|
||||
"repo": "flake-compat",
|
||||
"type": "github"
|
||||
}
|
||||
@@ -77,11 +77,11 @@
|
||||
"nixpkgs-lib": "nixpkgs-lib"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1768135262,
|
||||
"narHash": "sha256-PVvu7OqHBGWN16zSi6tEmPwwHQ4rLPU9Plvs8/1TUBY=",
|
||||
"lastModified": 1777988971,
|
||||
"narHash": "sha256-qIoWPDs+0/8JecyYgE3gpKQxW/4bLW/gp45vow9ioCQ=",
|
||||
"owner": "hercules-ci",
|
||||
"repo": "flake-parts",
|
||||
"rev": "80daad04eddbbf5a4d883996a73f3f542fa437ac",
|
||||
"rev": "0678d8986be1661af6bb555f3489f2fdfc31f6ff",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -159,11 +159,11 @@
|
||||
"nixpkgs-regression": "nixpkgs-regression"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1769182158,
|
||||
"narHash": "sha256-QE+cj0eHW8kzuEbzfxQXfweSbps8sJyzHUjfrQKfWYo=",
|
||||
"lastModified": 1778096718,
|
||||
"narHash": "sha256-RnGxdnwhrEpLIqkRnXeb9caVLsKOboOMjMifEz+RaOk=",
|
||||
"owner": "DeterminateSystems",
|
||||
"repo": "nix-src",
|
||||
"rev": "731906053f4feba452c9bc782768263f9baf9ee4",
|
||||
"rev": "8fb8ad7b69f5715c66720106c8b3a6dd8d0a51d8",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -185,11 +185,11 @@
|
||||
"treefmt": "treefmt"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1769840916,
|
||||
"narHash": "sha256-bjtDp0NHjLjDOjklQVHCDCVM5q39zDzuwenNri0p4Ys=",
|
||||
"lastModified": 1778141945,
|
||||
"narHash": "sha256-unLKBy8SaNxEXaqMmXDUZyZIzf3K1H46azxFJDMq1jU=",
|
||||
"owner": "90-008",
|
||||
"repo": "nix-cargo-integration",
|
||||
"rev": "6d583e2098fa3df490c2597df06386e3efcc39b6",
|
||||
"rev": "aceca529e10a880594b11c31a0e98d70d4a9c975",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -200,16 +200,16 @@
|
||||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1761597516,
|
||||
"narHash": "sha256-wxX7u6D2rpkJLWkZ2E932SIvDJW8+ON/0Yy8+a5vsDU=",
|
||||
"rev": "daf6dc47aa4b44791372d6139ab7b25269184d55",
|
||||
"revCount": 811874,
|
||||
"lastModified": 1773222311,
|
||||
"narHash": "sha256-BHoB/XpbqoZkVYZCfXJXfkR+GXFqwb/4zbWnOr2cRcU=",
|
||||
"rev": "0590cd39f728e129122770c029970378a79d076a",
|
||||
"revCount": 909248,
|
||||
"type": "tarball",
|
||||
"url": "https://api.flakehub.com/f/pinned/NixOS/nixpkgs/0.2505.811874%2Brev-daf6dc47aa4b44791372d6139ab7b25269184d55/019a3494-3498-707e-9086-1fb81badc7fe/source.tar.gz"
|
||||
"url": "https://api.flakehub.com/f/pinned/NixOS/nixpkgs/0.2511.909248%2Brev-0590cd39f728e129122770c029970378a79d076a/019ce32b-8ace-7339-b129-cceaa8dd10c6/source.tar.gz"
|
||||
},
|
||||
"original": {
|
||||
"type": "tarball",
|
||||
"url": "https://flakehub.com/f/NixOS/nixpkgs/0.2505"
|
||||
"url": "https://flakehub.com/f/NixOS/nixpkgs/0.2511"
|
||||
}
|
||||
},
|
||||
"nixpkgs-23-11": {
|
||||
@@ -230,11 +230,11 @@
|
||||
},
|
||||
"nixpkgs-lib": {
|
||||
"locked": {
|
||||
"lastModified": 1765674936,
|
||||
"narHash": "sha256-k00uTP4JNfmejrCLJOwdObYC9jHRrr/5M/a/8L2EIdo=",
|
||||
"lastModified": 1777168982,
|
||||
"narHash": "sha256-GOkGPcboWE9BmGCRMLX3worL4EMnsnG8MyKmXNeYuhQ=",
|
||||
"owner": "nix-community",
|
||||
"repo": "nixpkgs.lib",
|
||||
"rev": "2075416fcb47225d9b68ac469a5c4801a9c4dd85",
|
||||
"rev": "f5901329dade4a6ea039af1433fb087bd9c1fe14",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -261,11 +261,11 @@
|
||||
},
|
||||
"nixpkgs_2": {
|
||||
"locked": {
|
||||
"lastModified": 1766902085,
|
||||
"narHash": "sha256-coBu0ONtFzlwwVBzmjacUQwj3G+lybcZ1oeNSQkgC0M=",
|
||||
"lastModified": 1777954456,
|
||||
"narHash": "sha256-hGdgeU2Nk87RAuZyYjyDjFL6LK7dAZN5RE9+hrDTkDU=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "c0b0e0fddf73fd517c3471e546c0df87a42d53f4",
|
||||
"rev": "549bd84d6279f9852cae6225e372cc67fb91a4c1",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -283,11 +283,11 @@
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1768135262,
|
||||
"narHash": "sha256-PVvu7OqHBGWN16zSi6tEmPwwHQ4rLPU9Plvs8/1TUBY=",
|
||||
"lastModified": 1777988971,
|
||||
"narHash": "sha256-qIoWPDs+0/8JecyYgE3gpKQxW/4bLW/gp45vow9ioCQ=",
|
||||
"owner": "hercules-ci",
|
||||
"repo": "flake-parts",
|
||||
"rev": "80daad04eddbbf5a4d883996a73f3f542fa437ac",
|
||||
"rev": "0678d8986be1661af6bb555f3489f2fdfc31f6ff",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -358,11 +358,11 @@
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1769828398,
|
||||
"narHash": "sha256-zmnvRUm15QrlKH0V1BZoiT3U+Q+tr+P5Osi8qgtL9fY=",
|
||||
"lastModified": 1778123869,
|
||||
"narHash": "sha256-hV9D8ET33kXjdoMXBT2bwM/j8WQM1SP/dVKZtjQKhAQ=",
|
||||
"owner": "oxalica",
|
||||
"repo": "rust-overlay",
|
||||
"rev": "a1d32c90c8a4ea43e9586b7e5894c179d5747425",
|
||||
"rev": "592e5dedf04f0eaff1ed0f01ce5db7407d9fc7be",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -402,11 +402,11 @@
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1769691507,
|
||||
"narHash": "sha256-8aAYwyVzSSwIhP2glDhw/G0i5+wOrren3v6WmxkVonM=",
|
||||
"lastModified": 1775636079,
|
||||
"narHash": "sha256-pc20NRoMdiar8oPQceQT47UUZMBTiMdUuWrYu2obUP0=",
|
||||
"owner": "numtide",
|
||||
"repo": "treefmt-nix",
|
||||
"rev": "28b19c5844cc6e2257801d43f2772a4b4c050a1b",
|
||||
"rev": "790751ff7fd3801feeaf96d7dc416a8d581265ba",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
||||
@@ -2,5 +2,5 @@ use nix_bindings_util::nix_version::emit_version_cfg;
|
||||
|
||||
fn main() {
|
||||
let nix_version = pkg_config::probe_library("nix-expr-c").unwrap().version;
|
||||
emit_version_cfg(&nix_version, &["2.26"]);
|
||||
emit_version_cfg(&nix_version, &["2.26", "2.34.0pre"]);
|
||||
}
|
||||
|
||||
+289
-190
File diff suppressed because it is too large
Load Diff
@@ -2,11 +2,15 @@ use crate::eval_state::{EvalState, EvalStateError};
|
||||
use crate::value::Value;
|
||||
use nix_bindings_expr_sys as raw;
|
||||
use nix_bindings_util::check_call;
|
||||
use nix_bindings_util::context::Context;
|
||||
use nix_bindings_util_sys as raw_util;
|
||||
use std::error::Error;
|
||||
use std::ffi::{c_int, c_void, CStr, CString};
|
||||
use std::ptr::{null, null_mut};
|
||||
|
||||
#[cfg(nix_at_least = "2.34.0pre")]
|
||||
use nix_bindings_util::Error as NixError;
|
||||
|
||||
/// Metadata for a primop, used with `PrimOp::new`.
|
||||
pub struct PrimOpMeta<'a, const N: usize> {
|
||||
/// Name of the primop. Note that primops do not have to be registered as
|
||||
@@ -28,6 +32,7 @@ pub struct PrimOp<'a> {
|
||||
ptr: *mut raw::PrimOp,
|
||||
eval_state: &'a mut EvalState,
|
||||
}
|
||||
|
||||
impl Drop for PrimOp<'_> {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
@@ -35,7 +40,13 @@ impl Drop for PrimOp<'_> {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> PrimOp<'a> {
|
||||
/// Create a new primop with the given metadata and implementation.
|
||||
///
|
||||
/// When `f` returns an `Err`, the error is propagated to the Nix evaluator.
|
||||
/// To return a [recoverable error](RecoverableError), include it in the
|
||||
/// error chain (e.g. `Err(RecoverableError::new("...").into())`).
|
||||
pub fn new<const N: usize>(
|
||||
eval_state: &'a mut EvalState,
|
||||
meta: PrimOpMeta<N>,
|
||||
@@ -61,9 +72,10 @@ impl<'a> PrimOp<'a> {
|
||||
}));
|
||||
user_data as *const PrimOpContext as *mut c_void
|
||||
};
|
||||
let mut ctx = Context::new();
|
||||
let op = unsafe {
|
||||
check_call!(raw::alloc_primop(
|
||||
&mut eval_state.context,
|
||||
&mut ctx,
|
||||
FUNCTION_ADAPTER,
|
||||
N as c_int,
|
||||
meta.name.as_ptr(),
|
||||
@@ -90,8 +102,9 @@ impl<'a> PrimOp<'a> {
|
||||
pub fn new_value(mut self) -> Result<Value, EvalStateError> {
|
||||
self.with_state_and_ptr(|ptr, this| {
|
||||
let value = this.new_value_uninitialized()?;
|
||||
let mut ctx = Context::new();
|
||||
unsafe {
|
||||
check_call!(raw::init_primop(&mut this.context, value.raw_ptr(), ptr))?;
|
||||
check_call!(raw::init_primop(&mut ctx, value.raw_ptr(), ptr))?;
|
||||
};
|
||||
Ok(value)
|
||||
})
|
||||
@@ -134,13 +147,26 @@ unsafe extern "C" fn function_adapter(
|
||||
raw::copy_value(context_out, ret, v.raw_ptr());
|
||||
},
|
||||
Err(e) => unsafe {
|
||||
let cstr = CString::new(e.to_string()).unwrap_or_else(|_e| {
|
||||
let err_str = e.to_string();
|
||||
let err_code = error_code(e);
|
||||
let cstr = CString::new(err_str).unwrap_or_else(|_e| {
|
||||
CString::new("<rust nix-expr application error message contained null byte>")
|
||||
.unwrap()
|
||||
});
|
||||
raw_util::set_err_msg(context_out, raw_util::err_NIX_ERR_UNKNOWN, cstr.as_ptr());
|
||||
raw_util::set_err_msg(context_out, err_code, cstr.as_ptr());
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(not(nix_at_least = "2.34.0pre"), allow(unused))]
|
||||
fn error_code(e: Box<dyn Error>) -> raw_util::err {
|
||||
#[cfg(nix_at_least = "2.34.0pre")]
|
||||
if e.downcast_ref::<NixError>()
|
||||
.is_some_and(|e| matches!(e, NixError::RecoverableError(_)))
|
||||
{
|
||||
return raw_util::err_NIX_ERR_RECOVERABLE;
|
||||
}
|
||||
raw_util::err_NIX_ERR_UNKNOWN
|
||||
}
|
||||
|
||||
static FUNCTION_ADAPTER: raw::PrimOpFun = Some(function_adapter);
|
||||
|
||||
@@ -83,6 +83,10 @@ impl ValueType {
|
||||
pub struct Value {
|
||||
inner: NonNull<raw::Value>,
|
||||
}
|
||||
|
||||
unsafe impl Send for Value {}
|
||||
unsafe impl Sync for Value {}
|
||||
|
||||
impl Value {
|
||||
/// Take ownership of a new [`Value`].
|
||||
///
|
||||
|
||||
@@ -13,6 +13,10 @@ use nix_bindings_util::{Error, Result};
|
||||
pub struct FlakeSettings {
|
||||
pub(crate) ptr: *mut raw::flake_settings,
|
||||
}
|
||||
|
||||
unsafe impl Send for FlakeSettings {}
|
||||
unsafe impl Sync for FlakeSettings {}
|
||||
|
||||
impl Drop for FlakeSettings {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
@@ -20,6 +24,7 @@ impl Drop for FlakeSettings {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FlakeSettings {
|
||||
pub fn new() -> Result<Self> {
|
||||
let mut ctx = Context::new();
|
||||
@@ -49,6 +54,7 @@ pub trait EvalStateBuilderExt {
|
||||
settings: &FlakeSettings,
|
||||
) -> Result<nix_bindings_expr::eval_state::EvalStateBuilder>;
|
||||
}
|
||||
|
||||
impl EvalStateBuilderExt for nix_bindings_expr::eval_state::EvalStateBuilder {
|
||||
/// Configures the eval state to provide flakes features such as `builtins.getFlake`.
|
||||
fn flakes(
|
||||
@@ -206,6 +212,10 @@ impl FlakeLockFlags {
|
||||
pub struct LockedFlake {
|
||||
pub(crate) ptr: NonNull<raw::locked_flake>,
|
||||
}
|
||||
|
||||
unsafe impl Send for LockedFlake {}
|
||||
unsafe impl Sync for LockedFlake {}
|
||||
|
||||
impl Drop for LockedFlake {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
@@ -213,6 +223,7 @@ impl Drop for LockedFlake {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl LockedFlake {
|
||||
pub fn lock(
|
||||
fetch_settings: &FetchersSettings,
|
||||
@@ -295,7 +306,7 @@ mod tests {
|
||||
init();
|
||||
let gc_registration = gc_register_my_thread();
|
||||
let store = Store::open(None, []).unwrap();
|
||||
let mut eval_state = EvalStateBuilder::new(store)
|
||||
let eval_state = EvalStateBuilder::new(store)
|
||||
.unwrap()
|
||||
.flakes(&FlakeSettings::new().unwrap())
|
||||
.unwrap()
|
||||
|
||||
@@ -20,6 +20,9 @@ pub struct StorePath {
|
||||
raw: NonNull<raw::StorePath>,
|
||||
}
|
||||
|
||||
unsafe impl Send for StorePath {}
|
||||
unsafe impl Sync for StorePath {}
|
||||
|
||||
impl StorePath {
|
||||
/// Get the name of the store path.
|
||||
///
|
||||
|
||||
@@ -56,10 +56,7 @@ impl StoreWeak {
|
||||
///
|
||||
/// If no normal reference to the [Store] is around anymore elsewhere, this fails by returning `None`.
|
||||
pub fn upgrade(&self) -> Option<Store> {
|
||||
self.inner.upgrade().map(|inner| Store {
|
||||
inner,
|
||||
context: Context::new(),
|
||||
})
|
||||
self.inner.upgrade().map(|inner| Store { inner })
|
||||
}
|
||||
}
|
||||
|
||||
@@ -144,8 +141,6 @@ fn callback_make_drv_outputs_data(vec: &mut HashMap<String, String>) -> *mut std
|
||||
#[clippy::has_significant_drop]
|
||||
pub struct Store {
|
||||
inner: Arc<StoreRef>,
|
||||
/* An error context to reuse. This way we don't have to allocate them for each store operation. */
|
||||
context: Context,
|
||||
}
|
||||
impl Store {
|
||||
/// Open a store.
|
||||
@@ -236,7 +231,6 @@ impl Store {
|
||||
inner: Arc::new(StoreRef {
|
||||
inner: NonNull::new(store).unwrap(),
|
||||
}),
|
||||
context,
|
||||
};
|
||||
Ok(store)
|
||||
}
|
||||
@@ -251,9 +245,10 @@ impl Store {
|
||||
#[doc(alias = "nix_store_get_uri")]
|
||||
pub fn get_uri(&mut self) -> Result<String> {
|
||||
let mut r = Err(Error::StringInit);
|
||||
let mut ctx = Context::new();
|
||||
unsafe {
|
||||
check_call!(raw::store_get_uri(
|
||||
&mut self.context,
|
||||
&mut ctx,
|
||||
self.inner.ptr(),
|
||||
Some(callback_get_result_string),
|
||||
callback_get_result_string_data(&mut r)
|
||||
@@ -266,9 +261,10 @@ impl Store {
|
||||
#[doc(alias = "nix_store_get_storedir")]
|
||||
pub fn get_storedir(&mut self) -> Result<String> {
|
||||
let mut r = Err(Error::StringInit);
|
||||
let mut ctx = Context::new();
|
||||
unsafe {
|
||||
check_call!(raw::store_get_storedir(
|
||||
&mut self.context,
|
||||
&mut ctx,
|
||||
self.inner.ptr(),
|
||||
Some(callback_get_result_string),
|
||||
callback_get_result_string_data(&mut r)
|
||||
@@ -280,9 +276,10 @@ impl Store {
|
||||
#[doc(alias = "nix_store_get_version")]
|
||||
pub fn get_version(&mut self) -> Result<String> {
|
||||
let mut r = Err(Error::StringInit);
|
||||
let mut ctx = Context::new();
|
||||
unsafe {
|
||||
check_call!(raw::store_get_version(
|
||||
&mut self.context,
|
||||
&mut ctx,
|
||||
self.inner.ptr(),
|
||||
Some(callback_get_result_string),
|
||||
callback_get_result_string_data(&mut r)
|
||||
@@ -294,9 +291,10 @@ impl Store {
|
||||
#[doc(alias = "nix_store_parse_path")]
|
||||
pub fn parse_store_path(&mut self, path: &str) -> Result<StorePath> {
|
||||
let path = CString::new(path)?;
|
||||
let mut ctx = Context::new();
|
||||
unsafe {
|
||||
let store_path = check_call!(raw::store_parse_path(
|
||||
&mut self.context,
|
||||
&mut ctx,
|
||||
self.inner.ptr(),
|
||||
path.as_ptr()
|
||||
))?;
|
||||
@@ -309,9 +307,10 @@ impl Store {
|
||||
#[doc(alias = "nix_store_real_path")]
|
||||
pub fn real_path(&mut self, path: &StorePath) -> Result<String> {
|
||||
let mut r = Err(Error::StringInit);
|
||||
let mut ctx = Context::new();
|
||||
unsafe {
|
||||
check_call!(raw::store_real_path(
|
||||
&mut self.context,
|
||||
&mut ctx,
|
||||
self.inner.ptr(),
|
||||
path.as_ptr(),
|
||||
Some(callback_get_result_string),
|
||||
@@ -339,9 +338,10 @@ impl Store {
|
||||
#[doc(alias = "nix_derivation_from_json")]
|
||||
pub fn derivation_from_json(&mut self, json: &str) -> Result<Derivation> {
|
||||
let json_cstr = CString::new(json)?;
|
||||
let mut ctx = Context::new();
|
||||
unsafe {
|
||||
let drv = check_call!(raw::derivation_from_json(
|
||||
&mut self.context,
|
||||
&mut ctx,
|
||||
self.inner.ptr(),
|
||||
json_cstr.as_ptr()
|
||||
))?;
|
||||
@@ -365,9 +365,10 @@ impl Store {
|
||||
#[cfg(nix_at_least = "2.31")]
|
||||
#[doc(alias = "nix_add_derivation")]
|
||||
pub fn add_derivation(&mut self, drv: &Derivation) -> Result<StorePath> {
|
||||
let mut ctx = Context::new();
|
||||
unsafe {
|
||||
let path = check_call!(raw::add_derivation(
|
||||
&mut self.context,
|
||||
&mut ctx,
|
||||
self.inner.ptr(),
|
||||
drv.inner.as_ptr()
|
||||
))?;
|
||||
@@ -380,9 +381,10 @@ impl Store {
|
||||
pub fn make_drv_outputs(&mut self, json: &str) -> Result<HashMap<String, String>> {
|
||||
let json = CString::new(json)?;
|
||||
let mut r = HashMap::new();
|
||||
let mut ctx = Context::new();
|
||||
unsafe {
|
||||
check_call!(raw::derivation_make_outputs(
|
||||
&mut self.context,
|
||||
&mut ctx,
|
||||
self.inner.ptr(),
|
||||
json.as_ptr(),
|
||||
Some(callback_make_drv_outputs),
|
||||
@@ -425,9 +427,10 @@ impl Store {
|
||||
.map(|p| unsafe { p.as_ptr() as *const raw::StorePath })
|
||||
.collect();
|
||||
|
||||
let mut ctx = Context::new();
|
||||
unsafe {
|
||||
check_call!(raw::store_build_paths(
|
||||
&mut self.context,
|
||||
&mut ctx,
|
||||
self.inner.ptr(),
|
||||
paths.as_mut_ptr(),
|
||||
paths.len() as std::os::raw::c_uint,
|
||||
@@ -479,9 +482,10 @@ impl Store {
|
||||
outputs.insert(name, path);
|
||||
}
|
||||
|
||||
let mut ctx = Context::new();
|
||||
unsafe {
|
||||
check_call!(raw::store_realise(
|
||||
&mut self.context,
|
||||
&mut ctx,
|
||||
self.inner.ptr(),
|
||||
path.as_ptr(),
|
||||
userdata,
|
||||
@@ -520,9 +524,10 @@ impl Store {
|
||||
include_derivers: bool,
|
||||
) -> Result<Vec<StorePath>> {
|
||||
let mut r = Vec::new();
|
||||
let mut ctx = Context::new();
|
||||
unsafe {
|
||||
check_call!(raw::store_get_fs_closure(
|
||||
&mut self.context,
|
||||
&mut ctx,
|
||||
self.inner.ptr(),
|
||||
store_path.as_ptr(),
|
||||
flip_direction,
|
||||
@@ -538,9 +543,10 @@ impl Store {
|
||||
#[doc(alias = "nix_store_drv_from_path")]
|
||||
pub fn drv_from_path(&mut self, path: &StorePath) -> Result<Derivation> {
|
||||
let mut r = Err(Error::DerivationInit);
|
||||
let mut ctx = Context::new();
|
||||
unsafe {
|
||||
check_call!(raw::store_drv_from_path(
|
||||
&mut self.context,
|
||||
&mut ctx,
|
||||
self.inner.ptr(),
|
||||
path.as_ptr(),
|
||||
Some(callback_get_result_derivation),
|
||||
@@ -553,9 +559,10 @@ impl Store {
|
||||
#[doc(alias = "nix_store_query_path_info")]
|
||||
pub fn query_path_info(&mut self, path: &StorePath) -> Result<String> {
|
||||
let mut r = Err(Error::StringInit);
|
||||
let mut ctx = Context::new();
|
||||
unsafe {
|
||||
check_call!(raw::store_query_path_info(
|
||||
&mut self.context,
|
||||
&mut ctx,
|
||||
self.inner.ptr(),
|
||||
path.as_ptr(),
|
||||
callback_get_result_string_data(&mut r),
|
||||
@@ -576,7 +583,6 @@ impl Clone for Store {
|
||||
fn clone(&self) -> Self {
|
||||
Store {
|
||||
inner: self.inner.clone(),
|
||||
context: Context::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -960,7 +966,8 @@ mod tests {
|
||||
};
|
||||
|
||||
assert!(
|
||||
err.contains("required system or feature not available"),
|
||||
err.contains("required system or feature not available")
|
||||
|| err.contains("platform mismatch"),
|
||||
"Error should mention system not available, got: {}",
|
||||
err
|
||||
);
|
||||
|
||||
@@ -12,6 +12,8 @@ pub struct Context {
|
||||
inner: NonNull<raw::c_context>,
|
||||
}
|
||||
|
||||
unsafe impl Send for Context {}
|
||||
|
||||
impl Default for Context {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
|
||||
@@ -6,6 +6,7 @@ use std::{ffi::NulError, str::Utf8Error, string::FromUtf8Error};
|
||||
use thiserror::Error;
|
||||
|
||||
pub mod context;
|
||||
pub mod logger;
|
||||
pub mod settings;
|
||||
#[macro_use]
|
||||
pub mod string_return;
|
||||
@@ -91,6 +92,21 @@ pub enum Error {
|
||||
/// A string that needed to be valid UTF-8 was not valid UTF-8.
|
||||
#[error(transparent)]
|
||||
StringUtf8Error(#[from] FromUtf8Error),
|
||||
|
||||
/// A primop error that is not memoized in the thunk that triggered it,
|
||||
/// allowing the thunk to be forced again.
|
||||
///
|
||||
/// Since [Nix 2.34](https://nix.dev/manual/nix/2.34/release-notes/rl-2.34.html#c-api-changes),
|
||||
/// primop errors are memoized by default: once a thunk fails, forcing it
|
||||
/// again returns the same error. Use `RecoverableError` for errors that
|
||||
/// are transient, so the caller can retry.
|
||||
///
|
||||
/// On Nix < 2.34, all errors are already recoverable, so this type has
|
||||
/// no additional effect.
|
||||
///
|
||||
/// Available since nix-bindings-expr 0.2.1.
|
||||
#[error("{0}")]
|
||||
RecoverableError(String),
|
||||
}
|
||||
|
||||
/// An error as returned from the Nix API.
|
||||
|
||||
@@ -0,0 +1,382 @@
|
||||
//! Custom Nix logger.
|
||||
//!
|
||||
//! Replace Nix's global logger with a Rust callback-based one that
|
||||
//! receives log messages, activities, and string-valued results from
|
||||
//! anywhere in libnixutil, libnixstore, and libnixexpr.
|
||||
//!
|
||||
//! See the [`Logger`] trait and [`set_logger`].
|
||||
|
||||
use crate::raw_sys as raw;
|
||||
use crate::{check_call, context, Result};
|
||||
use std::ffi::{c_char, c_void, CStr};
|
||||
use std::sync::Mutex;
|
||||
|
||||
/// Verbosity level of a log message.
|
||||
///
|
||||
/// Mirrors the C `nix_verbosity` enum.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
#[non_exhaustive]
|
||||
pub enum Verbosity {
|
||||
Error,
|
||||
Warn,
|
||||
Notice,
|
||||
Info,
|
||||
Talkative,
|
||||
Chatty,
|
||||
Debug,
|
||||
Vomit,
|
||||
/// A verbosity level not (yet) modelled by this crate.
|
||||
Unknown(u32),
|
||||
}
|
||||
|
||||
impl Verbosity {
|
||||
fn from_raw(v: raw::verbosity) -> Self {
|
||||
match v {
|
||||
raw::verbosity_NIX_LVL_ERROR => Verbosity::Error,
|
||||
raw::verbosity_NIX_LVL_WARN => Verbosity::Warn,
|
||||
raw::verbosity_NIX_LVL_NOTICE => Verbosity::Notice,
|
||||
raw::verbosity_NIX_LVL_INFO => Verbosity::Info,
|
||||
raw::verbosity_NIX_LVL_TALKATIVE => Verbosity::Talkative,
|
||||
raw::verbosity_NIX_LVL_CHATTY => Verbosity::Chatty,
|
||||
raw::verbosity_NIX_LVL_DEBUG => Verbosity::Debug,
|
||||
raw::verbosity_NIX_LVL_VOMIT => Verbosity::Vomit,
|
||||
other => Verbosity::Unknown(other),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Identifier for a logger activity. `0` is the implicit root.
|
||||
pub type ActivityId = u64;
|
||||
|
||||
/// Type of an activity.
|
||||
///
|
||||
/// Mirrors the C `nix_activity_type` enum.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
#[non_exhaustive]
|
||||
pub enum ActivityType {
|
||||
Unknown,
|
||||
CopyPath,
|
||||
FileTransfer,
|
||||
Realise,
|
||||
CopyPaths,
|
||||
Builds,
|
||||
Build,
|
||||
OptimiseStore,
|
||||
VerifyPaths,
|
||||
Substitute,
|
||||
QueryPathInfo,
|
||||
PostBuildHook,
|
||||
BuildWaiting,
|
||||
FetchTree,
|
||||
/// An activity type not (yet) modelled by this crate.
|
||||
Other(u32),
|
||||
}
|
||||
|
||||
impl ActivityType {
|
||||
fn from_raw(t: raw::activity_type) -> Self {
|
||||
match t {
|
||||
raw::activity_type_NIX_ACTIVITY_TYPE_UNKNOWN => ActivityType::Unknown,
|
||||
raw::activity_type_NIX_ACTIVITY_TYPE_COPY_PATH => ActivityType::CopyPath,
|
||||
raw::activity_type_NIX_ACTIVITY_TYPE_FILE_TRANSFER => ActivityType::FileTransfer,
|
||||
raw::activity_type_NIX_ACTIVITY_TYPE_REALISE => ActivityType::Realise,
|
||||
raw::activity_type_NIX_ACTIVITY_TYPE_COPY_PATHS => ActivityType::CopyPaths,
|
||||
raw::activity_type_NIX_ACTIVITY_TYPE_BUILDS => ActivityType::Builds,
|
||||
raw::activity_type_NIX_ACTIVITY_TYPE_BUILD => ActivityType::Build,
|
||||
raw::activity_type_NIX_ACTIVITY_TYPE_OPTIMISE_STORE => ActivityType::OptimiseStore,
|
||||
raw::activity_type_NIX_ACTIVITY_TYPE_VERIFY_PATHS => ActivityType::VerifyPaths,
|
||||
raw::activity_type_NIX_ACTIVITY_TYPE_SUBSTITUTE => ActivityType::Substitute,
|
||||
raw::activity_type_NIX_ACTIVITY_TYPE_QUERY_PATH_INFO => ActivityType::QueryPathInfo,
|
||||
raw::activity_type_NIX_ACTIVITY_TYPE_POST_BUILD_HOOK => ActivityType::PostBuildHook,
|
||||
raw::activity_type_NIX_ACTIVITY_TYPE_BUILD_WAITING => ActivityType::BuildWaiting,
|
||||
raw::activity_type_NIX_ACTIVITY_TYPE_FETCH_TREE => ActivityType::FetchTree,
|
||||
other => ActivityType::Other(other),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Type of a result reported by an activity.
|
||||
///
|
||||
/// Only string-valued result types are delivered to
|
||||
/// [`Logger::result_string`]; other variants exist for forward
|
||||
/// compatibility.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
#[non_exhaustive]
|
||||
pub enum ResultType {
|
||||
FileLinked,
|
||||
BuildLogLine,
|
||||
UntrustedPath,
|
||||
CorruptedPath,
|
||||
SetPhase,
|
||||
Progress,
|
||||
SetExpected,
|
||||
PostBuildLogLine,
|
||||
FetchStatus,
|
||||
HashMismatch,
|
||||
BuildResult,
|
||||
/// A result type not (yet) modelled by this crate.
|
||||
Other(u32),
|
||||
}
|
||||
|
||||
impl ResultType {
|
||||
fn from_raw(t: raw::result_type) -> Self {
|
||||
match t {
|
||||
raw::result_type_NIX_RESULT_TYPE_FILE_LINKED => ResultType::FileLinked,
|
||||
raw::result_type_NIX_RESULT_TYPE_BUILD_LOG_LINE => ResultType::BuildLogLine,
|
||||
raw::result_type_NIX_RESULT_TYPE_UNTRUSTED_PATH => ResultType::UntrustedPath,
|
||||
raw::result_type_NIX_RESULT_TYPE_CORRUPTED_PATH => ResultType::CorruptedPath,
|
||||
raw::result_type_NIX_RESULT_TYPE_SET_PHASE => ResultType::SetPhase,
|
||||
raw::result_type_NIX_RESULT_TYPE_PROGRESS => ResultType::Progress,
|
||||
raw::result_type_NIX_RESULT_TYPE_SET_EXPECTED => ResultType::SetExpected,
|
||||
raw::result_type_NIX_RESULT_TYPE_POST_BUILD_LOG_LINE => ResultType::PostBuildLogLine,
|
||||
raw::result_type_NIX_RESULT_TYPE_FETCH_STATUS => ResultType::FetchStatus,
|
||||
raw::result_type_NIX_RESULT_TYPE_HASH_MISMATCH => ResultType::HashMismatch,
|
||||
raw::result_type_NIX_RESULT_TYPE_BUILD_RESULT => ResultType::BuildResult,
|
||||
other => ResultType::Other(other),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A logger that receives messages from Nix.
|
||||
///
|
||||
/// Implementations must be [`Send`] and [`Sync`] because Nix may
|
||||
/// invoke the callbacks from arbitrary threads (e.g. during parallel
|
||||
/// builds).
|
||||
///
|
||||
/// All methods have empty default implementations, so an
|
||||
/// implementation only needs to override the events it cares about.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Callback methods must not unwind across the FFI boundary.
|
||||
/// Panics from any of these methods are caught and silently
|
||||
/// discarded; if your implementation may panic, prefer to handle
|
||||
/// it yourself.
|
||||
pub trait Logger: Send + Sync {
|
||||
/// An ordinary log message.
|
||||
///
|
||||
/// Receives `builtins.trace` output, formatted warnings/errors,
|
||||
/// and messages produced through the C++ `printError` /
|
||||
/// `printInfo` / `debug` macros.
|
||||
///
|
||||
/// `msg` is decoded with [`String::from_utf8_lossy`] to keep the
|
||||
/// signature ergonomic.
|
||||
fn log(&self, _level: Verbosity, _msg: &str) {}
|
||||
|
||||
/// An activity (build, substitution, ...) has started.
|
||||
///
|
||||
/// `parent_id` is `0` for top-level activities.
|
||||
fn start_activity(
|
||||
&self,
|
||||
_activity_id: ActivityId,
|
||||
_level: Verbosity,
|
||||
_type_: ActivityType,
|
||||
_description: &str,
|
||||
_parent_id: ActivityId,
|
||||
) {
|
||||
}
|
||||
|
||||
/// An activity has stopped.
|
||||
fn stop_activity(&self, _activity_id: ActivityId) {}
|
||||
|
||||
/// An activity reported a string-valued result.
|
||||
///
|
||||
/// Result types that carry non-string fields (such as
|
||||
/// [`ResultType::Progress`]) are not delivered through this
|
||||
/// method.
|
||||
fn result_string(&self, _activity_id: ActivityId, _type_: ResultType, _msg: &str) {}
|
||||
}
|
||||
|
||||
/// Erased trait object passed across the FFI boundary as `userdata`.
|
||||
type LoggerObj = Box<dyn Logger + Send + Sync>;
|
||||
|
||||
/// Serializes calls to [`set_logger`] from the Rust side.
|
||||
///
|
||||
/// Note this does *not* protect against C++ code (or other clients of
|
||||
/// the C API) replacing the global logger concurrently — it only
|
||||
/// avoids racing with ourselves.
|
||||
static SET_LOGGER_MUTEX: Mutex<()> = Mutex::new(());
|
||||
|
||||
unsafe extern "C" fn thunk_log(userdata: *mut c_void, level: raw::verbosity, msg: *const c_char) {
|
||||
assert!(!userdata.is_null());
|
||||
let _ = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
|
||||
let logger = &*(userdata as *const LoggerObj);
|
||||
let msg_str = if msg.is_null() {
|
||||
std::borrow::Cow::Borrowed("")
|
||||
} else {
|
||||
CStr::from_ptr(msg).to_string_lossy()
|
||||
};
|
||||
logger.log(Verbosity::from_raw(level), msg_str.as_ref());
|
||||
}));
|
||||
}
|
||||
|
||||
unsafe extern "C" fn thunk_start_activity(
|
||||
userdata: *mut c_void,
|
||||
activity_id: raw::activity_id,
|
||||
level: raw::verbosity,
|
||||
type_: raw::activity_type,
|
||||
s: *const c_char,
|
||||
parent_id: raw::activity_id,
|
||||
) {
|
||||
assert!(!userdata.is_null());
|
||||
let _ = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
|
||||
let logger = &*(userdata as *const LoggerObj);
|
||||
let s_str = if s.is_null() {
|
||||
std::borrow::Cow::Borrowed("")
|
||||
} else {
|
||||
CStr::from_ptr(s).to_string_lossy()
|
||||
};
|
||||
logger.start_activity(
|
||||
activity_id,
|
||||
Verbosity::from_raw(level),
|
||||
ActivityType::from_raw(type_),
|
||||
s_str.as_ref(),
|
||||
parent_id,
|
||||
);
|
||||
}));
|
||||
}
|
||||
|
||||
unsafe extern "C" fn thunk_stop_activity(userdata: *mut c_void, activity_id: raw::activity_id) {
|
||||
assert!(!userdata.is_null());
|
||||
let _ = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
|
||||
let logger = &*(userdata as *const LoggerObj);
|
||||
logger.stop_activity(activity_id);
|
||||
}));
|
||||
}
|
||||
|
||||
unsafe extern "C" fn thunk_result_string(
|
||||
userdata: *mut c_void,
|
||||
activity_id: raw::activity_id,
|
||||
type_: raw::result_type,
|
||||
msg: *const c_char,
|
||||
) {
|
||||
assert!(!userdata.is_null());
|
||||
let _ = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
|
||||
let logger = &*(userdata as *const LoggerObj);
|
||||
let msg_str = if msg.is_null() {
|
||||
std::borrow::Cow::Borrowed("")
|
||||
} else {
|
||||
CStr::from_ptr(msg).to_string_lossy()
|
||||
};
|
||||
logger.result_string(activity_id, ResultType::from_raw(type_), msg_str.as_ref());
|
||||
}));
|
||||
}
|
||||
|
||||
unsafe extern "C" fn thunk_destroy(userdata: *mut c_void) {
|
||||
if userdata.is_null() {
|
||||
return;
|
||||
}
|
||||
let _ = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
|
||||
drop(Box::from_raw(userdata as *mut LoggerObj));
|
||||
}));
|
||||
}
|
||||
|
||||
static VTABLE: raw::logger = raw::logger {
|
||||
log: Some(thunk_log),
|
||||
start_activity: Some(thunk_start_activity),
|
||||
stop_activity: Some(thunk_stop_activity),
|
||||
result_string: Some(thunk_result_string),
|
||||
destroy: Some(thunk_destroy),
|
||||
};
|
||||
|
||||
/// Replace Nix's global logger with `logger`.
|
||||
///
|
||||
/// `logger` is moved into a heap allocation owned by Nix; it is
|
||||
/// dropped when this function is called again (replacing the logger)
|
||||
/// or at process shutdown.
|
||||
///
|
||||
/// # Thread safety
|
||||
///
|
||||
/// This function serializes concurrent Rust callers via an internal
|
||||
/// mutex. It does **not** protect against C++ code mutating
|
||||
/// `nix::logger` directly. Like [`crate::settings::set`], prefer to
|
||||
/// install a logger during single-threaded initialization.
|
||||
#[doc(alias = "nix_set_logger")]
|
||||
pub fn set_logger<L: Logger + 'static>(logger: L) -> Result<()> {
|
||||
let _guard = SET_LOGGER_MUTEX.lock().unwrap();
|
||||
|
||||
let boxed: Box<LoggerObj> = Box::new(Box::new(logger));
|
||||
let userdata = Box::into_raw(boxed) as *mut c_void;
|
||||
|
||||
let mut ctx = context::Context::new();
|
||||
let res = unsafe { check_call!(raw::set_logger(&mut ctx, &VTABLE, userdata)) };
|
||||
|
||||
if let Err(e) = res {
|
||||
// The C side did not accept the logger, so it will not invoke
|
||||
// the destroy callback. Reclaim the box ourselves to avoid
|
||||
// leaking the user-supplied logger.
|
||||
unsafe {
|
||||
drop(Box::from_raw(userdata as *mut LoggerObj));
|
||||
}
|
||||
return Err(e);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
|
||||
use std::sync::Arc;
|
||||
|
||||
#[ctor::ctor]
|
||||
fn setup() {
|
||||
crate::init().unwrap();
|
||||
}
|
||||
|
||||
/// Logger that records when it's dropped, so tests can observe the
|
||||
/// destroy callback firing on replacement.
|
||||
struct DropFlag {
|
||||
dropped: Arc<AtomicBool>,
|
||||
log_count: Arc<AtomicUsize>,
|
||||
}
|
||||
|
||||
impl Logger for DropFlag {
|
||||
fn log(&self, _level: Verbosity, _msg: &str) {
|
||||
self.log_count.fetch_add(1, Ordering::SeqCst);
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for DropFlag {
|
||||
fn drop(&mut self) {
|
||||
self.dropped.store(true, Ordering::SeqCst);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn set_and_replace_calls_destroy() {
|
||||
let dropped = Arc::new(AtomicBool::new(false));
|
||||
let log_count = Arc::new(AtomicUsize::new(0));
|
||||
|
||||
set_logger(DropFlag {
|
||||
dropped: dropped.clone(),
|
||||
log_count: log_count.clone(),
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
assert!(!dropped.load(Ordering::SeqCst));
|
||||
|
||||
// Replace with a fresh logger; this should drop the previous one.
|
||||
set_logger(DropFlag {
|
||||
dropped: Arc::new(AtomicBool::new(false)),
|
||||
log_count: Arc::new(AtomicUsize::new(0)),
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
assert!(
|
||||
dropped.load(Ordering::SeqCst),
|
||||
"previous logger should be dropped when the logger is replaced"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn verbosity_round_trips_known_values() {
|
||||
assert_eq!(
|
||||
Verbosity::from_raw(raw::verbosity_NIX_LVL_ERROR),
|
||||
Verbosity::Error
|
||||
);
|
||||
assert_eq!(
|
||||
Verbosity::from_raw(raw::verbosity_NIX_LVL_VOMIT),
|
||||
Verbosity::Vomit
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user