refactor(tvix/eval): Builderize Evaluation

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>
This commit is contained in:
Aspen Smith 2024-07-05 20:29:41 -04:00 committed by clbot
parent d5964c1d54
commit dfe137786c
15 changed files with 325 additions and 154 deletions

View file

@ -167,26 +167,25 @@ fn evaluate(
span.pb_set_style(&tvix_tracing::PB_SPINNER_STYLE); span.pb_set_style(&tvix_tracing::PB_SPINNER_STYLE);
span.pb_set_message("Setting up evaluator…"); span.pb_set_message("Setting up evaluator…");
let mut eval = tvix_eval::Evaluation::new( let mut eval_builder = tvix_eval::Evaluation::builder(Box::new(TvixIO::new(
Box::new(TvixIO::new(tvix_store_io.clone() as Rc<dyn EvalIO>)) as Box<dyn EvalIO>, tvix_store_io.clone() as Rc<dyn EvalIO>,
true, )) as Box<dyn EvalIO>)
); .enable_import()
eval.strict = args.strict; .with_strict(args.strict)
eval.builtins.extend(impure_builtins()); .add_builtins(impure_builtins())
if let Some(env) = env { .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(); 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 result = {
let mut compiler_observer = let mut compiler_observer =
DisassemblingObserver::new(source_map.clone(), std::io::stderr()); DisassemblingObserver::new(source_map.clone(), std::io::stderr());
if args.dump_bytecode { 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()); let mut runtime_observer = TracingObserver::new(std::io::stderr());
@ -194,10 +193,12 @@ fn evaluate(
if args.trace_runtime_timing { if args.trace_runtime_timing {
runtime_observer.enable_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…"); span.pb_set_message("Evaluating…");
let eval = eval_builder.build();
eval.evaluate(code, path) eval.evaluate(code, path)
}; };
@ -262,21 +263,21 @@ fn interpret(
/// Interpret the given code snippet, but only run the Tvix compiler /// Interpret the given code snippet, but only run the Tvix compiler
/// on it and return errors and warnings. /// on it and return errors and warnings.
fn lint(code: &str, path: Option<PathBuf>, args: &Args) -> bool { fn lint(code: &str, path: Option<PathBuf>, args: &Args) -> bool {
let mut eval = tvix_eval::Evaluation::new_impure(); let mut eval_builder = tvix_eval::Evaluation::builder_impure().with_strict(args.strict);
eval.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()); let mut compiler_observer = DisassemblingObserver::new(source_map.clone(), std::io::stderr());
if args.dump_bytecode { if args.dump_bytecode {
eval.compiler_observer = Some(&mut compiler_observer); eval_builder.set_compiler_observer(Some(&mut compiler_observer));
} }
if args.trace_runtime { if args.trace_runtime {
eprintln!("warning: --trace-runtime has no effect with --compile-only!"); eprintln!("warning: --trace-runtime has no effect with --compile-only!");
} }
let eval = eval_builder.build();
let result = eval.compile_only(code, path); let result = eval.compile_only(code, path);
if args.display_ast { if args.display_ast {

View file

@ -8,7 +8,9 @@ use tikv_jemallocator::Jemalloc;
static GLOBAL: Jemalloc = Jemalloc; static GLOBAL: Jemalloc = Jemalloc;
fn interpret(code: &str) { 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) { fn eval_literals(c: &mut Criterion) {

View file

@ -64,6 +64,192 @@ pub use crate::value::{Builtin, CoercionKind, NixAttrs, NixList, NixString, Valu
#[cfg(feature = "impure")] #[cfg(feature = "impure")]
pub use crate::io::StdIO; 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 /// 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 /// instantiated and configured directly, or it can be accessed through the
/// various simplified helper methods available below. /// 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 /// This defaults to all pure builtins. Users might want to add
/// the set of impure builtins, or other custom builtins. /// 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 /// Set of builtins that are implemented in Nix itself and should
/// be compiled and inserted in the builtins set. /// 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 /// 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 /// Implementation of file-IO to use during evaluation, e.g. for
/// impure builtins. /// impure builtins.
/// ///
/// Defaults to [`DummyIO`] if not set explicitly. /// Defaults to [`DummyIO`] if not set explicitly.
pub io_handle: IO, io_handle: IO,
/// Determines whether the `import` builtin should be made /// Determines whether the `import` builtin should be made
/// available. Note that this depends on the `io_handle` being /// available. Note that this depends on the `io_handle` being
/// able to read the files specified as arguments to `import`. /// 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 /// Determines whether the returned value should be strictly
/// evaluated, that is whether its list and attribute set elements /// evaluated, that is whether its list and attribute set elements
/// should be forced recursively. /// should be forced recursively.
pub strict: bool, strict: bool,
/// (optional) Nix search path, e.g. the value of `NIX_PATH` used /// (optional) Nix search path, e.g. the value of `NIX_PATH` used
/// for resolving items on the search path (such as `<nixpkgs>`). /// 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 /// (optional) compiler observer for reporting on compilation
/// details, like the emitted bytecode. /// 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 /// (optional) runtime observer, for reporting on execution steps
/// of Nix code. /// 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 /// 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>, pub expr: Option<rnix::ast::Expr>,
} }
impl<'co, 'ro, 'env, IO> Evaluation<'co, 'ro, 'env, IO> impl<'co, 'ro, 'env, IO> Evaluation<'co, 'ro, 'env, IO> {
where pub fn builder(io_handle: IO) -> EvaluationBuilder<'co, 'ro, 'env, IO> {
IO: AsRef<dyn EvalIO> + 'static, EvaluationBuilder::new(io_handle)
{
/// 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> Evaluation<'co, 'ro, 'env, Box<dyn EvalIO>> { impl<'co, 'ro, 'env> Evaluation<'co, 'ro, 'env, Box<dyn EvalIO>> {
/// Initialize an `Evaluation`, without the import statement available, and #[cfg(feature = "impure")]
/// all IO operations stubbed out. pub fn builder_impure() -> EvaluationBuilder<'co, 'ro, 'env, Box<dyn EvalIO>> {
pub fn new_pure() -> Self { EvaluationBuilder::new_impure()
Self::new(Box::new(DummyIO) as Box<dyn EvalIO>, false)
} }
#[cfg(feature = "impure")] pub fn builder_pure() -> EvaluationBuilder<'co, 'ro, 'env, Box<dyn EvalIO>> {
/// Configure an `Evaluation` to have impure features available EvaluationBuilder::new_pure()
/// 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();
}
}
#[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
} }
} }

View file

@ -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 code = std::fs::read_to_string(&code_path).expect("should be able to read test code");
let mut eval = crate::Evaluation::new_impure(); let eval = crate::Evaluation::builder_impure()
eval.strict = true; .strict()
eval.builtins.extend(mock_builtins::builtins()); .add_builtins(mock_builtins::builtins())
.build();
let result = eval.evaluate(code, Some(code_path.clone())); let result = eval.evaluate(code, Some(code_path.clone()));
let failed = match result.value { 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 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); let eval = crate::Evaluation::builder(Box::new(crate::StdIO) as Box<dyn EvalIO>)
eval.strict = true; .disable_import()
.strict()
.build();
let result = eval.evaluate(&code, None); let result = eval.evaluate(&code, None);
assert!( assert!(

View file

@ -5,8 +5,9 @@ fn test_source_builtin() {
// Test an evaluation with a source-only builtin. The test ensures // Test an evaluation with a source-only builtin. The test ensures
// that the artificially constructed thunking is correct. // that the artificially constructed thunking is correct.
let mut eval = Evaluation::new_pure(); let eval = Evaluation::builder_pure()
eval.src_builtins.push(("testSourceBuiltin", "42")); .add_src_builtin("testSourceBuiltin", "42")
.build();
let result = eval.evaluate("builtins.testSourceBuiltin", None); let result = eval.evaluate("builtins.testSourceBuiltin", None);
assert!( assert!(
@ -25,7 +26,9 @@ fn test_source_builtin() {
#[test] #[test]
fn skip_broken_bytecode() { 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); assert_eq!(result.errors.len(), 1);

View file

@ -58,10 +58,16 @@ fn nix_eval(expr: &str, strictness: Strictness) -> String {
#[track_caller] #[track_caller]
#[cfg(feature = "impure")] #[cfg(feature = "impure")]
fn compare_eval(expr: &str, strictness: Strictness) { fn compare_eval(expr: &str, strictness: Strictness) {
use tvix_eval::EvalIO;
let nix_result = nix_eval(expr, strictness); let nix_result = nix_eval(expr, strictness);
let mut eval = tvix_eval::Evaluation::new_pure(); let mut eval_builder = tvix_eval::Evaluation::builder_pure();
eval.strict = matches!(strictness, Strictness::Strict); if matches!(strictness, Strictness::Strict) {
eval.io_handle = Box::new(tvix_eval::StdIO); 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 let tvix_result = eval
.evaluate(expr, None) .evaluate(expr, None)

View file

@ -40,22 +40,23 @@ fn interpret(code: &str) {
TOKIO_RUNTIME.handle().clone(), TOKIO_RUNTIME.handle().clone(),
)); ));
let mut eval = tvix_eval::Evaluation::new( let mut eval_builder = tvix_eval::Evaluation::builder(Box::new(TvixIO::new(
Box::new(TvixIO::new(tvix_store_io.clone() as Rc<dyn EvalIO>)) as Box<dyn EvalIO>, tvix_store_io.clone() as Rc<dyn EvalIO>,
true, )) as Box<dyn EvalIO>)
); .enable_import()
.add_builtins(impure_builtins());
eval.builtins.extend(impure_builtins()); eval_builder = add_derivation_builtins(eval_builder, Rc::clone(&tvix_store_io));
add_derivation_builtins(&mut eval, Rc::clone(&tvix_store_io)); eval_builder = add_fetcher_builtins(eval_builder, Rc::clone(&tvix_store_io));
add_fetcher_builtins(&mut eval, Rc::clone(&tvix_store_io)); eval_builder = add_import_builtins(eval_builder, tvix_store_io);
add_import_builtins(&mut eval, tvix_store_io); eval_builder = configure_nix_path(
configure_nix_path( eval_builder,
&mut eval,
// The benchmark requires TVIX_BENCH_NIX_PATH to be set, so barf out // The benchmark requires TVIX_BENCH_NIX_PATH to be set, so barf out
// early, rather than benchmarking tvix returning an error. // early, rather than benchmarking tvix returning an error.
&Some(env::var("TVIX_BENCH_NIX_PATH").expect("TVIX_BENCH_NIX_PATH must be set")), &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); let result = eval.evaluate(code, None);
assert!(result.errors.is_empty()); assert!(result.errors.is_empty());

View file

@ -18,13 +18,14 @@ pub use errors::{DerivationError, FetcherError, ImportError};
/// ///
/// As they need to interact with `known_paths`, we also need to pass in /// As they need to interact with `known_paths`, we also need to pass in
/// `known_paths`. /// `known_paths`.
pub fn add_derivation_builtins<IO>(eval: &mut tvix_eval::Evaluation<IO>, io: Rc<TvixStoreIO>) { pub fn add_derivation_builtins<'co, 'ro, 'env, IO>(
eval.builtins eval_builder: tvix_eval::EvaluationBuilder<'co, 'ro, 'env, IO>,
.extend(derivation::derivation_builtins::builtins(Rc::clone(&io))); io: Rc<TvixStoreIO>,
) -> tvix_eval::EvaluationBuilder<'co, 'ro, 'env, IO> {
// Add the actual `builtins.derivation` from compiled Nix code eval_builder
eval.src_builtins .add_builtins(derivation::derivation_builtins::builtins(Rc::clone(&io)))
.push(("derivation", include_str!("derivation.nix"))); // 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]: /// 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` /// * `fetchurl`
/// * `fetchTarball` /// * `fetchTarball`
/// * `fetchGit` /// * `fetchGit`
pub fn add_fetcher_builtins<IO>(eval: &mut tvix_eval::Evaluation<IO>, io: Rc<TvixStoreIO>) { pub fn add_fetcher_builtins<'co, 'ro, 'env, IO>(
eval.builtins eval_builder: tvix_eval::EvaluationBuilder<'co, 'ro, 'env, IO>,
.extend(fetchers::fetcher_builtins::builtins(Rc::clone(&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]. /// 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` /// These are `filterSource` and `path`
/// ///
/// As they need to interact with the store implementation, we pass [`TvixStoreIO`]. /// 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>) { pub fn add_import_builtins<'co, 'ro, 'env, IO>(
eval.builtins.extend(import::import_builtins(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) // TODO(raitobezarius): evaluate expressing filterSource as Nix code using path (b/372)
eval_builder.add_builtins(import::import_builtins(io))
} }
#[cfg(test)] #[cfg(test)]
@ -81,11 +86,11 @@ mod tests {
runtime.handle().clone(), runtime.handle().clone(),
)); ));
let mut eval = tvix_eval::Evaluation::new(io.clone() as Rc<dyn EvalIO>, false); let mut eval_builder = tvix_eval::Evaluation::builder(io.clone() as Rc<dyn EvalIO>);
eval_builder = add_derivation_builtins(eval_builder, Rc::clone(&io));
add_derivation_builtins(&mut eval, Rc::clone(&io)); eval_builder = add_fetcher_builtins(eval_builder, Rc::clone(&io));
add_fetcher_builtins(&mut eval, Rc::clone(&io)); eval_builder = add_import_builtins(eval_builder, io);
add_import_builtins(&mut eval, io); let eval = eval_builder.build();
// run the evaluation itself. // run the evaluation itself.
eval.evaluate(str, None) eval.evaluate(str, None)

View file

@ -14,12 +14,14 @@ mod tests;
/// Tell the Evaluator to resolve `<nix>` to the path `/__corepkgs__`, /// Tell the Evaluator to resolve `<nix>` to the path `/__corepkgs__`,
/// which has special handling in [tvix_io::TvixIO]. /// which has special handling in [tvix_io::TvixIO].
/// This is used in nixpkgs to import `fetchurl.nix` from `<nix>`. /// This is used in nixpkgs to import `fetchurl.nix` from `<nix>`.
pub fn configure_nix_path<IO>( pub fn configure_nix_path<'co, 'ro, 'env, IO>(
eval: &mut tvix_eval::Evaluation<IO>, eval_builder: tvix_eval::EvaluationBuilder<'co, 'ro, 'env, IO>,
nix_search_path: &Option<String>, nix_search_path: &Option<String>,
) { ) -> tvix_eval::EvaluationBuilder<'co, 'ro, 'env, IO> {
eval.nix_path = nix_search_path eval_builder.nix_path(
.as_ref() nix_search_path
.map(|p| format!("nix=/__corepkgs__:{}", p)) .as_ref()
.or_else(|| Some("nix=/__corepkgs__".to_string())); .map(|p| format!("nix=/__corepkgs__:{}", p))
.or_else(|| Some("nix=/__corepkgs__".to_string())),
)
} }

View file

@ -47,16 +47,18 @@ fn eval_test(code_path: PathBuf, expect_success: bool) {
tokio_runtime.handle().clone(), tokio_runtime.handle().clone(),
)); ));
// Wrap with TvixIO, so <nix/fetchurl.nix can be imported. // Wrap with TvixIO, so <nix/fetchurl.nix can be imported.
let mut eval = tvix_eval::Evaluation::new( let mut eval_builder = tvix_eval::Evaluation::builder(Box::new(TvixIO::new(
Box::new(TvixIO::new(tvix_store_io.clone() as Rc<dyn EvalIO>)) as Box<dyn EvalIO>, tvix_store_io.clone() as Rc<dyn EvalIO>,
true, )) as Box<dyn EvalIO>)
); .enable_import()
.strict();
eval.strict = true; eval_builder = add_derivation_builtins(eval_builder, Rc::clone(&tvix_store_io));
add_derivation_builtins(&mut eval, tvix_store_io.clone()); eval_builder = add_fetcher_builtins(eval_builder, Rc::clone(&tvix_store_io));
add_fetcher_builtins(&mut eval, tvix_store_io.clone()); eval_builder = add_import_builtins(eval_builder, tvix_store_io);
add_import_builtins(&mut eval, tvix_store_io.clone()); eval_builder = configure_nix_path(eval_builder, &None);
configure_nix_path(&mut eval, &None);
let eval = eval_builder.build();
let result = eval.evaluate(code, Some(code_path.clone())); let result = eval.evaluate(code, Some(code_path.clone()));
let failed = match result.value { let failed = match result.value {

View file

@ -653,11 +653,13 @@ mod tests {
Arc::<DummyBuildService>::default(), Arc::<DummyBuildService>::default(),
tokio_runtime.handle().clone(), 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()); let mut eval_builder =
add_fetcher_builtins(&mut eval, io.clone()); tvix_eval::Evaluation::builder(io.clone() as Rc<dyn EvalIO>).enable_import();
add_import_builtins(&mut eval, io); 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. // run the evaluation itself.
eval.evaluate(str, None) eval.evaluate(str, None)

View file

@ -23,8 +23,8 @@ fn main() {
} }
"#; "#;
let result = tvix_serde::from_str_with_config::<Config, _>(code, |eval| { let result = tvix_serde::from_str_with_config::<Config, _>(code, |eval_builder| {
eval.enable_impure(None); eval_builder.enable_impure(None)
}); });
match result { match result {

View file

@ -3,8 +3,7 @@
use bstr::ByteSlice; use bstr::ByteSlice;
use serde::de::value::{MapDeserializer, SeqDeserializer}; use serde::de::value::{MapDeserializer, SeqDeserializer};
use serde::de::{self, EnumAccess, VariantAccess}; use serde::de::{self, EnumAccess, VariantAccess};
pub use tvix_eval::Evaluation; use tvix_eval::{EvalIO, EvaluationBuilder, Value};
use tvix_eval::{EvalIO, Value};
use crate::error::Error; use crate::error::Error;
@ -36,7 +35,7 @@ pub fn from_str<'code, T>(src: &'code str) -> Result<T, Error>
where where
T: serde::Deserialize<'code>, 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 /// 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> pub fn from_str_with_config<'code, T, F>(src: &'code str, config: F) -> Result<T, Error>
where where
T: serde::Deserialize<'code>, 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 ... // First step is to evaluate the Nix code ...
let mut eval = Evaluation::new_pure(); let eval = config(EvaluationBuilder::new_pure().strict()).build();
config(&mut eval);
eval.strict = true;
let result = eval.evaluate(src, None); let result = eval.evaluate(src, None);
if !result.errors.is_empty() { if !result.errors.is_empty() {

View file

@ -204,7 +204,7 @@ fn deserialize_enum_all() {
fn deserialize_with_config() { fn deserialize_with_config() {
let result: String = from_str_with_config("builtins.testWithConfig", |eval| { let result: String = from_str_with_config("builtins.testWithConfig", |eval| {
// Add a literal string builtin that just returns `"ok"`. // Add a literal string builtin that just returns `"ok"`.
eval.src_builtins.push(("testWithConfig", "\"ok\"")); eval.add_src_builtin("testWithConfig", "\"ok\"")
}) })
.expect("should deserialize"); .expect("should deserialize");
@ -237,7 +237,7 @@ fn deserialize_with_extra_builtin() {
let code = "builtins.prependHello \"world\""; let code = "builtins.prependHello \"world\"";
let result: String = from_str_with_config(code, |eval| { 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"); .expect("should deserialize");

View file

@ -265,18 +265,19 @@ fn eval(model: &Model) -> Output {
return out; return out;
} }
let mut eval = tvix_eval::Evaluation::new_pure(); let mut eval_builder = tvix_eval::Evaluation::builder_pure();
let source = eval.source_map(); let source = eval_builder.source_map().clone();
let result = { let result = {
let mut compiler_observer = DisassemblingObserver::new(source.clone(), &mut out.bytecode); 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); let mut runtime_observer = TracingObserver::new(&mut out.trace);
if model.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())) eval.evaluate(&model.code, Some("/nixbolt".into()))
}; };