diff options
author | Aspen Smith <root@gws.fyi> | 2024-07-06T00·29-0400 |
---|---|---|
committer | clbot <clbot@tvl.fyi> | 2024-07-06T15·03+0000 |
commit | dfe137786c98f7e610cf95dd7cbc1fc476a766fd (patch) | |
tree | 2b03183b7cac856f2c5dd509027e3df58fc5db5c | |
parent | d5964c1d548615aea1f3165c478ad91ad87822de (diff) |
refactor(tvix/eval): Builderize Evaluation r/8351
Make constructing of a new Evaluation use the builder pattern rather than setting public mutable fields. This is currently a pure refactor (no functionality has changed) but has a few advantages: - We've encapsulated the internals of the fields in Evaluation, meaning we can change them without too much breakage of clients - We have type safety that prevents us from ever changing the fields of an Evaluation after it's built (which matters more in a world where we reuse Evaluations). More importantly, this paves the road for doing different things with the construction of an Evaluation - notably, sharing certain things like the GlobalsMap across subsequent evaluations in eg the REPL. Fixes: b/262 Change-Id: I4a27116faac14cdd144fc7c992d14ae095a1aca4 Reviewed-on: https://cl.tvl.fyi/c/depot/+/11956 Tested-by: BuildkiteCI Autosubmit: aspen <root@gws.fyi> Reviewed-by: flokli <flokli@flokli.de>
-rw-r--r-- | tvix/cli/src/main.rs | 43 | ||||
-rw-r--r-- | tvix/eval/benches/eval.rs | 4 | ||||
-rw-r--r-- | tvix/eval/src/lib.rs | 259 | ||||
-rw-r--r-- | tvix/eval/src/tests/nix_tests.rs | 13 | ||||
-rw-r--r-- | tvix/eval/src/tests/one_offs.rs | 9 | ||||
-rw-r--r-- | tvix/eval/tests/nix_oracle.rs | 12 | ||||
-rw-r--r-- | tvix/glue/benches/eval.rs | 21 | ||||
-rw-r--r-- | tvix/glue/src/builtins/mod.rs | 41 | ||||
-rw-r--r-- | tvix/glue/src/lib.rs | 16 | ||||
-rw-r--r-- | tvix/glue/src/tests/mod.rs | 22 | ||||
-rw-r--r-- | tvix/glue/src/tvix_store_io.rs | 10 | ||||
-rw-r--r-- | tvix/serde/examples/nixpkgs.rs | 4 | ||||
-rw-r--r-- | tvix/serde/src/de.rs | 14 | ||||
-rw-r--r-- | tvix/serde/src/de_tests.rs | 4 | ||||
-rw-r--r-- | web/tvixbolt/src/lib.rs | 9 |
15 files changed, 326 insertions, 155 deletions
diff --git a/tvix/cli/src/main.rs b/tvix/cli/src/main.rs index c4c70d2a1777..2f7dc58a23aa 100644 --- a/tvix/cli/src/main.rs +++ b/tvix/cli/src/main.rs @@ -167,26 +167,25 @@ fn evaluate( span.pb_set_style(&tvix_tracing::PB_SPINNER_STYLE); span.pb_set_message("Setting up evaluator…"); - let mut eval = tvix_eval::Evaluation::new( - Box::new(TvixIO::new(tvix_store_io.clone() as Rc<dyn EvalIO>)) as Box<dyn EvalIO>, - true, - ); - eval.strict = args.strict; - eval.builtins.extend(impure_builtins()); - if let Some(env) = env { - eval.env = Some(env); - } - add_derivation_builtins(&mut eval, Rc::clone(&tvix_store_io)); - add_fetcher_builtins(&mut eval, Rc::clone(&tvix_store_io)); - add_import_builtins(&mut eval, tvix_store_io); - configure_nix_path(&mut eval, &args.nix_search_path); - - let source_map = eval.source_map(); + let mut eval_builder = tvix_eval::Evaluation::builder(Box::new(TvixIO::new( + tvix_store_io.clone() as Rc<dyn EvalIO>, + )) as Box<dyn EvalIO>) + .enable_import() + .with_strict(args.strict) + .add_builtins(impure_builtins()) + .env(env); + + eval_builder = add_derivation_builtins(eval_builder, Rc::clone(&tvix_store_io)); + eval_builder = add_fetcher_builtins(eval_builder, Rc::clone(&tvix_store_io)); + eval_builder = add_import_builtins(eval_builder, tvix_store_io); + eval_builder = configure_nix_path(eval_builder, &args.nix_search_path); + + let source_map = eval_builder.source_map().clone(); let result = { let mut compiler_observer = DisassemblingObserver::new(source_map.clone(), std::io::stderr()); if args.dump_bytecode { - eval.compiler_observer = Some(&mut compiler_observer); + eval_builder.set_compiler_observer(Some(&mut compiler_observer)); } let mut runtime_observer = TracingObserver::new(std::io::stderr()); @@ -194,10 +193,12 @@ fn evaluate( if args.trace_runtime_timing { runtime_observer.enable_timing() } - eval.runtime_observer = Some(&mut runtime_observer); + eval_builder.set_runtime_observer(Some(&mut runtime_observer)); } span.pb_set_message("Evaluating…"); + + let eval = eval_builder.build(); eval.evaluate(code, path) }; @@ -262,21 +263,21 @@ fn interpret( /// Interpret the given code snippet, but only run the Tvix compiler /// on it and return errors and warnings. fn lint(code: &str, path: Option<PathBuf>, args: &Args) -> bool { - let mut eval = tvix_eval::Evaluation::new_impure(); - eval.strict = args.strict; + let mut eval_builder = tvix_eval::Evaluation::builder_impure().with_strict(args.strict); - let source_map = eval.source_map(); + let source_map = eval_builder.source_map().clone(); let mut compiler_observer = DisassemblingObserver::new(source_map.clone(), std::io::stderr()); if args.dump_bytecode { - eval.compiler_observer = Some(&mut compiler_observer); + eval_builder.set_compiler_observer(Some(&mut compiler_observer)); } if args.trace_runtime { eprintln!("warning: --trace-runtime has no effect with --compile-only!"); } + let eval = eval_builder.build(); let result = eval.compile_only(code, path); if args.display_ast { diff --git a/tvix/eval/benches/eval.rs b/tvix/eval/benches/eval.rs index 1333f5018cb3..36ca310f4465 100644 --- a/tvix/eval/benches/eval.rs +++ b/tvix/eval/benches/eval.rs @@ -8,7 +8,9 @@ use tikv_jemallocator::Jemalloc; static GLOBAL: Jemalloc = Jemalloc; fn interpret(code: &str) { - tvix_eval::Evaluation::new_pure().evaluate(code, None); + tvix_eval::Evaluation::builder_pure() + .build() + .evaluate(code, None); } fn eval_literals(c: &mut Criterion) { diff --git a/tvix/eval/src/lib.rs b/tvix/eval/src/lib.rs index 97690d13610e..bfdd30154acf 100644 --- a/tvix/eval/src/lib.rs +++ b/tvix/eval/src/lib.rs @@ -64,6 +64,192 @@ pub use crate::value::{Builtin, CoercionKind, NixAttrs, NixList, NixString, Valu #[cfg(feature = "impure")] pub use crate::io::StdIO; +/// Builder for building an [`Evaluation`]. +/// +/// Construct an [`EvaluationBuilder`] by calling one of: +/// +/// - [`Evaluation::builder`] / [`EvaluationBuilder::new`] +/// - [`Evaluation::builder_impure`] [`EvaluationBuilder::new_impure`] +/// - [`Evaluation::builder_pure`] [`EvaluationBuilder::new_pure`] +/// +/// Then configure the fields by calling the various methods on [`EvaluationBuilder`], and finally +/// call [`build`](Self::build) to construct an [`Evaluation`] +pub struct EvaluationBuilder<'co, 'ro, 'env, IO> { + source_map: SourceCode, + builtins: Vec<(&'static str, Value)>, + src_builtins: Vec<(&'static str, &'static str)>, + env: Option<&'env HashMap<SmolStr, Value>>, + io_handle: IO, + enable_import: bool, + strict: bool, + nix_path: Option<String>, + compiler_observer: Option<&'co mut dyn CompilerObserver>, + runtime_observer: Option<&'ro mut dyn RuntimeObserver>, +} + +// NOTE(aspen): The methods here are intentionally incomplete; feel free to add new ones (ideally +// with similar naming conventions to the ones already present) but don't expose fields publically! +impl<'co, 'ro, 'env, IO> EvaluationBuilder<'co, 'ro, 'env, IO> { + pub fn new(io_handle: IO) -> Self { + let mut builtins = builtins::pure_builtins(); + builtins.extend(builtins::placeholders()); // these are temporary + + Self { + source_map: SourceCode::default(), + enable_import: false, + io_handle, + builtins, + src_builtins: vec![], + env: None, + strict: false, + nix_path: None, + compiler_observer: None, + runtime_observer: None, + } + } + + pub fn build(self) -> Evaluation<'co, 'ro, 'env, IO> { + Evaluation { + source_map: self.source_map, + builtins: self.builtins, + src_builtins: self.src_builtins, + env: self.env, + io_handle: self.io_handle, + enable_import: self.enable_import, + strict: self.strict, + nix_path: self.nix_path, + compiler_observer: self.compiler_observer, + runtime_observer: self.runtime_observer, + } + } + + pub fn io_handle<IO2>(self, io_handle: IO2) -> EvaluationBuilder<'co, 'ro, 'env, IO2> { + EvaluationBuilder { + io_handle, + source_map: self.source_map, + builtins: self.builtins, + src_builtins: self.src_builtins, + env: self.env, + enable_import: self.enable_import, + strict: self.strict, + nix_path: self.nix_path, + compiler_observer: self.compiler_observer, + runtime_observer: self.runtime_observer, + } + } + + pub fn with_enable_import(self, enable_import: bool) -> Self { + Self { + enable_import, + ..self + } + } + + pub fn disable_import(self) -> Self { + self.with_enable_import(false) + } + + pub fn enable_import(self) -> Self { + self.with_enable_import(true) + } + + pub fn add_builtins<I>(mut self, builtins: I) -> Self + where + I: IntoIterator<Item = (&'static str, Value)>, + { + self.builtins.extend(builtins); + self + } + + pub fn with_strict(self, strict: bool) -> Self { + Self { strict, ..self } + } + + pub fn strict(self) -> Self { + self.with_strict(true) + } + + pub fn add_src_builtin(mut self, name: &'static str, src: &'static str) -> Self { + self.src_builtins.push((name, src)); + self + } + + pub fn nix_path(self, nix_path: Option<String>) -> Self { + Self { nix_path, ..self } + } + + pub fn env(self, env: Option<&'env HashMap<SmolStr, Value>>) -> Self { + Self { env, ..self } + } + + pub fn compiler_observer( + self, + compiler_observer: Option<&'co mut dyn CompilerObserver>, + ) -> Self { + Self { + compiler_observer, + ..self + } + } + + pub fn set_compiler_observer( + &mut self, + compiler_observer: Option<&'co mut dyn CompilerObserver>, + ) { + self.compiler_observer = compiler_observer; + } + + pub fn runtime_observer(self, runtime_observer: Option<&'ro mut dyn RuntimeObserver>) -> Self { + Self { + runtime_observer, + ..self + } + } + + pub fn set_runtime_observer(&mut self, runtime_observer: Option<&'ro mut dyn RuntimeObserver>) { + self.runtime_observer = runtime_observer; + } +} + +impl<'co, 'ro, 'env, IO> EvaluationBuilder<'co, 'ro, 'env, IO> { + pub fn source_map(&self) -> &SourceCode { + &self.source_map + } +} + +impl<'co, 'ro, 'env> EvaluationBuilder<'co, 'ro, 'env, Box<dyn EvalIO>> { + /// Initialize an `Evaluation`, without the import statement available, and + /// all IO operations stubbed out. + pub fn new_pure() -> Self { + Self::new(Box::new(DummyIO) as Box<dyn EvalIO>).with_enable_import(false) + } + + #[cfg(feature = "impure")] + /// Configure an `Evaluation` to have impure features available + /// with the given I/O implementation. + /// + /// If no I/O implementation is supplied, [`StdIO`] is used by + /// default. + pub fn enable_impure(mut self, io: Option<Box<dyn EvalIO>>) -> Self { + self.io_handle = io.unwrap_or_else(|| Box::new(StdIO) as Box<dyn EvalIO>); + self.enable_import = true; + self.builtins.extend(builtins::impure_builtins()); + + // Make `NIX_PATH` resolutions work by default, unless the + // user already overrode this with something else. + if self.nix_path.is_none() { + self.nix_path = std::env::var("NIX_PATH").ok(); + } + self + } + + #[cfg(feature = "impure")] + /// Initialise an `Evaluation`, with all impure features turned on by default. + pub fn new_impure() -> Self { + Self::new_pure().enable_impure(None) + } +} + /// An `Evaluation` represents how a piece of Nix code is evaluated. It can be /// instantiated and configured directly, or it can be accessed through the /// various simplified helper methods available below. @@ -79,42 +265,42 @@ pub struct Evaluation<'co, 'ro, 'env, IO> { /// /// This defaults to all pure builtins. Users might want to add /// the set of impure builtins, or other custom builtins. - pub builtins: Vec<(&'static str, Value)>, + builtins: Vec<(&'static str, Value)>, /// Set of builtins that are implemented in Nix itself and should /// be compiled and inserted in the builtins set. - pub src_builtins: Vec<(&'static str, &'static str)>, + src_builtins: Vec<(&'static str, &'static str)>, /// Top-level variables to define in the evaluation - pub env: Option<&'env HashMap<SmolStr, Value>>, + env: Option<&'env HashMap<SmolStr, Value>>, /// Implementation of file-IO to use during evaluation, e.g. for /// impure builtins. /// /// Defaults to [`DummyIO`] if not set explicitly. - pub io_handle: IO, + io_handle: IO, /// Determines whether the `import` builtin should be made /// available. Note that this depends on the `io_handle` being /// able to read the files specified as arguments to `import`. - pub enable_import: bool, + enable_import: bool, /// Determines whether the returned value should be strictly /// evaluated, that is whether its list and attribute set elements /// should be forced recursively. - pub strict: bool, + strict: bool, /// (optional) Nix search path, e.g. the value of `NIX_PATH` used /// for resolving items on the search path (such as `<nixpkgs>`). - pub nix_path: Option<String>, + nix_path: Option<String>, /// (optional) compiler observer for reporting on compilation /// details, like the emitted bytecode. - pub compiler_observer: Option<&'co mut dyn CompilerObserver>, + compiler_observer: Option<&'co mut dyn CompilerObserver>, /// (optional) runtime observer, for reporting on execution steps /// of Nix code. - pub runtime_observer: Option<&'ro mut dyn RuntimeObserver>, + runtime_observer: Option<&'ro mut dyn RuntimeObserver>, } /// Result of evaluating a piece of Nix code. If evaluation succeeded, a value @@ -136,61 +322,20 @@ pub struct EvaluationResult { pub expr: Option<rnix::ast::Expr>, } -impl<'co, 'ro, 'env, IO> Evaluation<'co, 'ro, 'env, IO> -where - IO: AsRef<dyn EvalIO> + 'static, -{ - /// Initialize an `Evaluation`. - pub fn new(io_handle: IO, enable_import: bool) -> Self { - let mut builtins = builtins::pure_builtins(); - builtins.extend(builtins::placeholders()); // these are temporary - - Self { - source_map: SourceCode::default(), - enable_import, - io_handle, - builtins, - src_builtins: vec![], - env: None, - strict: false, - nix_path: None, - compiler_observer: None, - runtime_observer: None, - } +impl<'co, 'ro, 'env, IO> Evaluation<'co, 'ro, 'env, IO> { + pub fn builder(io_handle: IO) -> EvaluationBuilder<'co, 'ro, 'env, IO> { + EvaluationBuilder::new(io_handle) } } impl<'co, 'ro, 'env> Evaluation<'co, 'ro, 'env, Box<dyn EvalIO>> { - /// Initialize an `Evaluation`, without the import statement available, and - /// all IO operations stubbed out. - pub fn new_pure() -> Self { - Self::new(Box::new(DummyIO) as Box<dyn EvalIO>, false) - } - #[cfg(feature = "impure")] - /// Configure an `Evaluation` to have impure features available - /// with the given I/O implementation. - /// - /// If no I/O implementation is supplied, [`StdIO`] is used by - /// default. - pub fn enable_impure(&mut self, io: Option<Box<dyn EvalIO>>) { - self.io_handle = io.unwrap_or_else(|| Box::new(StdIO) as Box<dyn EvalIO>); - self.enable_import = true; - self.builtins.extend(builtins::impure_builtins()); - - // Make `NIX_PATH` resolutions work by default, unless the - // user already overrode this with something else. - if self.nix_path.is_none() { - self.nix_path = std::env::var("NIX_PATH").ok(); - } + pub fn builder_impure() -> EvaluationBuilder<'co, 'ro, 'env, Box<dyn EvalIO>> { + EvaluationBuilder::new_impure() } - #[cfg(feature = "impure")] - /// Initialise an `Evaluation`, with all impure features turned on by default. - pub fn new_impure() -> Self { - let mut eval = Self::new_pure(); - eval.enable_impure(None); - eval + pub fn builder_pure() -> EvaluationBuilder<'co, 'ro, 'env, Box<dyn EvalIO>> { + EvaluationBuilder::new_pure() } } diff --git a/tvix/eval/src/tests/nix_tests.rs b/tvix/eval/src/tests/nix_tests.rs index 17968e4bdb09..cdaed193f206 100644 --- a/tvix/eval/src/tests/nix_tests.rs +++ b/tvix/eval/src/tests/nix_tests.rs @@ -48,9 +48,10 @@ fn eval_test(code_path: PathBuf, expect_success: bool) { let code = std::fs::read_to_string(&code_path).expect("should be able to read test code"); - let mut eval = crate::Evaluation::new_impure(); - eval.strict = true; - eval.builtins.extend(mock_builtins::builtins()); + let eval = crate::Evaluation::builder_impure() + .strict() + .add_builtins(mock_builtins::builtins()) + .build(); let result = eval.evaluate(code, Some(code_path.clone())); let failed = match result.value { @@ -128,8 +129,10 @@ fn identity(#[files("src/tests/tvix_tests/identity-*.nix")] code_path: PathBuf) let code = std::fs::read_to_string(code_path).expect("should be able to read test code"); - let mut eval = crate::Evaluation::new(Box::new(crate::StdIO) as Box<dyn EvalIO>, false); - eval.strict = true; + let eval = crate::Evaluation::builder(Box::new(crate::StdIO) as Box<dyn EvalIO>) + .disable_import() + .strict() + .build(); let result = eval.evaluate(&code, None); assert!( diff --git a/tvix/eval/src/tests/one_offs.rs b/tvix/eval/src/tests/one_offs.rs index 21e9144baf64..49ed713fc96c 100644 --- a/tvix/eval/src/tests/one_offs.rs +++ b/tvix/eval/src/tests/one_offs.rs @@ -5,8 +5,9 @@ fn test_source_builtin() { // Test an evaluation with a source-only builtin. The test ensures // that the artificially constructed thunking is correct. - let mut eval = Evaluation::new_pure(); - eval.src_builtins.push(("testSourceBuiltin", "42")); + let eval = Evaluation::builder_pure() + .add_src_builtin("testSourceBuiltin", "42") + .build(); let result = eval.evaluate("builtins.testSourceBuiltin", None); assert!( @@ -25,7 +26,9 @@ fn test_source_builtin() { #[test] fn skip_broken_bytecode() { - let result = Evaluation::new_pure().evaluate(/* code = */ "x", None); + let result = Evaluation::builder_pure() + .build() + .evaluate(/* code = */ "x", None); assert_eq!(result.errors.len(), 1); diff --git a/tvix/eval/tests/nix_oracle.rs b/tvix/eval/tests/nix_oracle.rs index 3d3abc73635f..cc2a7b519e8f 100644 --- a/tvix/eval/tests/nix_oracle.rs +++ b/tvix/eval/tests/nix_oracle.rs @@ -58,10 +58,16 @@ fn nix_eval(expr: &str, strictness: Strictness) -> String { #[track_caller] #[cfg(feature = "impure")] fn compare_eval(expr: &str, strictness: Strictness) { + use tvix_eval::EvalIO; + let nix_result = nix_eval(expr, strictness); - let mut eval = tvix_eval::Evaluation::new_pure(); - eval.strict = matches!(strictness, Strictness::Strict); - eval.io_handle = Box::new(tvix_eval::StdIO); + let mut eval_builder = tvix_eval::Evaluation::builder_pure(); + if matches!(strictness, Strictness::Strict) { + eval_builder = eval_builder.strict(); + } + let eval = eval_builder + .io_handle(Box::new(tvix_eval::StdIO) as Box<dyn EvalIO>) + .build(); let tvix_result = eval .evaluate(expr, None) diff --git a/tvix/glue/benches/eval.rs b/tvix/glue/benches/eval.rs index 9e0154cad787..55f37f2d58ed 100644 --- a/tvix/glue/benches/eval.rs +++ b/tvix/glue/benches/eval.rs @@ -40,22 +40,23 @@ fn interpret(code: &str) { TOKIO_RUNTIME.handle().clone(), )); - let mut eval = tvix_eval::Evaluation::new( - Box::new(TvixIO::new(tvix_store_io.clone() as Rc<dyn EvalIO>)) as Box<dyn EvalIO>, - true, - ); + let mut eval_builder = tvix_eval::Evaluation::builder(Box::new(TvixIO::new( + tvix_store_io.clone() as Rc<dyn EvalIO>, + )) as Box<dyn EvalIO>) + .enable_import() + .add_builtins(impure_builtins()); - eval.builtins.extend(impure_builtins()); - add_derivation_builtins(&mut eval, Rc::clone(&tvix_store_io)); - add_fetcher_builtins(&mut eval, Rc::clone(&tvix_store_io)); - add_import_builtins(&mut eval, tvix_store_io); - configure_nix_path( - &mut eval, + eval_builder = add_derivation_builtins(eval_builder, Rc::clone(&tvix_store_io)); + eval_builder = add_fetcher_builtins(eval_builder, Rc::clone(&tvix_store_io)); + eval_builder = add_import_builtins(eval_builder, tvix_store_io); + eval_builder = configure_nix_path( + eval_builder, // The benchmark requires TVIX_BENCH_NIX_PATH to be set, so barf out // early, rather than benchmarking tvix returning an error. &Some(env::var("TVIX_BENCH_NIX_PATH").expect("TVIX_BENCH_NIX_PATH must be set")), ); + let eval = eval_builder.build(); let result = eval.evaluate(code, None); assert!(result.errors.is_empty()); diff --git a/tvix/glue/src/builtins/mod.rs b/tvix/glue/src/builtins/mod.rs index 3d6263286dc4..e1c20e461ffc 100644 --- a/tvix/glue/src/builtins/mod.rs +++ b/tvix/glue/src/builtins/mod.rs @@ -18,13 +18,14 @@ pub use errors::{DerivationError, FetcherError, ImportError}; /// /// As they need to interact with `known_paths`, we also need to pass in /// `known_paths`. -pub fn add_derivation_builtins<IO>(eval: &mut tvix_eval::Evaluation<IO>, io: Rc<TvixStoreIO>) { - eval.builtins - .extend(derivation::derivation_builtins::builtins(Rc::clone(&io))); - - // Add the actual `builtins.derivation` from compiled Nix code - eval.src_builtins - .push(("derivation", include_str!("derivation.nix"))); +pub fn add_derivation_builtins<'co, 'ro, 'env, IO>( + eval_builder: tvix_eval::EvaluationBuilder<'co, 'ro, 'env, IO>, + io: Rc<TvixStoreIO>, +) -> tvix_eval::EvaluationBuilder<'co, 'ro, 'env, IO> { + eval_builder + .add_builtins(derivation::derivation_builtins::builtins(Rc::clone(&io))) + // Add the actual `builtins.derivation` from compiled Nix code + .add_src_builtin("derivation", include_str!("derivation.nix")) } /// Adds fetcher builtins to the passed [tvix_eval::Evaluation]: @@ -32,9 +33,11 @@ pub fn add_derivation_builtins<IO>(eval: &mut tvix_eval::Evaluation<IO>, io: Rc< /// * `fetchurl` /// * `fetchTarball` /// * `fetchGit` -pub fn add_fetcher_builtins<IO>(eval: &mut tvix_eval::Evaluation<IO>, io: Rc<TvixStoreIO>) { - eval.builtins - .extend(fetchers::fetcher_builtins::builtins(Rc::clone(&io))); +pub fn add_fetcher_builtins<'co, 'ro, 'env, IO>( + eval_builder: tvix_eval::EvaluationBuilder<'co, 'ro, 'env, IO>, + io: Rc<TvixStoreIO>, +) -> tvix_eval::EvaluationBuilder<'co, 'ro, 'env, IO> { + eval_builder.add_builtins(fetchers::fetcher_builtins::builtins(Rc::clone(&io))) } /// Adds import-related builtins to the passed [tvix_eval::Evaluation]. @@ -42,10 +45,12 @@ pub fn add_fetcher_builtins<IO>(eval: &mut tvix_eval::Evaluation<IO>, io: Rc<Tvi /// These are `filterSource` and `path` /// /// As they need to interact with the store implementation, we pass [`TvixStoreIO`]. -pub fn add_import_builtins<IO>(eval: &mut tvix_eval::Evaluation<IO>, io: Rc<TvixStoreIO>) { - eval.builtins.extend(import::import_builtins(io)); - +pub fn add_import_builtins<'co, 'ro, 'env, IO>( + eval_builder: tvix_eval::EvaluationBuilder<'co, 'ro, 'env, IO>, + io: Rc<TvixStoreIO>, +) -> tvix_eval::EvaluationBuilder<'co, 'ro, 'env, IO> { // TODO(raitobezarius): evaluate expressing filterSource as Nix code using path (b/372) + eval_builder.add_builtins(import::import_builtins(io)) } #[cfg(test)] @@ -81,11 +86,11 @@ mod tests { runtime.handle().clone(), )); - let mut eval = tvix_eval::Evaluation::new(io.clone() as Rc<dyn EvalIO>, false); - - add_derivation_builtins(&mut eval, Rc::clone(&io)); - add_fetcher_builtins(&mut eval, Rc::clone(&io)); - add_import_builtins(&mut eval, io); + let mut eval_builder = tvix_eval::Evaluation::builder(io.clone() as Rc<dyn EvalIO>); + eval_builder = add_derivation_builtins(eval_builder, Rc::clone(&io)); + eval_builder = add_fetcher_builtins(eval_builder, Rc::clone(&io)); + eval_builder = add_import_builtins(eval_builder, io); + let eval = eval_builder.build(); // run the evaluation itself. eval.evaluate(str, None) diff --git a/tvix/glue/src/lib.rs b/tvix/glue/src/lib.rs index a5dbdb8742fc..f6f99f3c32a3 100644 --- a/tvix/glue/src/lib.rs +++ b/tvix/glue/src/lib.rs @@ -14,12 +14,14 @@ mod tests; /// Tell the Evaluator to resolve `<nix>` to the path `/__corepkgs__`, /// which has special handling in [tvix_io::TvixIO]. /// This is used in nixpkgs to import `fetchurl.nix` from `<nix>`. -pub fn configure_nix_path<IO>( - eval: &mut tvix_eval::Evaluation<IO>, +pub fn configure_nix_path<'co, 'ro, 'env, IO>( + eval_builder: tvix_eval::EvaluationBuilder<'co, 'ro, 'env, IO>, nix_search_path: &Option<String>, -) { - eval.nix_path = nix_search_path - .as_ref() - .map(|p| format!("nix=/__corepkgs__:{}", p)) - .or_else(|| Some("nix=/__corepkgs__".to_string())); +) -> tvix_eval::EvaluationBuilder<'co, 'ro, 'env, IO> { + eval_builder.nix_path( + nix_search_path + .as_ref() + .map(|p| format!("nix=/__corepkgs__:{}", p)) + .or_else(|| Some("nix=/__corepkgs__".to_string())), + ) } diff --git a/tvix/glue/src/tests/mod.rs b/tvix/glue/src/tests/mod.rs index 5b474be73657..142ae1a07714 100644 --- a/tvix/glue/src/tests/mod.rs +++ b/tvix/glue/src/tests/mod.rs @@ -47,16 +47,18 @@ fn eval_test(code_path: PathBuf, expect_success: bool) { tokio_runtime.handle().clone(), )); // Wrap with TvixIO, so <nix/fetchurl.nix can be imported. - let mut eval = tvix_eval::Evaluation::new( - Box::new(TvixIO::new(tvix_store_io.clone() as Rc<dyn EvalIO>)) as Box<dyn EvalIO>, - true, - ); - - eval.strict = true; - add_derivation_builtins(&mut eval, tvix_store_io.clone()); - add_fetcher_builtins(&mut eval, tvix_store_io.clone()); - add_import_builtins(&mut eval, tvix_store_io.clone()); - configure_nix_path(&mut eval, &None); + let mut eval_builder = tvix_eval::Evaluation::builder(Box::new(TvixIO::new( + tvix_store_io.clone() as Rc<dyn EvalIO>, + )) as Box<dyn EvalIO>) + .enable_import() + .strict(); + + eval_builder = add_derivation_builtins(eval_builder, Rc::clone(&tvix_store_io)); + eval_builder = add_fetcher_builtins(eval_builder, Rc::clone(&tvix_store_io)); + eval_builder = add_import_builtins(eval_builder, tvix_store_io); + eval_builder = configure_nix_path(eval_builder, &None); + + let eval = eval_builder.build(); let result = eval.evaluate(code, Some(code_path.clone())); let failed = match result.value { diff --git a/tvix/glue/src/tvix_store_io.rs b/tvix/glue/src/tvix_store_io.rs index 4e5488067f8f..e8c09af0ec4c 100644 --- a/tvix/glue/src/tvix_store_io.rs +++ b/tvix/glue/src/tvix_store_io.rs @@ -653,11 +653,13 @@ mod tests { Arc::<DummyBuildService>::default(), tokio_runtime.handle().clone(), )); - let mut eval = tvix_eval::Evaluation::new(io.clone() as Rc<dyn EvalIO>, true); - add_derivation_builtins(&mut eval, io.clone()); - add_fetcher_builtins(&mut eval, io.clone()); - add_import_builtins(&mut eval, io); + let mut eval_builder = + tvix_eval::Evaluation::builder(io.clone() as Rc<dyn EvalIO>).enable_import(); + eval_builder = add_derivation_builtins(eval_builder, Rc::clone(&io)); + eval_builder = add_fetcher_builtins(eval_builder, Rc::clone(&io)); + eval_builder = add_import_builtins(eval_builder, io); + let eval = eval_builder.build(); // run the evaluation itself. eval.evaluate(str, None) diff --git a/tvix/serde/examples/nixpkgs.rs b/tvix/serde/examples/nixpkgs.rs index ad8c4160b5d0..e3c589dba118 100644 --- a/tvix/serde/examples/nixpkgs.rs +++ b/tvix/serde/examples/nixpkgs.rs @@ -23,8 +23,8 @@ fn main() { } "#; - let result = tvix_serde::from_str_with_config::<Config, _>(code, |eval| { - eval.enable_impure(None); + let result = tvix_serde::from_str_with_config::<Config, _>(code, |eval_builder| { + eval_builder.enable_impure(None) }); match result { diff --git a/tvix/serde/src/de.rs b/tvix/serde/src/de.rs index 428b9e2e8114..88cb46d618ce 100644 --- a/tvix/serde/src/de.rs +++ b/tvix/serde/src/de.rs @@ -3,8 +3,7 @@ use bstr::ByteSlice; use serde::de::value::{MapDeserializer, SeqDeserializer}; use serde::de::{self, EnumAccess, VariantAccess}; -pub use tvix_eval::Evaluation; -use tvix_eval::{EvalIO, Value}; +use tvix_eval::{EvalIO, EvaluationBuilder, Value}; use crate::error::Error; @@ -36,7 +35,7 @@ pub fn from_str<'code, T>(src: &'code str) -> Result<T, Error> where T: serde::Deserialize<'code>, { - from_str_with_config(src, |_| /* no extra config */ ()) + from_str_with_config(src, |b| /* no extra config */ b) } /// Evaluate the Nix code in `src`, with extra configuration for the @@ -44,13 +43,12 @@ where pub fn from_str_with_config<'code, T, F>(src: &'code str, config: F) -> Result<T, Error> where T: serde::Deserialize<'code>, - F: FnOnce(&mut Evaluation<Box<dyn EvalIO>>), + F: for<'co, 'ro, 'env> FnOnce( + EvaluationBuilder<'co, 'ro, 'env, Box<dyn EvalIO>>, + ) -> EvaluationBuilder<'co, 'ro, 'env, Box<dyn EvalIO>>, { // First step is to evaluate the Nix code ... - let mut eval = Evaluation::new_pure(); - config(&mut eval); - - eval.strict = true; + let eval = config(EvaluationBuilder::new_pure().strict()).build(); let result = eval.evaluate(src, None); if !result.errors.is_empty() { diff --git a/tvix/serde/src/de_tests.rs b/tvix/serde/src/de_tests.rs index 1c3acd1c2f52..fea7c8290bd7 100644 --- a/tvix/serde/src/de_tests.rs +++ b/tvix/serde/src/de_tests.rs @@ -204,7 +204,7 @@ fn deserialize_enum_all() { fn deserialize_with_config() { let result: String = from_str_with_config("builtins.testWithConfig", |eval| { // Add a literal string builtin that just returns `"ok"`. - eval.src_builtins.push(("testWithConfig", "\"ok\"")); + eval.add_src_builtin("testWithConfig", "\"ok\"") }) .expect("should deserialize"); @@ -237,7 +237,7 @@ fn deserialize_with_extra_builtin() { let code = "builtins.prependHello \"world\""; let result: String = from_str_with_config(code, |eval| { - eval.builtins.append(&mut test_builtins::builtins()); + eval.add_builtins(test_builtins::builtins()) }) .expect("should deserialize"); diff --git a/web/tvixbolt/src/lib.rs b/web/tvixbolt/src/lib.rs index 1f47b0eaf59d..87627619d79d 100644 --- a/web/tvixbolt/src/lib.rs +++ b/web/tvixbolt/src/lib.rs @@ -265,18 +265,19 @@ fn eval(model: &Model) -> Output { return out; } - let mut eval = tvix_eval::Evaluation::new_pure(); - let source = eval.source_map(); + let mut eval_builder = tvix_eval::Evaluation::builder_pure(); + let source = eval_builder.source_map().clone(); let result = { let mut compiler_observer = DisassemblingObserver::new(source.clone(), &mut out.bytecode); - eval.compiler_observer = Some(&mut compiler_observer); + eval_builder.set_compiler_observer(Some(&mut compiler_observer)); let mut runtime_observer = TracingObserver::new(&mut out.trace); if model.trace { - eval.runtime_observer = Some(&mut runtime_observer); + eval_builder.set_runtime_observer(Some(&mut runtime_observer)); } + let eval = eval_builder.build(); eval.evaluate(&model.code, Some("/nixbolt".into())) }; |