feat(tvix/cli): implement builtins.derivationStrict
Implements the logic for converting an evaluator value supplied as arguments to builtins.derivationStrict into an actual, fully-functional derivation struct. This skips the implementation of structuredAttrs, which are left for a subsequent commit. Note: We will need to port some eval tests over to CLI to test this correct, which will be done in a separate commit later on. Change-Id: I0db69dcf12716180de0eb0b126e3da4683712966 Reviewed-on: https://cl.tvl.fyi/c/depot/+/7756 Reviewed-by: flokli <flokli@flokli.de> Tested-by: BuildkiteCI
This commit is contained in:
		
							parent
							
								
									3d7c371e22
								
							
						
					
					
						commit
						8a9aa018dc
					
				
					 2 changed files with 154 additions and 4 deletions
				
			
		| 
						 | 
					@ -1,13 +1,17 @@
 | 
				
			||||||
//! Implements `builtins.derivation`, the core of what makes Nix build packages.
 | 
					//! Implements `builtins.derivation`, the core of what makes Nix build packages.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use std::cell::RefCell;
 | 
				
			||||||
use std::collections::{btree_map, BTreeSet};
 | 
					use std::collections::{btree_map, BTreeSet};
 | 
				
			||||||
 | 
					use std::rc::Rc;
 | 
				
			||||||
use tvix_derivation::{Derivation, Hash};
 | 
					use tvix_derivation::{Derivation, Hash};
 | 
				
			||||||
use tvix_eval::{AddContext, CoercionKind, ErrorKind, NixList, Value, VM};
 | 
					use tvix_eval::builtin_macros::builtins;
 | 
				
			||||||
 | 
					use tvix_eval::{AddContext, CoercionKind, ErrorKind, NixAttrs, NixList, Value, VM};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use crate::errors::Error;
 | 
					use crate::errors::Error;
 | 
				
			||||||
use crate::known_paths::{KnownPaths, PathType};
 | 
					use crate::known_paths::{KnownPaths, PathType};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Constants used for strangely named fields in derivation inputs.
 | 
					// Constants used for strangely named fields in derivation inputs.
 | 
				
			||||||
 | 
					const STRUCTURED_ATTRS: &str = "__structuredAttrs";
 | 
				
			||||||
const IGNORE_NULLS: &str = "__ignoreNulls";
 | 
					const IGNORE_NULLS: &str = "__ignoreNulls";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/// Helper function for populating the `drv.outputs` field from a
 | 
					/// Helper function for populating the `drv.outputs` field from a
 | 
				
			||||||
| 
						 | 
					@ -188,11 +192,155 @@ fn handle_derivation_parameters(
 | 
				
			||||||
    Ok(true)
 | 
					    Ok(true)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[builtins(state = "Rc<RefCell<KnownPaths>>")]
 | 
				
			||||||
 | 
					mod derivation_builtins {
 | 
				
			||||||
 | 
					    use super::*;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /// Strictly construct a Nix derivation from the supplied arguments.
 | 
				
			||||||
 | 
					    ///
 | 
				
			||||||
 | 
					    /// This is considered an internal function, users usually want to
 | 
				
			||||||
 | 
					    /// use the higher-level `builtins.derivation` instead.
 | 
				
			||||||
 | 
					    #[builtin("derivationStrict")]
 | 
				
			||||||
 | 
					    fn builtin_derivation_strict(
 | 
				
			||||||
 | 
					        state: Rc<RefCell<KnownPaths>>,
 | 
				
			||||||
 | 
					        vm: &mut VM,
 | 
				
			||||||
 | 
					        input: Value,
 | 
				
			||||||
 | 
					    ) -> Result<Value, ErrorKind> {
 | 
				
			||||||
 | 
					        let input = input.to_attrs()?;
 | 
				
			||||||
 | 
					        let name = input
 | 
				
			||||||
 | 
					            .select_required("name")?
 | 
				
			||||||
 | 
					            .force(vm)?
 | 
				
			||||||
 | 
					            .to_str()
 | 
				
			||||||
 | 
					            .context("determining derivation name")?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Check whether attributes should be passed as a JSON file.
 | 
				
			||||||
 | 
					        // TODO: the JSON serialisation has to happen here.
 | 
				
			||||||
 | 
					        if let Some(sa) = input.select(STRUCTURED_ATTRS) {
 | 
				
			||||||
 | 
					            if sa.force(vm)?.as_bool()? {
 | 
				
			||||||
 | 
					                return Err(ErrorKind::NotImplemented(STRUCTURED_ATTRS));
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Check whether null attributes should be ignored or passed through.
 | 
				
			||||||
 | 
					        let ignore_nulls = match input.select(IGNORE_NULLS) {
 | 
				
			||||||
 | 
					            Some(b) => b.force(vm)?.as_bool()?,
 | 
				
			||||||
 | 
					            None => false,
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        let mut drv = Derivation::default();
 | 
				
			||||||
 | 
					        drv.outputs.insert("out".to_string(), Default::default());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Configure fixed-output derivations if required.
 | 
				
			||||||
 | 
					        populate_output_configuration(
 | 
				
			||||||
 | 
					            &mut drv,
 | 
				
			||||||
 | 
					            vm,
 | 
				
			||||||
 | 
					            input.select("outputHash"),
 | 
				
			||||||
 | 
					            input.select("outputHashAlgo"),
 | 
				
			||||||
 | 
					            input.select("outputHashMode"),
 | 
				
			||||||
 | 
					        )?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        for (name, value) in input.into_iter_sorted() {
 | 
				
			||||||
 | 
					            if ignore_nulls && matches!(*value.force(vm)?, Value::Null) {
 | 
				
			||||||
 | 
					                continue;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            let val_str = value
 | 
				
			||||||
 | 
					                .force(vm)?
 | 
				
			||||||
 | 
					                .coerce_to_string(CoercionKind::Strong, vm)?
 | 
				
			||||||
 | 
					                .as_str()
 | 
				
			||||||
 | 
					                .to_string();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // handle_derivation_parameters tells us whether the
 | 
				
			||||||
 | 
					            // argument should be added to the environment; continue
 | 
				
			||||||
 | 
					            // to the next one otherwise
 | 
				
			||||||
 | 
					            if !handle_derivation_parameters(&mut drv, vm, name.as_str(), &value, &val_str)? {
 | 
				
			||||||
 | 
					                continue;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Most of these are also added to the builder's environment in "raw" form.
 | 
				
			||||||
 | 
					            if drv
 | 
				
			||||||
 | 
					                .environment
 | 
				
			||||||
 | 
					                .insert(name.as_str().to_string(), val_str)
 | 
				
			||||||
 | 
					                .is_some()
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                return Err(Error::DuplicateEnvVar(name.as_str().to_string()).into());
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Scan references in relevant attributes to detect any build-references.
 | 
				
			||||||
 | 
					        let mut refscan = state.borrow().reference_scanner();
 | 
				
			||||||
 | 
					        drv.arguments.iter().for_each(|s| refscan.scan_str(s));
 | 
				
			||||||
 | 
					        drv.environment.values().for_each(|s| refscan.scan_str(s));
 | 
				
			||||||
 | 
					        refscan.scan_str(&drv.builder);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Each output name needs to exist in the environment, at this
 | 
				
			||||||
 | 
					        // point initialised as an empty string because that is the
 | 
				
			||||||
 | 
					        // way of Golang ;)
 | 
				
			||||||
 | 
					        for output in drv.outputs.keys() {
 | 
				
			||||||
 | 
					            if drv
 | 
				
			||||||
 | 
					                .environment
 | 
				
			||||||
 | 
					                .insert(output.to_string(), String::new())
 | 
				
			||||||
 | 
					                .is_some()
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                return Err(Error::ShadowedOutput(output.to_string()).into());
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        let mut known_paths = state.borrow_mut();
 | 
				
			||||||
 | 
					        populate_inputs(&mut drv, &known_paths, refscan.finalise());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // At this point, derivation fields are fully populated from
 | 
				
			||||||
 | 
					        // eval data structures.
 | 
				
			||||||
 | 
					        drv.validate(false).map_err(Error::InvalidDerivation)?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        let tmp_replacement_str =
 | 
				
			||||||
 | 
					            drv.calculate_drv_replacement_str(|drv| known_paths.get_replacement_string(drv));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        drv.calculate_output_paths(&name, &tmp_replacement_str)
 | 
				
			||||||
 | 
					            .map_err(Error::InvalidDerivation)?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        let actual_replacement_str =
 | 
				
			||||||
 | 
					            drv.calculate_drv_replacement_str(|drv| known_paths.get_replacement_string(drv));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        let derivation_path = drv
 | 
				
			||||||
 | 
					            .calculate_derivation_path(&name)
 | 
				
			||||||
 | 
					            .map_err(Error::InvalidDerivation)?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        known_paths
 | 
				
			||||||
 | 
					            .add_replacement_string(derivation_path.to_absolute_path(), &actual_replacement_str);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // mark all the new paths as known
 | 
				
			||||||
 | 
					        let output_names: Vec<String> = drv.outputs.keys().map(Clone::clone).collect();
 | 
				
			||||||
 | 
					        known_paths.drv(derivation_path.to_absolute_path(), &output_names);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        for (output_name, output) in &drv.outputs {
 | 
				
			||||||
 | 
					            known_paths.output(
 | 
				
			||||||
 | 
					                &output.path,
 | 
				
			||||||
 | 
					                output_name,
 | 
				
			||||||
 | 
					                derivation_path.to_absolute_path(),
 | 
				
			||||||
 | 
					            );
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        let mut new_attrs: Vec<(String, String)> = drv
 | 
				
			||||||
 | 
					            .outputs
 | 
				
			||||||
 | 
					            .into_iter()
 | 
				
			||||||
 | 
					            .map(|(name, output)| (name, output.path))
 | 
				
			||||||
 | 
					            .collect();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        new_attrs.push(("drvPath".to_string(), derivation_path.to_absolute_path()));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        Ok(Value::Attrs(Box::new(NixAttrs::from_iter(
 | 
				
			||||||
 | 
					            new_attrs.into_iter(),
 | 
				
			||||||
 | 
					        ))))
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub use derivation_builtins::builtins as derivation_builtins;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[cfg(test)]
 | 
					#[cfg(test)]
 | 
				
			||||||
mod tests {
 | 
					mod tests {
 | 
				
			||||||
    use super::*;
 | 
					    use super::*;
 | 
				
			||||||
    use tvix_eval::observer::NoOpObserver;
 | 
					    use tvix_eval::observer::NoOpObserver;
 | 
				
			||||||
    use tvix_eval::Value;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    static mut OBSERVER: NoOpObserver = NoOpObserver {};
 | 
					    static mut OBSERVER: NoOpObserver = NoOpObserver {};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -12,7 +12,7 @@ use clap::Parser;
 | 
				
			||||||
use known_paths::KnownPaths;
 | 
					use known_paths::KnownPaths;
 | 
				
			||||||
use rustyline::{error::ReadlineError, Editor};
 | 
					use rustyline::{error::ReadlineError, Editor};
 | 
				
			||||||
use tvix_eval::observer::{DisassemblingObserver, TracingObserver};
 | 
					use tvix_eval::observer::{DisassemblingObserver, TracingObserver};
 | 
				
			||||||
use tvix_eval::Value;
 | 
					use tvix_eval::{Builtin, BuiltinArgument, Value, VM};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[derive(Parser)]
 | 
					#[derive(Parser)]
 | 
				
			||||||
struct Args {
 | 
					struct Args {
 | 
				
			||||||
| 
						 | 
					@ -55,8 +55,10 @@ fn interpret(code: &str, path: Option<PathBuf>, args: &Args, explain: bool) -> b
 | 
				
			||||||
    let mut eval = tvix_eval::Evaluation::new_impure(code, path);
 | 
					    let mut eval = tvix_eval::Evaluation::new_impure(code, path);
 | 
				
			||||||
    let known_paths: Rc<RefCell<KnownPaths>> = Default::default();
 | 
					    let known_paths: Rc<RefCell<KnownPaths>> = Default::default();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    eval.io_handle = Box::new(nix_compat::NixCompatIO::new(known_paths));
 | 
					    eval.io_handle = Box::new(nix_compat::NixCompatIO::new(known_paths.clone()));
 | 
				
			||||||
    eval.nix_path = args.nix_search_path.clone();
 | 
					    eval.nix_path = args.nix_search_path.clone();
 | 
				
			||||||
 | 
					    eval.builtins
 | 
				
			||||||
 | 
					        .extend(derivation::derivation_builtins(known_paths));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    let source_map = eval.source_map();
 | 
					    let source_map = eval.source_map();
 | 
				
			||||||
    let result = {
 | 
					    let result = {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue