Right now `builtins.hashFile` always reads the entire file into memory before hashing, which is not ideal for large files. This replaces `read_to_string` with `open_file` which allows calculating the hash of the file without buffering it entirely into memory. Other callers can continue to buffer into memory if they choose, but they still use the `open_file` VM request and then call `read_to_string` or `read_to_end` on the `std::io::Reader`. Fixes b/380 Change-Id: Ifa1c8324bcee8f751604b0b449feab875c632fda Reviewed-on: https://cl.tvl.fyi/c/depot/+/11236 Reviewed-by: flokli <flokli@flokli.de> Tested-by: BuildkiteCI
120 lines
3.8 KiB
Rust
120 lines
3.8 KiB
Rust
//! This module implements the Nix language's `import` feature, which
|
|
//! is exposed as a builtin in the Nix language.
|
|
//!
|
|
//! This is not a typical builtin, as it needs access to internal
|
|
//! compiler and VM state (such as the [`crate::SourceCode`]
|
|
//! instance, or observers).
|
|
|
|
use super::GlobalsMap;
|
|
use genawaiter::rc::Gen;
|
|
use std::rc::Weak;
|
|
|
|
use crate::{
|
|
builtins::coerce_value_to_path,
|
|
generators::pin_generator,
|
|
observer::NoOpObserver,
|
|
value::{Builtin, Thunk},
|
|
vm::generators::{self, GenCo},
|
|
ErrorKind, SourceCode, Value,
|
|
};
|
|
|
|
async fn import_impl(
|
|
co: GenCo,
|
|
globals: Weak<GlobalsMap>,
|
|
source: SourceCode,
|
|
mut args: Vec<Value>,
|
|
) -> Result<Value, ErrorKind> {
|
|
// TODO(sterni): canon_path()?
|
|
let mut path = match coerce_value_to_path(&co, args.pop().unwrap()).await? {
|
|
Err(cek) => return Ok(Value::Catchable(Box::new(cek))),
|
|
Ok(path) => path,
|
|
};
|
|
|
|
if path.is_dir() {
|
|
path.push("default.nix");
|
|
}
|
|
|
|
if let Some(cached) = generators::request_import_cache_lookup(&co, path.clone()).await {
|
|
return Ok(cached);
|
|
}
|
|
|
|
let mut reader = generators::request_open_file(&co, path.clone()).await;
|
|
// We read to a String instead of a Vec<u8> because rnix only supports
|
|
// string source files.
|
|
let mut contents = String::new();
|
|
reader.read_to_string(&mut contents)?;
|
|
|
|
let parsed = rnix::ast::Root::parse(&contents);
|
|
let errors = parsed.errors();
|
|
let file = source.add_file(path.to_string_lossy().to_string(), contents.to_owned());
|
|
|
|
if !errors.is_empty() {
|
|
return Err(ErrorKind::ImportParseError {
|
|
path,
|
|
file,
|
|
errors: errors.to_vec(),
|
|
});
|
|
}
|
|
|
|
let result = crate::compiler::compile(
|
|
&parsed.tree().expr().unwrap(),
|
|
Some(path.clone()),
|
|
// The VM must ensure that a strong reference to the globals outlives
|
|
// any self-references (which are weak) embedded within the globals. If
|
|
// the expect() below panics, it means that did not happen.
|
|
globals
|
|
.upgrade()
|
|
.expect("globals dropped while still in use"),
|
|
&source,
|
|
&file,
|
|
&mut NoOpObserver::default(),
|
|
)
|
|
.map_err(|err| ErrorKind::ImportCompilerError {
|
|
path: path.clone(),
|
|
errors: vec![err],
|
|
})?;
|
|
|
|
if !result.errors.is_empty() {
|
|
return Err(ErrorKind::ImportCompilerError {
|
|
path,
|
|
errors: result.errors,
|
|
});
|
|
}
|
|
|
|
for warning in result.warnings {
|
|
generators::emit_warning(&co, warning).await;
|
|
}
|
|
|
|
// Compilation succeeded, we can construct a thunk from whatever it spat
|
|
// out and return that.
|
|
let res = Value::Thunk(Thunk::new_suspended(
|
|
result.lambda,
|
|
generators::request_span(&co).await,
|
|
));
|
|
|
|
generators::request_import_cache_put(&co, path, res.clone()).await;
|
|
|
|
Ok(res)
|
|
}
|
|
|
|
/// Constructs the `import` builtin. This builtin is special in that
|
|
/// it needs to capture the [crate::SourceCode] structure to correctly
|
|
/// track source code locations while invoking a compiler.
|
|
// TODO: need to be able to pass through a CompilationObserver, too.
|
|
// TODO: can the `SourceCode` come from the compiler?
|
|
pub(super) fn builtins_import(globals: &Weak<GlobalsMap>, source: SourceCode) -> Builtin {
|
|
// This (very cheap, once-per-compiler-startup) clone exists
|
|
// solely in order to keep the borrow checker happy. It
|
|
// resolves the tension between the requirements of
|
|
// Rc::new_cyclic() and Builtin::new()
|
|
let globals = globals.clone();
|
|
|
|
Builtin::new(
|
|
"import",
|
|
Some("Import the given file and return the Nix value it evaluates to"),
|
|
1,
|
|
move |args| {
|
|
Gen::new(|co| pin_generator(import_impl(co, globals.clone(), source.clone(), args)))
|
|
},
|
|
)
|
|
}
|