Fixes b/212. Based on feedback in https://cl.tvl.fyi/c/depot/+/7492, all uses of `NixAttrs::from_map` have been removed. Only `from_iter` and `from_kv` remain. Change-Id: I52e25f73018c2aa1843197427516b7a852503e2c Reviewed-on: https://cl.tvl.fyi/c/depot/+/7500 Reviewed-by: tazjin <tazjin@tvl.su> Tested-by: BuildkiteCI Autosubmit: IslandUsurper <lyle@menteeth.us>
		
			
				
	
	
		
			190 lines
		
	
	
	
		
			6.3 KiB
		
	
	
	
		
			Rust
		
	
	
	
	
	
			
		
		
	
	
			190 lines
		
	
	
	
		
			6.3 KiB
		
	
	
	
		
			Rust
		
	
	
	
	
	
| use builtin_macros::builtins;
 | |
| use std::{
 | |
|     collections::BTreeMap,
 | |
|     env,
 | |
|     fs::File,
 | |
|     io::{self, Read},
 | |
|     rc::{Rc, Weak},
 | |
|     time::{SystemTime, UNIX_EPOCH},
 | |
| };
 | |
| 
 | |
| use crate::{
 | |
|     compiler::GlobalsMap,
 | |
|     errors::ErrorKind,
 | |
|     observer::NoOpObserver,
 | |
|     value::{Builtin, BuiltinArgument, NixAttrs, Thunk},
 | |
|     vm::VM,
 | |
|     SourceCode, Value,
 | |
| };
 | |
| 
 | |
| #[builtins]
 | |
| mod impure_builtins {
 | |
|     use super::*;
 | |
|     use crate::builtins::coerce_value_to_path;
 | |
| 
 | |
|     #[builtin("getEnv")]
 | |
|     fn builtin_get_env(_: &mut VM, var: Value) -> Result<Value, ErrorKind> {
 | |
|         Ok(env::var(var.to_str()?).unwrap_or_else(|_| "".into()).into())
 | |
|     }
 | |
| 
 | |
|     #[builtin("pathExists")]
 | |
|     fn builtin_path_exists(vm: &mut VM, s: Value) -> Result<Value, ErrorKind> {
 | |
|         Ok(coerce_value_to_path(&s, vm)?.exists().into())
 | |
|     }
 | |
| 
 | |
|     #[builtin("readDir")]
 | |
|     fn builtin_read_dir(vm: &mut VM, path: Value) -> Result<Value, ErrorKind> {
 | |
|         let path = coerce_value_to_path(&path, vm)?;
 | |
|         let mk_err = |err: io::Error| ErrorKind::IO {
 | |
|             path: Some(path.clone()),
 | |
|             error: Rc::new(err),
 | |
|         };
 | |
| 
 | |
|         let res = path.read_dir().map_err(mk_err)?.into_iter().flat_map(
 | |
|             |entry| -> Result<(String, &str), ErrorKind> {
 | |
|                 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"
 | |
|                 };
 | |
|                 Ok((entry.file_name().to_string_lossy().to_string(), val))
 | |
|             },
 | |
|         );
 | |
|         Ok(Value::attrs(NixAttrs::from_iter(res)))
 | |
|     }
 | |
| 
 | |
|     #[builtin("readFile")]
 | |
|     fn builtin_read_file(vm: &mut VM, path: Value) -> Result<Value, ErrorKind> {
 | |
|         let mut buf = String::new();
 | |
|         File::open(&coerce_value_to_path(&path, 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<&'static str, Value> {
 | |
|     let mut map: BTreeMap<&'static str, Value> = impure_builtins::builtins()
 | |
|         .into_iter()
 | |
|         .map(|b| (b.name(), 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("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: &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",
 | |
|         &[BuiltinArgument {
 | |
|             strict: true,
 | |
|             name: "path",
 | |
|         }],
 | |
|         None,
 | |
|         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 current_span = vm.current_span();
 | |
|             let entry = match vm.import_cache.entry(path.clone()) {
 | |
|                 std::collections::btree_map::Entry::Occupied(oe) => return Ok(oe.get().clone()),
 | |
|                 std::collections::btree_map::Entry::Vacant(ve) => ve,
 | |
|             };
 | |
| 
 | |
|             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::compiler::compile(
 | |
|                 &parsed.tree().expr().unwrap(),
 | |
|                 Some(path.clone()),
 | |
|                 file,
 | |
|                 // 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"),
 | |
|                 &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,
 | |
|                 });
 | |
|             }
 | |
| 
 | |
|             // Compilation succeeded, we can construct a thunk from whatever it spat
 | |
|             // out and return that.
 | |
|             let res = entry
 | |
|                 .insert(Value::Thunk(Thunk::new_suspended(
 | |
|                     result.lambda,
 | |
|                     current_span,
 | |
|                 )))
 | |
|                 .clone();
 | |
| 
 | |
|             for warning in result.warnings {
 | |
|                 vm.push_warning(warning);
 | |
|             }
 | |
| 
 | |
|             Ok(res)
 | |
|         },
 | |
|     )
 | |
| }
 |