refactor: move ops to runtime/ops.rs
This commit is contained in:
@@ -30,8 +30,7 @@ macro_rules! eval {
|
|||||||
let code = self.compile(source)?;
|
let code = self.compile(source)?;
|
||||||
|
|
||||||
tracing::debug!("Executing JavaScript");
|
tracing::debug!("Executing JavaScript");
|
||||||
self.runtime
|
self.runtime.eval(format!($wrapper, code), &mut self.ctx)
|
||||||
.eval(format!($wrapper, code), &mut self.ctx)
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ pub use download::Downloader;
|
|||||||
pub use metadata_cache::MetadataCache;
|
pub use metadata_cache::MetadataCache;
|
||||||
|
|
||||||
use crate::nar;
|
use crate::nar;
|
||||||
use crate::runtime::NixError;
|
use crate::runtime::NixRuntimeError;
|
||||||
|
|
||||||
#[derive(Serialize)]
|
#[derive(Serialize)]
|
||||||
pub struct FetchUrlResult {
|
pub struct FetchUrlResult {
|
||||||
@@ -62,14 +62,15 @@ pub fn op_fetch_url<Ctx: RuntimeContext>(
|
|||||||
#[string] expected_hash: Option<String>,
|
#[string] expected_hash: Option<String>,
|
||||||
#[string] name: Option<String>,
|
#[string] name: Option<String>,
|
||||||
executable: bool,
|
executable: bool,
|
||||||
) -> Result<FetchUrlResult, NixError> {
|
) -> Result<FetchUrlResult, NixRuntimeError> {
|
||||||
let _span = tracing::info_span!("op_fetch_url", url = %url).entered();
|
let _span = tracing::info_span!("op_fetch_url", url = %url).entered();
|
||||||
info!("fetchurl started");
|
info!("fetchurl started");
|
||||||
|
|
||||||
let file_name =
|
let file_name =
|
||||||
name.unwrap_or_else(|| url.rsplit('/').next().unwrap_or("download").to_string());
|
name.unwrap_or_else(|| url.rsplit('/').next().unwrap_or("download").to_string());
|
||||||
|
|
||||||
let metadata_cache = MetadataCache::new(3600).map_err(|e| NixError::from(e.to_string()))?;
|
let metadata_cache =
|
||||||
|
MetadataCache::new(3600).map_err(|e| NixRuntimeError::from(e.to_string()))?;
|
||||||
|
|
||||||
let input = serde_json::json!({
|
let input = serde_json::json!({
|
||||||
"type": "file",
|
"type": "file",
|
||||||
@@ -80,7 +81,7 @@ pub fn op_fetch_url<Ctx: RuntimeContext>(
|
|||||||
|
|
||||||
if let Some(cached_entry) = metadata_cache
|
if let Some(cached_entry) = metadata_cache
|
||||||
.lookup(&input)
|
.lookup(&input)
|
||||||
.map_err(|e| NixError::from(e.to_string()))?
|
.map_err(|e| NixRuntimeError::from(e.to_string()))?
|
||||||
{
|
{
|
||||||
let cached_hash = cached_entry
|
let cached_hash = cached_entry
|
||||||
.info
|
.info
|
||||||
@@ -112,7 +113,7 @@ pub fn op_fetch_url<Ctx: RuntimeContext>(
|
|||||||
let downloader = Downloader::new();
|
let downloader = Downloader::new();
|
||||||
let data = downloader
|
let data = downloader
|
||||||
.download(&url)
|
.download(&url)
|
||||||
.map_err(|e| NixError::from(e.to_string()))?;
|
.map_err(|e| NixRuntimeError::from(e.to_string()))?;
|
||||||
|
|
||||||
info!(bytes = data.len(), "Download complete");
|
info!(bytes = data.len(), "Download complete");
|
||||||
|
|
||||||
@@ -121,7 +122,7 @@ pub fn op_fetch_url<Ctx: RuntimeContext>(
|
|||||||
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);
|
||||||
if hash != normalized_expected {
|
if hash != normalized_expected {
|
||||||
return Err(NixError::from(format!(
|
return Err(NixRuntimeError::from(format!(
|
||||||
"hash mismatch for '{}': expected {}, got {}",
|
"hash mismatch for '{}': expected {}, got {}",
|
||||||
url, normalized_expected, hash
|
url, normalized_expected, hash
|
||||||
)));
|
)));
|
||||||
@@ -132,7 +133,7 @@ pub fn op_fetch_url<Ctx: RuntimeContext>(
|
|||||||
let store = ctx.get_store();
|
let store = ctx.get_store();
|
||||||
let store_path = store
|
let store_path = store
|
||||||
.add_to_store(&file_name, &data, false, vec![])
|
.add_to_store(&file_name, &data, false, vec![])
|
||||||
.map_err(|e| NixError::from(e.to_string()))?;
|
.map_err(|e| NixRuntimeError::from(e.to_string()))?;
|
||||||
|
|
||||||
info!(store_path = %store_path, "Added to store");
|
info!(store_path = %store_path, "Added to store");
|
||||||
|
|
||||||
@@ -153,7 +154,7 @@ pub fn op_fetch_url<Ctx: RuntimeContext>(
|
|||||||
|
|
||||||
metadata_cache
|
metadata_cache
|
||||||
.add(&input, &info, &store_path, true)
|
.add(&input, &info, &store_path, true)
|
||||||
.map_err(|e| NixError::from(e.to_string()))?;
|
.map_err(|e| NixRuntimeError::from(e.to_string()))?;
|
||||||
|
|
||||||
Ok(FetchUrlResult { store_path, hash })
|
Ok(FetchUrlResult { store_path, hash })
|
||||||
}
|
}
|
||||||
@@ -166,12 +167,13 @@ pub fn op_fetch_tarball<Ctx: RuntimeContext>(
|
|||||||
#[string] expected_hash: Option<String>,
|
#[string] expected_hash: Option<String>,
|
||||||
#[string] expected_nar_hash: Option<String>,
|
#[string] expected_nar_hash: Option<String>,
|
||||||
#[string] name: Option<String>,
|
#[string] name: Option<String>,
|
||||||
) -> Result<FetchTarballResult, NixError> {
|
) -> 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");
|
||||||
|
|
||||||
let dir_name = name.unwrap_or_else(|| "source".to_string());
|
let dir_name = name.unwrap_or_else(|| "source".to_string());
|
||||||
let metadata_cache = MetadataCache::new(3600).map_err(|e| NixError::from(e.to_string()))?;
|
let metadata_cache =
|
||||||
|
MetadataCache::new(3600).map_err(|e| NixRuntimeError::from(e.to_string()))?;
|
||||||
|
|
||||||
let input = serde_json::json!({
|
let input = serde_json::json!({
|
||||||
"type": "tarball",
|
"type": "tarball",
|
||||||
@@ -181,7 +183,7 @@ pub fn op_fetch_tarball<Ctx: RuntimeContext>(
|
|||||||
|
|
||||||
if let Some(cached_entry) = metadata_cache
|
if let Some(cached_entry) = metadata_cache
|
||||||
.lookup(&input)
|
.lookup(&input)
|
||||||
.map_err(|e| NixError::from(e.to_string()))?
|
.map_err(|e| NixRuntimeError::from(e.to_string()))?
|
||||||
{
|
{
|
||||||
let cached_nar_hash = cached_entry
|
let cached_nar_hash = cached_entry
|
||||||
.info
|
.info
|
||||||
@@ -218,7 +220,7 @@ pub fn op_fetch_tarball<Ctx: RuntimeContext>(
|
|||||||
let downloader = Downloader::new();
|
let downloader = Downloader::new();
|
||||||
let data = downloader
|
let data = downloader
|
||||||
.download(&url)
|
.download(&url)
|
||||||
.map_err(|e| NixError::from(e.to_string()))?;
|
.map_err(|e| NixRuntimeError::from(e.to_string()))?;
|
||||||
|
|
||||||
info!(bytes = data.len(), "Download complete");
|
info!(bytes = data.len(), "Download complete");
|
||||||
|
|
||||||
@@ -227,7 +229,7 @@ pub fn op_fetch_tarball<Ctx: RuntimeContext>(
|
|||||||
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);
|
||||||
if tarball_hash != normalized_expected {
|
if tarball_hash != normalized_expected {
|
||||||
return Err(NixError::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, normalized_expected, tarball_hash
|
||||||
)));
|
)));
|
||||||
@@ -235,14 +237,14 @@ pub fn op_fetch_tarball<Ctx: RuntimeContext>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
info!("Extracting tarball");
|
info!("Extracting tarball");
|
||||||
let cache = FetcherCache::new().map_err(|e| NixError::from(e.to_string()))?;
|
let cache = FetcherCache::new().map_err(|e| NixRuntimeError::from(e.to_string()))?;
|
||||||
let (extracted_path, _temp_dir) = cache
|
let (extracted_path, _temp_dir) = cache
|
||||||
.extract_tarball_to_temp(&data)
|
.extract_tarball_to_temp(&data)
|
||||||
.map_err(|e| NixError::from(e.to_string()))?;
|
.map_err(|e| NixRuntimeError::from(e.to_string()))?;
|
||||||
|
|
||||||
info!("Computing NAR hash");
|
info!("Computing NAR hash");
|
||||||
let nar_hash =
|
let nar_hash =
|
||||||
nar::compute_nar_hash(&extracted_path).map_err(|e| NixError::from(e.to_string()))?;
|
nar::compute_nar_hash(&extracted_path).map_err(|e| NixRuntimeError::from(e.to_string()))?;
|
||||||
|
|
||||||
debug!(
|
debug!(
|
||||||
tarball_hash = %tarball_hash,
|
tarball_hash = %tarball_hash,
|
||||||
@@ -253,7 +255,7 @@ pub fn op_fetch_tarball<Ctx: RuntimeContext>(
|
|||||||
if let Some(ref expected) = expected_nar_hash {
|
if let Some(ref expected) = expected_nar_hash {
|
||||||
let normalized_expected = normalize_hash(expected);
|
let normalized_expected = normalize_hash(expected);
|
||||||
if nar_hash != normalized_expected {
|
if nar_hash != normalized_expected {
|
||||||
return Err(NixError::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, normalized_expected, nar_hash
|
||||||
)));
|
)));
|
||||||
@@ -265,7 +267,7 @@ pub fn op_fetch_tarball<Ctx: RuntimeContext>(
|
|||||||
let store = ctx.get_store();
|
let store = ctx.get_store();
|
||||||
let store_path = store
|
let store_path = store
|
||||||
.add_to_store_from_path(&dir_name, &extracted_path, vec![])
|
.add_to_store_from_path(&dir_name, &extracted_path, vec![])
|
||||||
.map_err(|e| NixError::from(e.to_string()))?;
|
.map_err(|e| NixRuntimeError::from(e.to_string()))?;
|
||||||
|
|
||||||
info!(store_path = %store_path, "Added to store");
|
info!(store_path = %store_path, "Added to store");
|
||||||
|
|
||||||
@@ -278,7 +280,7 @@ pub fn op_fetch_tarball<Ctx: RuntimeContext>(
|
|||||||
let immutable = expected_nar_hash.is_some();
|
let immutable = expected_nar_hash.is_some();
|
||||||
metadata_cache
|
metadata_cache
|
||||||
.add(&input, &info, &store_path, immutable)
|
.add(&input, &info, &store_path, immutable)
|
||||||
.map_err(|e| NixError::from(e.to_string()))?;
|
.map_err(|e| NixRuntimeError::from(e.to_string()))?;
|
||||||
|
|
||||||
Ok(FetchTarballResult {
|
Ok(FetchTarballResult {
|
||||||
store_path,
|
store_path,
|
||||||
@@ -298,10 +300,10 @@ pub fn op_fetch_git<Ctx: RuntimeContext>(
|
|||||||
submodules: bool,
|
submodules: bool,
|
||||||
all_refs: bool,
|
all_refs: bool,
|
||||||
#[string] name: Option<String>,
|
#[string] name: Option<String>,
|
||||||
) -> Result<FetchGitResult, NixError> {
|
) -> Result<FetchGitResult, NixRuntimeError> {
|
||||||
let _span = tracing::info_span!("op_fetch_git", url = %url).entered();
|
let _span = tracing::info_span!("op_fetch_git", url = %url).entered();
|
||||||
info!("fetchGit started");
|
info!("fetchGit started");
|
||||||
let cache = FetcherCache::new().map_err(|e| NixError::from(e.to_string()))?;
|
let cache = FetcherCache::new().map_err(|e| NixRuntimeError::from(e.to_string()))?;
|
||||||
let dir_name = name.unwrap_or_else(|| "source".to_string());
|
let dir_name = name.unwrap_or_else(|| "source".to_string());
|
||||||
|
|
||||||
let ctx: &Ctx = state.get_ctx();
|
let ctx: &Ctx = state.get_ctx();
|
||||||
@@ -318,7 +320,7 @@ pub fn op_fetch_git<Ctx: RuntimeContext>(
|
|||||||
all_refs,
|
all_refs,
|
||||||
&dir_name,
|
&dir_name,
|
||||||
)
|
)
|
||||||
.map_err(|e| NixError::from(e.to_string()))
|
.map_err(|e| NixRuntimeError::from(e.to_string()))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[op2]
|
#[op2]
|
||||||
@@ -327,11 +329,12 @@ pub fn op_fetch_hg(
|
|||||||
#[string] url: String,
|
#[string] url: String,
|
||||||
#[string] rev: Option<String>,
|
#[string] rev: Option<String>,
|
||||||
#[string] name: Option<String>,
|
#[string] name: Option<String>,
|
||||||
) -> Result<FetchHgResult, NixError> {
|
) -> Result<FetchHgResult, NixRuntimeError> {
|
||||||
let cache = FetcherCache::new().map_err(|e| NixError::from(e.to_string()))?;
|
let cache = FetcherCache::new().map_err(|e| NixRuntimeError::from(e.to_string()))?;
|
||||||
let dir_name = name.unwrap_or_else(|| "source".to_string());
|
let dir_name = name.unwrap_or_else(|| "source".to_string());
|
||||||
|
|
||||||
hg::fetch_hg(&cache, &url, rev.as_deref(), &dir_name).map_err(|e| NixError::from(e.to_string()))
|
hg::fetch_hg(&cache, &url, rev.as_deref(), &dir_name)
|
||||||
|
.map_err(|e| NixRuntimeError::from(e.to_string()))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn normalize_hash(hash: &str) -> String {
|
fn normalize_hash(hash: &str) -> String {
|
||||||
|
|||||||
@@ -1,15 +1,16 @@
|
|||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
use std::marker::PhantomData;
|
use std::marker::PhantomData;
|
||||||
use std::path::{Component, Path, PathBuf};
|
use std::path::Path;
|
||||||
use std::sync::{Arc, Once};
|
|
||||||
|
|
||||||
use deno_core::{Extension, ExtensionFileSource, JsRuntime, OpState, RuntimeOptions, v8};
|
use deno_core::{Extension, ExtensionFileSource, JsRuntime, OpState, RuntimeOptions, v8};
|
||||||
use rust_embed::Embed;
|
|
||||||
|
|
||||||
use crate::error::{Error, Result, Source};
|
use crate::error::{Error, Result, Source};
|
||||||
use crate::store::Store;
|
use crate::store::Store;
|
||||||
use crate::value::{AttrSet, List, Symbol, Value};
|
use crate::value::{AttrSet, List, Symbol, Value};
|
||||||
|
|
||||||
|
mod ops;
|
||||||
|
use ops::*;
|
||||||
|
|
||||||
type ScopeRef<'p, 's> = v8::PinnedRef<'p, v8::HandleScope<'s>>;
|
type ScopeRef<'p, 's> = v8::PinnedRef<'p, v8::HandleScope<'s>>;
|
||||||
type LocalValue<'a> = v8::Local<'a, v8::Value>;
|
type LocalValue<'a> = v8::Local<'a, v8::Value>;
|
||||||
type LocalSymbol<'a> = v8::Local<'a, v8::Symbol>;
|
type LocalSymbol<'a> = v8::Local<'a, v8::Symbol>;
|
||||||
@@ -87,633 +88,20 @@ mod private {
|
|||||||
}
|
}
|
||||||
impl std::error::Error for SimpleErrorWrapper {}
|
impl std::error::Error for SimpleErrorWrapper {}
|
||||||
|
|
||||||
js_error_wrapper!(SimpleErrorWrapper, NixError, "Error");
|
js_error_wrapper!(SimpleErrorWrapper, NixRuntimeError, "Error");
|
||||||
|
|
||||||
impl From<String> for NixError {
|
impl From<String> for NixRuntimeError {
|
||||||
fn from(value: String) -> Self {
|
fn from(value: String) -> Self {
|
||||||
NixError(SimpleErrorWrapper(value))
|
NixRuntimeError(SimpleErrorWrapper(value))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl From<&str> for NixError {
|
impl From<&str> for NixRuntimeError {
|
||||||
fn from(value: &str) -> Self {
|
fn from(value: &str) -> Self {
|
||||||
NixError(SimpleErrorWrapper(value.to_string()))
|
NixRuntimeError(SimpleErrorWrapper(value.to_string()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub(crate) use private::NixError;
|
pub(crate) use private::NixRuntimeError;
|
||||||
|
|
||||||
#[derive(Embed)]
|
|
||||||
#[folder = "src/runtime/corepkgs"]
|
|
||||||
pub(crate) struct CorePkgs;
|
|
||||||
|
|
||||||
#[deno_core::op2]
|
|
||||||
#[string]
|
|
||||||
fn op_import<Ctx: RuntimeContext>(
|
|
||||||
state: &mut OpState,
|
|
||||||
#[string] path: String,
|
|
||||||
) -> std::result::Result<String, NixError> {
|
|
||||||
let _span = tracing::info_span!("op_import", path = %path).entered();
|
|
||||||
let ctx: &mut Ctx = state.get_ctx_mut();
|
|
||||||
|
|
||||||
// FIXME: special path type
|
|
||||||
if path.starts_with("<nix/") && path.ends_with(">") {
|
|
||||||
let corepkg_name = &path[5..path.len() - 1];
|
|
||||||
if let Some(file) = CorePkgs::get(corepkg_name) {
|
|
||||||
tracing::info!("Importing corepkg: {}", corepkg_name);
|
|
||||||
let source = Source {
|
|
||||||
ty: crate::error::SourceType::Eval(Arc::new(ctx.get_current_dir().to_path_buf())),
|
|
||||||
src: str::from_utf8(&file.data)
|
|
||||||
.expect("corrupted corepkgs file")
|
|
||||||
.into(),
|
|
||||||
};
|
|
||||||
ctx.add_source(source.clone());
|
|
||||||
return Ok(ctx.compile_code(source).map_err(|err| err.to_string())?);
|
|
||||||
} else {
|
|
||||||
return Err(format!("Corepkg not found: {}", corepkg_name).into());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let current_dir = ctx.get_current_dir();
|
|
||||||
let mut absolute_path = current_dir.join(&path);
|
|
||||||
// Do NOT resolve symlinks (eval-okay-symlink-resolution)
|
|
||||||
// TODO: is this correct?
|
|
||||||
// .canonicalize()
|
|
||||||
// .map_err(|e| format!("Failed to resolve path {}: {}", path, e))?;
|
|
||||||
if absolute_path.is_dir() {
|
|
||||||
absolute_path.push("default.nix")
|
|
||||||
}
|
|
||||||
|
|
||||||
tracing::info!("Importing file: {}", absolute_path.display());
|
|
||||||
|
|
||||||
let source = Source::new_file(absolute_path.clone())
|
|
||||||
.map_err(|e| format!("Failed to read {}: {}", absolute_path.display(), e))?;
|
|
||||||
|
|
||||||
tracing::debug!("Compiling file");
|
|
||||||
ctx.add_source(source.clone());
|
|
||||||
|
|
||||||
Ok(ctx.compile_code(source).map_err(|err| err.to_string())?)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[deno_core::op2]
|
|
||||||
#[string]
|
|
||||||
fn op_read_file(#[string] path: String) -> std::result::Result<String, NixError> {
|
|
||||||
Ok(std::fs::read_to_string(&path).map_err(|e| format!("Failed to read {}: {}", path, e))?)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[deno_core::op2(fast)]
|
|
||||||
fn op_path_exists(#[string] path: String) -> bool {
|
|
||||||
let must_be_dir = path.ends_with('/') || path.ends_with("/.");
|
|
||||||
let p = Path::new(&path);
|
|
||||||
|
|
||||||
if must_be_dir {
|
|
||||||
match std::fs::metadata(p) {
|
|
||||||
Ok(m) => m.is_dir(),
|
|
||||||
Err(_) => false,
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
std::fs::symlink_metadata(p).is_ok()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[deno_core::op2]
|
|
||||||
#[string]
|
|
||||||
fn op_read_file_type(#[string] path: String) -> std::result::Result<String, NixError> {
|
|
||||||
let path = Path::new(&path);
|
|
||||||
let metadata = std::fs::symlink_metadata(path)
|
|
||||||
.map_err(|e| format!("Failed to read file type for {}: {}", path.display(), e))?;
|
|
||||||
|
|
||||||
let file_type = metadata.file_type();
|
|
||||||
let type_str = if file_type.is_dir() {
|
|
||||||
"directory"
|
|
||||||
} else if file_type.is_symlink() {
|
|
||||||
"symlink"
|
|
||||||
} else if file_type.is_file() {
|
|
||||||
"regular"
|
|
||||||
} else {
|
|
||||||
"unknown"
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(type_str.to_string())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[deno_core::op2]
|
|
||||||
#[serde]
|
|
||||||
fn op_read_dir(
|
|
||||||
#[string] path: String,
|
|
||||||
) -> std::result::Result<std::collections::HashMap<String, String>, NixError> {
|
|
||||||
let path = Path::new(&path);
|
|
||||||
|
|
||||||
if !path.is_dir() {
|
|
||||||
return Err(format!("{} is not a directory", path.display()).into());
|
|
||||||
}
|
|
||||||
|
|
||||||
let entries = std::fs::read_dir(path)
|
|
||||||
.map_err(|e| format!("Failed to read directory {}: {}", path.display(), e))?;
|
|
||||||
|
|
||||||
let mut result = std::collections::HashMap::new();
|
|
||||||
|
|
||||||
for entry in entries {
|
|
||||||
let entry = entry.map_err(|e| format!("Failed to read directory entry: {}", e))?;
|
|
||||||
let file_name = entry.file_name().to_string_lossy().to_string();
|
|
||||||
|
|
||||||
let file_type = entry.file_type().map_err(|e| {
|
|
||||||
format!(
|
|
||||||
"Failed to read file type for {}: {}",
|
|
||||||
entry.path().display(),
|
|
||||||
e
|
|
||||||
)
|
|
||||||
})?;
|
|
||||||
|
|
||||||
let type_str = if file_type.is_dir() {
|
|
||||||
"directory"
|
|
||||||
} else if file_type.is_symlink() {
|
|
||||||
"symlink"
|
|
||||||
} else if file_type.is_file() {
|
|
||||||
"regular"
|
|
||||||
} else {
|
|
||||||
"unknown"
|
|
||||||
};
|
|
||||||
|
|
||||||
result.insert(file_name, type_str.to_string());
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(result)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[deno_core::op2]
|
|
||||||
#[string]
|
|
||||||
fn op_resolve_path(
|
|
||||||
#[string] current_dir: String,
|
|
||||||
#[string] path: String,
|
|
||||||
) -> std::result::Result<String, NixError> {
|
|
||||||
let _span = tracing::debug_span!("op_resolve_path").entered();
|
|
||||||
tracing::debug!(current_dir, path);
|
|
||||||
|
|
||||||
// If already absolute, return as-is
|
|
||||||
if path.starts_with('/') {
|
|
||||||
return Ok(path);
|
|
||||||
}
|
|
||||||
// Resolve relative path against current file directory (or CWD)
|
|
||||||
let current_dir = if !path.starts_with("~/") {
|
|
||||||
let mut dir = PathBuf::from(current_dir);
|
|
||||||
dir.push(path);
|
|
||||||
dir
|
|
||||||
} else {
|
|
||||||
let mut dir = std::env::home_dir().ok_or("home dir not defined")?;
|
|
||||||
dir.push(&path[2..]);
|
|
||||||
dir
|
|
||||||
};
|
|
||||||
let mut normalized = PathBuf::new();
|
|
||||||
for component in current_dir.components() {
|
|
||||||
match component {
|
|
||||||
Component::Prefix(p) => normalized.push(p.as_os_str()),
|
|
||||||
Component::RootDir => normalized.push("/"),
|
|
||||||
Component::CurDir => {}
|
|
||||||
Component::ParentDir => {
|
|
||||||
normalized.pop();
|
|
||||||
}
|
|
||||||
Component::Normal(c) => normalized.push(c),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
tracing::debug!(normalized = normalized.display().to_string());
|
|
||||||
Ok(normalized.to_string_lossy().to_string())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[deno_core::op2]
|
|
||||||
#[string]
|
|
||||||
fn op_sha256_hex(#[string] data: String) -> String {
|
|
||||||
crate::nix_hash::sha256_hex(&data)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[deno_core::op2]
|
|
||||||
#[string]
|
|
||||||
fn op_make_placeholder(#[string] output: String) -> String {
|
|
||||||
use sha2::{Digest, Sha256};
|
|
||||||
let input = format!("nix-output:{}", output);
|
|
||||||
let mut hasher = Sha256::new();
|
|
||||||
hasher.update(input.as_bytes());
|
|
||||||
let hash: [u8; 32] = hasher.finalize().into();
|
|
||||||
let encoded = crate::nix_hash::nix_base32_encode(&hash);
|
|
||||||
format!("/{}", encoded)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[deno_core::op2]
|
|
||||||
#[serde]
|
|
||||||
fn op_decode_span<Ctx: RuntimeContext>(
|
|
||||||
state: &mut OpState,
|
|
||||||
#[string] span_str: String,
|
|
||||||
) -> std::result::Result<serde_json::Value, NixError> {
|
|
||||||
let parts: Vec<&str> = span_str.split(':').collect();
|
|
||||||
if parts.len() != 3 {
|
|
||||||
return Ok(serde_json::json!({
|
|
||||||
"file": serde_json::Value::Null,
|
|
||||||
"line": serde_json::Value::Null,
|
|
||||||
"column": serde_json::Value::Null
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
let source_id: usize = parts[0].parse().map_err(|_| "Invalid source ID")?;
|
|
||||||
let start: u32 = parts[1].parse().map_err(|_| "Invalid start offset")?;
|
|
||||||
|
|
||||||
let ctx: &Ctx = state.get_ctx();
|
|
||||||
let source = ctx.get_source(source_id);
|
|
||||||
let content = &source.src;
|
|
||||||
|
|
||||||
let (line, column) = byte_offset_to_line_col(content, start as usize);
|
|
||||||
|
|
||||||
Ok(serde_json::json!({
|
|
||||||
"file": source.get_name(),
|
|
||||||
"line": line,
|
|
||||||
"column": column
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn byte_offset_to_line_col(content: &str, offset: usize) -> (u32, u32) {
|
|
||||||
let mut line = 1u32;
|
|
||||||
let mut col = 1u32;
|
|
||||||
|
|
||||||
for (idx, ch) in content.char_indices() {
|
|
||||||
if idx >= offset {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if ch == '\n' {
|
|
||||||
line += 1;
|
|
||||||
col = 1;
|
|
||||||
} else {
|
|
||||||
col += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
(line, col)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[deno_core::op2]
|
|
||||||
#[string]
|
|
||||||
fn op_make_store_path<Ctx: RuntimeContext>(
|
|
||||||
state: &mut OpState,
|
|
||||||
#[string] ty: String,
|
|
||||||
#[string] hash_hex: String,
|
|
||||||
#[string] name: String,
|
|
||||||
) -> String {
|
|
||||||
let ctx: &Ctx = state.get_ctx();
|
|
||||||
let store = ctx.get_store();
|
|
||||||
let store_dir = store.get_store_dir();
|
|
||||||
crate::nix_hash::make_store_path(store_dir, &ty, &hash_hex, &name)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[deno_core::op2]
|
|
||||||
#[string]
|
|
||||||
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]
|
|
||||||
#[string]
|
|
||||||
fn op_output_path_name(#[string] drv_name: String, #[string] output_name: String) -> String {
|
|
||||||
crate::nix_hash::output_path_name(&drv_name, &output_name)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[deno_core::op2]
|
|
||||||
#[string]
|
|
||||||
fn op_make_fixed_output_path<Ctx: RuntimeContext>(
|
|
||||||
state: &mut OpState,
|
|
||||||
#[string] hash_algo: String,
|
|
||||||
#[string] hash: String,
|
|
||||||
#[string] hash_mode: String,
|
|
||||||
#[string] name: String,
|
|
||||||
) -> String {
|
|
||||||
use sha2::{Digest, Sha256};
|
|
||||||
|
|
||||||
let ctx: &Ctx = state.get_ctx();
|
|
||||||
let store = ctx.get_store();
|
|
||||||
let store_dir = store.get_store_dir();
|
|
||||||
|
|
||||||
if hash_algo == "sha256" && hash_mode == "recursive" {
|
|
||||||
crate::nix_hash::make_store_path(store_dir, "source", &hash, &name)
|
|
||||||
} else {
|
|
||||||
let prefix = if hash_mode == "recursive" { "r:" } else { "" };
|
|
||||||
let inner_input = format!("fixed:out:{}{}:{}:", prefix, hash_algo, hash);
|
|
||||||
let mut hasher = Sha256::new();
|
|
||||||
hasher.update(inner_input.as_bytes());
|
|
||||||
let inner_hash = hex::encode(hasher.finalize());
|
|
||||||
|
|
||||||
crate::nix_hash::make_store_path(store_dir, "output:out", &inner_hash, &name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[deno_core::op2]
|
|
||||||
#[string]
|
|
||||||
fn op_add_path<Ctx: RuntimeContext>(
|
|
||||||
state: &mut OpState,
|
|
||||||
#[string] path: String,
|
|
||||||
#[string] name: Option<String>,
|
|
||||||
recursive: bool,
|
|
||||||
#[string] sha256: Option<String>,
|
|
||||||
) -> std::result::Result<String, NixError> {
|
|
||||||
use sha2::{Digest, Sha256};
|
|
||||||
use std::fs;
|
|
||||||
use std::path::Path;
|
|
||||||
|
|
||||||
let path_obj = Path::new(&path);
|
|
||||||
|
|
||||||
if !path_obj.exists() {
|
|
||||||
return Err(NixError::from(format!("path '{}' does not exist", path)));
|
|
||||||
}
|
|
||||||
|
|
||||||
let computed_name = name.unwrap_or_else(|| {
|
|
||||||
path_obj
|
|
||||||
.file_name()
|
|
||||||
.and_then(|n| n.to_str())
|
|
||||||
.unwrap_or("source")
|
|
||||||
.to_string()
|
|
||||||
});
|
|
||||||
|
|
||||||
let computed_hash = if recursive {
|
|
||||||
crate::nar::compute_nar_hash(path_obj)
|
|
||||||
.map_err(|e| NixError::from(format!("failed to compute NAR hash: {}", e)))?
|
|
||||||
} else {
|
|
||||||
if !path_obj.is_file() {
|
|
||||||
return Err(NixError::from(
|
|
||||||
"when 'recursive' is false, path must be a regular file",
|
|
||||||
));
|
|
||||||
}
|
|
||||||
let contents = fs::read(path_obj)
|
|
||||||
.map_err(|e| NixError::from(format!("failed to read '{}': {}", path, e)))?;
|
|
||||||
|
|
||||||
let mut hasher = Sha256::new();
|
|
||||||
hasher.update(&contents);
|
|
||||||
hex::encode(hasher.finalize())
|
|
||||||
};
|
|
||||||
|
|
||||||
if let Some(expected_hash) = sha256 {
|
|
||||||
let expected_hex = crate::nix_hash::decode_hash_to_hex(&expected_hash)
|
|
||||||
.ok_or_else(|| NixError::from(format!("invalid hash format: {}", expected_hash)))?;
|
|
||||||
if computed_hash != expected_hex {
|
|
||||||
return Err(NixError::from(format!(
|
|
||||||
"hash mismatch for path '{}': expected {}, got {}",
|
|
||||||
path, expected_hex, computed_hash
|
|
||||||
)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let ctx: &Ctx = state.get_ctx();
|
|
||||||
let store = ctx.get_store();
|
|
||||||
|
|
||||||
let store_path = if recursive {
|
|
||||||
store
|
|
||||||
.add_to_store_from_path(&computed_name, path_obj, vec![])
|
|
||||||
.map_err(|e| NixError::from(format!("failed to add path to store: {}", e)))?
|
|
||||||
} else {
|
|
||||||
let contents = fs::read(path_obj)
|
|
||||||
.map_err(|e| NixError::from(format!("failed to read '{}': {}", path, e)))?;
|
|
||||||
store
|
|
||||||
.add_to_store(&computed_name, &contents, false, vec![])
|
|
||||||
.map_err(|e| NixError::from(format!("failed to add to store: {}", e)))?
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(store_path)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[deno_core::op2]
|
|
||||||
#[string]
|
|
||||||
fn op_store_path<Ctx: RuntimeContext>(
|
|
||||||
state: &mut OpState,
|
|
||||||
#[string] path: String,
|
|
||||||
) -> std::result::Result<String, NixError> {
|
|
||||||
use crate::store::validate_store_path;
|
|
||||||
|
|
||||||
let ctx: &Ctx = state.get_ctx();
|
|
||||||
let store = ctx.get_store();
|
|
||||||
let store_dir = store.get_store_dir();
|
|
||||||
|
|
||||||
validate_store_path(store_dir, &path).map_err(|e| NixError::from(e.to_string()))?;
|
|
||||||
|
|
||||||
store
|
|
||||||
.ensure_path(&path)
|
|
||||||
.map_err(|e| NixError::from(e.to_string()))?;
|
|
||||||
|
|
||||||
Ok(path)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[deno_core::op2]
|
|
||||||
#[string]
|
|
||||||
fn op_to_file<Ctx: RuntimeContext>(
|
|
||||||
state: &mut OpState,
|
|
||||||
#[string] name: String,
|
|
||||||
#[string] contents: String,
|
|
||||||
#[serde] references: Vec<String>,
|
|
||||||
) -> std::result::Result<String, NixError> {
|
|
||||||
let ctx: &Ctx = state.get_ctx();
|
|
||||||
let store = ctx.get_store();
|
|
||||||
let store_path = store
|
|
||||||
.add_text_to_store(&name, &contents, references)
|
|
||||||
.map_err(|e| NixError::from(format!("builtins.toFile failed: {}", e)))?;
|
|
||||||
|
|
||||||
Ok(store_path)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[deno_core::op2]
|
|
||||||
#[string]
|
|
||||||
fn op_copy_path_to_store<Ctx: RuntimeContext>(
|
|
||||||
state: &mut OpState,
|
|
||||||
#[string] path: String,
|
|
||||||
) -> std::result::Result<String, NixError> {
|
|
||||||
use std::path::Path;
|
|
||||||
|
|
||||||
let path_obj = Path::new(&path);
|
|
||||||
|
|
||||||
if !path_obj.exists() {
|
|
||||||
return Err(NixError::from(format!("path '{}' does not exist", path)));
|
|
||||||
}
|
|
||||||
|
|
||||||
let name = path_obj
|
|
||||||
.file_name()
|
|
||||||
.and_then(|n| n.to_str())
|
|
||||||
.unwrap_or("source")
|
|
||||||
.to_string();
|
|
||||||
|
|
||||||
let ctx: &Ctx = state.get_ctx();
|
|
||||||
let store = ctx.get_store();
|
|
||||||
let store_path = store
|
|
||||||
.add_to_store_from_path(&name, path_obj, vec![])
|
|
||||||
.map_err(|e| NixError::from(format!("failed to copy path to store: {}", e)))?;
|
|
||||||
|
|
||||||
Ok(store_path)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[deno_core::op2]
|
|
||||||
#[string]
|
|
||||||
fn op_get_env(#[string] key: String) -> std::result::Result<String, NixError> {
|
|
||||||
match std::env::var(key) {
|
|
||||||
Ok(val) => Ok(val),
|
|
||||||
Err(std::env::VarError::NotPresent) => Ok("".into()),
|
|
||||||
Err(err) => Err(format!("Failed to read env var: {err}").into()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[deno_core::op2]
|
|
||||||
#[serde]
|
|
||||||
fn op_walk_dir(#[string] path: String) -> std::result::Result<Vec<(String, String)>, NixError> {
|
|
||||||
fn walk_recursive(
|
|
||||||
base: &Path,
|
|
||||||
current: &Path,
|
|
||||||
results: &mut Vec<(String, String)>,
|
|
||||||
) -> std::result::Result<(), NixError> {
|
|
||||||
let entries = std::fs::read_dir(current)
|
|
||||||
.map_err(|e| NixError::from(format!("failed to read directory: {}", e)))?;
|
|
||||||
|
|
||||||
for entry in entries {
|
|
||||||
let entry =
|
|
||||||
entry.map_err(|e| NixError::from(format!("failed to read entry: {}", e)))?;
|
|
||||||
let path = entry.path();
|
|
||||||
let rel_path = path
|
|
||||||
.strip_prefix(base)
|
|
||||||
.map_err(|e| NixError::from(format!("failed to get relative path: {}", e)))?
|
|
||||||
.to_string_lossy()
|
|
||||||
.to_string();
|
|
||||||
|
|
||||||
let file_type = entry
|
|
||||||
.file_type()
|
|
||||||
.map_err(|e| NixError::from(format!("failed to get file type: {}", e)))?;
|
|
||||||
|
|
||||||
let type_str = if file_type.is_dir() {
|
|
||||||
"directory"
|
|
||||||
} else if file_type.is_symlink() {
|
|
||||||
"symlink"
|
|
||||||
} else {
|
|
||||||
"regular"
|
|
||||||
};
|
|
||||||
|
|
||||||
results.push((rel_path.clone(), type_str.to_string()));
|
|
||||||
|
|
||||||
if file_type.is_dir() {
|
|
||||||
walk_recursive(base, &path, results)?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
let path = Path::new(&path);
|
|
||||||
if !path.is_dir() {
|
|
||||||
return Err(NixError::from(format!(
|
|
||||||
"{} is not a directory",
|
|
||||||
path.display()
|
|
||||||
)));
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut results = Vec::new();
|
|
||||||
walk_recursive(path, path, &mut results)?;
|
|
||||||
Ok(results)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[deno_core::op2]
|
|
||||||
#[string]
|
|
||||||
fn op_add_filtered_path<Ctx: RuntimeContext>(
|
|
||||||
state: &mut OpState,
|
|
||||||
#[string] src_path: String,
|
|
||||||
#[string] name: Option<String>,
|
|
||||||
recursive: bool,
|
|
||||||
#[string] sha256: Option<String>,
|
|
||||||
#[serde] include_paths: Vec<String>,
|
|
||||||
) -> std::result::Result<String, NixError> {
|
|
||||||
use sha2::{Digest, Sha256};
|
|
||||||
use std::fs;
|
|
||||||
|
|
||||||
let src = Path::new(&src_path);
|
|
||||||
if !src.exists() {
|
|
||||||
return Err(NixError::from(format!(
|
|
||||||
"path '{}' does not exist",
|
|
||||||
src_path
|
|
||||||
)));
|
|
||||||
}
|
|
||||||
|
|
||||||
let computed_name = name.unwrap_or_else(|| {
|
|
||||||
src.file_name()
|
|
||||||
.and_then(|n| n.to_str())
|
|
||||||
.unwrap_or("source")
|
|
||||||
.to_string()
|
|
||||||
});
|
|
||||||
|
|
||||||
let temp_dir = tempfile::tempdir()
|
|
||||||
.map_err(|e| NixError::from(format!("failed to create temp dir: {}", e)))?;
|
|
||||||
let dest = temp_dir.path().join(&computed_name);
|
|
||||||
|
|
||||||
fs::create_dir_all(&dest)
|
|
||||||
.map_err(|e| NixError::from(format!("failed to create dest dir: {}", e)))?;
|
|
||||||
|
|
||||||
for rel_path in &include_paths {
|
|
||||||
let src_file = src.join(rel_path);
|
|
||||||
let dest_file = dest.join(rel_path);
|
|
||||||
|
|
||||||
if let Some(parent) = dest_file.parent() {
|
|
||||||
fs::create_dir_all(parent)
|
|
||||||
.map_err(|e| NixError::from(format!("failed to create dir: {}", e)))?;
|
|
||||||
}
|
|
||||||
|
|
||||||
let metadata = fs::symlink_metadata(&src_file)
|
|
||||||
.map_err(|e| NixError::from(format!("failed to read metadata: {}", e)))?;
|
|
||||||
|
|
||||||
if metadata.is_symlink() {
|
|
||||||
let target = fs::read_link(&src_file)
|
|
||||||
.map_err(|e| NixError::from(format!("failed to read symlink: {}", e)))?;
|
|
||||||
#[cfg(unix)]
|
|
||||||
std::os::unix::fs::symlink(&target, &dest_file)
|
|
||||||
.map_err(|e| NixError::from(format!("failed to create symlink: {}", e)))?;
|
|
||||||
#[cfg(not(unix))]
|
|
||||||
return Err(NixError::from("symlinks not supported on this platform"));
|
|
||||||
} else if metadata.is_dir() {
|
|
||||||
fs::create_dir_all(&dest_file)
|
|
||||||
.map_err(|e| NixError::from(format!("failed to create dir: {}", e)))?;
|
|
||||||
} else {
|
|
||||||
fs::copy(&src_file, &dest_file)
|
|
||||||
.map_err(|e| NixError::from(format!("failed to copy file: {}", e)))?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let computed_hash = if recursive {
|
|
||||||
crate::nar::compute_nar_hash(&dest)
|
|
||||||
.map_err(|e| NixError::from(format!("failed to compute NAR hash: {}", e)))?
|
|
||||||
} else {
|
|
||||||
if !dest.is_file() {
|
|
||||||
return Err(NixError::from(
|
|
||||||
"when 'recursive' is false, path must be a regular file",
|
|
||||||
));
|
|
||||||
}
|
|
||||||
let contents =
|
|
||||||
fs::read(&dest).map_err(|e| NixError::from(format!("failed to read file: {}", e)))?;
|
|
||||||
let mut hasher = Sha256::new();
|
|
||||||
hasher.update(&contents);
|
|
||||||
hex::encode(hasher.finalize())
|
|
||||||
};
|
|
||||||
|
|
||||||
if let Some(expected_hash) = sha256 {
|
|
||||||
let expected_hex = crate::nix_hash::decode_hash_to_hex(&expected_hash)
|
|
||||||
.ok_or_else(|| NixError::from(format!("invalid hash format: {}", expected_hash)))?;
|
|
||||||
if computed_hash != expected_hex {
|
|
||||||
return Err(NixError::from(format!(
|
|
||||||
"hash mismatch for path '{}': expected {}, got {}",
|
|
||||||
src_path, expected_hex, computed_hash
|
|
||||||
)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let ctx: &Ctx = state.get_ctx();
|
|
||||||
let store = ctx.get_store();
|
|
||||||
|
|
||||||
let store_path = store
|
|
||||||
.add_to_store_from_path(&computed_name, &dest, vec![])
|
|
||||||
.map_err(|e| NixError::from(format!("failed to add path to store: {}", e)))?;
|
|
||||||
|
|
||||||
Ok(store_path)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) struct Runtime<Ctx: RuntimeContext> {
|
pub(crate) struct Runtime<Ctx: RuntimeContext> {
|
||||||
js_runtime: JsRuntime,
|
js_runtime: JsRuntime,
|
||||||
@@ -727,11 +115,16 @@ pub(crate) struct Runtime<Ctx: RuntimeContext> {
|
|||||||
|
|
||||||
impl<Ctx: RuntimeContext> Runtime<Ctx> {
|
impl<Ctx: RuntimeContext> Runtime<Ctx> {
|
||||||
pub(crate) fn new() -> Result<Self> {
|
pub(crate) fn new() -> Result<Self> {
|
||||||
|
use std::sync::Once;
|
||||||
|
|
||||||
// Initialize V8 once
|
// Initialize V8 once
|
||||||
static INIT: Once = Once::new();
|
static INIT: Once = Once::new();
|
||||||
INIT.call_once(|| {
|
INIT.call_once(|| {
|
||||||
// First flag is always not recognized
|
// First flag is always not recognized
|
||||||
assert_eq!(deno_core::v8_set_flags(vec!["".into(), format!("--stack-size={}", 8 * 1024)]), [""]);
|
assert_eq!(
|
||||||
|
deno_core::v8_set_flags(vec!["".into(), format!("--stack-size={}", 8 * 1024)]),
|
||||||
|
[""]
|
||||||
|
);
|
||||||
JsRuntime::init_platform(
|
JsRuntime::init_platform(
|
||||||
Some(v8::new_default_platform(0, false).make_shared()),
|
Some(v8::new_default_platform(0, false).make_shared()),
|
||||||
false,
|
false,
|
||||||
@@ -773,7 +166,6 @@ impl<Ctx: RuntimeContext> Runtime<Ctx> {
|
|||||||
.js_runtime
|
.js_runtime
|
||||||
.execute_script("<eval>", script)
|
.execute_script("<eval>", script)
|
||||||
.map_err(|error| {
|
.map_err(|error| {
|
||||||
// Get current source from Context
|
|
||||||
let op_state = self.js_runtime.op_state();
|
let op_state = self.js_runtime.op_state();
|
||||||
let op_state_borrow = op_state.borrow();
|
let op_state_borrow = op_state.borrow();
|
||||||
let ctx: &Ctx = op_state_borrow.get_ctx();
|
let ctx: &Ctx = op_state_borrow.get_ctx();
|
||||||
|
|||||||
641
nix-js/src/runtime/ops.rs
Normal file
641
nix-js/src/runtime/ops.rs
Normal file
@@ -0,0 +1,641 @@
|
|||||||
|
use std::path::{Component, Path, PathBuf};
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use deno_core::OpState;
|
||||||
|
use rust_embed::Embed;
|
||||||
|
|
||||||
|
use crate::error::Source;
|
||||||
|
|
||||||
|
use super::{NixRuntimeError, OpStateExt, RuntimeContext};
|
||||||
|
|
||||||
|
#[derive(Embed)]
|
||||||
|
#[folder = "src/runtime/corepkgs"]
|
||||||
|
pub(crate) struct CorePkgs;
|
||||||
|
|
||||||
|
#[deno_core::op2]
|
||||||
|
#[string]
|
||||||
|
pub(super) fn op_import<Ctx: RuntimeContext>(
|
||||||
|
state: &mut OpState,
|
||||||
|
#[string] path: String,
|
||||||
|
) -> std::result::Result<String, NixRuntimeError> {
|
||||||
|
let _span = tracing::info_span!("op_import", path = %path).entered();
|
||||||
|
let ctx: &mut Ctx = state.get_ctx_mut();
|
||||||
|
|
||||||
|
// FIXME: special path type
|
||||||
|
if path.starts_with("<nix/") && path.ends_with(">") {
|
||||||
|
let corepkg_name = &path[5..path.len() - 1];
|
||||||
|
if let Some(file) = CorePkgs::get(corepkg_name) {
|
||||||
|
tracing::info!("Importing corepkg: {}", corepkg_name);
|
||||||
|
let source = Source {
|
||||||
|
ty: crate::error::SourceType::Eval(Arc::new(ctx.get_current_dir().to_path_buf())),
|
||||||
|
src: str::from_utf8(&file.data)
|
||||||
|
.expect("corrupted corepkgs file")
|
||||||
|
.into(),
|
||||||
|
};
|
||||||
|
ctx.add_source(source.clone());
|
||||||
|
return Ok(ctx.compile_code(source).map_err(|err| err.to_string())?);
|
||||||
|
} else {
|
||||||
|
return Err(format!("Corepkg not found: {}", corepkg_name).into());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let current_dir = ctx.get_current_dir();
|
||||||
|
let mut absolute_path = current_dir.join(&path);
|
||||||
|
// Do NOT resolve symlinks (eval-okay-symlink-resolution)
|
||||||
|
// TODO: is this correct?
|
||||||
|
// .canonicalize()
|
||||||
|
// .map_err(|e| format!("Failed to resolve path {}: {}", path, e))?;
|
||||||
|
if absolute_path.is_dir() {
|
||||||
|
absolute_path.push("default.nix")
|
||||||
|
}
|
||||||
|
|
||||||
|
tracing::info!("Importing file: {}", absolute_path.display());
|
||||||
|
|
||||||
|
let source = Source::new_file(absolute_path.clone())
|
||||||
|
.map_err(|e| format!("Failed to read {}: {}", absolute_path.display(), e))?;
|
||||||
|
|
||||||
|
tracing::debug!("Compiling file");
|
||||||
|
ctx.add_source(source.clone());
|
||||||
|
|
||||||
|
Ok(ctx.compile_code(source).map_err(|err| err.to_string())?)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[deno_core::op2]
|
||||||
|
#[string]
|
||||||
|
pub(super) fn op_read_file(#[string] path: String) -> std::result::Result<String, NixRuntimeError> {
|
||||||
|
Ok(std::fs::read_to_string(&path).map_err(|e| format!("Failed to read {}: {}", path, e))?)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[deno_core::op2(fast)]
|
||||||
|
pub(super) fn op_path_exists(#[string] path: String) -> bool {
|
||||||
|
let must_be_dir = path.ends_with('/') || path.ends_with("/.");
|
||||||
|
let p = Path::new(&path);
|
||||||
|
|
||||||
|
if must_be_dir {
|
||||||
|
match std::fs::metadata(p) {
|
||||||
|
Ok(m) => m.is_dir(),
|
||||||
|
Err(_) => false,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
std::fs::symlink_metadata(p).is_ok()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[deno_core::op2]
|
||||||
|
#[string]
|
||||||
|
pub(super) fn op_read_file_type(
|
||||||
|
#[string] path: String,
|
||||||
|
) -> std::result::Result<String, NixRuntimeError> {
|
||||||
|
let path = Path::new(&path);
|
||||||
|
let metadata = std::fs::symlink_metadata(path)
|
||||||
|
.map_err(|e| format!("Failed to read file type for {}: {}", path.display(), e))?;
|
||||||
|
|
||||||
|
let file_type = metadata.file_type();
|
||||||
|
let type_str = if file_type.is_dir() {
|
||||||
|
"directory"
|
||||||
|
} else if file_type.is_symlink() {
|
||||||
|
"symlink"
|
||||||
|
} else if file_type.is_file() {
|
||||||
|
"regular"
|
||||||
|
} else {
|
||||||
|
"unknown"
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(type_str.to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[deno_core::op2]
|
||||||
|
#[serde]
|
||||||
|
pub(super) fn op_read_dir(
|
||||||
|
#[string] path: String,
|
||||||
|
) -> std::result::Result<std::collections::HashMap<String, String>, NixRuntimeError> {
|
||||||
|
let path = Path::new(&path);
|
||||||
|
|
||||||
|
if !path.is_dir() {
|
||||||
|
return Err(format!("{} is not a directory", path.display()).into());
|
||||||
|
}
|
||||||
|
|
||||||
|
let entries = std::fs::read_dir(path)
|
||||||
|
.map_err(|e| format!("Failed to read directory {}: {}", path.display(), e))?;
|
||||||
|
|
||||||
|
let mut result = std::collections::HashMap::new();
|
||||||
|
|
||||||
|
for entry in entries {
|
||||||
|
let entry = entry.map_err(|e| format!("Failed to read directory entry: {}", e))?;
|
||||||
|
let file_name = entry.file_name().to_string_lossy().to_string();
|
||||||
|
|
||||||
|
let file_type = entry.file_type().map_err(|e| {
|
||||||
|
format!(
|
||||||
|
"Failed to read file type for {}: {}",
|
||||||
|
entry.path().display(),
|
||||||
|
e
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let type_str = if file_type.is_dir() {
|
||||||
|
"directory"
|
||||||
|
} else if file_type.is_symlink() {
|
||||||
|
"symlink"
|
||||||
|
} else if file_type.is_file() {
|
||||||
|
"regular"
|
||||||
|
} else {
|
||||||
|
"unknown"
|
||||||
|
};
|
||||||
|
|
||||||
|
result.insert(file_name, type_str.to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[deno_core::op2]
|
||||||
|
#[string]
|
||||||
|
pub(super) fn op_resolve_path(
|
||||||
|
#[string] current_dir: String,
|
||||||
|
#[string] path: String,
|
||||||
|
) -> std::result::Result<String, NixRuntimeError> {
|
||||||
|
let _span = tracing::debug_span!("op_resolve_path").entered();
|
||||||
|
tracing::debug!(current_dir, path);
|
||||||
|
|
||||||
|
// If already absolute, return as-is
|
||||||
|
if path.starts_with('/') {
|
||||||
|
return Ok(path);
|
||||||
|
}
|
||||||
|
// Resolve relative path against current file directory (or CWD)
|
||||||
|
let current_dir = if !path.starts_with("~/") {
|
||||||
|
let mut dir = PathBuf::from(current_dir);
|
||||||
|
dir.push(path);
|
||||||
|
dir
|
||||||
|
} else {
|
||||||
|
let mut dir = std::env::home_dir().ok_or("home dir not defined")?;
|
||||||
|
dir.push(&path[2..]);
|
||||||
|
dir
|
||||||
|
};
|
||||||
|
let mut normalized = PathBuf::new();
|
||||||
|
for component in current_dir.components() {
|
||||||
|
match component {
|
||||||
|
Component::Prefix(p) => normalized.push(p.as_os_str()),
|
||||||
|
Component::RootDir => normalized.push("/"),
|
||||||
|
Component::CurDir => {}
|
||||||
|
Component::ParentDir => {
|
||||||
|
normalized.pop();
|
||||||
|
}
|
||||||
|
Component::Normal(c) => normalized.push(c),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tracing::debug!(normalized = normalized.display().to_string());
|
||||||
|
Ok(normalized.to_string_lossy().to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[deno_core::op2]
|
||||||
|
#[string]
|
||||||
|
pub(super) fn op_sha256_hex(#[string] data: String) -> String {
|
||||||
|
crate::nix_hash::sha256_hex(&data)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[deno_core::op2]
|
||||||
|
#[string]
|
||||||
|
pub(super) fn op_make_placeholder(#[string] output: String) -> String {
|
||||||
|
use sha2::{Digest, Sha256};
|
||||||
|
let input = format!("nix-output:{}", output);
|
||||||
|
let mut hasher = Sha256::new();
|
||||||
|
hasher.update(input.as_bytes());
|
||||||
|
let hash: [u8; 32] = hasher.finalize().into();
|
||||||
|
let encoded = crate::nix_hash::nix_base32_encode(&hash);
|
||||||
|
format!("/{}", encoded)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[deno_core::op2]
|
||||||
|
#[serde]
|
||||||
|
pub(super) fn op_decode_span<Ctx: RuntimeContext>(
|
||||||
|
state: &mut OpState,
|
||||||
|
#[string] span_str: String,
|
||||||
|
) -> std::result::Result<serde_json::Value, NixRuntimeError> {
|
||||||
|
let parts: Vec<&str> = span_str.split(':').collect();
|
||||||
|
if parts.len() != 3 {
|
||||||
|
return Ok(serde_json::json!({
|
||||||
|
"file": serde_json::Value::Null,
|
||||||
|
"line": serde_json::Value::Null,
|
||||||
|
"column": serde_json::Value::Null
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
let source_id: usize = parts[0].parse().map_err(|_| "Invalid source ID")?;
|
||||||
|
let start: u32 = parts[1].parse().map_err(|_| "Invalid start offset")?;
|
||||||
|
|
||||||
|
let ctx: &Ctx = state.get_ctx();
|
||||||
|
let source = ctx.get_source(source_id);
|
||||||
|
let content = &source.src;
|
||||||
|
|
||||||
|
let (line, column) = byte_offset_to_line_col(content, start as usize);
|
||||||
|
|
||||||
|
Ok(serde_json::json!({
|
||||||
|
"file": source.get_name(),
|
||||||
|
"line": line,
|
||||||
|
"column": column
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn byte_offset_to_line_col(content: &str, offset: usize) -> (u32, u32) {
|
||||||
|
let mut line = 1u32;
|
||||||
|
let mut col = 1u32;
|
||||||
|
|
||||||
|
for (idx, ch) in content.char_indices() {
|
||||||
|
if idx >= offset {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if ch == '\n' {
|
||||||
|
line += 1;
|
||||||
|
col = 1;
|
||||||
|
} else {
|
||||||
|
col += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
(line, col)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[deno_core::op2]
|
||||||
|
#[string]
|
||||||
|
pub(super) fn op_make_store_path<Ctx: RuntimeContext>(
|
||||||
|
state: &mut OpState,
|
||||||
|
#[string] ty: String,
|
||||||
|
#[string] hash_hex: String,
|
||||||
|
#[string] name: String,
|
||||||
|
) -> String {
|
||||||
|
let ctx: &Ctx = state.get_ctx();
|
||||||
|
let store = ctx.get_store();
|
||||||
|
let store_dir = store.get_store_dir();
|
||||||
|
crate::nix_hash::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]
|
||||||
|
#[string]
|
||||||
|
pub(super) fn op_output_path_name(
|
||||||
|
#[string] drv_name: String,
|
||||||
|
#[string] output_name: String,
|
||||||
|
) -> String {
|
||||||
|
crate::nix_hash::output_path_name(&drv_name, &output_name)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[deno_core::op2]
|
||||||
|
#[string]
|
||||||
|
pub(super) fn op_make_fixed_output_path<Ctx: RuntimeContext>(
|
||||||
|
state: &mut OpState,
|
||||||
|
#[string] hash_algo: String,
|
||||||
|
#[string] hash: String,
|
||||||
|
#[string] hash_mode: String,
|
||||||
|
#[string] name: String,
|
||||||
|
) -> String {
|
||||||
|
use sha2::{Digest, Sha256};
|
||||||
|
|
||||||
|
let ctx: &Ctx = state.get_ctx();
|
||||||
|
let store = ctx.get_store();
|
||||||
|
let store_dir = store.get_store_dir();
|
||||||
|
|
||||||
|
if hash_algo == "sha256" && hash_mode == "recursive" {
|
||||||
|
crate::nix_hash::make_store_path(store_dir, "source", &hash, &name)
|
||||||
|
} else {
|
||||||
|
let prefix = if hash_mode == "recursive" { "r:" } else { "" };
|
||||||
|
let inner_input = format!("fixed:out:{}{}:{}:", prefix, hash_algo, hash);
|
||||||
|
let mut hasher = Sha256::new();
|
||||||
|
hasher.update(inner_input.as_bytes());
|
||||||
|
let inner_hash = hex::encode(hasher.finalize());
|
||||||
|
|
||||||
|
crate::nix_hash::make_store_path(store_dir, "output:out", &inner_hash, &name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[deno_core::op2]
|
||||||
|
#[string]
|
||||||
|
pub(super) fn op_add_path<Ctx: RuntimeContext>(
|
||||||
|
state: &mut OpState,
|
||||||
|
#[string] path: String,
|
||||||
|
#[string] name: Option<String>,
|
||||||
|
recursive: bool,
|
||||||
|
#[string] sha256: Option<String>,
|
||||||
|
) -> std::result::Result<String, NixRuntimeError> {
|
||||||
|
use sha2::{Digest, Sha256};
|
||||||
|
use std::fs;
|
||||||
|
use std::path::Path;
|
||||||
|
|
||||||
|
let path_obj = Path::new(&path);
|
||||||
|
|
||||||
|
if !path_obj.exists() {
|
||||||
|
return Err(NixRuntimeError::from(format!(
|
||||||
|
"path '{}' does not exist",
|
||||||
|
path
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
|
||||||
|
let computed_name = name.unwrap_or_else(|| {
|
||||||
|
path_obj
|
||||||
|
.file_name()
|
||||||
|
.and_then(|n| n.to_str())
|
||||||
|
.unwrap_or("source")
|
||||||
|
.to_string()
|
||||||
|
});
|
||||||
|
|
||||||
|
let computed_hash = if recursive {
|
||||||
|
crate::nar::compute_nar_hash(path_obj)
|
||||||
|
.map_err(|e| NixRuntimeError::from(format!("failed to compute NAR hash: {}", e)))?
|
||||||
|
} else {
|
||||||
|
if !path_obj.is_file() {
|
||||||
|
return Err(NixRuntimeError::from(
|
||||||
|
"when 'recursive' is false, path must be a regular file",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
let contents = fs::read(path_obj)
|
||||||
|
.map_err(|e| NixRuntimeError::from(format!("failed to read '{}': {}", path, e)))?;
|
||||||
|
|
||||||
|
let mut hasher = Sha256::new();
|
||||||
|
hasher.update(&contents);
|
||||||
|
hex::encode(hasher.finalize())
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(expected_hash) = sha256 {
|
||||||
|
let expected_hex =
|
||||||
|
crate::nix_hash::decode_hash_to_hex(&expected_hash).ok_or_else(|| {
|
||||||
|
NixRuntimeError::from(format!("invalid hash format: {}", expected_hash))
|
||||||
|
})?;
|
||||||
|
if computed_hash != expected_hex {
|
||||||
|
return Err(NixRuntimeError::from(format!(
|
||||||
|
"hash mismatch for path '{}': expected {}, got {}",
|
||||||
|
path, expected_hex, computed_hash
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let ctx: &Ctx = state.get_ctx();
|
||||||
|
let store = ctx.get_store();
|
||||||
|
|
||||||
|
let store_path = if recursive {
|
||||||
|
store
|
||||||
|
.add_to_store_from_path(&computed_name, path_obj, vec![])
|
||||||
|
.map_err(|e| NixRuntimeError::from(format!("failed to add path to store: {}", e)))?
|
||||||
|
} else {
|
||||||
|
let contents = fs::read(path_obj)
|
||||||
|
.map_err(|e| NixRuntimeError::from(format!("failed to read '{}': {}", path, e)))?;
|
||||||
|
store
|
||||||
|
.add_to_store(&computed_name, &contents, false, vec![])
|
||||||
|
.map_err(|e| NixRuntimeError::from(format!("failed to add to store: {}", e)))?
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(store_path)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[deno_core::op2]
|
||||||
|
#[string]
|
||||||
|
pub(super) fn op_store_path<Ctx: RuntimeContext>(
|
||||||
|
state: &mut OpState,
|
||||||
|
#[string] path: String,
|
||||||
|
) -> std::result::Result<String, NixRuntimeError> {
|
||||||
|
use crate::store::validate_store_path;
|
||||||
|
|
||||||
|
let ctx: &Ctx = state.get_ctx();
|
||||||
|
let store = ctx.get_store();
|
||||||
|
let store_dir = store.get_store_dir();
|
||||||
|
|
||||||
|
validate_store_path(store_dir, &path).map_err(|e| NixRuntimeError::from(e.to_string()))?;
|
||||||
|
|
||||||
|
store
|
||||||
|
.ensure_path(&path)
|
||||||
|
.map_err(|e| NixRuntimeError::from(e.to_string()))?;
|
||||||
|
|
||||||
|
Ok(path)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[deno_core::op2]
|
||||||
|
#[string]
|
||||||
|
pub(super) fn op_to_file<Ctx: RuntimeContext>(
|
||||||
|
state: &mut OpState,
|
||||||
|
#[string] name: String,
|
||||||
|
#[string] contents: String,
|
||||||
|
#[serde] references: Vec<String>,
|
||||||
|
) -> std::result::Result<String, NixRuntimeError> {
|
||||||
|
let ctx: &Ctx = state.get_ctx();
|
||||||
|
let store = ctx.get_store();
|
||||||
|
let store_path = store
|
||||||
|
.add_text_to_store(&name, &contents, references)
|
||||||
|
.map_err(|e| NixRuntimeError::from(format!("builtins.toFile failed: {}", e)))?;
|
||||||
|
|
||||||
|
Ok(store_path)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[deno_core::op2]
|
||||||
|
#[string]
|
||||||
|
pub(super) fn op_copy_path_to_store<Ctx: RuntimeContext>(
|
||||||
|
state: &mut OpState,
|
||||||
|
#[string] path: String,
|
||||||
|
) -> std::result::Result<String, NixRuntimeError> {
|
||||||
|
use std::path::Path;
|
||||||
|
|
||||||
|
let path_obj = Path::new(&path);
|
||||||
|
|
||||||
|
if !path_obj.exists() {
|
||||||
|
return Err(NixRuntimeError::from(format!(
|
||||||
|
"path '{}' does not exist",
|
||||||
|
path
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
|
||||||
|
let name = path_obj
|
||||||
|
.file_name()
|
||||||
|
.and_then(|n| n.to_str())
|
||||||
|
.unwrap_or("source")
|
||||||
|
.to_string();
|
||||||
|
|
||||||
|
let ctx: &Ctx = state.get_ctx();
|
||||||
|
let store = ctx.get_store();
|
||||||
|
let store_path = store
|
||||||
|
.add_to_store_from_path(&name, path_obj, vec![])
|
||||||
|
.map_err(|e| NixRuntimeError::from(format!("failed to copy path to store: {}", e)))?;
|
||||||
|
|
||||||
|
Ok(store_path)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[deno_core::op2]
|
||||||
|
#[string]
|
||||||
|
pub(super) fn op_get_env(#[string] key: String) -> std::result::Result<String, NixRuntimeError> {
|
||||||
|
match std::env::var(key) {
|
||||||
|
Ok(val) => Ok(val),
|
||||||
|
Err(std::env::VarError::NotPresent) => Ok("".into()),
|
||||||
|
Err(err) => Err(format!("Failed to read env var: {err}").into()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[deno_core::op2]
|
||||||
|
#[serde]
|
||||||
|
pub(super) fn op_walk_dir(
|
||||||
|
#[string] path: String,
|
||||||
|
) -> std::result::Result<Vec<(String, String)>, NixRuntimeError> {
|
||||||
|
fn walk_recursive(
|
||||||
|
base: &Path,
|
||||||
|
current: &Path,
|
||||||
|
results: &mut Vec<(String, String)>,
|
||||||
|
) -> std::result::Result<(), NixRuntimeError> {
|
||||||
|
let entries = std::fs::read_dir(current)
|
||||||
|
.map_err(|e| NixRuntimeError::from(format!("failed to read directory: {}", e)))?;
|
||||||
|
|
||||||
|
for entry in entries {
|
||||||
|
let entry =
|
||||||
|
entry.map_err(|e| NixRuntimeError::from(format!("failed to read entry: {}", e)))?;
|
||||||
|
let path = entry.path();
|
||||||
|
let rel_path = path
|
||||||
|
.strip_prefix(base)
|
||||||
|
.map_err(|e| NixRuntimeError::from(format!("failed to get relative path: {}", e)))?
|
||||||
|
.to_string_lossy()
|
||||||
|
.to_string();
|
||||||
|
|
||||||
|
let file_type = entry
|
||||||
|
.file_type()
|
||||||
|
.map_err(|e| NixRuntimeError::from(format!("failed to get file type: {}", e)))?;
|
||||||
|
|
||||||
|
let type_str = if file_type.is_dir() {
|
||||||
|
"directory"
|
||||||
|
} else if file_type.is_symlink() {
|
||||||
|
"symlink"
|
||||||
|
} else {
|
||||||
|
"regular"
|
||||||
|
};
|
||||||
|
|
||||||
|
results.push((rel_path.clone(), type_str.to_string()));
|
||||||
|
|
||||||
|
if file_type.is_dir() {
|
||||||
|
walk_recursive(base, &path, results)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
let path = Path::new(&path);
|
||||||
|
if !path.is_dir() {
|
||||||
|
return Err(NixRuntimeError::from(format!(
|
||||||
|
"{} is not a directory",
|
||||||
|
path.display()
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut results = Vec::new();
|
||||||
|
walk_recursive(path, path, &mut results)?;
|
||||||
|
Ok(results)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[deno_core::op2]
|
||||||
|
#[string]
|
||||||
|
pub(super) fn op_add_filtered_path<Ctx: RuntimeContext>(
|
||||||
|
state: &mut OpState,
|
||||||
|
#[string] src_path: String,
|
||||||
|
#[string] name: Option<String>,
|
||||||
|
recursive: bool,
|
||||||
|
#[string] sha256: Option<String>,
|
||||||
|
#[serde] include_paths: Vec<String>,
|
||||||
|
) -> std::result::Result<String, NixRuntimeError> {
|
||||||
|
use sha2::{Digest, Sha256};
|
||||||
|
use std::fs;
|
||||||
|
|
||||||
|
let src = Path::new(&src_path);
|
||||||
|
if !src.exists() {
|
||||||
|
return Err(NixRuntimeError::from(format!(
|
||||||
|
"path '{}' does not exist",
|
||||||
|
src_path
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
|
||||||
|
let computed_name = name.unwrap_or_else(|| {
|
||||||
|
src.file_name()
|
||||||
|
.and_then(|n| n.to_str())
|
||||||
|
.unwrap_or("source")
|
||||||
|
.to_string()
|
||||||
|
});
|
||||||
|
|
||||||
|
let temp_dir = tempfile::tempdir()
|
||||||
|
.map_err(|e| NixRuntimeError::from(format!("failed to create temp dir: {}", e)))?;
|
||||||
|
let dest = temp_dir.path().join(&computed_name);
|
||||||
|
|
||||||
|
fs::create_dir_all(&dest)
|
||||||
|
.map_err(|e| NixRuntimeError::from(format!("failed to create dest dir: {}", e)))?;
|
||||||
|
|
||||||
|
for rel_path in &include_paths {
|
||||||
|
let src_file = src.join(rel_path);
|
||||||
|
let dest_file = dest.join(rel_path);
|
||||||
|
|
||||||
|
if let Some(parent) = dest_file.parent() {
|
||||||
|
fs::create_dir_all(parent)
|
||||||
|
.map_err(|e| NixRuntimeError::from(format!("failed to create dir: {}", e)))?;
|
||||||
|
}
|
||||||
|
|
||||||
|
let metadata = fs::symlink_metadata(&src_file)
|
||||||
|
.map_err(|e| NixRuntimeError::from(format!("failed to read metadata: {}", e)))?;
|
||||||
|
|
||||||
|
if metadata.is_symlink() {
|
||||||
|
let target = fs::read_link(&src_file)
|
||||||
|
.map_err(|e| NixRuntimeError::from(format!("failed to read symlink: {}", e)))?;
|
||||||
|
#[cfg(unix)]
|
||||||
|
std::os::unix::fs::symlink(&target, &dest_file)
|
||||||
|
.map_err(|e| NixRuntimeError::from(format!("failed to create symlink: {}", e)))?;
|
||||||
|
#[cfg(not(unix))]
|
||||||
|
return Err(NixRuntimeError::from(
|
||||||
|
"symlinks not supported on this platform",
|
||||||
|
));
|
||||||
|
} else if metadata.is_dir() {
|
||||||
|
fs::create_dir_all(&dest_file)
|
||||||
|
.map_err(|e| NixRuntimeError::from(format!("failed to create dir: {}", e)))?;
|
||||||
|
} else {
|
||||||
|
fs::copy(&src_file, &dest_file)
|
||||||
|
.map_err(|e| NixRuntimeError::from(format!("failed to copy file: {}", e)))?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let computed_hash = if recursive {
|
||||||
|
crate::nar::compute_nar_hash(&dest)
|
||||||
|
.map_err(|e| NixRuntimeError::from(format!("failed to compute NAR hash: {}", e)))?
|
||||||
|
} else {
|
||||||
|
if !dest.is_file() {
|
||||||
|
return Err(NixRuntimeError::from(
|
||||||
|
"when 'recursive' is false, path must be a regular file",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
let contents = fs::read(&dest)
|
||||||
|
.map_err(|e| NixRuntimeError::from(format!("failed to read file: {}", e)))?;
|
||||||
|
let mut hasher = Sha256::new();
|
||||||
|
hasher.update(&contents);
|
||||||
|
hex::encode(hasher.finalize())
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(expected_hash) = sha256 {
|
||||||
|
let expected_hex =
|
||||||
|
crate::nix_hash::decode_hash_to_hex(&expected_hash).ok_or_else(|| {
|
||||||
|
NixRuntimeError::from(format!("invalid hash format: {}", expected_hash))
|
||||||
|
})?;
|
||||||
|
if computed_hash != expected_hex {
|
||||||
|
return Err(NixRuntimeError::from(format!(
|
||||||
|
"hash mismatch for path '{}': expected {}, got {}",
|
||||||
|
src_path, expected_hex, computed_hash
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let ctx: &Ctx = state.get_ctx();
|
||||||
|
let store = ctx.get_store();
|
||||||
|
|
||||||
|
let store_path = store
|
||||||
|
.add_to_store_from_path(&computed_name, &dest, vec![])
|
||||||
|
.map_err(|e| NixRuntimeError::from(format!("failed to add path to store: {}", e)))?;
|
||||||
|
|
||||||
|
Ok(store_path)
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user