From 28ce6ae18506ad8cdc69ed4ea6f15dbb0956a722 Mon Sep 17 00:00:00 2001 From: Pascal Jungblut Date: Mon, 13 Apr 2026 22:17:59 +0100 Subject: [PATCH 1/2] Update to Nix 3.25 --- flake.lock | 46 +++++++++++++++++++++++++++++----------------- flake.nix | 7 +++++-- 2 files changed, 34 insertions(+), 19 deletions(-) diff --git a/flake.lock b/flake.lock index da940104..9662537b 100644 --- a/flake.lock +++ b/flake.lock @@ -163,22 +163,21 @@ "flake-compat": "flake-compat", "flake-parts": "flake-parts_2", "git-hooks-nix": "git-hooks-nix", - "nixpkgs": [ - "nixpkgs" - ], + "nixpkgs": "nixpkgs", "nixpkgs-23-11": "nixpkgs-23-11", "nixpkgs-regression": "nixpkgs-regression" }, "locked": { - "lastModified": 1772224943, - "narHash": "sha256-jJIlRLPPVYu860MVFx4gsRx3sskmLDSRWXXue5tYncw=", + "lastModified": 1776091293, + "narHash": "sha256-+MorH5LajRRZDBwDboBfvIJZfqGxB06ccYo0taLl7k4=", "owner": "NixOS", "repo": "nix", - "rev": "0acd0566e85e4597269482824711bcde7b518600", + "rev": "ef97ad9c34a8c60d3e1d90ff7565d76b30fa5c9e", "type": "github" }, "original": { "owner": "NixOS", + "ref": "pull/15675/merge", "repo": "nix", "type": "github" } @@ -211,18 +210,15 @@ }, "nixpkgs": { "locked": { - "lastModified": 1772198003, - "narHash": "sha256-I45esRSssFtJ8p/gLHUZ1OUaaTaVLluNkABkk6arQwE=", - "owner": "NixOS", - "repo": "nixpkgs", - "rev": "dd9b079222d43e1943b6ebd802f04fd959dc8e61", - "type": "github" + "lastModified": 1771903837, + "narHash": "sha256-jEA8WggGKtMFeNeCKq3NK8cLEjJmG6/RLUElYYbBZ0E=", + "rev": "e764fc9a405871f1f6ca3d1394fb422e0a0c3951", + "type": "tarball", + "url": "https://releases.nixos.org/nixos/25.11/nixos-25.11.6495.e764fc9a4058/nixexprs.tar.xz" }, "original": { - "owner": "NixOS", - "ref": "nixos-unstable", - "repo": "nixpkgs", - "type": "github" + "type": "tarball", + "url": "https://channels.nixos.org/nixos-25.11/nixexprs.tar.xz" } }, "nixpkgs-23-11": { @@ -272,6 +268,22 @@ "type": "github" } }, + "nixpkgs_2": { + "locked": { + "lastModified": 1772198003, + "narHash": "sha256-I45esRSssFtJ8p/gLHUZ1OUaaTaVLluNkABkk6arQwE=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "dd9b079222d43e1943b6ebd802f04fd959dc8e61", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, "parts": { "inputs": { "nixpkgs-lib": [ @@ -344,7 +356,7 @@ "flake-parts": "flake-parts", "nix": "nix", "nix-cargo-integration": "nix-cargo-integration", - "nixpkgs": "nixpkgs" + "nixpkgs": "nixpkgs_2" } }, "rust-overlay": { diff --git a/flake.nix b/flake.nix index a8295dd3..e320e675 100644 --- a/flake.nix +++ b/flake.nix @@ -3,8 +3,11 @@ inputs = { flake-parts.url = "github:hercules-ci/flake-parts"; - nix.url = "github:NixOS/nix"; - nix.inputs.nixpkgs.follows = "nixpkgs"; + nix = { + # TODO: Change before merging + url = "github:NixOS/nix?ref=pull/15675/merge"; + }; + # nix.inputs.nixpkgs.follows = "nixpkgs"; nix-cargo-integration.url = "github:90-008/nix-cargo-integration"; nix-cargo-integration.inputs.nixpkgs.follows = "nixpkgs"; nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; From 9ea1f7728e11766f9b3e0770177c7f03a1d88438 Mon Sep 17 00:00:00 2001 From: Pascal Jungblut Date: Mon, 13 Apr 2026 22:16:47 +0100 Subject: [PATCH 2/2] feat(nix-bindings-store): Add support for the path_info API --- nix-bindings-store/build.rs | 2 +- nix-bindings-store/src/lib.rs | 1 + nix-bindings-store/src/path_info/mod.rs | 151 ++++++++++++++++++++++++ nix-bindings-store/src/store.rs | 72 +++++++++++ 4 files changed, 225 insertions(+), 1 deletion(-) create mode 100644 nix-bindings-store/src/path_info/mod.rs diff --git a/nix-bindings-store/build.rs b/nix-bindings-store/build.rs index 85a20d62..112b246b 100644 --- a/nix-bindings-store/build.rs +++ b/nix-bindings-store/build.rs @@ -2,5 +2,5 @@ use nix_bindings_util::nix_version::emit_version_cfg; fn main() { let nix_version = pkg_config::probe_library("nix-store-c").unwrap().version; - emit_version_cfg(&nix_version, &["2.26", "2.33.0pre", "2.33"]); + emit_version_cfg(&nix_version, &["2.26", "2.33.0pre", "2.33", "2.35.0pre"]); } diff --git a/nix-bindings-store/src/lib.rs b/nix-bindings-store/src/lib.rs index 6010f2e2..01d174fb 100644 --- a/nix-bindings-store/src/lib.rs +++ b/nix-bindings-store/src/lib.rs @@ -1,3 +1,4 @@ pub mod derivation; pub mod path; +pub mod path_info; pub mod store; diff --git a/nix-bindings-store/src/path_info/mod.rs b/nix-bindings-store/src/path_info/mod.rs new file mode 100644 index 00000000..048ffdd0 --- /dev/null +++ b/nix-bindings-store/src/path_info/mod.rs @@ -0,0 +1,151 @@ +#![cfg(nix_at_least = "2.35.0pre")] + +use std::os::raw::{c_char, c_uint, c_void}; +use std::ptr::NonNull; + +use anyhow::Result; +use nix_bindings_store_sys as raw; +use nix_bindings_util::{ + check_call, + context::Context, + result_string_init, + string_return::{callback_get_result_string, callback_get_result_string_data}, +}; + +use crate::path::StorePath; + +/// Metadata about a store path. +/// +/// **Requires Nix 2.35 or later.** +pub struct PathInfo { + pub(crate) inner: NonNull, +} + +impl PathInfo { + pub(crate) fn new_raw(inner: NonNull) -> Self { + PathInfo { inner } + } + + /// This is a low level function that you shouldn't have to call unless you are developing the bindings. + /// + /// Get a pointer to the underlying API path_info. + /// + /// # Safety + /// + /// The returned pointer is only valid as long as this `PathInfo` is alive. + pub unsafe fn as_ptr(&self) -> *mut raw::path_info { + self.inner.as_ptr() + } + + /// Get the NAR hash of this store path. + /// + /// Returns a string with algorithm prefix in base-32 encoding, e.g. `"sha256:1b8m..."`. + pub fn nar_hash(&self) -> Result { + let mut ctx = Context::new(); + let mut r = result_string_init!(); + unsafe { + check_call!(raw::path_info_get_nar_hash( + &mut ctx, + self.inner.as_ptr(), + Some(callback_get_result_string), + callback_get_result_string_data(&mut r) + ))?; + } + r + } + + /// Get the NAR size of this store path in bytes. + /// + /// Returns 0 if the size is unknown. + pub fn nar_size(&self) -> Result { + let mut ctx = Context::new(); + let size = + unsafe { check_call!(raw::path_info_get_nar_size(&mut ctx, self.inner.as_ptr()))? }; + Ok(size) + } + + /// Get the references of this store path. + pub fn references(&self) -> Result> { + let mut ctx = Context::new(); + let mut refs: Vec = Vec::new(); + + unsafe extern "C" fn callback(user_data: *mut c_void, store_path: *const raw::StorePath) { + let refs = &mut *(user_data as *mut Vec); + let cloned = raw::store_path_clone(store_path); + let cloned = NonNull::new(cloned).expect("store_path_clone returned null"); + refs.push(StorePath::new_raw(cloned)); + } + + let user_data = &mut refs as *mut Vec as *mut c_void; + unsafe { + check_call!(raw::path_info_get_references( + &mut ctx, + self.inner.as_ptr(), + user_data, + Some(callback) + ))?; + } + Ok(refs) + } + + /// Get the deriver of this store path, if known. + /// + /// Returns `None` if no deriver is recorded (e.g., paths added directly). + pub fn deriver(&self) -> Result> { + let mut ctx = Context::new(); + let ptr = + unsafe { check_call!(raw::path_info_get_deriver(&mut ctx, self.inner.as_ptr()))? }; + Ok(NonNull::new(ptr).map(|p| unsafe { StorePath::new_raw(p) })) + } + + /// Get the signatures of this store path. + /// + /// Returns an empty vector for unsigned paths (e.g., a local store). + /// Each signature has the format `"keyName:base64sig"`. + pub fn sigs(&self) -> Result> { + let mut ctx = Context::new(); + let mut sigs: Vec = Vec::new(); + + unsafe extern "C" fn callback(user_data: *mut c_void, sig: *const c_char, sig_len: c_uint) { + let sigs = &mut *(user_data as *mut Vec); + let bytes = std::slice::from_raw_parts(sig as *const u8, sig_len as usize); + sigs.push(String::from_utf8_lossy(bytes).into_owned()); + } + + let user_data = &mut sigs as *mut Vec as *mut c_void; + unsafe { + check_call!(raw::path_info_get_sigs( + &mut ctx, + self.inner.as_ptr(), + user_data, + Some(callback) + ))?; + } + Ok(sigs) + } + + /// Get the content address of this store path, if it is content-addressed. + /// + /// Returns `None` for input-addressed paths. + pub fn ca(&self) -> Result> { + let mut ctx = Context::new(); + let mut r = result_string_init!(); + unsafe { + check_call!(raw::path_info_get_ca( + &mut ctx, + self.inner.as_ptr(), + Some(callback_get_result_string), + callback_get_result_string_data(&mut r) + ))?; + } + Ok(r.ok().filter(|s| !s.is_empty())) + } +} + +impl Drop for PathInfo { + fn drop(&mut self) { + unsafe { + raw::path_info_free(self.inner.as_ptr()); + } + } +} diff --git a/nix-bindings-store/src/store.rs b/nix-bindings-store/src/store.rs index cdd09776..f836e2d2 100644 --- a/nix-bindings-store/src/store.rs +++ b/nix-bindings-store/src/store.rs @@ -17,6 +17,8 @@ use std::sync::{Arc, LazyLock, Mutex, Weak}; #[cfg(nix_at_least = "2.33.0pre")] use crate::derivation::Derivation; use crate::path::StorePath; +#[cfg(nix_at_least = "2.35.0pre")] +use crate::path_info::PathInfo; /* TODO make Nix itself thread safe */ static INIT: LazyLock> = LazyLock::new(|| unsafe { @@ -410,6 +412,26 @@ impl Store { Ok(r) } + /// Query path info for a store path. + /// + /// **Requires Nix 2.35 or later.** + /// + /// Returns the [`PathInfo`] for a path that exists in the store. + #[cfg(nix_at_least = "2.35.0pre")] + #[doc(alias = "nix_store_query_path_info")] + pub fn query_path_info(&mut self, path: &StorePath) -> Result { + unsafe { + let ptr = check_call!(raw::store_query_path_info( + &mut self.context, + self.inner.ptr(), + path.as_ptr() + ))?; + let inner = NonNull::new(ptr) + .ok_or_else(|| Error::msg("store_query_path_info returned null"))?; + Ok(PathInfo::new_raw(inner)) + } + } + pub fn weak_ref(&self) -> StoreWeak { StoreWeak { inner: Arc::downgrade(&self.inner), @@ -991,6 +1013,56 @@ mod tests { drop(temp_dir); } + #[test] + #[cfg(nix_at_least = "2.35.0pre")] + fn query_path_info() { + let (mut store, temp_dir) = create_temp_store(); + let drv_json = create_test_derivation_json(); + let drv = store.derivation_from_json(&drv_json.to_string()).unwrap(); + let drv_path = store.add_derivation(&drv).unwrap(); + + let outputs = store.realise(&drv_path).unwrap(); + let out_path = &outputs["out"]; + + let info = store.query_path_info(out_path).unwrap(); + + let nar_hash = info.nar_hash().unwrap(); + assert!( + nar_hash.starts_with("sha256:"), + "Expected sha256 nar hash, got: {nar_hash}" + ); + + let nar_size = info.nar_size().unwrap(); + assert!(nar_size > 0, "Expected non-zero nar_size, got {nar_size}"); + + let refs = info.references().unwrap(); + assert!(refs.is_empty(), "Expected no references for simple path"); + + let deriver = info.deriver().unwrap(); + assert!(deriver.is_some(), "Expected a deriver"); + assert!(deriver.unwrap().name().unwrap().ends_with(".drv")); + + let sigs = info.sigs().unwrap(); + assert!( + sigs.is_empty(), + "Expected no sigs on unsigned local store path" + ); + + let ca = info.ca().unwrap(); + let ca_str = ca.expect("Expected CA for content-addressed derivation output"); + assert!( + !ca_str.is_empty(), + "Expected non-empty CA string, got empty string" + ); + assert!( + ca_str.starts_with("fixed:"), + "Expected CA to start with 'fixed:', got: {ca_str}" + ); + + drop(store); + drop(temp_dir); + } + #[test] #[cfg(nix_at_least = "2.33")] fn get_fs_closure_include_derivers() {