about summary refs log tree commit diff
diff options
context:
space:
mode:
authorVincent Ambo <mail@tazj.in>2023-01-22T22·44+0300
committertazjin <tazjin@tvl.su>2023-01-27T12·21+0000
commit8a9aa018dcf54f00344993c8467610be3442eb8f (patch)
treea46fad927107961cf9a7087db259efe56c7e88ff
parent3d7c371e2208c6171a70c0b0485fcd7e6ea6d47b (diff)
feat(tvix/cli): implement builtins.derivationStrict r/5768
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
-rw-r--r--tvix/cli/src/derivation.rs152
-rw-r--r--tvix/cli/src/main.rs6
2 files changed, 154 insertions, 4 deletions
diff --git a/tvix/cli/src/derivation.rs b/tvix/cli/src/derivation.rs
index 6047117991..260e27e570 100644
--- a/tvix/cli/src/derivation.rs
+++ b/tvix/cli/src/derivation.rs
@@ -1,13 +1,17 @@
 //! Implements `builtins.derivation`, the core of what makes Nix build packages.
 
+use std::cell::RefCell;
 use std::collections::{btree_map, BTreeSet};
+use std::rc::Rc;
 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::known_paths::{KnownPaths, PathType};
 
 // Constants used for strangely named fields in derivation inputs.
+const STRUCTURED_ATTRS: &str = "__structuredAttrs";
 const IGNORE_NULLS: &str = "__ignoreNulls";
 
 /// Helper function for populating the `drv.outputs` field from a
@@ -188,11 +192,155 @@ fn handle_derivation_parameters(
     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)]
 mod tests {
     use super::*;
     use tvix_eval::observer::NoOpObserver;
-    use tvix_eval::Value;
 
     static mut OBSERVER: NoOpObserver = NoOpObserver {};
 
diff --git a/tvix/cli/src/main.rs b/tvix/cli/src/main.rs
index 0150d83bc6..552b8e941c 100644
--- a/tvix/cli/src/main.rs
+++ b/tvix/cli/src/main.rs
@@ -12,7 +12,7 @@ use clap::Parser;
 use known_paths::KnownPaths;
 use rustyline::{error::ReadlineError, Editor};
 use tvix_eval::observer::{DisassemblingObserver, TracingObserver};
-use tvix_eval::Value;
+use tvix_eval::{Builtin, BuiltinArgument, Value, VM};
 
 #[derive(Parser)]
 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 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.builtins
+        .extend(derivation::derivation_builtins(known_paths));
 
     let source_map = eval.source_map();
     let result = {