tvix_cli/
lib.rs

1use std::path::PathBuf;
2use std::rc::Rc;
3use std::str::FromStr;
4
5use rustc_hash::FxHashMap;
6use smol_str::SmolStr;
7use std::fmt::Write;
8use tracing::instrument;
9use tvix_eval::{
10    builtins::impure_builtins,
11    observer::{DisassemblingObserver, TracingObserver},
12    ErrorKind, EvalIO, EvalMode, GlobalsMap, SourceCode, Value,
13};
14use tvix_glue::{
15    builtins::{add_derivation_builtins, add_import_builtins},
16    configure_nix_path,
17    tvix_io::TvixIO,
18    tvix_store_io::TvixStoreIO,
19};
20use tvix_simstore::simulated_store_builtins;
21
22pub mod args;
23pub mod assignment;
24pub mod repl;
25
26pub use args::Args;
27pub use repl::Repl;
28
29pub fn init_io_handle(args: &Args) -> Rc<TvixStoreIO> {
30    // TODO(tazjin): ugly for now, but this is temporary while we drop the old
31    // store, this whole function will go away probably.
32    let mut simstore = tvix_simstore::SimulatedStoreIO::default();
33    if let (Some(nix_path), Some(store_dir)) = (args.nix_path(), simstore.store_dir()) {
34        let search_path =
35            tvix_eval::NixSearchPath::from_str(&nix_path).expect("NIX_PATH was invalid");
36        for entry in search_path.get_entries() {
37            let path = entry.get_path();
38            if !path.starts_with(&store_dir) {
39                continue;
40            }
41
42            simstore
43                .add_passthru(&path.to_string_lossy(), path.to_path_buf())
44                .expect("setting passthru failed");
45        }
46    }
47
48    Rc::new(TvixStoreIO::new(simstore))
49}
50
51#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
52pub enum AllowIncomplete {
53    Allow,
54    #[default]
55    RequireComplete,
56}
57
58impl AllowIncomplete {
59    fn allow(&self) -> bool {
60        matches!(self, Self::Allow)
61    }
62}
63
64#[derive(Debug, Clone, Copy, PartialEq, Eq)]
65pub struct IncompleteInput;
66
67pub struct EvalResult {
68    value: Option<Value>,
69    globals: Rc<GlobalsMap>,
70}
71
72/// Interprets the given code snippet, printing out warnings and errors and returning the result
73#[allow(clippy::too_many_arguments)]
74pub fn evaluate(
75    tvix_store_io: Rc<TvixStoreIO>,
76    code: &str,
77    path: Option<PathBuf>,
78    args: &Args,
79    allow_incomplete: AllowIncomplete,
80    env: Option<&FxHashMap<SmolStr, Value>>,
81    globals: Option<Rc<GlobalsMap>>,
82    source_map: Option<SourceCode>,
83) -> Result<EvalResult, IncompleteInput> {
84    let mut eval_builder = tvix_eval::Evaluation::builder(Rc::new(TvixIO::new(
85        tvix_store_io.clone() as Rc<dyn EvalIO>,
86    )) as Rc<dyn EvalIO>)
87    .enable_import()
88    .env(env);
89
90    if args.strict {
91        eval_builder = eval_builder.mode(EvalMode::Strict);
92    }
93
94    match globals {
95        Some(globals) => {
96            eval_builder = eval_builder.with_globals(globals);
97        }
98        None => {
99            eval_builder = eval_builder.add_builtins(impure_builtins());
100            eval_builder = eval_builder.add_builtins(simulated_store_builtins());
101            eval_builder = add_derivation_builtins(eval_builder, Rc::clone(&tvix_store_io));
102            // eval_builder = add_fetcher_builtins(eval_builder, Rc::clone(&tvix_store_io));
103            eval_builder = add_import_builtins(eval_builder, Rc::clone(&tvix_store_io));
104        }
105    };
106    eval_builder = configure_nix_path(eval_builder, &args.nix_path());
107
108    if let Some(source_map) = source_map {
109        eval_builder = eval_builder.with_source_map(source_map);
110    }
111
112    let source_map = eval_builder.source_map().clone();
113    let (result, globals) = {
114        let mut compiler_observer =
115            DisassemblingObserver::new(source_map.clone(), std::io::stderr());
116        if args.dump_bytecode {
117            eval_builder.set_compiler_observer(Some(&mut compiler_observer));
118        }
119
120        let mut runtime_observer = TracingObserver::new(std::io::stderr());
121        if args.trace_runtime {
122            if args.trace_runtime_timing {
123                runtime_observer.enable_timing()
124            }
125            eval_builder.set_runtime_observer(Some(&mut runtime_observer));
126        }
127
128        let eval = eval_builder.build();
129        let globals = eval.globals();
130        let result = eval.evaluate(code, path);
131        (result, globals)
132    };
133
134    if allow_incomplete.allow()
135        && result.errors.iter().any(|err| {
136            matches!(
137                &err.kind,
138                ErrorKind::ParseErrors(pes)
139                    if pes.iter().any(|pe| matches!(pe, rnix::parser::ParseError::UnexpectedEOF))
140            )
141        })
142    {
143        return Err(IncompleteInput);
144    }
145
146    if args.display_ast {
147        if let Some(ref expr) = result.expr {
148            eprintln!("AST: {}", tvix_eval::pretty_print_expr(expr));
149        }
150    }
151
152    for error in &result.errors {
153        error.fancy_format_stderr();
154    }
155
156    if !args.no_warnings {
157        for warning in &result.warnings {
158            warning.fancy_format_stderr(&source_map);
159        }
160    }
161
162    if let Some(dumpdir) = &args.drv_dumpdir {
163        // Dump all known derivations files to `dumpdir`.
164        std::fs::create_dir_all(dumpdir).expect("failed to create drv dumpdir");
165        tvix_store_io
166            .known_paths
167            .borrow()
168            .get_derivations()
169            // Skip already dumped derivations.
170            .filter(|(drv_path, _)| !dumpdir.join(drv_path.to_string()).exists())
171            .for_each(|(drv_path, drv)| {
172                std::fs::write(dumpdir.join(drv_path.to_string()), drv.to_aterm_bytes())
173                    .expect("failed to write drv to dumpdir");
174            })
175    }
176
177    Ok(EvalResult {
178        globals,
179        value: result.value,
180    })
181}
182
183pub struct InterpretResult {
184    output: String,
185    success: bool,
186    pub(crate) globals: Option<Rc<GlobalsMap>>,
187}
188
189impl InterpretResult {
190    pub fn empty_success(globals: Option<Rc<GlobalsMap>>) -> Self {
191        Self {
192            output: String::new(),
193            success: true,
194            globals,
195        }
196    }
197
198    pub fn finalize(self) -> bool {
199        print!("{}", self.output);
200        self.success
201    }
202
203    pub fn output(&self) -> &str {
204        &self.output
205    }
206
207    pub fn success(&self) -> bool {
208        self.success
209    }
210}
211
212/// Interprets the given code snippet, printing out warnings, errors
213/// and the result itself. The return value indicates whether
214/// evaluation succeeded.
215#[instrument(skip_all, fields(indicatif.pb_show=tracing::field::Empty))]
216#[allow(clippy::too_many_arguments)]
217pub fn interpret(
218    tvix_store_io: Rc<TvixStoreIO>,
219    code: &str,
220    path: Option<PathBuf>,
221    args: &Args,
222    explain: bool,
223    allow_incomplete: AllowIncomplete,
224    env: Option<&FxHashMap<SmolStr, Value>>,
225    globals: Option<Rc<GlobalsMap>>,
226    source_map: Option<SourceCode>,
227) -> Result<InterpretResult, IncompleteInput> {
228    let mut output = String::new();
229    let result = evaluate(
230        tvix_store_io,
231        code,
232        path,
233        args,
234        allow_incomplete,
235        env,
236        globals,
237        source_map,
238    )?;
239
240    if let Some(value) = result.value.as_ref() {
241        if explain {
242            writeln!(&mut output, "=> {}", value.explain()).unwrap();
243        } else if args.raw {
244            writeln!(&mut output, "{}", value.to_contextful_str().unwrap()).unwrap();
245        } else {
246            writeln!(&mut output, "=> {} :: {}", value, value.type_of()).unwrap();
247        }
248    }
249
250    // inform the caller about any errors
251    Ok(InterpretResult {
252        output,
253        success: result.value.is_some(),
254        globals: Some(result.globals),
255    })
256}