about summary refs log tree commit diff
path: root/tvix/eval/src/io.rs
diff options
context:
space:
mode:
Diffstat (limited to 'tvix/eval/src/io.rs')
-rw-r--r--tvix/eval/src/io.rs141
1 files changed, 141 insertions, 0 deletions
diff --git a/tvix/eval/src/io.rs b/tvix/eval/src/io.rs
new file mode 100644
index 000000000000..931a0f45cc47
--- /dev/null
+++ b/tvix/eval/src/io.rs
@@ -0,0 +1,141 @@
+//! Interface for injecting I/O-related functionality into tvix-eval.
+//!
+//! The Nix language contains several builtins (e.g. `builtins.readDir`), as
+//! well as language feature (e.g. string-"coercion" of paths) that interact
+//! with the filesystem.
+//!
+//! The language evaluator implemented by this crate does not depend on any
+//! particular filesystem interaction model. Instead, this module provides a
+//! trait that can be implemented by tvix-eval callers to provide the
+//! functionality they desire.
+//!
+//! In theory this can be used to implement "mocked" filesystem interactions, or
+//! interaction with remote filesystems, etc.
+//!
+//! In the context of Nix builds, callers also use this interface to determine
+//! how store paths are opened and so on.
+
+use std::{
+    io,
+    path::{Path, PathBuf},
+};
+
+#[cfg(target_family = "unix")]
+use std::os::unix::ffi::OsStringExt;
+
+/// Types of files as represented by `builtins.readDir` in Nix.
+#[derive(Debug)]
+pub enum FileType {
+    Directory,
+    Regular,
+    Symlink,
+    Unknown,
+}
+
+/// Defines how filesystem interaction occurs inside of tvix-eval.
+pub trait EvalIO {
+    /// Verify whether the file at the specified path exists.
+    fn path_exists(&self, path: &Path) -> Result<bool, io::Error>;
+
+    /// Read the file at the specified path to a string.
+    fn read_to_string(&self, path: &Path) -> Result<String, io::Error>;
+
+    /// Read the directory at the specified path and return the names
+    /// of its entries associated with their [`FileType`].
+    fn read_dir(&self, path: &Path) -> Result<Vec<(bytes::Bytes, FileType)>, io::Error>;
+
+    /// Import the given path. What this means depends on the
+    /// implementation, for example for a `std::io`-based
+    /// implementation this might be a no-op, while for a Tvix store
+    /// this might be a copy of the given files to the store.
+    ///
+    /// This is primarily used in the context of things like coercing
+    /// a local path to a string, or builtins like `path`.
+    fn import_path(&self, path: &Path) -> Result<PathBuf, io::Error>;
+
+    /// Returns the root of the store directory, if such a thing
+    /// exists in the evaluation context.
+    fn store_dir(&self) -> Option<String> {
+        None
+    }
+}
+
+/// Implementation of [`EvalIO`] that simply uses the equivalent
+/// standard library functions, i.e. does local file-IO.
+#[cfg(feature = "impure")]
+pub struct StdIO;
+
+// TODO: we might want to make this whole impl to be target_family = "unix".
+#[cfg(feature = "impure")]
+impl EvalIO for StdIO {
+    fn path_exists(&self, path: &Path) -> Result<bool, io::Error> {
+        path.try_exists()
+    }
+
+    fn read_to_string(&self, path: &Path) -> Result<String, io::Error> {
+        std::fs::read_to_string(path)
+    }
+
+    fn read_dir(&self, path: &Path) -> Result<Vec<(bytes::Bytes, FileType)>, io::Error> {
+        let mut result = vec![];
+
+        for entry in path.read_dir()? {
+            let entry = entry?;
+            let file_type = entry.metadata()?.file_type();
+
+            let val = if file_type.is_dir() {
+                FileType::Directory
+            } else if file_type.is_file() {
+                FileType::Regular
+            } else if file_type.is_symlink() {
+                FileType::Symlink
+            } else {
+                FileType::Unknown
+            };
+
+            result.push((entry.file_name().into_vec().into(), val))
+        }
+
+        Ok(result)
+    }
+
+    // this is a no-op for `std::io`, as the user can already refer to
+    // the path directly
+    fn import_path(&self, path: &Path) -> Result<PathBuf, io::Error> {
+        Ok(path.to_path_buf())
+    }
+}
+
+/// Dummy implementation of [`EvalIO`], can be used in contexts where
+/// IO is not available but code should "pretend" that it is.
+pub struct DummyIO;
+
+impl EvalIO for DummyIO {
+    fn path_exists(&self, _: &Path) -> Result<bool, io::Error> {
+        Err(io::Error::new(
+            io::ErrorKind::Unsupported,
+            "I/O methods are not implemented in DummyIO",
+        ))
+    }
+
+    fn read_to_string(&self, _: &Path) -> Result<String, io::Error> {
+        Err(io::Error::new(
+            io::ErrorKind::Unsupported,
+            "I/O methods are not implemented in DummyIO",
+        ))
+    }
+
+    fn read_dir(&self, _: &Path) -> Result<Vec<(bytes::Bytes, FileType)>, io::Error> {
+        Err(io::Error::new(
+            io::ErrorKind::Unsupported,
+            "I/O methods are not implemented in DummyIO",
+        ))
+    }
+
+    fn import_path(&self, _: &Path) -> Result<PathBuf, io::Error> {
+        Err(io::Error::new(
+            io::ErrorKind::Unsupported,
+            "I/O methods are not implemented in DummyIO",
+        ))
+    }
+}