refactor
This commit is contained in:
11
.lazy.lua
11
.lazy.lua
@@ -3,16 +3,5 @@ vim.lsp.config("biome", {
|
|||||||
on_dir(vim.fn.getcwd())
|
on_dir(vim.fn.getcwd())
|
||||||
end
|
end
|
||||||
})
|
})
|
||||||
vim.lsp.config("rust_analyzer", {
|
|
||||||
settings = {
|
|
||||||
["rust-analyzer"] = {
|
|
||||||
cargo = {
|
|
||||||
features = {
|
|
||||||
"daemon"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
return {}
|
return {}
|
||||||
|
|||||||
@@ -4,14 +4,10 @@ version = "0.1.0"
|
|||||||
edition = "2024"
|
edition = "2024"
|
||||||
build = "build.rs"
|
build = "build.rs"
|
||||||
|
|
||||||
[features]
|
|
||||||
default = ["daemon"]
|
|
||||||
daemon = ["dep:tokio"]
|
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
mimalloc = "0.1"
|
mimalloc = "0.1"
|
||||||
|
|
||||||
tokio = { version = "1.41", features = ["rt-multi-thread", "sync", "net", "io-util"], optional = true }
|
tokio = { version = "1.41", features = ["rt-multi-thread", "sync", "net", "io-util"] }
|
||||||
nix-compat = { git = "https://git.snix.dev/snix/snix.git", version = "0.1.0", features = ["wire", "async"] }
|
nix-compat = { git = "https://git.snix.dev/snix/snix.git", version = "0.1.0", features = ["wire", "async"] }
|
||||||
|
|
||||||
# REPL
|
# REPL
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import type { NixValue, NixAttrs } from "../types";
|
import type { NixValue, NixAttrs } from "../types";
|
||||||
import { forceStringValue, forceList } from "../type-assert";
|
import { forceStringValue, forceList, forceStringNoCtx } from "../type-assert";
|
||||||
import { force, createThunk } from "../thunk";
|
import { force, createThunk } from "../thunk";
|
||||||
import {
|
import {
|
||||||
type DerivationData,
|
type DerivationData,
|
||||||
@@ -241,14 +241,13 @@ const extractFixedOutputInfo = (attrs: NixAttrs, ignoreNulls: boolean): FixedOut
|
|||||||
if (ignoreNulls && hashValue === null) {
|
if (ignoreNulls && hashValue === null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
const hashRaw = forceStringValue(attrs.outputHash);
|
const hashRaw = forceStringNoCtx(attrs.outputHash);
|
||||||
|
|
||||||
// FIXME: default value?
|
let hashAlgo = null;
|
||||||
let hashAlgo = "sha256";
|
|
||||||
if ("outputHashAlgo" in attrs) {
|
if ("outputHashAlgo" in attrs) {
|
||||||
const algoValue = force(attrs.outputHashAlgo);
|
const algoValue = force(attrs.outputHashAlgo);
|
||||||
if (!(ignoreNulls && algoValue === null)) {
|
if (!(ignoreNulls && algoValue === null)) {
|
||||||
hashAlgo = forceStringValue(attrs.outputHashAlgo);
|
hashAlgo = forceStringNoCtx(attrs.outputHashAlgo);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ import { coerceToPath, coerceToString, StringCoercionMode } from "./conversion";
|
|||||||
import { getPathValue } from "../path";
|
import { getPathValue } from "../path";
|
||||||
import type { NixStringContext, StringWithContext } from "../string-context";
|
import type { NixStringContext, StringWithContext } from "../string-context";
|
||||||
import { mkStringWithContext } from "../string-context";
|
import { mkStringWithContext } from "../string-context";
|
||||||
import { isPath } from "./type-check";
|
import { isAttrs, isPath } from "./type-check";
|
||||||
|
|
||||||
const importCache = new Map<string, NixValue>();
|
const importCache = new Map<string, NixValue>();
|
||||||
|
|
||||||
@@ -139,22 +139,19 @@ const normalizeUrlInput = (
|
|||||||
|
|
||||||
const normalizeTarballInput = (
|
const normalizeTarballInput = (
|
||||||
args: NixValue,
|
args: NixValue,
|
||||||
): { url: string; hash?: string; narHash?: string; name?: string } => {
|
): { url: string; sha256?: string; name?: string } => {
|
||||||
const forced = force(args);
|
const forced = force(args);
|
||||||
if (typeof forced === "string") {
|
if (isAttrs(forced)) {
|
||||||
return { url: forced };
|
const url = forceStringNoCtx(forced.url);
|
||||||
}
|
const sha256 =
|
||||||
const attrs = forceAttrs(args);
|
"sha256" in forced
|
||||||
const url = forceStringValue(attrs.url);
|
? forceStringNoCtx(forced.sha256)
|
||||||
const hash = "hash" in attrs ? forceStringValue(attrs.hash) : undefined;
|
|
||||||
const narHash =
|
|
||||||
"narHash" in attrs
|
|
||||||
? forceStringValue(attrs.narHash)
|
|
||||||
: "sha256" in attrs
|
|
||||||
? forceStringValue(attrs.sha256)
|
|
||||||
: undefined;
|
: undefined;
|
||||||
const name = "name" in attrs ? forceStringValue(attrs.name) : undefined;
|
const name = "name" in forced ? forceStringNoCtx(forced.name) : undefined;
|
||||||
return { url, hash, narHash, name };
|
return { url, sha256, name };
|
||||||
|
} else {
|
||||||
|
return { url: forceStringNoCtx(forced) };
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const fetchurl = (args: NixValue): string => {
|
export const fetchurl = (args: NixValue): string => {
|
||||||
@@ -169,12 +166,11 @@ export const fetchurl = (args: NixValue): string => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const fetchTarball = (args: NixValue): string => {
|
export const fetchTarball = (args: NixValue): string => {
|
||||||
const { url, hash, narHash, name } = normalizeTarballInput(args);
|
const { url, name, sha256 } = normalizeTarballInput(args);
|
||||||
const result: FetchTarballResult = Deno.core.ops.op_fetch_tarball(
|
const result: FetchTarballResult = Deno.core.ops.op_fetch_tarball(
|
||||||
url,
|
url,
|
||||||
hash ?? null,
|
|
||||||
narHash ?? null,
|
|
||||||
name ?? null,
|
name ?? null,
|
||||||
|
sha256 ?? null,
|
||||||
);
|
);
|
||||||
return result.store_path;
|
return result.store_path;
|
||||||
};
|
};
|
||||||
|
|||||||
6
nix-js/runtime-ts/src/types/global.d.ts
vendored
6
nix-js/runtime-ts/src/types/global.d.ts
vendored
@@ -50,9 +50,8 @@ declare global {
|
|||||||
column: number | null;
|
column: number | null;
|
||||||
};
|
};
|
||||||
function op_make_store_path(ty: string, hash_hex: string, name: string): string;
|
function op_make_store_path(ty: string, hash_hex: string, name: string): string;
|
||||||
function op_make_text_store_path(hash_hex: string, name: string, references: string[]): string;
|
|
||||||
function op_output_path_name(drv_name: string, output_name: string): string;
|
function op_output_path_name(drv_name: string, output_name: string): string;
|
||||||
function op_parse_hash(hash_str: string, algo: string): { hex: string; algo: string };
|
function op_parse_hash(hash_str: string, algo: string | null): { hex: string; algo: string };
|
||||||
function op_make_fixed_output_path(
|
function op_make_fixed_output_path(
|
||||||
hash_algo: string,
|
hash_algo: string,
|
||||||
hash: string,
|
hash: string,
|
||||||
@@ -67,9 +66,8 @@ declare global {
|
|||||||
): FetchUrlResult;
|
): FetchUrlResult;
|
||||||
function op_fetch_tarball(
|
function op_fetch_tarball(
|
||||||
url: string,
|
url: string,
|
||||||
expected_hash: string | null,
|
|
||||||
expected_nar_hash: string | null,
|
|
||||||
name: string | null,
|
name: string | null,
|
||||||
|
sha256: string | null,
|
||||||
): FetchTarballResult;
|
): FetchTarballResult;
|
||||||
function op_fetch_git(
|
function op_fetch_git(
|
||||||
url: string,
|
url: string,
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ use crate::ir::{
|
|||||||
ToIr as _,
|
ToIr as _,
|
||||||
};
|
};
|
||||||
use crate::runtime::{Runtime, RuntimeContext};
|
use crate::runtime::{Runtime, RuntimeContext};
|
||||||
use crate::store::{Store, StoreBackend, StoreConfig};
|
use crate::store::{DaemonStore, Store, StoreConfig};
|
||||||
use crate::value::{Symbol, Value};
|
use crate::value::{Symbol, Value};
|
||||||
|
|
||||||
pub struct Context {
|
pub struct Context {
|
||||||
@@ -93,7 +93,7 @@ pub(crate) struct Ctx {
|
|||||||
symbols: DefaultStringInterner,
|
symbols: DefaultStringInterner,
|
||||||
global: NonNull<HashMap<SymId, ExprId>>,
|
global: NonNull<HashMap<SymId, ExprId>>,
|
||||||
sources: Vec<Source>,
|
sources: Vec<Source>,
|
||||||
store: StoreBackend,
|
store: DaemonStore,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Ctx {
|
impl Ctx {
|
||||||
@@ -182,7 +182,7 @@ impl Ctx {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let config = StoreConfig::from_env();
|
let config = StoreConfig::from_env();
|
||||||
let store = StoreBackend::new(config)?;
|
let store = DaemonStore::connect(&config.daemon_socket)?;
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
symbols,
|
symbols,
|
||||||
@@ -296,7 +296,7 @@ impl CodegenContext for Ctx {
|
|||||||
self.sources.last().expect("current_source not set").clone()
|
self.sources.last().expect("current_source not set").clone()
|
||||||
}
|
}
|
||||||
fn get_store_dir(&self) -> &str {
|
fn get_store_dir(&self) -> &str {
|
||||||
self.store.as_store().get_store_dir()
|
self.store.get_store_dir()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -316,8 +316,8 @@ impl RuntimeContext for Ctx {
|
|||||||
fn get_source(&self, id: usize) -> Source {
|
fn get_source(&self, id: usize) -> Source {
|
||||||
self.get_source(id)
|
self.get_source(id)
|
||||||
}
|
}
|
||||||
fn get_store(&self) -> &dyn Store {
|
fn get_store(&self) -> &DaemonStore {
|
||||||
self.store.as_store()
|
&self.store
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,13 @@
|
|||||||
use deno_core::OpState;
|
use deno_core::OpState;
|
||||||
use deno_core::op2;
|
use deno_core::op2;
|
||||||
|
use nix_compat::nixhash::HashAlgo;
|
||||||
|
use nix_compat::nixhash::NixHash;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use tracing::{debug, info, warn};
|
use tracing::{debug, info, warn};
|
||||||
|
|
||||||
use crate::runtime::OpStateExt;
|
use crate::runtime::OpStateExt;
|
||||||
use crate::runtime::RuntimeContext;
|
use crate::runtime::RuntimeContext;
|
||||||
|
use crate::store::Store as _;
|
||||||
|
|
||||||
mod archive;
|
mod archive;
|
||||||
pub(crate) mod cache;
|
pub(crate) mod cache;
|
||||||
@@ -117,7 +120,7 @@ pub fn op_fetch_url<Ctx: RuntimeContext>(
|
|||||||
|
|
||||||
info!(bytes = data.len(), "Download complete");
|
info!(bytes = data.len(), "Download complete");
|
||||||
|
|
||||||
let hash = crate::nix_hash::sha256_hex(&String::from_utf8_lossy(&data));
|
let hash = crate::nix_utils::sha256_hex(&String::from_utf8_lossy(&data));
|
||||||
|
|
||||||
if let Some(ref expected) = expected_hash {
|
if let Some(ref expected) = expected_hash {
|
||||||
let normalized_expected = normalize_hash(expected);
|
let normalized_expected = normalize_hash(expected);
|
||||||
@@ -164,9 +167,8 @@ pub fn op_fetch_url<Ctx: RuntimeContext>(
|
|||||||
pub fn op_fetch_tarball<Ctx: RuntimeContext>(
|
pub fn op_fetch_tarball<Ctx: RuntimeContext>(
|
||||||
state: &mut OpState,
|
state: &mut OpState,
|
||||||
#[string] url: String,
|
#[string] url: String,
|
||||||
#[string] expected_hash: Option<String>,
|
|
||||||
#[string] expected_nar_hash: Option<String>,
|
|
||||||
#[string] name: Option<String>,
|
#[string] name: Option<String>,
|
||||||
|
#[string] sha256: Option<String>,
|
||||||
) -> Result<FetchTarballResult, NixRuntimeError> {
|
) -> Result<FetchTarballResult, NixRuntimeError> {
|
||||||
let _span = tracing::info_span!("op_fetch_tarball", url = %url).entered();
|
let _span = tracing::info_span!("op_fetch_tarball", url = %url).entered();
|
||||||
info!("fetchTarball started");
|
info!("fetchTarball started");
|
||||||
@@ -181,6 +183,16 @@ pub fn op_fetch_tarball<Ctx: RuntimeContext>(
|
|||||||
"name": dir_name,
|
"name": dir_name,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
let expected_sha256 = sha256
|
||||||
|
.map(
|
||||||
|
|ref sha256| match NixHash::from_str(sha256, Some(HashAlgo::Sha256)) {
|
||||||
|
Ok(NixHash::Sha256(digest)) => Ok(digest),
|
||||||
|
_ => Err(format!("fetchTarball: invalid sha256 '{sha256}'")),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.transpose()?;
|
||||||
|
let expected_hex = expected_sha256.map(hex::encode);
|
||||||
|
|
||||||
if let Some(cached_entry) = metadata_cache
|
if let Some(cached_entry) = metadata_cache
|
||||||
.lookup(&input)
|
.lookup(&input)
|
||||||
.map_err(|e| NixRuntimeError::from(e.to_string()))?
|
.map_err(|e| NixRuntimeError::from(e.to_string()))?
|
||||||
@@ -196,9 +208,8 @@ pub fn op_fetch_tarball<Ctx: RuntimeContext>(
|
|||||||
.and_then(|v| v.as_str())
|
.and_then(|v| v.as_str())
|
||||||
.unwrap_or("");
|
.unwrap_or("");
|
||||||
|
|
||||||
if let Some(ref expected_nar) = expected_nar_hash {
|
if let Some(ref hex) = expected_hex {
|
||||||
let normalized_expected = normalize_hash(expected_nar);
|
if cached_nar_hash == hex {
|
||||||
if cached_nar_hash == normalized_expected {
|
|
||||||
info!("Cache hit");
|
info!("Cache hit");
|
||||||
return Ok(FetchTarballResult {
|
return Ok(FetchTarballResult {
|
||||||
store_path: cached_entry.store_path.clone(),
|
store_path: cached_entry.store_path.clone(),
|
||||||
@@ -224,17 +235,16 @@ pub fn op_fetch_tarball<Ctx: RuntimeContext>(
|
|||||||
|
|
||||||
info!(bytes = data.len(), "Download complete");
|
info!(bytes = data.len(), "Download complete");
|
||||||
|
|
||||||
let tarball_hash = crate::nix_hash::sha256_hex(&String::from_utf8_lossy(&data));
|
let tarball_hash = crate::nix_utils::sha256_hex(&String::from_utf8_lossy(&data));
|
||||||
|
|
||||||
if let Some(ref expected) = expected_hash {
|
if let Some(ref expected) = expected_hex
|
||||||
let normalized_expected = normalize_hash(expected);
|
&& tarball_hash != *expected
|
||||||
if tarball_hash != normalized_expected {
|
{
|
||||||
return Err(NixRuntimeError::from(format!(
|
return Err(NixRuntimeError::from(format!(
|
||||||
"Tarball hash mismatch for '{}': expected {}, got {}",
|
"Tarball hash mismatch for '{}': expected {}, got {}",
|
||||||
url, normalized_expected, tarball_hash
|
url, expected, tarball_hash
|
||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
info!("Extracting tarball");
|
info!("Extracting tarball");
|
||||||
let cache = FetcherCache::new().map_err(|e| NixRuntimeError::from(e.to_string()))?;
|
let cache = FetcherCache::new().map_err(|e| NixRuntimeError::from(e.to_string()))?;
|
||||||
@@ -245,22 +255,23 @@ pub fn op_fetch_tarball<Ctx: RuntimeContext>(
|
|||||||
info!("Computing NAR hash");
|
info!("Computing NAR hash");
|
||||||
let nar_hash =
|
let nar_hash =
|
||||||
nar::compute_nar_hash(&extracted_path).map_err(|e| NixRuntimeError::from(e.to_string()))?;
|
nar::compute_nar_hash(&extracted_path).map_err(|e| NixRuntimeError::from(e.to_string()))?;
|
||||||
|
let nar_hash_hex = hex::encode(nar_hash);
|
||||||
|
|
||||||
debug!(
|
debug!(
|
||||||
tarball_hash = %tarball_hash,
|
tarball_hash = %tarball_hash,
|
||||||
nar_hash = %nar_hash,
|
nar_hash = %nar_hash_hex,
|
||||||
"Hash computation complete"
|
"Hash computation complete"
|
||||||
);
|
);
|
||||||
|
|
||||||
if let Some(ref expected) = expected_nar_hash {
|
if let Some(ref expected) = expected_sha256
|
||||||
let normalized_expected = normalize_hash(expected);
|
&& nar_hash != *expected {
|
||||||
if nar_hash != normalized_expected {
|
|
||||||
return Err(NixRuntimeError::from(format!(
|
return Err(NixRuntimeError::from(format!(
|
||||||
"NAR hash mismatch for '{}': expected {}, got {}",
|
"NAR hash mismatch for '{}': expected {}, got {}",
|
||||||
url, normalized_expected, nar_hash
|
url,
|
||||||
|
expected_hex.expect("must be Some"),
|
||||||
|
nar_hash_hex
|
||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
info!("Adding to store");
|
info!("Adding to store");
|
||||||
let ctx: &Ctx = state.get_ctx();
|
let ctx: &Ctx = state.get_ctx();
|
||||||
@@ -277,7 +288,7 @@ pub fn op_fetch_tarball<Ctx: RuntimeContext>(
|
|||||||
"url": url,
|
"url": url,
|
||||||
});
|
});
|
||||||
|
|
||||||
let immutable = expected_nar_hash.is_some();
|
let immutable = expected_sha256.is_some();
|
||||||
metadata_cache
|
metadata_cache
|
||||||
.add(&input, &info, &store_path, immutable)
|
.add(&input, &info, &store_path, immutable)
|
||||||
.map_err(|e| NixRuntimeError::from(e.to_string()))?;
|
.map_err(|e| NixRuntimeError::from(e.to_string()))?;
|
||||||
@@ -285,7 +296,7 @@ pub fn op_fetch_tarball<Ctx: RuntimeContext>(
|
|||||||
Ok(FetchTarballResult {
|
Ok(FetchTarballResult {
|
||||||
store_path,
|
store_path,
|
||||||
hash: tarball_hash,
|
hash: tarball_hash,
|
||||||
nar_hash,
|
nar_hash: nar_hash_hex,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
use std::fs;
|
use std::fs;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::PathBuf;
|
||||||
|
|
||||||
use super::archive::ArchiveError;
|
use super::archive::ArchiveError;
|
||||||
|
|
||||||
@@ -65,7 +65,7 @@ impl FetcherCache {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn hash_key(url: &str) -> String {
|
fn hash_key(url: &str) -> String {
|
||||||
crate::nix_hash::sha256_hex(url)
|
crate::nix_utils::sha256_hex(url)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_git_bare(&self, url: &str) -> PathBuf {
|
pub fn get_git_bare(&self, url: &str) -> PathBuf {
|
||||||
@@ -87,21 +87,3 @@ impl FetcherCache {
|
|||||||
Ok((extracted_path, temp_dir))
|
Ok((extracted_path, temp_dir))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn copy_dir_recursive(src: &Path, dst: &Path) -> Result<(), std::io::Error> {
|
|
||||||
fs::create_dir_all(dst)?;
|
|
||||||
|
|
||||||
for entry in fs::read_dir(src)? {
|
|
||||||
let entry = entry?;
|
|
||||||
let path = entry.path();
|
|
||||||
let dest_path = dst.join(entry.file_name());
|
|
||||||
|
|
||||||
if path.is_dir() {
|
|
||||||
copy_dir_recursive(&path, &dest_path)?;
|
|
||||||
} else {
|
|
||||||
fs::copy(&path, &dest_path)?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -31,8 +31,8 @@ pub fn fetch_git(
|
|||||||
let temp_dir = tempfile::tempdir()?;
|
let temp_dir = tempfile::tempdir()?;
|
||||||
let checkout_dir = checkout_rev_to_temp(&bare_repo, &target_rev, submodules, temp_dir.path())?;
|
let checkout_dir = checkout_rev_to_temp(&bare_repo, &target_rev, submodules, temp_dir.path())?;
|
||||||
|
|
||||||
let nar_hash = crate::nar::compute_nar_hash(&checkout_dir)
|
let nar_hash = hex::encode(crate::nar::compute_nar_hash(&checkout_dir)
|
||||||
.map_err(|e| GitError::NarHashError(e.to_string()))?;
|
.map_err(|e| GitError::NarHashError(e.to_string()))?);
|
||||||
|
|
||||||
let store_path = store
|
let store_path = store
|
||||||
.add_to_store_from_path(name, &checkout_dir, vec![])
|
.add_to_store_from_path(name, &checkout_dir, vec![])
|
||||||
|
|||||||
@@ -124,7 +124,7 @@ fn checkout_rev(
|
|||||||
name: &str,
|
name: &str,
|
||||||
cache: &FetcherCache,
|
cache: &FetcherCache,
|
||||||
) -> Result<PathBuf, HgError> {
|
) -> Result<PathBuf, HgError> {
|
||||||
let hash = crate::nix_hash::sha256_hex(&format!("{}:{}", bare_repo.display(), rev));
|
let hash = crate::nix_utils::sha256_hex(&format!("{}:{}", bare_repo.display(), rev));
|
||||||
let checkout_dir = cache.make_store_path(&hash, name);
|
let checkout_dir = cache.make_store_path(&hash, name);
|
||||||
|
|
||||||
if checkout_dir.exists() {
|
if checkout_dir.exists() {
|
||||||
|
|||||||
@@ -10,9 +10,9 @@ mod downgrade;
|
|||||||
mod fetcher;
|
mod fetcher;
|
||||||
mod ir;
|
mod ir;
|
||||||
mod nar;
|
mod nar;
|
||||||
mod nix_hash;
|
|
||||||
mod runtime;
|
mod runtime;
|
||||||
mod store;
|
mod store;
|
||||||
|
mod nix_utils;
|
||||||
|
|
||||||
#[global_allocator]
|
#[global_allocator]
|
||||||
static GLOBAL: mimalloc::MiMalloc = mimalloc::MiMalloc;
|
static GLOBAL: mimalloc::MiMalloc = mimalloc::MiMalloc;
|
||||||
|
|||||||
@@ -5,14 +5,14 @@ use std::path::Path;
|
|||||||
|
|
||||||
use crate::error::{Error, Result};
|
use crate::error::{Error, Result};
|
||||||
|
|
||||||
pub fn compute_nar_hash(path: &Path) -> Result<String> {
|
pub fn compute_nar_hash(path: &Path) -> Result<[u8; 32]> {
|
||||||
let mut hasher = Sha256::new();
|
let mut hasher = Sha256::new();
|
||||||
std::io::copy(
|
std::io::copy(
|
||||||
&mut Encoder::new(path).map_err(|err| Error::internal(err.to_string()))?,
|
&mut Encoder::new(path).map_err(|err| Error::internal(err.to_string()))?,
|
||||||
&mut hasher,
|
&mut hasher,
|
||||||
)
|
)
|
||||||
.map_err(|err| Error::internal(err.to_string()))?;
|
.map_err(|err| Error::internal(err.to_string()))?;
|
||||||
Ok(hex::encode(hasher.finalize()))
|
Ok(hasher.finalize().into())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn pack_nar(path: &Path) -> Result<Vec<u8>> {
|
pub fn pack_nar(path: &Path) -> Result<Vec<u8>> {
|
||||||
@@ -37,7 +37,7 @@ mod tests {
|
|||||||
let file_path = temp.path().join("test.txt");
|
let file_path = temp.path().join("test.txt");
|
||||||
fs::write(&file_path, "hello").unwrap();
|
fs::write(&file_path, "hello").unwrap();
|
||||||
|
|
||||||
let hash = compute_nar_hash(&file_path).unwrap();
|
let hash = hex::encode(compute_nar_hash(&file_path).unwrap());
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
hash,
|
hash,
|
||||||
"0a430879c266f8b57f4092a0f935cf3facd48bbccde5760d4748ca405171e969"
|
"0a430879c266f8b57f4092a0f935cf3facd48bbccde5760d4748ca405171e969"
|
||||||
@@ -52,7 +52,7 @@ mod tests {
|
|||||||
fs::write(temp.path().join("a.txt"), "aaa").unwrap();
|
fs::write(temp.path().join("a.txt"), "aaa").unwrap();
|
||||||
fs::write(temp.path().join("b.txt"), "bbb").unwrap();
|
fs::write(temp.path().join("b.txt"), "bbb").unwrap();
|
||||||
|
|
||||||
let hash = compute_nar_hash(temp.path()).unwrap();
|
let hash = hex::encode(compute_nar_hash(temp.path()).unwrap());
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
hash,
|
hash,
|
||||||
"0036c14209749bc9b9631e2077b108b701c322ab53853cd26f2746268a86fc0f"
|
"0036c14209749bc9b9631e2077b108b701c322ab53853cd26f2746268a86fc0f"
|
||||||
|
|||||||
@@ -1,175 +0,0 @@
|
|||||||
use sha2::{Digest, Sha256};
|
|
||||||
|
|
||||||
const NIX_BASE32_CHARS: &[u8; 32] = b"0123456789abcdfghijklmnpqrsvwxyz";
|
|
||||||
|
|
||||||
pub fn sha256_hex(data: &str) -> String {
|
|
||||||
let mut hasher = Sha256::new();
|
|
||||||
hasher.update(data.as_bytes());
|
|
||||||
hex::encode(hasher.finalize())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn compress_hash(hash: &[u8; 32], new_size: usize) -> Vec<u8> {
|
|
||||||
let mut result = vec![0u8; new_size];
|
|
||||||
for i in 0..32 {
|
|
||||||
result[i % new_size] ^= hash[i];
|
|
||||||
}
|
|
||||||
result
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn nix_base32_encode(bytes: &[u8]) -> String {
|
|
||||||
let len = (bytes.len() * 8 - 1) / 5 + 1;
|
|
||||||
let mut result = String::with_capacity(len);
|
|
||||||
|
|
||||||
for n in (0..len).rev() {
|
|
||||||
let b = n * 5;
|
|
||||||
let i = b / 8;
|
|
||||||
let j = b % 8;
|
|
||||||
|
|
||||||
let c = if i >= bytes.len() {
|
|
||||||
0
|
|
||||||
} else {
|
|
||||||
let mut c = (bytes[i] as u16) >> j;
|
|
||||||
if j > 3 && i + 1 < bytes.len() {
|
|
||||||
c |= (bytes[i + 1] as u16) << (8 - j);
|
|
||||||
}
|
|
||||||
c
|
|
||||||
};
|
|
||||||
|
|
||||||
result.push(NIX_BASE32_CHARS[(c & 0x1f) as usize] as char);
|
|
||||||
}
|
|
||||||
|
|
||||||
result
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn nix_base32_decode(input: &str) -> Option<Vec<u8>> {
|
|
||||||
let len = input.len() * 5 / 8;
|
|
||||||
let mut bytes = vec![0u8; len];
|
|
||||||
|
|
||||||
for (n, ch) in input.chars().rev().enumerate() {
|
|
||||||
let digit = NIX_BASE32_CHARS.iter().position(|&c| c == ch as u8)? as u16;
|
|
||||||
let b = n * 5;
|
|
||||||
let i = b / 8;
|
|
||||||
let j = b % 8;
|
|
||||||
if i < len {
|
|
||||||
bytes[i] |= (digit << j) as u8;
|
|
||||||
}
|
|
||||||
if j > 3 && i + 1 < len {
|
|
||||||
bytes[i + 1] |= (digit >> (8 - j)) as u8;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Some(bytes)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn decode_hash_to_hex(hash_str: &str) -> Option<String> {
|
|
||||||
if let Some(rest) = hash_str.strip_prefix("sha256:") {
|
|
||||||
return decode_hash_to_hex(rest);
|
|
||||||
}
|
|
||||||
if let Some(base64_str) = hash_str.strip_prefix("sha256-") {
|
|
||||||
use base64::{Engine, engine::general_purpose::STANDARD};
|
|
||||||
let bytes = STANDARD.decode(base64_str).ok()?;
|
|
||||||
return Some(hex::encode(bytes));
|
|
||||||
}
|
|
||||||
if hash_str.len() == 64 && hash_str.chars().all(|c| c.is_ascii_hexdigit()) {
|
|
||||||
return Some(hash_str.to_string());
|
|
||||||
}
|
|
||||||
if hash_str.len() == 52 {
|
|
||||||
let bytes = nix_base32_decode(hash_str)?;
|
|
||||||
return Some(hex::encode(bytes));
|
|
||||||
}
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn make_store_path(store_dir: &str, ty: &str, hash_hex: &str, name: &str) -> String {
|
|
||||||
let s = format!("{}:sha256:{}:{}:{}", ty, hash_hex, store_dir, name);
|
|
||||||
|
|
||||||
let mut hasher = Sha256::new();
|
|
||||||
hasher.update(s.as_bytes());
|
|
||||||
let hash: [u8; 32] = hasher.finalize().into();
|
|
||||||
|
|
||||||
let compressed = compress_hash(&hash, 20);
|
|
||||||
let encoded = nix_base32_encode(&compressed);
|
|
||||||
|
|
||||||
format!("{}/{}-{}", store_dir, encoded, name)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn make_text_store_path(
|
|
||||||
store_dir: &str,
|
|
||||||
hash_hex: &str,
|
|
||||||
name: &str,
|
|
||||||
references: &[String],
|
|
||||||
) -> String {
|
|
||||||
let mut ty = String::from("text");
|
|
||||||
for reference in references {
|
|
||||||
ty.push(':');
|
|
||||||
ty.push_str(reference);
|
|
||||||
}
|
|
||||||
make_store_path(store_dir, &ty, hash_hex, name)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn output_path_name(drv_name: &str, output_name: &str) -> String {
|
|
||||||
if output_name == "out" {
|
|
||||||
drv_name.to_string()
|
|
||||||
} else {
|
|
||||||
format!("{}-{}", drv_name, output_name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_nix_base32_encode() {
|
|
||||||
let bytes = [0xFF, 0xFF, 0xFF, 0xFF, 0xFF];
|
|
||||||
let encoded = nix_base32_encode(&bytes);
|
|
||||||
assert_eq!(encoded.len(), 8);
|
|
||||||
|
|
||||||
let bytes_zero = [0u8; 20];
|
|
||||||
let encoded_zero = nix_base32_encode(&bytes_zero);
|
|
||||||
assert_eq!(encoded_zero.len(), 32);
|
|
||||||
assert!(encoded_zero.chars().all(|c| c == '0'));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_compress_hash() {
|
|
||||||
let hash = [0u8; 32];
|
|
||||||
let compressed = compress_hash(&hash, 20);
|
|
||||||
assert_eq!(compressed.len(), 20);
|
|
||||||
assert!(compressed.iter().all(|&b| b == 0));
|
|
||||||
|
|
||||||
let hash_ones = [0xFF; 32];
|
|
||||||
let compressed_ones = compress_hash(&hash_ones, 20);
|
|
||||||
assert_eq!(compressed_ones.len(), 20);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_sha256_hex() {
|
|
||||||
let data = "hello world";
|
|
||||||
let hash = sha256_hex(data);
|
|
||||||
assert_eq!(hash.len(), 64);
|
|
||||||
assert_eq!(
|
|
||||||
hash,
|
|
||||||
"b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_output_path_name() {
|
|
||||||
assert_eq!(output_path_name("hello", "out"), "hello");
|
|
||||||
assert_eq!(output_path_name("hello", "dev"), "hello-dev");
|
|
||||||
assert_eq!(output_path_name("hello", "doc"), "hello-doc");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_make_store_path() {
|
|
||||||
let path = make_store_path("/nix/store", "output:out", "abc123", "hello");
|
|
||||||
assert!(path.starts_with("/nix/store/"));
|
|
||||||
assert!(path.ends_with("-hello"));
|
|
||||||
|
|
||||||
let hash_parts: Vec<&str> = path.split('/').collect();
|
|
||||||
assert_eq!(hash_parts.len(), 4);
|
|
||||||
let name_part = hash_parts[3];
|
|
||||||
assert!(name_part.contains('-'));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
29
nix-js/src/nix_utils.rs
Normal file
29
nix-js/src/nix_utils.rs
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
use nix_compat::store_path::compress_hash;
|
||||||
|
use sha2::{Digest as _, Sha256};
|
||||||
|
|
||||||
|
pub fn sha256_hex(data: &str) -> String {
|
||||||
|
let mut hasher = Sha256::new();
|
||||||
|
hasher.update(data.as_bytes());
|
||||||
|
hex::encode(hasher.finalize())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn make_store_path(store_dir: &str, ty: &str, hash_hex: &str, name: &str) -> String {
|
||||||
|
let s = format!("{}:sha256:{}:{}:{}", ty, hash_hex, store_dir, name);
|
||||||
|
|
||||||
|
let mut hasher = Sha256::new();
|
||||||
|
hasher.update(s.as_bytes());
|
||||||
|
let hash: [u8; 32] = hasher.finalize().into();
|
||||||
|
|
||||||
|
let compressed = compress_hash::<20>(&hash);
|
||||||
|
let encoded = nix_compat::nixbase32::encode(&compressed);
|
||||||
|
|
||||||
|
format!("{}/{}-{}", store_dir, encoded, name)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn output_path_name(drv_name: &str, output_name: &str) -> String {
|
||||||
|
if output_name == "out" {
|
||||||
|
drv_name.to_string()
|
||||||
|
} else {
|
||||||
|
format!("{}-{}", drv_name, output_name)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,7 +5,7 @@ use std::path::Path;
|
|||||||
use deno_core::{Extension, ExtensionFileSource, JsRuntime, OpState, RuntimeOptions, v8};
|
use deno_core::{Extension, ExtensionFileSource, JsRuntime, OpState, RuntimeOptions, v8};
|
||||||
|
|
||||||
use crate::error::{Error, Result, Source};
|
use crate::error::{Error, Result, Source};
|
||||||
use crate::store::Store;
|
use crate::store::DaemonStore;
|
||||||
use crate::value::{AttrSet, List, Symbol, Value};
|
use crate::value::{AttrSet, List, Symbol, Value};
|
||||||
|
|
||||||
mod ops;
|
mod ops;
|
||||||
@@ -21,7 +21,7 @@ pub(crate) trait RuntimeContext: 'static {
|
|||||||
fn compile(&mut self, source: Source) -> Result<String>;
|
fn compile(&mut self, source: Source) -> Result<String>;
|
||||||
fn compile_scoped(&mut self, source: Source, scope: Vec<String>) -> Result<String>;
|
fn compile_scoped(&mut self, source: Source, scope: Vec<String>) -> Result<String>;
|
||||||
fn get_source(&self, id: usize) -> Source;
|
fn get_source(&self, id: usize) -> Source;
|
||||||
fn get_store(&self) -> &dyn Store;
|
fn get_store(&self) -> &DaemonStore;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) trait OpStateExt<Ctx: RuntimeContext> {
|
pub(crate) trait OpStateExt<Ctx: RuntimeContext> {
|
||||||
@@ -55,7 +55,6 @@ fn runtime_extension<Ctx: RuntimeContext>() -> Extension {
|
|||||||
op_make_placeholder(),
|
op_make_placeholder(),
|
||||||
op_decode_span::<Ctx>(),
|
op_decode_span::<Ctx>(),
|
||||||
op_make_store_path::<Ctx>(),
|
op_make_store_path::<Ctx>(),
|
||||||
op_make_text_store_path::<Ctx>(),
|
|
||||||
op_output_path_name(),
|
op_output_path_name(),
|
||||||
op_parse_hash(),
|
op_parse_hash(),
|
||||||
op_make_fixed_output_path::<Ctx>(),
|
op_make_fixed_output_path::<Ctx>(),
|
||||||
|
|||||||
@@ -8,9 +8,9 @@ use deno_core::OpState;
|
|||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
use rust_embed::Embed;
|
use rust_embed::Embed;
|
||||||
|
|
||||||
use crate::error::Source;
|
|
||||||
|
|
||||||
use super::{NixRuntimeError, OpStateExt, RuntimeContext};
|
use super::{NixRuntimeError, OpStateExt, RuntimeContext};
|
||||||
|
use crate::error::Source;
|
||||||
|
use crate::store::Store as _;
|
||||||
|
|
||||||
#[derive(Debug, Default)]
|
#[derive(Debug, Default)]
|
||||||
pub(super) struct RegexCache {
|
pub(super) struct RegexCache {
|
||||||
@@ -247,7 +247,7 @@ pub(super) fn op_resolve_path(
|
|||||||
#[deno_core::op2]
|
#[deno_core::op2]
|
||||||
#[string]
|
#[string]
|
||||||
pub(super) fn op_sha256_hex(#[string] data: String) -> String {
|
pub(super) fn op_sha256_hex(#[string] data: String) -> String {
|
||||||
crate::nix_hash::sha256_hex(&data)
|
crate::nix_utils::sha256_hex(&data)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[deno_core::op2]
|
#[deno_core::op2]
|
||||||
@@ -323,21 +323,7 @@ pub(super) fn op_make_store_path<Ctx: RuntimeContext>(
|
|||||||
let ctx: &Ctx = state.get_ctx();
|
let ctx: &Ctx = state.get_ctx();
|
||||||
let store = ctx.get_store();
|
let store = ctx.get_store();
|
||||||
let store_dir = store.get_store_dir();
|
let store_dir = store.get_store_dir();
|
||||||
crate::nix_hash::make_store_path(store_dir, &ty, &hash_hex, &name)
|
crate::nix_utils::make_store_path(store_dir, &ty, &hash_hex, &name)
|
||||||
}
|
|
||||||
|
|
||||||
#[deno_core::op2]
|
|
||||||
#[string]
|
|
||||||
pub(super) fn op_make_text_store_path<Ctx: RuntimeContext>(
|
|
||||||
state: &mut OpState,
|
|
||||||
#[string] hash_hex: String,
|
|
||||||
#[string] name: String,
|
|
||||||
#[serde] references: Vec<String>,
|
|
||||||
) -> String {
|
|
||||||
let ctx: &Ctx = state.get_ctx();
|
|
||||||
let store = ctx.get_store();
|
|
||||||
let store_dir = store.get_store_dir();
|
|
||||||
crate::nix_hash::make_text_store_path(store_dir, &hash_hex, &name, &references)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[deno_core::op2]
|
#[deno_core::op2]
|
||||||
@@ -346,7 +332,7 @@ pub(super) fn op_output_path_name(
|
|||||||
#[string] drv_name: String,
|
#[string] drv_name: String,
|
||||||
#[string] output_name: String,
|
#[string] output_name: String,
|
||||||
) -> String {
|
) -> String {
|
||||||
crate::nix_hash::output_path_name(&drv_name, &output_name)
|
crate::nix_utils::output_path_name(&drv_name, &output_name)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(serde::Serialize)]
|
#[derive(serde::Serialize)]
|
||||||
@@ -359,21 +345,26 @@ pub(super) struct ParsedHash {
|
|||||||
#[serde]
|
#[serde]
|
||||||
pub(super) fn op_parse_hash(
|
pub(super) fn op_parse_hash(
|
||||||
#[string] hash_str: String,
|
#[string] hash_str: String,
|
||||||
#[string] algo: String,
|
#[string] algo: Option<String>,
|
||||||
) -> std::result::Result<ParsedHash, NixRuntimeError> {
|
) -> std::result::Result<ParsedHash, NixRuntimeError> {
|
||||||
use nix_compat::nixhash::{HashAlgo, NixHash};
|
use nix_compat::nixhash::{HashAlgo, NixHash};
|
||||||
|
|
||||||
let hash_algo = HashAlgo::from_str(&algo).ok();
|
let hash_algo = algo
|
||||||
let nix_hash = NixHash::from_str(&hash_str, hash_algo).map_err(|e| {
|
.as_deref()
|
||||||
|
.and_then(|algo| HashAlgo::from_str(algo).ok());
|
||||||
|
|
||||||
|
let hash = NixHash::from_str(&hash_str, hash_algo).map_err(|e| {
|
||||||
NixRuntimeError::from(format!(
|
NixRuntimeError::from(format!(
|
||||||
"invalid hash '{}' for algorithm '{}': {}",
|
"invalid hash '{}'{}: {}",
|
||||||
hash_str, algo, e
|
hash_str,
|
||||||
|
algo.map_or("".to_string(), |algo| format!(" for algorithm '{algo}'")),
|
||||||
|
e
|
||||||
))
|
))
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
Ok(ParsedHash {
|
Ok(ParsedHash {
|
||||||
hex: hex::encode(nix_hash.digest_as_bytes()),
|
hex: hex::encode(hash.digest_as_bytes()),
|
||||||
algo,
|
algo: hash.algo().to_string(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -393,7 +384,7 @@ pub(super) fn op_make_fixed_output_path<Ctx: RuntimeContext>(
|
|||||||
let store_dir = store.get_store_dir();
|
let store_dir = store.get_store_dir();
|
||||||
|
|
||||||
if hash_algo == "sha256" && hash_mode == "recursive" {
|
if hash_algo == "sha256" && hash_mode == "recursive" {
|
||||||
crate::nix_hash::make_store_path(store_dir, "source", &hash, &name)
|
crate::nix_utils::make_store_path(store_dir, "source", &hash, &name)
|
||||||
} else {
|
} else {
|
||||||
let prefix = if hash_mode == "recursive" { "r:" } else { "" };
|
let prefix = if hash_mode == "recursive" { "r:" } else { "" };
|
||||||
let inner_input = format!("fixed:out:{}{}:{}:", prefix, hash_algo, hash);
|
let inner_input = format!("fixed:out:{}{}:{}:", prefix, hash_algo, hash);
|
||||||
@@ -401,7 +392,7 @@ pub(super) fn op_make_fixed_output_path<Ctx: RuntimeContext>(
|
|||||||
hasher.update(inner_input.as_bytes());
|
hasher.update(inner_input.as_bytes());
|
||||||
let inner_hash = hex::encode(hasher.finalize());
|
let inner_hash = hex::encode(hasher.finalize());
|
||||||
|
|
||||||
crate::nix_hash::make_store_path(store_dir, "output:out", &inner_hash, &name)
|
crate::nix_utils::make_store_path(store_dir, "output:out", &inner_hash, &name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -414,6 +405,7 @@ pub(super) fn op_add_path<Ctx: RuntimeContext>(
|
|||||||
recursive: bool,
|
recursive: bool,
|
||||||
#[string] sha256: Option<String>,
|
#[string] sha256: Option<String>,
|
||||||
) -> std::result::Result<String, NixRuntimeError> {
|
) -> std::result::Result<String, NixRuntimeError> {
|
||||||
|
use nix_compat::nixhash::{HashAlgo, NixHash};
|
||||||
use sha2::{Digest, Sha256};
|
use sha2::{Digest, Sha256};
|
||||||
use std::fs;
|
use std::fs;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
@@ -449,18 +441,19 @@ pub(super) fn op_add_path<Ctx: RuntimeContext>(
|
|||||||
|
|
||||||
let mut hasher = Sha256::new();
|
let mut hasher = Sha256::new();
|
||||||
hasher.update(&contents);
|
hasher.update(&contents);
|
||||||
hex::encode(hasher.finalize())
|
hasher.finalize().into()
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Some(expected_hash) = sha256 {
|
if let Some(ref expected_hash) = sha256 {
|
||||||
let expected_hex =
|
let expected_hex =
|
||||||
crate::nix_hash::decode_hash_to_hex(&expected_hash).ok_or_else(|| {
|
NixHash::from_str(expected_hash, Some(HashAlgo::Sha256))
|
||||||
NixRuntimeError::from(format!("invalid hash format: {}", expected_hash))
|
.map_err(|err| err.to_string())?;
|
||||||
})?;
|
if computed_hash != expected_hex.digest_as_bytes() {
|
||||||
if computed_hash != expected_hex {
|
|
||||||
return Err(NixRuntimeError::from(format!(
|
return Err(NixRuntimeError::from(format!(
|
||||||
"hash mismatch for path '{}': expected {}, got {}",
|
"hash mismatch for path '{}': expected {}, got {}",
|
||||||
path, expected_hex, computed_hash
|
path,
|
||||||
|
hex::encode(expected_hex.digest_as_bytes()),
|
||||||
|
hex::encode(computed_hash)
|
||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -548,8 +541,9 @@ pub(super) fn op_write_derivation<Ctx: RuntimeContext>(
|
|||||||
pub(super) fn op_read_derivation_outputs(
|
pub(super) fn op_read_derivation_outputs(
|
||||||
#[string] drv_path: String,
|
#[string] drv_path: String,
|
||||||
) -> std::result::Result<Vec<String>, NixRuntimeError> {
|
) -> std::result::Result<Vec<String>, NixRuntimeError> {
|
||||||
let content = std::fs::read_to_string(&drv_path)
|
let content = std::fs::read_to_string(&drv_path).map_err(|e| {
|
||||||
.map_err(|e| NixRuntimeError::from(format!("failed to read derivation {}: {}", drv_path, e)))?;
|
NixRuntimeError::from(format!("failed to read derivation {}: {}", drv_path, e))
|
||||||
|
})?;
|
||||||
|
|
||||||
let outputs = parse_derivation_outputs(&content)
|
let outputs = parse_derivation_outputs(&content)
|
||||||
.ok_or_else(|| NixRuntimeError::from(format!("failed to parse derivation {}", drv_path)))?;
|
.ok_or_else(|| NixRuntimeError::from(format!("failed to parse derivation {}", drv_path)))?;
|
||||||
@@ -892,6 +886,7 @@ pub(super) fn op_add_filtered_path<Ctx: RuntimeContext>(
|
|||||||
#[string] sha256: Option<String>,
|
#[string] sha256: Option<String>,
|
||||||
#[serde] include_paths: Vec<String>,
|
#[serde] include_paths: Vec<String>,
|
||||||
) -> std::result::Result<String, NixRuntimeError> {
|
) -> std::result::Result<String, NixRuntimeError> {
|
||||||
|
use nix_compat::nixhash::{HashAlgo, NixHash};
|
||||||
use sha2::{Digest, Sha256};
|
use sha2::{Digest, Sha256};
|
||||||
use std::fs;
|
use std::fs;
|
||||||
|
|
||||||
@@ -961,18 +956,19 @@ pub(super) fn op_add_filtered_path<Ctx: RuntimeContext>(
|
|||||||
.map_err(|e| NixRuntimeError::from(format!("failed to read file: {}", e)))?;
|
.map_err(|e| NixRuntimeError::from(format!("failed to read file: {}", e)))?;
|
||||||
let mut hasher = Sha256::new();
|
let mut hasher = Sha256::new();
|
||||||
hasher.update(&contents);
|
hasher.update(&contents);
|
||||||
hex::encode(hasher.finalize())
|
hasher.finalize().into()
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Some(expected_hash) = sha256 {
|
if let Some(ref expected_hash) = sha256 {
|
||||||
let expected_hex =
|
let expected_hex =
|
||||||
crate::nix_hash::decode_hash_to_hex(&expected_hash).ok_or_else(|| {
|
NixHash::from_str(expected_hash, Some(HashAlgo::Sha256))
|
||||||
NixRuntimeError::from(format!("invalid hash format: {}", expected_hash))
|
.map_err(|err| err.to_string())?;
|
||||||
})?;
|
if computed_hash != expected_hex.digest_as_bytes() {
|
||||||
if computed_hash != expected_hex {
|
|
||||||
return Err(NixRuntimeError::from(format!(
|
return Err(NixRuntimeError::from(format!(
|
||||||
"hash mismatch for path '{}': expected {}, got {}",
|
"hash mismatch for path '{}': expected {}, got {}",
|
||||||
src_path, expected_hex, computed_hash
|
src_path,
|
||||||
|
hex::encode(expected_hex.digest_as_bytes()),
|
||||||
|
hex::encode(computed_hash)
|
||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,14 +1,16 @@
|
|||||||
#![allow(dead_code)]
|
#![allow(dead_code)]
|
||||||
|
|
||||||
|
use crate::error::Result;
|
||||||
|
|
||||||
mod config;
|
mod config;
|
||||||
|
mod daemon;
|
||||||
mod error;
|
mod error;
|
||||||
mod validation;
|
mod validation;
|
||||||
|
|
||||||
pub use config::{StoreConfig, StoreMode};
|
pub use config::StoreConfig;
|
||||||
|
pub use daemon::DaemonStore;
|
||||||
pub use validation::validate_store_path;
|
pub use validation::validate_store_path;
|
||||||
|
|
||||||
use crate::error::Result;
|
|
||||||
|
|
||||||
pub trait Store: Send + Sync {
|
pub trait Store: Send + Sync {
|
||||||
fn get_store_dir(&self) -> &str;
|
fn get_store_dir(&self) -> &str;
|
||||||
|
|
||||||
@@ -46,66 +48,3 @@ pub trait Store: Send + Sync {
|
|||||||
name: &str,
|
name: &str,
|
||||||
) -> Result<String>;
|
) -> Result<String>;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub enum StoreBackend {
|
|
||||||
Simulated(SimulatedStore),
|
|
||||||
#[cfg(feature = "daemon")]
|
|
||||||
Daemon(Box<DaemonStore>),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl StoreBackend {
|
|
||||||
pub fn new(config: StoreConfig) -> Result<Self> {
|
|
||||||
match config.mode {
|
|
||||||
#[cfg(feature = "daemon")]
|
|
||||||
StoreMode::Daemon => {
|
|
||||||
let daemon = Box::new(DaemonStore::connect(&config.daemon_socket)?);
|
|
||||||
Ok(StoreBackend::Daemon(daemon))
|
|
||||||
}
|
|
||||||
#[cfg(not(feature = "daemon"))]
|
|
||||||
StoreMode::Daemon => {
|
|
||||||
tracing::warn!(
|
|
||||||
"Daemon mode not available (nix-js not compiled with 'daemon' feature), falling back to simulated store"
|
|
||||||
);
|
|
||||||
let simulated = SimulatedStore::new()?;
|
|
||||||
Ok(StoreBackend::Simulated(simulated))
|
|
||||||
}
|
|
||||||
StoreMode::Simulated => {
|
|
||||||
let simulated = SimulatedStore::new()?;
|
|
||||||
Ok(StoreBackend::Simulated(simulated))
|
|
||||||
}
|
|
||||||
#[cfg(feature = "daemon")]
|
|
||||||
StoreMode::Auto => match DaemonStore::connect(&config.daemon_socket) {
|
|
||||||
Ok(daemon) => {
|
|
||||||
tracing::debug!("Using nix-daemon at {}", config.daemon_socket.display());
|
|
||||||
Ok(StoreBackend::Daemon(Box::new(daemon)))
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
tracing::warn!("Daemon unavailable ({}), using simulated store", e);
|
|
||||||
let simulated = SimulatedStore::new()?;
|
|
||||||
Ok(StoreBackend::Simulated(simulated))
|
|
||||||
}
|
|
||||||
},
|
|
||||||
#[cfg(not(feature = "daemon"))]
|
|
||||||
StoreMode::Auto => {
|
|
||||||
let simulated = SimulatedStore::new()?;
|
|
||||||
Ok(StoreBackend::Simulated(simulated))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn as_store(&self) -> &dyn Store {
|
|
||||||
match self {
|
|
||||||
StoreBackend::Simulated(s) => s,
|
|
||||||
#[cfg(feature = "daemon")]
|
|
||||||
StoreBackend::Daemon(d) => d.as_ref(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
mod simulated;
|
|
||||||
pub use simulated::SimulatedStore;
|
|
||||||
|
|
||||||
#[cfg(feature = "daemon")]
|
|
||||||
mod daemon;
|
|
||||||
#[cfg(feature = "daemon")]
|
|
||||||
pub use daemon::DaemonStore;
|
|
||||||
|
|||||||
@@ -1,40 +1,17 @@
|
|||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
||||||
pub enum StoreMode {
|
|
||||||
Daemon,
|
|
||||||
Simulated,
|
|
||||||
Auto,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct StoreConfig {
|
pub struct StoreConfig {
|
||||||
pub mode: StoreMode,
|
|
||||||
pub daemon_socket: PathBuf,
|
pub daemon_socket: PathBuf,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl StoreConfig {
|
impl StoreConfig {
|
||||||
pub fn from_env() -> Self {
|
pub fn from_env() -> Self {
|
||||||
let mode = match std::env::var("NIX_JS_STORE_MODE")
|
|
||||||
.as_deref()
|
|
||||||
.map(|s| s.to_lowercase())
|
|
||||||
.as_deref()
|
|
||||||
{
|
|
||||||
Ok("daemon") => StoreMode::Daemon,
|
|
||||||
Ok("simulated") => StoreMode::Simulated,
|
|
||||||
Ok("auto") | Err(_) => StoreMode::Auto,
|
|
||||||
Ok(other) => {
|
|
||||||
tracing::warn!("Invalid NIX_JS_STORE_MODE '{}', using 'auto'", other);
|
|
||||||
StoreMode::Auto
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let daemon_socket = std::env::var("NIX_DAEMON_SOCKET")
|
let daemon_socket = std::env::var("NIX_DAEMON_SOCKET")
|
||||||
.map(PathBuf::from)
|
.map(PathBuf::from)
|
||||||
.unwrap_or_else(|_| PathBuf::from("/nix/var/nix/daemon-socket/socket"));
|
.unwrap_or_else(|_| PathBuf::from("/nix/var/nix/daemon-socket/socket"));
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
mode,
|
|
||||||
daemon_socket,
|
daemon_socket,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
#![allow(dead_code)]
|
|
||||||
|
|
||||||
use std::io::{Error as IoError, ErrorKind as IoErrorKind, Result as IoResult};
|
use std::io::{Error as IoError, ErrorKind as IoErrorKind, Result as IoResult};
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
@@ -16,7 +14,6 @@ use tokio::sync::Mutex;
|
|||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
use crate::error::{Error, Result};
|
use crate::error::{Error, Result};
|
||||||
|
|
||||||
use super::Store;
|
use super::Store;
|
||||||
|
|
||||||
pub struct DaemonStore {
|
pub struct DaemonStore {
|
||||||
@@ -510,19 +507,6 @@ impl NixDaemonClient {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn execute<T, F1, F2>(&mut self, operation: Operation, write: F1, read: F2) -> IoResult<T>
|
|
||||||
where
|
|
||||||
T: nix_compat::wire::de::NixDeserialize,
|
|
||||||
F1: FnOnce() -> IoResult<()>,
|
|
||||||
F2: FnOnce() -> IoResult<T>
|
|
||||||
{
|
|
||||||
// Send operation
|
|
||||||
self.writer.write_value(&operation).await?;
|
|
||||||
self.writer.flush().await?;
|
|
||||||
|
|
||||||
self.read_response().await
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Execute an operation with a single parameter
|
/// Execute an operation with a single parameter
|
||||||
async fn execute_with<P, T>(&mut self, operation: Operation, param: &P) -> IoResult<T>
|
async fn execute_with<P, T>(&mut self, operation: Operation, param: &P) -> IoResult<T>
|
||||||
where
|
where
|
||||||
|
|||||||
@@ -1,121 +0,0 @@
|
|||||||
use super::Store;
|
|
||||||
use crate::error::{Error, Result};
|
|
||||||
use crate::fetcher::cache::FetcherCache;
|
|
||||||
use std::fs;
|
|
||||||
use std::path::Path;
|
|
||||||
|
|
||||||
pub struct SimulatedStore {
|
|
||||||
cache: FetcherCache,
|
|
||||||
store_dir: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl SimulatedStore {
|
|
||||||
pub fn new() -> Result<Self> {
|
|
||||||
let cache = FetcherCache::new()
|
|
||||||
.map_err(|e| Error::internal(format!("Failed to create simulated store: {}", e)))?;
|
|
||||||
|
|
||||||
let store_dir = dirs::cache_dir()
|
|
||||||
.unwrap_or_else(|| std::path::PathBuf::from("/tmp"))
|
|
||||||
.join("nix-js")
|
|
||||||
.join("fetchers")
|
|
||||||
.join("store")
|
|
||||||
.to_string_lossy()
|
|
||||||
.to_string();
|
|
||||||
|
|
||||||
Ok(Self { cache, store_dir })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Store for SimulatedStore {
|
|
||||||
fn get_store_dir(&self) -> &str {
|
|
||||||
&self.store_dir
|
|
||||||
}
|
|
||||||
|
|
||||||
fn is_valid_path(&self, path: &str) -> Result<bool> {
|
|
||||||
Ok(Path::new(path).exists())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn ensure_path(&self, path: &str) -> Result<()> {
|
|
||||||
if !Path::new(path).exists() {
|
|
||||||
return Err(Error::eval_error(
|
|
||||||
format!(
|
|
||||||
"builtins.storePath: path '{}' does not exist in the simulated store",
|
|
||||||
path
|
|
||||||
),
|
|
||||||
None,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn add_to_store(
|
|
||||||
&self,
|
|
||||||
name: &str,
|
|
||||||
content: &[u8],
|
|
||||||
_recursive: bool,
|
|
||||||
_references: Vec<String>,
|
|
||||||
) -> Result<String> {
|
|
||||||
let hash = crate::nix_hash::sha256_hex(&String::from_utf8_lossy(content));
|
|
||||||
|
|
||||||
let store_path = self.cache.make_store_path(&hash, name);
|
|
||||||
|
|
||||||
if !store_path.exists() {
|
|
||||||
fs::create_dir_all(store_path.parent().unwrap_or(&store_path))
|
|
||||||
.map_err(|e| Error::internal(format!("Failed to create store directory: {}", e)))?;
|
|
||||||
|
|
||||||
fs::write(&store_path, content)
|
|
||||||
.map_err(|e| Error::internal(format!("Failed to write to store: {}", e)))?;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(store_path.to_string_lossy().to_string())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn add_to_store_from_path(
|
|
||||||
&self,
|
|
||||||
name: &str,
|
|
||||||
source_path: &Path,
|
|
||||||
_references: Vec<String>,
|
|
||||||
) -> Result<String> {
|
|
||||||
use crate::fetcher::cache::copy_dir_recursive;
|
|
||||||
|
|
||||||
let nar_hash = crate::nar::compute_nar_hash(source_path)
|
|
||||||
.map_err(|e| Error::internal(format!("Failed to compute NAR hash: {}", e)))?;
|
|
||||||
|
|
||||||
let store_path = self.cache.make_store_path(&nar_hash, name);
|
|
||||||
|
|
||||||
if !store_path.exists() {
|
|
||||||
fs::create_dir_all(&store_path)
|
|
||||||
.map_err(|e| Error::internal(format!("Failed to create store directory: {}", e)))?;
|
|
||||||
|
|
||||||
if source_path.is_dir() {
|
|
||||||
copy_dir_recursive(source_path, &store_path)
|
|
||||||
.map_err(|e| Error::internal(format!("Failed to copy to store: {}", e)))?;
|
|
||||||
} else {
|
|
||||||
fs::copy(source_path, &store_path)
|
|
||||||
.map_err(|e| Error::internal(format!("Failed to copy to store: {}", e)))?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(store_path.to_string_lossy().to_string())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn add_text_to_store(
|
|
||||||
&self,
|
|
||||||
name: &str,
|
|
||||||
content: &str,
|
|
||||||
references: Vec<String>,
|
|
||||||
) -> Result<String> {
|
|
||||||
self.add_to_store(name, content.as_bytes(), false, references)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn make_fixed_output_path(
|
|
||||||
&self,
|
|
||||||
_hash_algo: &str,
|
|
||||||
hash: &str,
|
|
||||||
_hash_mode: &str,
|
|
||||||
name: &str,
|
|
||||||
) -> Result<String> {
|
|
||||||
let store_path = self.cache.make_store_path(hash, name);
|
|
||||||
Ok(store_path.to_string_lossy().to_string())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,28 +1,10 @@
|
|||||||
mod utils;
|
mod utils;
|
||||||
|
|
||||||
use std::sync::Once;
|
|
||||||
|
|
||||||
use nix_js::value::Value;
|
use nix_js::value::Value;
|
||||||
use utils::eval_result;
|
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")
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn to_file_simple() {
|
fn to_file_simple() {
|
||||||
init();
|
|
||||||
|
|
||||||
let result =
|
let result =
|
||||||
eval_result(r#"builtins.toFile "hello.txt" "Hello, World!""#).expect("Failed to evaluate");
|
eval_result(r#"builtins.toFile "hello.txt" "Hello, World!""#).expect("Failed to evaluate");
|
||||||
|
|
||||||
@@ -40,8 +22,6 @@ fn to_file_simple() {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn to_file_with_references() {
|
fn to_file_with_references() {
|
||||||
init();
|
|
||||||
|
|
||||||
let result = eval_result(
|
let result = eval_result(
|
||||||
r#"
|
r#"
|
||||||
let
|
let
|
||||||
@@ -65,8 +45,6 @@ fn to_file_with_references() {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn to_file_invalid_name_with_slash() {
|
fn to_file_invalid_name_with_slash() {
|
||||||
init();
|
|
||||||
|
|
||||||
let result = eval_result(r#"builtins.toFile "foo/bar.txt" "content""#);
|
let result = eval_result(r#"builtins.toFile "foo/bar.txt" "content""#);
|
||||||
|
|
||||||
assert!(result.is_err());
|
assert!(result.is_err());
|
||||||
@@ -80,8 +58,6 @@ fn to_file_invalid_name_with_slash() {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn to_file_invalid_name_dot() {
|
fn to_file_invalid_name_dot() {
|
||||||
init();
|
|
||||||
|
|
||||||
let result = eval_result(r#"builtins.toFile "." "content""#);
|
let result = eval_result(r#"builtins.toFile "." "content""#);
|
||||||
|
|
||||||
assert!(result.is_err());
|
assert!(result.is_err());
|
||||||
@@ -90,8 +66,6 @@ fn to_file_invalid_name_dot() {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn to_file_invalid_name_dotdot() {
|
fn to_file_invalid_name_dotdot() {
|
||||||
init();
|
|
||||||
|
|
||||||
let result = eval_result(r#"builtins.toFile ".." "content""#);
|
let result = eval_result(r#"builtins.toFile ".." "content""#);
|
||||||
|
|
||||||
assert!(result.is_err());
|
assert!(result.is_err());
|
||||||
@@ -100,8 +74,6 @@ fn to_file_invalid_name_dotdot() {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn store_path_validation_not_in_store() {
|
fn store_path_validation_not_in_store() {
|
||||||
init();
|
|
||||||
|
|
||||||
let result = eval_result(r#"builtins.storePath "/tmp/foo""#);
|
let result = eval_result(r#"builtins.storePath "/tmp/foo""#);
|
||||||
|
|
||||||
assert!(result.is_err());
|
assert!(result.is_err());
|
||||||
@@ -115,8 +87,6 @@ fn store_path_validation_not_in_store() {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn store_path_validation_malformed_hash() {
|
fn store_path_validation_malformed_hash() {
|
||||||
init();
|
|
||||||
|
|
||||||
let dummy_file_result = eval_result(r#"builtins.toFile "dummy.txt" "content""#)
|
let dummy_file_result = eval_result(r#"builtins.toFile "dummy.txt" "content""#)
|
||||||
.expect("Failed to create dummy file");
|
.expect("Failed to create dummy file");
|
||||||
|
|
||||||
@@ -145,8 +115,6 @@ fn store_path_validation_malformed_hash() {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn store_path_validation_missing_name() {
|
fn store_path_validation_missing_name() {
|
||||||
init();
|
|
||||||
|
|
||||||
let dummy_file_result = eval_result(r#"builtins.toFile "dummy.txt" "content""#)
|
let dummy_file_result = eval_result(r#"builtins.toFile "dummy.txt" "content""#)
|
||||||
.expect("Failed to create dummy file");
|
.expect("Failed to create dummy file");
|
||||||
|
|
||||||
@@ -175,8 +143,6 @@ fn store_path_validation_missing_name() {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn to_file_curried_application() {
|
fn to_file_curried_application() {
|
||||||
init();
|
|
||||||
|
|
||||||
let result = eval_result(
|
let result = eval_result(
|
||||||
r#"
|
r#"
|
||||||
let
|
let
|
||||||
@@ -199,8 +165,6 @@ fn to_file_curried_application() {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn to_file_number_conversion() {
|
fn to_file_number_conversion() {
|
||||||
init();
|
|
||||||
|
|
||||||
let result = eval_result(r#"builtins.toFile "number.txt" (builtins.toString 42)"#)
|
let result = eval_result(r#"builtins.toFile "number.txt" (builtins.toString 42)"#)
|
||||||
.expect("Failed to evaluate");
|
.expect("Failed to evaluate");
|
||||||
|
|
||||||
@@ -215,8 +179,6 @@ fn to_file_number_conversion() {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn to_file_list_conversion() {
|
fn to_file_list_conversion() {
|
||||||
init();
|
|
||||||
|
|
||||||
let result = eval_result(
|
let result = eval_result(
|
||||||
r#"builtins.toFile "list.txt" (builtins.concatStringsSep "\n" ["line1" "line2" "line3"])"#,
|
r#"builtins.toFile "list.txt" (builtins.concatStringsSep "\n" ["line1" "line2" "line3"])"#,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
#![cfg(feature = "daemon")]
|
|
||||||
|
|
||||||
mod utils;
|
mod utils;
|
||||||
|
|
||||||
use nix_js::value::Value;
|
use nix_js::value::Value;
|
||||||
@@ -368,26 +366,18 @@ fn fixed_output_sha256_flat() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn fixed_output_default_algo() {
|
fn fixed_output_missing_hashalgo() {
|
||||||
let result = eval_deep(
|
assert!(
|
||||||
|
eval_deep_result(
|
||||||
r#"derivation {
|
r#"derivation {
|
||||||
name = "default";
|
name = "default";
|
||||||
builder = "/bin/sh";
|
builder = "/bin/sh";
|
||||||
system = "x86_64-linux";
|
system = "x86_64-linux";
|
||||||
outputHash = "0000000000000000000000000000000000000000000000000000000000000000";
|
outputHash = "0000000000000000000000000000000000000000000000000000000000000000";
|
||||||
}"#,
|
}"#,
|
||||||
|
)
|
||||||
|
.is_err()
|
||||||
);
|
);
|
||||||
|
|
||||||
match result {
|
|
||||||
Value::AttrSet(attrs) => {
|
|
||||||
assert!(attrs.contains_key("outPath"));
|
|
||||||
// Verify it defaults to sha256 (same as explicitly specifying it)
|
|
||||||
if let Some(Value::String(out_path)) = attrs.get("outPath") {
|
|
||||||
assert!(out_path.contains("/nix/store/"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => panic!("Expected AttrSet"),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -428,6 +418,7 @@ fn fixed_output_rejects_multi_output() {
|
|||||||
builder = "/bin/sh";
|
builder = "/bin/sh";
|
||||||
system = "x86_64-linux";
|
system = "x86_64-linux";
|
||||||
outputHash = "0000000000000000000000000000000000000000000000000000000000000000";
|
outputHash = "0000000000000000000000000000000000000000000000000000000000000000";
|
||||||
|
outputHashAlgo = "sha256";
|
||||||
outputs = ["out" "dev"];
|
outputs = ["out" "dev"];
|
||||||
}"#,
|
}"#,
|
||||||
);
|
);
|
||||||
@@ -644,6 +635,7 @@ fn fixed_output_with_structured_attrs() {
|
|||||||
builder = "/bin/sh";
|
builder = "/bin/sh";
|
||||||
system = "x86_64-linux";
|
system = "x86_64-linux";
|
||||||
outputHash = "0000000000000000000000000000000000000000000000000000000000000000";
|
outputHash = "0000000000000000000000000000000000000000000000000000000000000000";
|
||||||
|
outputHashAlgo = "sha256";
|
||||||
__structuredAttrs = true;
|
__structuredAttrs = true;
|
||||||
data = { key = "value"; };
|
data = { key = "value"; };
|
||||||
}"#,
|
}"#,
|
||||||
|
|||||||
Reference in New Issue
Block a user