From 611255d42c008d8b865aa7e891ac2f9a7c6dee6b Mon Sep 17 00:00:00 2001 From: imxyy_soope_ Date: Sat, 17 Jan 2026 22:39:38 +0800 Subject: [PATCH] feat: nix_nar --- Cargo.lock | 34 +++++++++ nix-js/Cargo.toml | 1 + nix-js/src/fetcher.rs | 2 +- nix-js/src/fetcher/cache.rs | 6 +- nix-js/src/fetcher/nar.rs | 127 --------------------------------- nix-js/src/lib.rs | 1 + nix-js/src/nar.rs | 63 ++++++++++++++++ nix-js/src/store.rs | 2 + nix-js/tests/builtins_store.rs | 3 + 9 files changed, 108 insertions(+), 131 deletions(-) delete mode 100644 nix-js/src/fetcher/nar.rs create mode 100644 nix-js/src/nar.rs diff --git a/Cargo.lock b/Cargo.lock index 7e5f23f..40e8b3c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -254,6 +254,12 @@ dependencies = [ "displaydoc", ] +[[package]] +name = "camino" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e629a66d692cb9ff1a1c664e41771b3dcaf961985a9774c0eb0bd1b51cf60a48" + [[package]] name = "capacity_builder" version = "0.5.0" @@ -1480,6 +1486,15 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "is_executable" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baabb8b4867b26294d818bf3f651a454b6901431711abb96e296245888d6e8c4" +dependencies = [ + "windows-sys 0.60.2", +] + [[package]] name = "itertools" version = "0.10.5" @@ -1765,6 +1780,7 @@ dependencies = [ "mimalloc", "nix-daemon", "nix-js-macros", + "nix-nar", "petgraph", "regex", "reqwest", @@ -1794,6 +1810,18 @@ dependencies = [ "syn", ] +[[package]] +name = "nix-nar" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15dbfa157df89f4283825ff1c21d53344cfe0d222ea8fde0f9514206dc62d9e0" +dependencies = [ + "camino", + "is_executable", + "symlink", + "thiserror 1.0.69", +] + [[package]] name = "nom" version = "7.1.3" @@ -2729,6 +2757,12 @@ version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" +[[package]] +name = "symlink" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7973cce6668464ea31f176d85b13c7ab3bba2cb3b77a2ed26abd7801688010a" + [[package]] name = "syn" version = "2.0.104" diff --git a/nix-js/Cargo.toml b/nix-js/Cargo.toml index 94ad8b8..b07a26c 100644 --- a/nix-js/Cargo.toml +++ b/nix-js/Cargo.toml @@ -36,6 +36,7 @@ regex = "1.11" deno_core = "0.376" deno_error = "0.7" +nix-nar = "0.3" sha2 = "0.10" hex = "0.4" diff --git a/nix-js/src/fetcher.rs b/nix-js/src/fetcher.rs index 2ad63a7..6aa59e8 100644 --- a/nix-js/src/fetcher.rs +++ b/nix-js/src/fetcher.rs @@ -7,12 +7,12 @@ pub(crate) mod cache; mod download; mod git; mod hg; -mod nar; pub use cache::FetcherCache; pub use download::Downloader; use crate::runtime::NixError; +use crate::nar; #[derive(Serialize)] pub struct FetchUrlResult { diff --git a/nix-js/src/fetcher/cache.rs b/nix-js/src/fetcher/cache.rs index ec547db..9f9e3ce 100644 --- a/nix-js/src/fetcher/cache.rs +++ b/nix-js/src/fetcher/cache.rs @@ -1,6 +1,6 @@ use std::fs::{self, File}; use std::io::Write; -use std::path::PathBuf; +use std::path::{Path, PathBuf}; use serde::{Deserialize, Serialize}; use tracing::debug; @@ -317,7 +317,7 @@ impl FetcherCache { &self, url: &str, hash: &str, - extracted_path: &PathBuf, + extracted_path: &Path, name: &str, ) -> Result { let cache_dir = self.tarball_cache_dir(); @@ -377,7 +377,7 @@ impl FetcherCache { } } -fn copy_dir_recursive(src: &PathBuf, dst: &PathBuf) -> Result<(), std::io::Error> { +fn copy_dir_recursive(src: &Path, dst: &Path) -> Result<(), std::io::Error> { fs::create_dir_all(dst)?; for entry in fs::read_dir(src)? { diff --git a/nix-js/src/fetcher/nar.rs b/nix-js/src/fetcher/nar.rs deleted file mode 100644 index 7b2bebb..0000000 --- a/nix-js/src/fetcher/nar.rs +++ /dev/null @@ -1,127 +0,0 @@ -use sha2::{Digest, Sha256}; -use std::fs; -use std::io::{self, Write}; -use std::path::Path; - -pub fn compute_nar_hash(path: &Path) -> Result { - let mut hasher = Sha256::new(); - dump_path(&mut hasher, path)?; - Ok(hex::encode(hasher.finalize())) -} - -fn dump_path(sink: &mut W, path: &Path) -> io::Result<()> { - write_string(sink, "nix-archive-1")?; - write_string(sink, "(")?; - dump_entry(sink, path)?; - write_string(sink, ")")?; - Ok(()) -} - -fn dump_entry(sink: &mut W, path: &Path) -> io::Result<()> { - let metadata = fs::symlink_metadata(path)?; - - if metadata.is_symlink() { - let target = fs::read_link(path)?; - write_string(sink, "type")?; - write_string(sink, "symlink")?; - write_string(sink, "target")?; - write_string(sink, &target.to_string_lossy())?; - } else if metadata.is_file() { - write_string(sink, "type")?; - write_string(sink, "regular")?; - - #[cfg(unix)] - { - use std::os::unix::fs::PermissionsExt; - if metadata.permissions().mode() & 0o111 != 0 { - write_string(sink, "executable")?; - write_string(sink, "")?; - } - } - - let contents = fs::read(path)?; - write_string(sink, "contents")?; - write_contents(sink, &contents)?; - } else if metadata.is_dir() { - write_string(sink, "type")?; - write_string(sink, "directory")?; - - let mut entries: Vec<_> = fs::read_dir(path)? - .filter_map(|e| e.ok()) - .map(|e| e.file_name().to_string_lossy().to_string()) - .collect(); - entries.sort(); - - for name in entries { - write_string(sink, "entry")?; - write_string(sink, "(")?; - write_string(sink, "name")?; - write_string(sink, &name)?; - write_string(sink, "node")?; - write_string(sink, "(")?; - dump_entry(sink, &path.join(&name))?; - write_string(sink, ")")?; - write_string(sink, ")")?; - } - } - - Ok(()) -} - -fn write_string(sink: &mut W, s: &str) -> io::Result<()> { - let bytes = s.as_bytes(); - let len = bytes.len() as u64; - - sink.write_all(&len.to_le_bytes())?; - sink.write_all(bytes)?; - - let padding = (8 - (len % 8)) % 8; - for _ in 0..padding { - sink.write_all(&[0])?; - } - - Ok(()) -} - -fn write_contents(sink: &mut W, contents: &[u8]) -> io::Result<()> { - let len = contents.len() as u64; - - sink.write_all(&len.to_le_bytes())?; - sink.write_all(contents)?; - - let padding = (8 - (len % 8)) % 8; - for _ in 0..padding { - sink.write_all(&[0])?; - } - - Ok(()) -} - -#[cfg(test)] -#[allow(clippy::unwrap_used)] -mod tests { - use super::*; - use tempfile::TempDir; - - #[test] - fn test_simple_file() { - let temp = TempDir::new().unwrap(); - let file_path = temp.path().join("test.txt"); - fs::write(&file_path, "hello").unwrap(); - - let hash = compute_nar_hash(&file_path).unwrap(); - assert!(!hash.is_empty()); - assert_eq!(hash.len(), 64); - } - - #[test] - fn test_directory() { - let temp = TempDir::new().unwrap(); - fs::write(temp.path().join("a.txt"), "aaa").unwrap(); - fs::write(temp.path().join("b.txt"), "bbb").unwrap(); - - let hash = compute_nar_hash(temp.path()).unwrap(); - assert!(!hash.is_empty()); - assert_eq!(hash.len(), 64); - } -} diff --git a/nix-js/src/lib.rs b/nix-js/src/lib.rs index 972bedf..76e6396 100644 --- a/nix-js/src/lib.rs +++ b/nix-js/src/lib.rs @@ -8,6 +8,7 @@ mod codegen; mod fetcher; mod ir; mod nix_hash; +mod nar; mod runtime; mod store; diff --git a/nix-js/src/nar.rs b/nix-js/src/nar.rs new file mode 100644 index 0000000..ab51867 --- /dev/null +++ b/nix-js/src/nar.rs @@ -0,0 +1,63 @@ +use nix_nar::Encoder; +use sha2::{Digest, Sha256}; +use std::io::Read; +use std::path::Path; + +use crate::error::{Error, Result}; + +pub fn compute_nar_hash(path: &Path) -> Result { + let mut hasher = Sha256::new(); + std::io::copy( + &mut Encoder::new(path).map_err(|err| Error::internal(err.to_string()))?, + &mut hasher, + ) + .map_err(|err| Error::internal(err.to_string()))?; + Ok(hex::encode(hasher.finalize())) +} + +pub fn pack_nar(path: &Path) -> Result> { + let mut buffer = Vec::new(); + Encoder::new(path) + .map_err(|err| Error::internal(err.to_string()))? + .read_to_end(&mut buffer) + .map_err(|err| Error::internal(err.to_string()))?; + Ok(buffer) +} + +#[cfg(test)] +#[allow(clippy::unwrap_used)] +mod tests { + use super::*; + use std::fs; + use tempfile::TempDir; + + #[test] + fn test_simple_file() { + let temp = TempDir::new().unwrap(); + let file_path = temp.path().join("test.txt"); + fs::write(&file_path, "hello").unwrap(); + + let hash = compute_nar_hash(&file_path).unwrap(); + assert_eq!( + hash, + "0a430879c266f8b57f4092a0f935cf3facd48bbccde5760d4748ca405171e969" + ); + assert!(!hash.is_empty()); + assert_eq!(hash.len(), 64); + } + + #[test] + fn test_directory() { + let temp = TempDir::new().unwrap(); + fs::write(temp.path().join("a.txt"), "aaa").unwrap(); + fs::write(temp.path().join("b.txt"), "bbb").unwrap(); + + let hash = compute_nar_hash(temp.path()).unwrap(); + assert_eq!( + hash, + "0036c14209749bc9b9631e2077b108b701c322ab53853cd26f2746268a86fc0f" + ); + assert!(!hash.is_empty()); + assert_eq!(hash.len(), 64); + } +} diff --git a/nix-js/src/store.rs b/nix-js/src/store.rs index cf47e86..5ae95fb 100644 --- a/nix-js/src/store.rs +++ b/nix-js/src/store.rs @@ -2,6 +2,8 @@ mod config; mod error; mod validation; +use std::path::Path; + pub use config::{StoreConfig, StoreMode}; pub use validation::validate_store_path; diff --git a/nix-js/tests/builtins_store.rs b/nix-js/tests/builtins_store.rs index ac9baa3..a4fe364 100644 --- a/nix-js/tests/builtins_store.rs +++ b/nix-js/tests/builtins_store.rs @@ -8,7 +8,10 @@ use utils::eval_result; fn init() { static INIT: Once = Once::new(); INIT.call_once(|| { + #[cfg(not(feature = "daemon"))] unsafe { std::env::set_var("NIX_JS_STORE_MODE", "simulated") }; + #[cfg(feature = "daemon")] + unsafe { std::env::set_var("NIX_JS_STORE_MODE", "daemon") }; }); }