feat(tvix/glue): Implement builtins.storePath
This one's relatively simple - we just check if the store path exists, and if it does we make a new contextful string containing the store path as its only context element. Automatic testing seems tricky for this (I think?) so I tested it manually: tvix-repl> builtins.storePath /nix/store/yn46i4xx5alh7gs6fpkxk430i34rp2q9-hello-2.12.1 => "/nix/store/yn46i4xx5alh7gs6fpkxk430i34rp2q9-hello-2.12.1" :: string Change-Id: I8a0d9726e4102ab872c53c2419679c2c855a5a18 Reviewed-on: https://cl.tvl.fyi/c/depot/+/11696 Tested-by: BuildkiteCI Autosubmit: aspen <root@gws.fyi> Reviewed-by: flokli <flokli@flokli.de>
This commit is contained in:
parent
c3572048d5
commit
72b9a126b8
7 changed files with 62 additions and 2 deletions
|
@ -54,6 +54,12 @@ impl From<HashSet<NixContextElement>> for NixContext {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<const N: usize> From<[NixContextElement; N]> for NixContext {
|
||||||
|
fn from(value: [NixContextElement; N]) -> Self {
|
||||||
|
Self(HashSet::from(value))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl NixContext {
|
impl NixContext {
|
||||||
/// Creates an empty context that can be populated
|
/// Creates an empty context that can be populated
|
||||||
/// and passed to form a contextful [NixString], albeit
|
/// and passed to form a contextful [NixString], albeit
|
||||||
|
|
|
@ -4,7 +4,7 @@ use nix_compat::{
|
||||||
store_path::BuildStorePathError,
|
store_path::BuildStorePathError,
|
||||||
};
|
};
|
||||||
use reqwest::Url;
|
use reqwest::Url;
|
||||||
use std::rc::Rc;
|
use std::{path::PathBuf, rc::Rc};
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
use tvix_castore::import;
|
use tvix_castore::import;
|
||||||
|
|
||||||
|
@ -65,8 +65,12 @@ pub enum FetcherError {
|
||||||
pub enum ImportError {
|
pub enum ImportError {
|
||||||
#[error("non-file '{0}' cannot be imported in 'flat' mode")]
|
#[error("non-file '{0}' cannot be imported in 'flat' mode")]
|
||||||
FlatImportOfNonFile(String),
|
FlatImportOfNonFile(String),
|
||||||
|
|
||||||
#[error("hash mismatch at ingestion of '{0}', expected: '{1}', got: '{2}'")]
|
#[error("hash mismatch at ingestion of '{0}', expected: '{1}', got: '{2}'")]
|
||||||
HashMismatch(String, NixHash, NixHash),
|
HashMismatch(String, NixHash, NixHash),
|
||||||
|
|
||||||
|
#[error("path '{}' is not in the Nix store", .0.display())]
|
||||||
|
PathNotInStore(PathBuf),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<ImportError> for tvix_eval::ErrorKind {
|
impl From<ImportError> for tvix_eval::ErrorKind {
|
||||||
|
|
|
@ -104,11 +104,13 @@ async fn filtered_ingest(
|
||||||
|
|
||||||
#[builtins(state = "Rc<TvixStoreIO>")]
|
#[builtins(state = "Rc<TvixStoreIO>")]
|
||||||
mod import_builtins {
|
mod import_builtins {
|
||||||
|
use std::os::unix::ffi::OsStrExt;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
use nix_compat::nixhash::{CAHash, NixHash};
|
use nix_compat::nixhash::{CAHash, NixHash};
|
||||||
|
use nix_compat::store_path::StorePath;
|
||||||
use tvix_eval::generators::Gen;
|
use tvix_eval::generators::Gen;
|
||||||
use tvix_eval::{generators::GenCo, ErrorKind, Value};
|
use tvix_eval::{generators::GenCo, ErrorKind, Value};
|
||||||
use tvix_eval::{NixContextElement, NixString};
|
use tvix_eval::{NixContextElement, NixString};
|
||||||
|
@ -280,6 +282,44 @@ mod import_builtins {
|
||||||
.into(),
|
.into(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[builtin("storePath")]
|
||||||
|
async fn builtin_store_path(
|
||||||
|
state: Rc<TvixStoreIO>,
|
||||||
|
co: GenCo,
|
||||||
|
path: Value,
|
||||||
|
) -> Result<Value, ErrorKind> {
|
||||||
|
let p = std::str::from_utf8(match &path {
|
||||||
|
Value::String(s) => s.as_bytes(),
|
||||||
|
Value::Path(p) => p.as_os_str().as_bytes(),
|
||||||
|
_ => {
|
||||||
|
return Err(ErrorKind::TypeError {
|
||||||
|
expected: "string or path",
|
||||||
|
actual: path.type_of(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let path_exists = if let Ok((store_path, sub_path)) = StorePath::from_absolute_path_full(p)
|
||||||
|
{
|
||||||
|
if !sub_path.as_os_str().is_empty() {
|
||||||
|
false
|
||||||
|
} else {
|
||||||
|
state.store_path_exists(store_path.as_ref()).await?
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
};
|
||||||
|
|
||||||
|
if !path_exists {
|
||||||
|
return Err(ImportError::PathNotInStore(p.into()).into());
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Value::String(NixString::new_context_from(
|
||||||
|
[NixContextElement::Plain(p.into())].into(),
|
||||||
|
p,
|
||||||
|
)))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub use import_builtins::builtins as import_builtins;
|
pub use import_builtins::builtins as import_builtins;
|
||||||
|
|
0
tvix/glue/src/tests/empty-file
Normal file
0
tvix/glue/src/tests/empty-file
Normal file
1
tvix/glue/src/tests/tvix_tests/eval-okay-storePath.exp
Normal file
1
tvix/glue/src/tests/tvix_tests/eval-okay-storePath.exp
Normal file
|
@ -0,0 +1 @@
|
||||||
|
{ contextMatches = true; hasContext = true; }
|
9
tvix/glue/src/tests/tvix_tests/eval-okay-storePath.nix
Normal file
9
tvix/glue/src/tests/tvix_tests/eval-okay-storePath.nix
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
let
|
||||||
|
path = builtins.unsafeDiscardStringContext "${../empty-file}";
|
||||||
|
storePath = builtins.storePath path;
|
||||||
|
context = builtins.getContext storePath;
|
||||||
|
in
|
||||||
|
{
|
||||||
|
hasContext = builtins.hasContext storePath;
|
||||||
|
contextMatches = context == { "${path}" = { path = true; }; };
|
||||||
|
}
|
|
@ -421,7 +421,7 @@ impl EvalIO for TvixStoreIO {
|
||||||
{
|
{
|
||||||
if self
|
if self
|
||||||
.tokio_handle
|
.tokio_handle
|
||||||
.block_on(async { self.store_path_to_node(&store_path, &sub_path).await })?
|
.block_on(self.store_path_to_node(&store_path, &sub_path))?
|
||||||
.is_some()
|
.is_some()
|
||||||
{
|
{
|
||||||
Ok(true)
|
Ok(true)
|
||||||
|
|
Loading…
Reference in a new issue