feat: implement hash related primops

This commit is contained in:
2026-02-15 18:16:50 +08:00
parent 7836f8c869
commit 5c48e5cfdd
7 changed files with 173 additions and 30 deletions

View File

@@ -37,7 +37,7 @@ deno_error = "0.7"
nix-nar = "0.3" nix-nar = "0.3"
sha2 = "0.10" sha2 = "0.10"
sha1 = "0.10" sha1 = "0.10"
md5 = "0.7" md5 = "0.8"
hex = "0.4" hex = "0.4"
base64 = "0.22" base64 = "0.22"

View File

@@ -1,21 +1,37 @@
import { forceStringNoCtx } from "../type-assert"; import { forceAttrs, forceStringNoCtx, forceStringValue } from "../type-assert";
import type { NixValue } from "../types"; import type { NixValue } from "../types";
import { realisePath } from "./io"; import { realisePath } from "./io";
export const hashFile = export const hashFile =
(type: NixValue) => (type: NixValue) =>
(p: NixValue): string => { (p: NixValue): string => {
const _ty = forceStringNoCtx(type); const algo = forceStringNoCtx(type);
const _pathStr = realisePath(p); const pathStr = realisePath(p);
throw new Error("Not implemented: hashFile"); return Deno.core.ops.op_hash_file(algo, pathStr);
}; };
export const hashString = export const hashString =
(_type: NixValue) => (type: NixValue) =>
(_p: NixValue): never => { (s: NixValue): string => {
throw new Error("Not implemented: hashString"); const algo = forceStringNoCtx(type);
const data = forceStringValue(s);
return Deno.core.ops.op_hash_string(algo, data);
}; };
export const convertHash = (_args: NixValue): never => { export const convertHash = (args: NixValue): string => {
throw new Error("Not implemented: convertHash"); const attrs = forceAttrs(args);
const hash = forceStringNoCtx(attrs.hash);
let hashAlgo: string | null = null;
if ("hashAlgo" in attrs) {
hashAlgo = forceStringNoCtx(attrs.hashAlgo);
}
const toHashFormat = forceStringNoCtx(attrs.toHashFormat);
return Deno.core.ops.op_convert_hash({
hash,
hashAlgo,
toHashFormat,
});
}; };

View File

@@ -20,6 +20,13 @@ declare global {
function op_make_placeholder(output: string): string; function op_make_placeholder(output: string): string;
function op_store_path(path: string): string; function op_store_path(path: string): string;
function op_convert_hash(input: {
hash: string;
hashAlgo: string | null;
toHashFormat: string;
}): string;
function op_hash_string(algo: string, data: string): string;
function op_hash_file(algo: string, path: string): string;
function op_parse_hash(hashStr: string, algo: string | null): { hex: string; algo: string }; function op_parse_hash(hashStr: string, algo: string | null): { hex: string; algo: string };
function op_add_path( function op_add_path(

View File

@@ -58,6 +58,9 @@ fn runtime_extension<Ctx: RuntimeContext>() -> Extension {
op_make_placeholder(), op_make_placeholder(),
op_store_path::<Ctx>(), op_store_path::<Ctx>(),
op_convert_hash(),
op_hash_string(),
op_hash_file(),
op_parse_hash(), op_parse_hash(),
op_add_path::<Ctx>(), op_add_path::<Ctx>(),

View File

@@ -1106,3 +1106,135 @@ fn op_make_fixed_output_path_impl(
crate::nix_utils::make_store_path(store_dir, "output:out", &inner_hash, name) crate::nix_utils::make_store_path(store_dir, "output:out", &inner_hash, name)
} }
} }
#[deno_core::op2]
#[string]
pub(super) fn op_hash_string(
#[string] algo: String,
#[string] data: String,
) -> std::result::Result<String, NixRuntimeError> {
use sha2::{Digest, Sha256, Sha512};
let hash_bytes: Vec<u8> = match algo.as_str() {
"sha256" => {
let mut hasher = Sha256::new();
hasher.update(data.as_bytes());
hasher.finalize().to_vec()
}
"sha512" => {
let mut hasher = Sha512::new();
hasher.update(data.as_bytes());
hasher.finalize().to_vec()
}
"sha1" => {
use sha1::Digest as _;
let mut hasher = sha1::Sha1::new();
hasher.update(data.as_bytes());
hasher.finalize().to_vec()
}
"md5" => {
let digest = md5::compute(data.as_bytes());
digest.to_vec()
}
_ => {
return Err(NixRuntimeError::from(format!(
"unknown hash algorithm '{}'",
algo
)));
}
};
Ok(hex::encode(hash_bytes))
}
#[deno_core::op2]
#[string]
pub(super) fn op_hash_file(
#[string] algo: String,
#[string] path: String,
) -> std::result::Result<String, NixRuntimeError> {
let data = std::fs::read(&path)
.map_err(|e| NixRuntimeError::from(format!("cannot read '{}': {}", path, e)))?;
use sha2::{Digest, Sha256, Sha512};
let hash_bytes: Vec<u8> = match algo.as_str() {
"sha256" => {
let mut hasher = Sha256::new();
hasher.update(&data);
hasher.finalize().to_vec()
}
"sha512" => {
let mut hasher = Sha512::new();
hasher.update(&data);
hasher.finalize().to_vec()
}
"sha1" => {
use sha1::Digest as _;
let mut hasher = sha1::Sha1::new();
hasher.update(&data);
hasher.finalize().to_vec()
}
"md5" => {
let digest = md5::compute(&data);
digest.to_vec()
}
_ => {
return Err(NixRuntimeError::from(format!(
"unknown hash algorithm '{}'",
algo
)));
}
};
Ok(hex::encode(hash_bytes))
}
#[deno_core::op2]
#[string]
pub(super) fn op_convert_hash(
#[serde] input: ConvertHashInput,
) -> std::result::Result<String, NixRuntimeError> {
use nix_compat::nixhash::{HashAlgo, NixHash};
let hash_algo = input
.hash_algo
.as_deref()
.and_then(|a| HashAlgo::from_str(a).ok());
let hash = NixHash::from_str(&input.hash, hash_algo).map_err(|e| {
NixRuntimeError::from(format!("cannot convert hash '{}': {}", input.hash, e))
})?;
let bytes = hash.digest_as_bytes();
match input.to_format.as_str() {
"base16" => Ok(hex::encode(bytes)),
"nix32" | "base32" => Ok(nix_compat::nixbase32::encode(bytes)),
"base64" => {
use base64::Engine as _;
Ok(base64::engine::general_purpose::STANDARD.encode(bytes))
}
"sri" => Ok(format!(
"{}-{}",
hash.algo(),
{
use base64::Engine as _;
base64::engine::general_purpose::STANDARD.encode(bytes)
}
)),
_ => Err(NixRuntimeError::from(format!(
"unknown hash format '{}'",
input.to_format
))),
}
}
#[derive(serde::Deserialize)]
pub(super) struct ConvertHashInput {
hash: String,
#[serde(rename = "hashAlgo")]
hash_algo: Option<String>,
#[serde(rename = "toHashFormat")]
to_format: String,
}

View File

@@ -122,10 +122,7 @@ eval_okay_test!(concatmap);
eval_okay_test!(concatstringssep); eval_okay_test!(concatstringssep);
eval_okay_test!(context); eval_okay_test!(context);
eval_okay_test!(context_introspection); eval_okay_test!(context_introspection);
eval_okay_test!( eval_okay_test!(convertHash);
#[ignore = "not implemented: convertHash"]
convertHash
);
eval_okay_test!(curpos); eval_okay_test!(curpos);
eval_okay_test!(deepseq); eval_okay_test!(deepseq);
eval_okay_test!(delayed_with); eval_okay_test!(delayed_with);
@@ -158,24 +155,15 @@ eval_okay_test!(
fromTOML_timestamps fromTOML_timestamps
); );
eval_okay_test!(functionargs); eval_okay_test!(functionargs);
eval_okay_test!( eval_okay_test!(hashfile);
#[ignore = "not implemented: hashFile"] eval_okay_test!(hashstring);
hashfile
);
eval_okay_test!(
#[ignore = "not implemented: hashString"]
hashstring
);
eval_okay_test!(getattrpos); eval_okay_test!(getattrpos);
eval_okay_test!(getattrpos_functionargs); eval_okay_test!(getattrpos_functionargs);
eval_okay_test!(getattrpos_undefined); eval_okay_test!(getattrpos_undefined);
eval_okay_test!(getenv, || { eval_okay_test!(getenv, || {
unsafe { std::env::set_var("TEST_VAR", "foo") }; unsafe { std::env::set_var("TEST_VAR", "foo") };
}); });
eval_okay_test!( eval_okay_test!(groupBy);
#[ignore = "not implemented: hashString"]
groupBy
);
eval_okay_test!(r#if); eval_okay_test!(r#if);
eval_okay_test!(ind_string); eval_okay_test!(ind_string);
eval_okay_test!(import); eval_okay_test!(import);
@@ -265,10 +253,7 @@ eval_okay_test!(tryeval);
eval_okay_test!(types); eval_okay_test!(types);
eval_okay_test!(versions); eval_okay_test!(versions);
eval_okay_test!(with); eval_okay_test!(with);
eval_okay_test!( eval_okay_test!(zipAttrsWith);
#[ignore = "not implemented: hashString"]
zipAttrsWith
);
eval_fail_test!(fail_abort); eval_fail_test!(fail_abort);
eval_fail_test!(fail_addDrvOutputDependencies_empty_context); eval_fail_test!(fail_addDrvOutputDependencies_empty_context);

Binary file not shown.