db70c672cf
Change-Id: I8aa878dee009901feb453c489ce37c12fa3a31a8 Reviewed-on: https://cl.tvl.fyi/c/depot/+/7026 Autosubmit: sterni <sternenseemann@systemli.org> Reviewed-by: Adam Joseph <adam@westernsemico.com> Reviewed-by: tazjin <tazjin@tvl.su> Tested-by: BuildkiteCI
157 lines
5.2 KiB
Rust
157 lines
5.2 KiB
Rust
use std::{
|
|
cell::RefCell,
|
|
collections::{BTreeMap, HashMap},
|
|
env,
|
|
fs::File,
|
|
io::{self, Read},
|
|
rc::Rc,
|
|
time::{SystemTime, UNIX_EPOCH},
|
|
};
|
|
|
|
use crate::{
|
|
errors::ErrorKind,
|
|
observer::NoOpObserver,
|
|
value::{Builtin, NixAttrs, NixString, Thunk},
|
|
vm::VM,
|
|
SourceCode, Value,
|
|
};
|
|
|
|
fn impure_builtins() -> Vec<Builtin> {
|
|
vec![
|
|
Builtin::new("getEnv", &[true], |args: Vec<Value>, _: &mut VM| {
|
|
Ok(env::var(args[0].to_str()?)
|
|
.unwrap_or_else(|_| "".into())
|
|
.into())
|
|
}),
|
|
Builtin::new("pathExists", &[true], |args: Vec<Value>, vm: &mut VM| {
|
|
Ok(super::coerce_value_to_path(&args[0], vm)?.exists().into())
|
|
}),
|
|
Builtin::new("readDir", &[true], |args: Vec<Value>, vm: &mut VM| {
|
|
let path = super::coerce_value_to_path(&args[0], vm)?;
|
|
let mk_err = |err: io::Error| ErrorKind::IO {
|
|
path: Some(path.clone()),
|
|
error: Rc::new(err),
|
|
};
|
|
|
|
let mut res = BTreeMap::new();
|
|
for entry in path.read_dir().map_err(mk_err)? {
|
|
let entry = entry.map_err(mk_err)?;
|
|
let file_type = entry
|
|
.metadata()
|
|
.map_err(|err| ErrorKind::IO {
|
|
path: Some(entry.path()),
|
|
error: Rc::new(err),
|
|
})?
|
|
.file_type();
|
|
let val = if file_type.is_dir() {
|
|
"directory"
|
|
} else if file_type.is_file() {
|
|
"regular"
|
|
} else if file_type.is_symlink() {
|
|
"symlink"
|
|
} else {
|
|
"unknown"
|
|
};
|
|
res.insert(
|
|
entry.file_name().to_string_lossy().as_ref().into(),
|
|
val.into(),
|
|
);
|
|
}
|
|
Ok(Value::attrs(NixAttrs::from_map(res)))
|
|
}),
|
|
Builtin::new("readFile", &[true], |args: Vec<Value>, vm: &mut VM| {
|
|
let mut buf = String::new();
|
|
File::open(&super::coerce_value_to_path(&args[0], vm)?)?.read_to_string(&mut buf)?;
|
|
Ok(buf.into())
|
|
}),
|
|
]
|
|
}
|
|
|
|
/// Return all impure builtins, that is all builtins which may perform I/O
|
|
/// outside of the VM and so cannot be used in all contexts (e.g. WASM).
|
|
pub(super) fn builtins() -> BTreeMap<NixString, Value> {
|
|
let mut map: BTreeMap<NixString, Value> = impure_builtins()
|
|
.into_iter()
|
|
.map(|b| (b.name().into(), Value::Builtin(b)))
|
|
.collect();
|
|
|
|
// currentTime pins the time at which evaluation was started
|
|
{
|
|
let seconds = match SystemTime::now().duration_since(UNIX_EPOCH) {
|
|
Ok(dur) => dur.as_secs() as i64,
|
|
|
|
// This case is hit if the system time is *before* epoch.
|
|
Err(err) => -(err.duration().as_secs() as i64),
|
|
};
|
|
|
|
map.insert(NixString::from("currentTime"), Value::Integer(seconds));
|
|
}
|
|
|
|
map
|
|
}
|
|
|
|
/// Constructs and inserts 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.
|
|
pub fn builtins_import(
|
|
globals: Rc<RefCell<HashMap<&'static str, Value>>>,
|
|
source: SourceCode,
|
|
) -> Builtin {
|
|
Builtin::new(
|
|
"import",
|
|
&[true],
|
|
move |mut args: Vec<Value>, vm: &mut VM| {
|
|
let mut path = super::coerce_value_to_path(&args.pop().unwrap(), vm)?;
|
|
if path.is_dir() {
|
|
path.push("default.nix");
|
|
}
|
|
|
|
let contents =
|
|
std::fs::read_to_string(&path).map_err(|err| ErrorKind::ReadFileError {
|
|
path: path.clone(),
|
|
error: Rc::new(err),
|
|
})?;
|
|
|
|
let parsed = rnix::ast::Root::parse(&contents);
|
|
let errors = parsed.errors();
|
|
|
|
let file = source.add_file(path.to_string_lossy().to_string(), contents);
|
|
|
|
if !errors.is_empty() {
|
|
return Err(ErrorKind::ImportParseError {
|
|
path,
|
|
file,
|
|
errors: errors.to_vec(),
|
|
});
|
|
}
|
|
|
|
let result = crate::compile(
|
|
&parsed.tree().expr().unwrap(),
|
|
Some(path.clone()),
|
|
file,
|
|
globals.clone(),
|
|
&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 {
|
|
vm.push_warning(warning);
|
|
}
|
|
|
|
// Compilation succeeded, we can construct a thunk from whatever it spat
|
|
// out and return that.
|
|
Ok(Value::Thunk(Thunk::new(result.lambda, vm.current_span())))
|
|
},
|
|
)
|
|
}
|