about summary refs log tree commit diff
path: root/tvix/eval/src
diff options
context:
space:
mode:
authorRyan Lahfa <tvl@lahfa.xyz>2024-03-25T00·25+0100
committerraitobezarius <tvl@lahfa.xyz>2024-04-04T17·24+0000
commite7af8e0d62603f5fdf85307dc57b2c9599aa1936 (patch)
tree57f8f4e4c5036b77c42b47549c3f966c5743d089 /tvix/eval/src
parente9a23bb4782615722374e7bec643cba669a492bc (diff)
feat(tvix/eval): implement `appendContext` r/7851
`appendContext s ctx` will just append a user-crafted context attrs to `s`.

The most important part of this builtin is to perform all the relevant invariant validations
to avoid letting the user craft invalid contexts which can never be built, e.g. invalid store paths,
inexistent derivations, etc.

This version is incomplete and full of TODOs, but passes all the Nix's
context strings tests, so we turn them on.

Change-Id: I625dc5e7c4f5b784f078b390f04b0ee5a8d65a7c
Signed-off-by: Ryan Lahfa <tvl@lahfa.xyz>
Reviewed-on: https://cl.tvl.fyi/c/depot/+/11263
Reviewed-by: flokli <flokli@flokli.de>
Tested-by: BuildkiteCI
Diffstat (limited to 'tvix/eval/src')
-rw-r--r--tvix/eval/src/builtins/mod.rs80
1 files changed, 80 insertions, 0 deletions
diff --git a/tvix/eval/src/builtins/mod.rs b/tvix/eval/src/builtins/mod.rs
index 048c8867a717..8973a25927ed 100644
--- a/tvix/eval/src/builtins/mod.rs
+++ b/tvix/eval/src/builtins/mod.rs
@@ -692,6 +692,86 @@ mod pure_builtins {
         Ok(Value::attrs(NixAttrs::from_iter(elements)))
     }
 
+    #[builtin("appendContext")]
+    #[allow(non_snake_case)]
+    async fn builtin_appendContext(
+        co: GenCo,
+        origin: Value,
+        added_context: Value,
+    ) -> Result<Value, ErrorKind> {
+        // `appendContext` is a "grow" context function.
+        // It cannot remove a context element, neither replace a piece of its contents.
+        //
+        // Growing context is always a safe operation, there's no loss of dependency tracking
+        // information.
+        //
+        // This is why this operation is not prefixed by `unsafe` and is deemed *safe*.
+        // Nonetheless, it is possible to craft nonsensical context elements referring
+        // to inexistent derivations, output paths or output names.
+        //
+        // In Nix, those nonsensical context elements are partially mitigated by checking
+        // that various parameters are indeed syntatically valid store paths in the context, i.e.
+        // starting with the same prefix as `builtins.storeDir`, or ending with `.drv`.
+        // In addition, if writing to the store is possible (evaluator not in read-only mode), Nix
+        // will realize some paths and ensures they are present in the store.
+        //
+        // In this implementation, we do none of that, no syntax checks, no realization.
+        // The next `TODO` are the checks that Nix implements.
+        let mut ctx_elements: HashSet<NixContextElement> = HashSet::new();
+        let span = generators::request_span(&co).await;
+        let origin = origin
+            .coerce_to_string(
+                co,
+                CoercionKind {
+                    strong: true,
+                    import_paths: true,
+                },
+                span,
+            )
+            .await?;
+        let mut origin = origin.to_contextful_str()?;
+
+        let added_context = added_context.to_attrs()?;
+        for (context_key, context_element) in added_context.into_iter() {
+            // Invariant checks:
+            // - TODO: context_key must be a syntactically valid store path.
+            // - Perform a deep force `context_element`.
+            let context_element = context_element.to_attrs()?;
+            if let Some(path) = context_element.select("path") {
+                if path.as_bool()? {
+                    ctx_elements.insert(NixContextElement::Plain(context_key.to_string()));
+                }
+            }
+            if let Some(all_outputs) = context_element.select("allOutputs") {
+                if all_outputs.as_bool()? {
+                    // TODO: check if `context_key` is a derivation path.
+                    // This may require realization.
+                    ctx_elements.insert(NixContextElement::Derivation(context_key.to_string()));
+                }
+            }
+            if let Some(some_outputs) = context_element.select("outputs") {
+                let some_outputs = some_outputs.to_list()?;
+                // TODO: check if `context_key` is a derivation path.
+                // This may require realization.
+                for output in some_outputs.into_iter() {
+                    let output = output.to_str()?;
+                    ctx_elements.insert(NixContextElement::Single {
+                        derivation: context_key.to_string(),
+                        name: output.to_string(),
+                    });
+                }
+            }
+        }
+
+        if let Some(origin_ctx) = origin.context_mut() {
+            // FUTUREWORK(performance): avoid this clone
+            // and extend in-place.
+            *origin_ctx = origin_ctx.clone().join(&mut ctx_elements.into());
+        }
+
+        Ok(origin.into())
+    }
+
     #[builtin("hashString")]
     #[allow(non_snake_case)]
     async fn builtin_hashString(co: GenCo, algo: Value, s: Value) -> Result<Value, ErrorKind> {