diff --git a/snix/eval/src/builtins/impure.rs b/snix/eval/src/builtins/impure.rs index 5a3350d23..8e6385624 100644 --- a/snix/eval/src/builtins/impure.rs +++ b/snix/eval/src/builtins/impure.rs @@ -1,10 +1,7 @@ use builtin_macros::builtins; use genawaiter::rc::Gen; -use std::{ - env, - time::{SystemTime, UNIX_EPOCH}, -}; +use std::time::{SystemTime, UNIX_EPOCH}; use crate::{ self as snix_eval, @@ -24,9 +21,10 @@ mod impure_builtins { #[builtin("getEnv")] async fn builtin_get_env(co: GenCo, var: Value) -> Result { - Ok(env::var(OsStr::from_bytes(&var.to_str()?)) - .unwrap_or_else(|_| "".into()) - .into()) + let key = OsStr::from_bytes(&var.to_str()?).to_os_string(); + + let env = generators::request_get_env(&co, key).await; + Ok(Value::String(NixString::from(env.as_bytes()))) } #[builtin("hashFile")] diff --git a/snix/eval/src/io.rs b/snix/eval/src/io.rs index 5aef7f931..5ad3d4183 100644 --- a/snix/eval/src/io.rs +++ b/snix/eval/src/io.rs @@ -16,6 +16,8 @@ //! how store paths are opened and so on. use std::{ + env, + ffi::{OsStr, OsString}, io, path::{Path, PathBuf}, }; @@ -114,6 +116,9 @@ pub trait EvalIO { fn store_dir(&self) -> Option { None } + + /// Fetches the environment variable key from the current process. + fn get_env(&self, key: &OsStr) -> Option; } /// Implementation of [`EvalIO`] that simply uses the equivalent @@ -178,6 +183,10 @@ impl EvalIO for StdIO { fn import_path(&self, path: &Path) -> io::Result { Ok(path.to_path_buf()) } + + fn get_env(&self, key: &OsStr) -> Option { + env::var_os(key) + } } /// Dummy implementation of [`EvalIO`], can be used in contexts where @@ -219,4 +228,8 @@ impl EvalIO for DummyIO { "I/O methods are not implemented in DummyIO", )) } + + fn get_env(&self, _: &OsStr) -> Option { + None + } } diff --git a/snix/eval/src/tests/nix_tests.rs b/snix/eval/src/tests/nix_tests.rs index c1106f0f5..834a7bf82 100644 --- a/snix/eval/src/tests/nix_tests.rs +++ b/snix/eval/src/tests/nix_tests.rs @@ -1,8 +1,13 @@ -use crate::value::Value; +use crate::{value::Value, EvalIO, FileType}; use builtin_macros::builtins; use pretty_assertions::assert_eq; use rstest::rstest; -use std::path::PathBuf; +use std::{ + ffi::{OsStr, OsString}, + io::{self}, + path::{Path, PathBuf}, + str::FromStr, +}; #[builtins] mod mock_builtins { @@ -35,11 +40,58 @@ mod mock_builtins { } } +struct MockIo { + // Actual underlying [EvalIO] implementation. + actual: T, +} + +impl MockIo { + pub fn new(actual: T) -> Self { + Self { actual } + } +} + +impl EvalIO for MockIo +where + T: AsRef, +{ + fn store_dir(&self) -> Option { + self.actual.as_ref().store_dir() + } + + fn import_path(&self, path: &Path) -> io::Result { + self.actual.as_ref().import_path(path) + } + + fn path_exists(&self, path: &Path) -> io::Result { + self.actual.as_ref().path_exists(path) + } + + fn open(&self, path: &Path) -> io::Result> { + self.actual.as_ref().open(path) + } + + fn file_type(&self, path: &Path) -> io::Result { + self.actual.as_ref().file_type(path) + } + + fn read_dir(&self, path: &Path) -> io::Result> { + self.actual.as_ref().read_dir(path) + } + + fn get_env(&self, key: &OsStr) -> Option { + // for eval-okay-getenv.nix + if key == "TEST_VAR" { + return Some(OsString::from_str("foo").expect("This conversion is infallible.")); + } + + self.actual.as_ref().get_env(key) + } +} + #[cfg(feature = "impure")] fn eval_test(code_path: PathBuf, expect_success: bool) { - use crate::vm::EvalMode; - - std::env::set_var("TEST_VAR", "foo"); // for eval-okay-getenv.nix + use crate::{vm::EvalMode, StdIO}; eprintln!("path: {}", code_path.display()); assert_eq!( @@ -52,6 +104,7 @@ fn eval_test(code_path: PathBuf, expect_success: bool) { let eval = crate::Evaluation::builder_impure() .mode(EvalMode::Strict) + .io_handle(Box::new(MockIo::new(Box::new(StdIO) as Box)) as Box) .add_builtins(mock_builtins::builtins()) .build(); diff --git a/snix/eval/src/vm/generators.rs b/snix/eval/src/vm/generators.rs index 928ddc74e..f80f79fb1 100644 --- a/snix/eval/src/vm/generators.rs +++ b/snix/eval/src/vm/generators.rs @@ -10,6 +10,7 @@ use core::pin::Pin; use genawaiter::rc::Co; pub use genawaiter::rc::Gen; +use std::ffi::OsString; use std::fmt::Display; use std::future::Future; @@ -119,6 +120,9 @@ pub enum VMRequest { /// Request the VM for the file type of the given path. ReadFileType(PathBuf), + + // Request that the VM reads the given environment variable. + GetEnv(OsString), } /// Human-readable representation of a generator message, used by observers. @@ -176,6 +180,7 @@ impl Display for VMRequest { VMRequest::Span => write!(f, "span"), VMRequest::TryForce(v) => write!(f, "try_force({})", v.type_of()), VMRequest::ReadFileType(p) => write!(f, "read_file_type({})", p.to_string_lossy()), + VMRequest::GetEnv(p) => write!(f, "get_env({})", p.to_string_lossy()), } } } @@ -202,6 +207,9 @@ pub enum VMResponse { Reader(Box), FileType(FileType), + + /// environment variable + Env(OsString), } impl Display for VMResponse { @@ -214,6 +222,7 @@ impl Display for VMResponse { VMResponse::Span(_) => write!(f, "span"), VMResponse::Reader(_) => write!(f, "reader"), VMResponse::FileType(t) => write!(f, "file_type({t})"), + VMResponse::Env(t) => write!(f, "env({})", t.to_string_lossy()), } } } @@ -504,6 +513,11 @@ where message = VMResponse::FileType(file_type); } + VMRequest::GetEnv(key) => { + let env = self.io_handle.as_ref().get_env(&key).unwrap_or_default(); + + message = VMResponse::Env(env); + } } } @@ -738,6 +752,14 @@ pub(crate) async fn request_read_file_type(co: &GenCo, path: PathBuf) -> FileTyp } } +#[cfg_attr(not(feature = "impure"), allow(unused))] +pub(crate) async fn request_get_env(co: &GenCo, key: OsString) -> OsString { + match co.yield_(VMRequest::GetEnv(key)).await { + VMResponse::Env(env) => env, + msg => panic!("Snix bug: VM responded with incorrect generator message: {msg}"), + } +} + /// Call the given value as if it was an attribute set containing a functor. The /// arguments must already be prepared on the stack when a generator frame from /// this function is invoked. diff --git a/snix/glue/src/snix_io.rs b/snix/glue/src/snix_io.rs index 8663e3ad4..a7ad2f2ac 100644 --- a/snix/glue/src/snix_io.rs +++ b/snix/glue/src/snix_io.rs @@ -7,6 +7,7 @@ //! otherwise nixpkgs bootstrapping will not work. use snix_eval::{EvalIO, FileType}; +use std::ffi::{OsStr, OsString}; use std::io::{self, Cursor}; use std::path::{Path, PathBuf}; @@ -65,4 +66,8 @@ where fn read_dir(&self, path: &Path) -> io::Result> { self.actual.as_ref().read_dir(path) } + + fn get_env(&self, key: &OsStr) -> Option { + self.actual.as_ref().get_env(key) + } } diff --git a/snix/glue/src/snix_store_io.rs b/snix/glue/src/snix_store_io.rs index 549642757..2ee06e3df 100644 --- a/snix/glue/src/snix_store_io.rs +++ b/snix/glue/src/snix_store_io.rs @@ -6,6 +6,8 @@ use snix_eval::{EvalIO, FileType, StdIO}; use snix_store::nar::NarCalculationService; use std::{ cell::RefCell, + env, + ffi::{OsStr, OsString}, io, path::{Path, PathBuf}, sync::Arc, @@ -490,6 +492,10 @@ impl EvalIO for SnixStoreIO { fn store_dir(&self) -> Option { Some("/nix/store".to_string()) } + + fn get_env(&self, key: &OsStr) -> Option { + env::var_os(key) + } } #[cfg(test)]