feat: nix_nar
This commit is contained in:
34
Cargo.lock
generated
34
Cargo.lock
generated
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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<PathBuf, CacheError> {
|
||||
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)? {
|
||||
|
||||
@@ -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<String, io::Error> {
|
||||
let mut hasher = Sha256::new();
|
||||
dump_path(&mut hasher, path)?;
|
||||
Ok(hex::encode(hasher.finalize()))
|
||||
}
|
||||
|
||||
fn dump_path<W: Write>(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<W: Write>(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<W: Write>(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<W: Write>(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);
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,7 @@ mod codegen;
|
||||
mod fetcher;
|
||||
mod ir;
|
||||
mod nix_hash;
|
||||
mod nar;
|
||||
mod runtime;
|
||||
mod store;
|
||||
|
||||
|
||||
63
nix-js/src/nar.rs
Normal file
63
nix-js/src/nar.rs
Normal file
@@ -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<String> {
|
||||
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<Vec<u8>> {
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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") };
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user