2022-09-18 21:59:59 +02:00
|
|
|
use clap::Parser;
|
2022-08-11 23:27:02 +02:00
|
|
|
use rustyline::{error::ReadlineError, Editor};
|
2024-01-16 05:31:20 +01:00
|
|
|
use std::rc::Rc;
|
|
|
|
use std::{fs, path::PathBuf};
|
2024-02-14 02:56:31 +01:00
|
|
|
use tracing::Level;
|
|
|
|
use tracing_subscriber::fmt::writer::MakeWriterExt;
|
|
|
|
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt};
|
2024-04-26 16:19:10 +02:00
|
|
|
use tracing_subscriber::{EnvFilter, Layer};
|
2024-02-13 17:05:50 +01:00
|
|
|
use tvix_build::buildservice;
|
2024-01-16 05:31:20 +01:00
|
|
|
use tvix_eval::builtins::impure_builtins;
|
2022-12-09 11:49:36 +01:00
|
|
|
use tvix_eval::observer::{DisassemblingObserver, TracingObserver};
|
2024-01-16 05:31:20 +01:00
|
|
|
use tvix_eval::{EvalIO, Value};
|
2024-02-19 16:17:13 +01:00
|
|
|
use tvix_glue::builtins::add_fetcher_builtins;
|
2024-01-17 07:45:55 +01:00
|
|
|
use tvix_glue::builtins::add_import_builtins;
|
2024-01-16 05:31:20 +01:00
|
|
|
use tvix_glue::tvix_io::TvixIO;
|
2023-11-03 12:34:37 +01:00
|
|
|
use tvix_glue::tvix_store_io::TvixStoreIO;
|
2024-01-16 05:31:20 +01:00
|
|
|
use tvix_glue::{builtins::add_derivation_builtins, configure_nix_path};
|
2022-08-04 15:43:51 +02:00
|
|
|
|
2022-09-18 21:59:59 +02:00
|
|
|
#[derive(Parser)]
|
|
|
|
struct Args {
|
2024-02-14 02:56:31 +01:00
|
|
|
#[arg(long)]
|
|
|
|
log_level: Option<Level>,
|
|
|
|
|
2022-09-18 21:59:59 +02:00
|
|
|
/// Path to a script to evaluate
|
|
|
|
script: Option<PathBuf>,
|
|
|
|
|
2022-10-11 01:41:42 +02:00
|
|
|
#[clap(long, short = 'E')]
|
|
|
|
expr: Option<String>,
|
2022-12-09 11:29:08 +01:00
|
|
|
|
2022-12-09 11:49:36 +01:00
|
|
|
/// Dump the raw AST to stdout before interpreting
|
|
|
|
#[clap(long, env = "TVIX_DISPLAY_AST")]
|
|
|
|
display_ast: bool,
|
|
|
|
|
|
|
|
/// Dump the bytecode to stdout before evaluating
|
|
|
|
#[clap(long, env = "TVIX_DUMP_BYTECODE")]
|
|
|
|
dump_bytecode: bool,
|
|
|
|
|
|
|
|
/// Trace the runtime of the VM
|
|
|
|
#[clap(long, env = "TVIX_TRACE_RUNTIME")]
|
|
|
|
trace_runtime: bool,
|
|
|
|
|
2024-01-30 19:53:20 +01:00
|
|
|
/// Capture the time (relative to the start time of evaluation) of all events traced with
|
|
|
|
/// `--trace-runtime`
|
|
|
|
#[clap(long, env = "TVIX_TRACE_RUNTIME_TIMING", requires("trace_runtime"))]
|
|
|
|
trace_runtime_timing: bool,
|
|
|
|
|
2023-01-05 10:56:19 +01:00
|
|
|
/// Only compile, but do not execute code. This will make Tvix act
|
|
|
|
/// sort of like a linter.
|
|
|
|
#[clap(long)]
|
|
|
|
compile_only: bool,
|
|
|
|
|
2023-05-25 10:50:13 +02:00
|
|
|
/// Don't print warnings.
|
|
|
|
#[clap(long)]
|
|
|
|
no_warnings: bool,
|
|
|
|
|
2022-12-09 11:33:06 +01:00
|
|
|
/// A colon-separated list of directories to use to resolve `<...>`-style paths
|
|
|
|
#[clap(long, short = 'I', env = "NIX_PATH")]
|
|
|
|
nix_search_path: Option<String>,
|
|
|
|
|
2022-12-09 11:29:08 +01:00
|
|
|
/// Print "raw" (unquoted) output.
|
|
|
|
#[clap(long)]
|
|
|
|
raw: bool,
|
2023-03-17 22:15:03 +01:00
|
|
|
|
|
|
|
/// Strictly evaluate values, traversing them and forcing e.g.
|
|
|
|
/// elements of lists and attribute sets before printing the
|
|
|
|
/// return value.
|
|
|
|
#[clap(long)]
|
|
|
|
strict: bool,
|
2023-12-31 13:28:51 +01:00
|
|
|
|
|
|
|
#[arg(long, env, default_value = "memory://")]
|
|
|
|
blob_service_addr: String,
|
|
|
|
|
|
|
|
#[arg(long, env, default_value = "memory://")]
|
|
|
|
directory_service_addr: String,
|
|
|
|
|
|
|
|
#[arg(long, env, default_value = "memory://")]
|
|
|
|
path_info_service_addr: String,
|
2024-02-13 17:05:50 +01:00
|
|
|
|
|
|
|
#[arg(long, env, default_value = "dummy://")]
|
|
|
|
build_service_addr: String,
|
2023-12-31 13:28:51 +01:00
|
|
|
}
|
|
|
|
|
2024-04-19 21:42:17 +02:00
|
|
|
fn init_io_handle(tokio_runtime: &tokio::runtime::Runtime, args: &Args) -> Rc<TvixStoreIO> {
|
2024-05-10 07:59:25 +02:00
|
|
|
let (blob_service, directory_service, path_info_service, nar_calculation_service) =
|
|
|
|
tokio_runtime
|
|
|
|
.block_on({
|
|
|
|
let blob_service_addr = args.blob_service_addr.clone();
|
|
|
|
let directory_service_addr = args.directory_service_addr.clone();
|
|
|
|
let path_info_service_addr = args.path_info_service_addr.clone();
|
|
|
|
async move {
|
|
|
|
tvix_store::utils::construct_services(
|
|
|
|
blob_service_addr,
|
|
|
|
directory_service_addr,
|
|
|
|
path_info_service_addr,
|
|
|
|
)
|
|
|
|
.await
|
|
|
|
}
|
|
|
|
})
|
|
|
|
.expect("unable to setup {blob|directory|pathinfo}service before interpreter setup");
|
2023-05-14 20:21:27 +02:00
|
|
|
|
2024-02-13 17:05:50 +01:00
|
|
|
let build_service = tokio_runtime
|
|
|
|
.block_on({
|
|
|
|
let blob_service = blob_service.clone();
|
|
|
|
let directory_service = directory_service.clone();
|
|
|
|
async move {
|
|
|
|
buildservice::from_addr(
|
|
|
|
&args.build_service_addr,
|
|
|
|
blob_service.clone(),
|
|
|
|
directory_service.clone(),
|
|
|
|
)
|
|
|
|
.await
|
|
|
|
}
|
|
|
|
})
|
|
|
|
.expect("unable to setup buildservice before interpreter setup");
|
|
|
|
|
2024-04-19 21:42:17 +02:00
|
|
|
Rc::new(TvixStoreIO::new(
|
2024-01-16 05:31:20 +01:00
|
|
|
blob_service.clone(),
|
|
|
|
directory_service.clone(),
|
|
|
|
path_info_service.into(),
|
2024-05-10 07:59:25 +02:00
|
|
|
nar_calculation_service.into(),
|
2024-02-13 17:05:50 +01:00
|
|
|
build_service.into(),
|
2023-12-26 02:03:05 +01:00
|
|
|
tokio_runtime.handle().clone(),
|
2024-04-19 21:42:17 +02:00
|
|
|
))
|
|
|
|
}
|
2024-01-16 05:31:20 +01:00
|
|
|
|
2024-04-19 21:42:17 +02:00
|
|
|
/// Interprets the given code snippet, printing out warnings, errors
|
|
|
|
/// and the result itself. The return value indicates whether
|
|
|
|
/// evaluation succeeded.
|
|
|
|
fn interpret(
|
|
|
|
tvix_store_io: Rc<TvixStoreIO>,
|
|
|
|
code: &str,
|
|
|
|
path: Option<PathBuf>,
|
|
|
|
args: &Args,
|
|
|
|
explain: bool,
|
|
|
|
) -> bool {
|
2024-01-16 05:31:20 +01:00
|
|
|
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());
|
2024-02-20 17:04:33 +01:00
|
|
|
add_derivation_builtins(&mut eval, Rc::clone(&tvix_store_io));
|
|
|
|
add_fetcher_builtins(&mut eval, Rc::clone(&tvix_store_io));
|
2024-01-17 07:45:55 +01:00
|
|
|
add_import_builtins(&mut eval, tvix_store_io);
|
2024-01-16 05:31:20 +01:00
|
|
|
configure_nix_path(&mut eval, &args.nix_search_path);
|
2023-03-04 01:43:59 +01:00
|
|
|
|
2022-12-09 11:16:01 +01:00
|
|
|
let source_map = eval.source_map();
|
2022-12-09 11:49:36 +01:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
|
|
|
let mut runtime_observer = TracingObserver::new(std::io::stderr());
|
|
|
|
if args.trace_runtime {
|
2024-01-30 19:53:20 +01:00
|
|
|
if args.trace_runtime_timing {
|
|
|
|
runtime_observer.enable_timing()
|
|
|
|
}
|
2022-12-09 11:49:36 +01:00
|
|
|
eval.runtime_observer = Some(&mut runtime_observer);
|
|
|
|
}
|
|
|
|
|
2023-12-30 21:36:48 +01:00
|
|
|
eval.evaluate(code, path)
|
2022-12-09 11:49:36 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
if args.display_ast {
|
|
|
|
if let Some(ref expr) = result.expr {
|
|
|
|
eprintln!("AST: {}", tvix_eval::pretty_print_expr(expr));
|
|
|
|
}
|
|
|
|
}
|
2022-12-08 22:19:22 +01:00
|
|
|
|
|
|
|
for error in &result.errors {
|
2024-02-20 09:38:33 +01:00
|
|
|
error.fancy_format_stderr();
|
2022-12-08 22:19:22 +01:00
|
|
|
}
|
|
|
|
|
2023-05-25 10:50:13 +02:00
|
|
|
if !args.no_warnings {
|
|
|
|
for warning in &result.warnings {
|
|
|
|
warning.fancy_format_stderr(&source_map);
|
|
|
|
}
|
2022-12-08 22:19:22 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
if let Some(value) = result.value.as_ref() {
|
2022-12-19 10:58:39 +01:00
|
|
|
if explain {
|
|
|
|
println!("=> {}", value.explain());
|
|
|
|
} else {
|
|
|
|
println_result(value, args.raw);
|
|
|
|
}
|
2022-12-08 22:19:22 +01:00
|
|
|
}
|
2022-10-11 01:41:42 +02:00
|
|
|
|
2022-12-08 22:19:22 +01:00
|
|
|
// inform the caller about any errors
|
|
|
|
result.errors.is_empty()
|
2022-09-18 21:59:59 +02:00
|
|
|
}
|
|
|
|
|
2023-01-05 10:56:19 +01:00
|
|
|
/// 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 {
|
2023-12-30 21:36:48 +01:00
|
|
|
let mut eval = tvix_eval::Evaluation::new_impure();
|
2023-03-17 22:15:03 +01:00
|
|
|
eval.strict = args.strict;
|
|
|
|
|
2023-01-05 10:56:19 +01:00
|
|
|
let source_map = eval.source_map();
|
|
|
|
|
|
|
|
let mut compiler_observer = DisassemblingObserver::new(source_map.clone(), std::io::stderr());
|
|
|
|
|
|
|
|
if args.dump_bytecode {
|
|
|
|
eval.compiler_observer = Some(&mut compiler_observer);
|
|
|
|
}
|
|
|
|
|
|
|
|
if args.trace_runtime {
|
|
|
|
eprintln!("warning: --trace-runtime has no effect with --compile-only!");
|
|
|
|
}
|
|
|
|
|
2023-12-30 21:36:48 +01:00
|
|
|
let result = eval.compile_only(code, path);
|
2023-01-05 10:56:19 +01:00
|
|
|
|
|
|
|
if args.display_ast {
|
|
|
|
if let Some(ref expr) = result.expr {
|
|
|
|
eprintln!("AST: {}", tvix_eval::pretty_print_expr(expr));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
for error in &result.errors {
|
2024-02-20 09:38:33 +01:00
|
|
|
error.fancy_format_stderr();
|
2023-01-05 10:56:19 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
for warning in &result.warnings {
|
|
|
|
warning.fancy_format_stderr(&source_map);
|
|
|
|
}
|
|
|
|
|
|
|
|
// inform the caller about any errors
|
|
|
|
result.errors.is_empty()
|
|
|
|
}
|
|
|
|
|
2022-08-04 15:29:38 +02:00
|
|
|
fn main() {
|
2022-09-18 21:59:59 +02:00
|
|
|
let args = Args::parse();
|
2022-08-04 15:43:51 +02:00
|
|
|
|
2024-02-14 02:56:31 +01:00
|
|
|
// configure log settings
|
|
|
|
let level = args.log_level.unwrap_or(Level::INFO);
|
|
|
|
|
|
|
|
let subscriber = tracing_subscriber::registry().with(
|
|
|
|
tracing_subscriber::fmt::Layer::new()
|
|
|
|
.with_writer(std::io::stderr.with_max_level(level))
|
2024-04-26 16:19:10 +02:00
|
|
|
.compact()
|
|
|
|
.with_filter(
|
|
|
|
EnvFilter::builder()
|
|
|
|
.with_default_directive(level.into())
|
|
|
|
.from_env()
|
|
|
|
.expect("invalid RUST_LOG"),
|
|
|
|
),
|
2024-02-14 02:56:31 +01:00
|
|
|
);
|
|
|
|
subscriber
|
|
|
|
.try_init()
|
|
|
|
.expect("unable to set up tracing subscriber");
|
|
|
|
|
2024-04-19 21:42:17 +02:00
|
|
|
let tokio_runtime = tokio::runtime::Runtime::new().expect("failed to setup tokio runtime");
|
|
|
|
|
|
|
|
let io_handle = init_io_handle(&tokio_runtime, &args);
|
|
|
|
|
2022-12-09 11:29:08 +01:00
|
|
|
if let Some(file) = &args.script {
|
2024-04-19 21:42:17 +02:00
|
|
|
run_file(io_handle, file.clone(), &args)
|
2022-12-09 11:29:08 +01:00
|
|
|
} else if let Some(expr) = &args.expr {
|
2024-04-19 21:42:17 +02:00
|
|
|
if !interpret(io_handle, expr, None, &args, false) {
|
2022-12-08 22:19:22 +01:00
|
|
|
std::process::exit(1);
|
2022-10-11 01:41:42 +02:00
|
|
|
}
|
2022-08-04 15:43:51 +02:00
|
|
|
} else {
|
2024-04-19 21:42:17 +02:00
|
|
|
run_prompt(io_handle, &args)
|
2022-08-04 15:43:51 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-04-19 21:42:17 +02:00
|
|
|
fn run_file(io_handle: Rc<TvixStoreIO>, mut path: PathBuf, args: &Args) {
|
2022-10-08 20:22:26 +02:00
|
|
|
if path.is_dir() {
|
|
|
|
path.push("default.nix");
|
|
|
|
}
|
|
|
|
let contents = fs::read_to_string(&path).expect("failed to read the input file");
|
2022-12-08 22:19:22 +01:00
|
|
|
|
2023-01-05 10:56:19 +01:00
|
|
|
let success = if args.compile_only {
|
|
|
|
lint(&contents, Some(path), args)
|
|
|
|
} else {
|
2024-04-19 21:42:17 +02:00
|
|
|
interpret(io_handle, &contents, Some(path), args, false)
|
2023-01-05 10:56:19 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
if !success {
|
2022-12-08 22:19:22 +01:00
|
|
|
std::process::exit(1);
|
2022-08-11 23:27:02 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-11-25 23:47:39 +01:00
|
|
|
fn println_result(result: &Value, raw: bool) {
|
|
|
|
if raw {
|
fix(tvix): Represent strings as byte arrays
C++ nix uses C-style zero-terminated char pointers to represent strings
internally - however, up to this point, tvix has used Rust `String` and
`str` for string values. Since those are required to be valid utf-8, we
haven't been able to properly represent all the string values that Nix
supports.
To fix that, this change converts the internal representation of the
NixString struct from `Box<str>` to `BString`, from the `bstr` crate -
this is a wrapper around a `Vec<u8>` with extra functions for treating
that byte vector as a "morally string-like" value, which is basically
exactly what we need.
Since this changes a pretty fundamental assumption about a pretty core
type, there are a *lot* of changes in a lot of places to make this work,
but I've tried to keep the general philosophy and intent of most of the
code in most places intact. Most notably, there's nothing that's been
done to make the derivation stuff in //tvix/glue work with non-utf8
strings everywhere, instead opting to just convert to String/str when
passing things into that - there *might* be something to be done there,
but I don't know what the rules should be and I don't want to figure
them out in this change.
To deal with OS-native paths in a way that also works in WASM for
tvixbolt, this also adds a dependency on the "os_str_bytes" crate.
Fixes: b/189
Fixes: b/337
Change-Id: I5e6eb29c62f47dd91af954f5e12bfc3d186f5526
Reviewed-on: https://cl.tvl.fyi/c/depot/+/10200
Reviewed-by: tazjin <tazjin@tvl.su>
Reviewed-by: flokli <flokli@flokli.de>
Reviewed-by: sterni <sternenseemann@systemli.org>
Autosubmit: aspen <root@gws.fyi>
Tested-by: BuildkiteCI
2023-12-05 23:25:52 +01:00
|
|
|
println!("{}", result.to_contextful_str().unwrap())
|
2022-11-25 23:47:39 +01:00
|
|
|
} else {
|
|
|
|
println!("=> {} :: {}", result, result.type_of())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-08-11 23:27:02 +02:00
|
|
|
fn state_dir() -> Option<PathBuf> {
|
|
|
|
let mut path = dirs::data_dir();
|
2022-08-14 01:51:09 +02:00
|
|
|
if let Some(p) = path.as_mut() {
|
|
|
|
p.push("tvix")
|
|
|
|
}
|
2022-08-11 23:27:02 +02:00
|
|
|
path
|
2022-08-04 15:43:51 +02:00
|
|
|
}
|
|
|
|
|
2024-04-19 21:42:17 +02:00
|
|
|
fn run_prompt(io_handle: Rc<TvixStoreIO>, args: &Args) {
|
2022-08-11 23:27:02 +02:00
|
|
|
let mut rl = Editor::<()>::new().expect("should be able to launch rustyline");
|
|
|
|
|
2023-01-05 10:56:19 +01:00
|
|
|
if args.compile_only {
|
|
|
|
eprintln!("warning: `--compile-only` has no effect on REPL usage!");
|
|
|
|
}
|
|
|
|
|
2022-08-11 23:27:02 +02:00
|
|
|
let history_path = match state_dir() {
|
2022-08-14 19:08:15 +02:00
|
|
|
// Attempt to set up these paths, but do not hard fail if it
|
|
|
|
// doesn't work.
|
2022-08-11 23:27:02 +02:00
|
|
|
Some(mut path) => {
|
2022-08-26 18:41:56 +02:00
|
|
|
let _ = std::fs::create_dir_all(&path);
|
2022-08-11 23:27:02 +02:00
|
|
|
path.push("history.txt");
|
2022-08-26 18:41:56 +02:00
|
|
|
let _ = rl.load_history(&path);
|
2022-08-11 23:27:02 +02:00
|
|
|
Some(path)
|
|
|
|
}
|
|
|
|
|
|
|
|
None => None,
|
|
|
|
};
|
2022-08-04 15:43:51 +02:00
|
|
|
|
|
|
|
loop {
|
2022-08-11 23:27:02 +02:00
|
|
|
let readline = rl.readline("tvix-repl> ");
|
|
|
|
match readline {
|
|
|
|
Ok(line) => {
|
|
|
|
if line.is_empty() {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2022-09-05 04:12:36 +02:00
|
|
|
rl.add_history_entry(&line);
|
2022-12-19 10:58:39 +01:00
|
|
|
|
|
|
|
if let Some(without_prefix) = line.strip_prefix(":d ") {
|
2024-04-19 21:42:17 +02:00
|
|
|
interpret(Rc::clone(&io_handle), without_prefix, None, args, true);
|
2022-12-19 10:58:39 +01:00
|
|
|
} else {
|
2024-04-19 21:42:17 +02:00
|
|
|
interpret(Rc::clone(&io_handle), &line, None, args, false);
|
2022-12-19 10:58:39 +01:00
|
|
|
}
|
2022-08-11 23:27:02 +02:00
|
|
|
}
|
|
|
|
Err(ReadlineError::Interrupted) | Err(ReadlineError::Eof) => break,
|
|
|
|
|
|
|
|
Err(err) => {
|
|
|
|
eprintln!("error: {}", err);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2022-08-04 15:43:51 +02:00
|
|
|
}
|
|
|
|
|
2022-08-11 23:27:02 +02:00
|
|
|
if let Some(path) = history_path {
|
|
|
|
rl.save_history(&path).unwrap();
|
2022-08-04 15:43:51 +02:00
|
|
|
}
|
2022-08-04 15:29:38 +02:00
|
|
|
}
|