about summary refs log tree commit diff
diff options
context:
space:
mode:
authorFlorian Klink <flokli@flokli.de>2023-01-04T12·38+0100
committerflokli <flokli@flokli.de>2023-01-04T21·58+0000
commitcc626d686cceed84e45d21bf32514a3a3f8e2b11 (patch)
tree8a211983e216149207a240713ff95ab0a662d173
parent407a9cd90f3a1ea3bb0cf4ced85cfacb29881b0c (diff)
feat(tvix/derivation): implement Derivation::validate() r/5591
Change-Id: I87dfadda872439e108e5f678a5da63dd5b1915d1
Reviewed-on: https://cl.tvl.fyi/c/depot/+/7732
Reviewed-by: tazjin <tazjin@tvl.su>
Tested-by: BuildkiteCI
-rw-r--r--tvix/Cargo.lock1
-rw-r--r--tvix/Cargo.nix83
-rw-r--r--tvix/derivation/Cargo.toml1
-rw-r--r--tvix/derivation/src/lib.rs4
-rw-r--r--tvix/derivation/src/output.rs6
-rw-r--r--tvix/derivation/src/tests/mod.rs10
-rw-r--r--tvix/derivation/src/validate.rs104
7 files changed, 207 insertions, 2 deletions
diff --git a/tvix/Cargo.lock b/tvix/Cargo.lock
index 9cc9ffb6d1..477b6173da 100644
--- a/tvix/Cargo.lock
+++ b/tvix/Cargo.lock
@@ -492,6 +492,7 @@ checksum = "23d8666cb01533c39dde32bcbab8e227b4ed6679b2c925eba05feabea39508fb"
 name = "derivation"
 version = "0.1.0"
 dependencies = [
+ "anyhow",
  "glob",
  "serde",
  "serde_json",
diff --git a/tvix/Cargo.nix b/tvix/Cargo.nix
index 3511c5e742..963b5dfae5 100644
--- a/tvix/Cargo.nix
+++ b/tvix/Cargo.nix
@@ -1155,6 +1155,33 @@ rec {
           "rustc-hash" = [ "dep:rustc-hash" ];
         };
       };
+      "cpufeatures" = rec {
+        crateName = "cpufeatures";
+        version = "0.2.5";
+        edition = "2018";
+        sha256 = "08535izlz4kx8z1kkcp0gy80gqk7k19dqiiysj6r5994bsyrgn98";
+        authors = [
+          "RustCrypto Developers"
+        ];
+        dependencies = [
+          {
+            name = "libc";
+            packageId = "libc";
+            target = { target, features }: (pkgs.rust.lib.toRustTarget stdenv.hostPlatform == "aarch64-apple-darwin");
+          }
+          {
+            name = "libc";
+            packageId = "libc";
+            target = { target, features }: (pkgs.rust.lib.toRustTarget stdenv.hostPlatform == "aarch64-linux-android");
+          }
+          {
+            name = "libc";
+            packageId = "libc";
+            target = { target, features }: (("aarch64" == target."arch") && ("linux" == target."os"));
+          }
+        ];
+
+      };
       "criterion" = rec {
         crateName = "criterion";
         version = "0.4.0";
@@ -1474,6 +1501,10 @@ rec {
           else ./derivation;
         dependencies = [
           {
+            name = "anyhow";
+            packageId = "anyhow";
+          }
+          {
             name = "glob";
             packageId = "glob";
           }
@@ -1482,6 +1513,14 @@ rec {
             packageId = "serde";
             features = [ "derive" ];
           }
+          {
+            name = "sha2";
+            packageId = "sha2";
+          }
+          {
+            name = "tvix-store";
+            packageId = "tvix-store";
+          }
         ];
         devDependencies = [
           {
@@ -1489,6 +1528,10 @@ rec {
             packageId = "serde_json";
           }
           {
+            name = "test-case";
+            packageId = "test-case";
+          }
+          {
             name = "test-generator";
             packageId = "test-generator";
           }
@@ -4775,6 +4818,46 @@ rec {
         };
         resolvedDefaultFeatures = [ "default" "std" ];
       };
+      "sha2" = rec {
+        crateName = "sha2";
+        version = "0.10.6";
+        edition = "2018";
+        sha256 = "1h5xrrv2y06kr1gsz4pwrm3lsp206nm2gjxgbf21wfrfzsavgrl2";
+        authors = [
+          "RustCrypto Developers"
+        ];
+        dependencies = [
+          {
+            name = "cfg-if";
+            packageId = "cfg-if";
+          }
+          {
+            name = "cpufeatures";
+            packageId = "cpufeatures";
+            target = { target, features }: (("aarch64" == target."arch") || ("x86_64" == target."arch") || ("x86" == target."arch"));
+          }
+          {
+            name = "digest";
+            packageId = "digest";
+          }
+        ];
+        devDependencies = [
+          {
+            name = "digest";
+            packageId = "digest";
+            features = [ "dev" ];
+          }
+        ];
+        features = {
+          "asm" = [ "sha2-asm" ];
+          "asm-aarch64" = [ "asm" ];
+          "default" = [ "std" ];
+          "oid" = [ "digest/oid" ];
+          "sha2-asm" = [ "dep:sha2-asm" ];
+          "std" = [ "digest/std" ];
+        };
+        resolvedDefaultFeatures = [ "default" "std" ];
+      };
       "sharded-slab" = rec {
         crateName = "sharded-slab";
         version = "0.1.4";
diff --git a/tvix/derivation/Cargo.toml b/tvix/derivation/Cargo.toml
index ec998c4465..b63b3ab9fd 100644
--- a/tvix/derivation/Cargo.toml
+++ b/tvix/derivation/Cargo.toml
@@ -6,6 +6,7 @@ edition = "2021"
 # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
 
 [dependencies]
+anyhow = "1.0.68"
 glob = "0.3.0"
 serde = { version = "1.0", features = ["derive"] }
 sha2 = "0.10.6"
diff --git a/tvix/derivation/src/lib.rs b/tvix/derivation/src/lib.rs
index a8360fbafc..f0a654e111 100644
--- a/tvix/derivation/src/lib.rs
+++ b/tvix/derivation/src/lib.rs
@@ -1,9 +1,9 @@
+mod derivation;
 mod nix_hash;
 mod output;
 mod string_escape;
+mod validate;
 mod write;
 
-mod derivation;
-
 #[cfg(test)]
 mod tests;
diff --git a/tvix/derivation/src/output.rs b/tvix/derivation/src/output.rs
index 0d668c3e99..2839110971 100644
--- a/tvix/derivation/src/output.rs
+++ b/tvix/derivation/src/output.rs
@@ -1,4 +1,5 @@
 use serde::{Deserialize, Serialize};
+use tvix_store::nixpath::NixPath;
 
 #[derive(Serialize, Deserialize)]
 pub struct Output {
@@ -20,4 +21,9 @@ impl Output {
     pub fn is_fixed(&self) -> bool {
         self.hash.is_some()
     }
+
+    pub fn validate(&self) -> anyhow::Result<()> {
+        NixPath::from_absolute_path(&self.path)?;
+        Ok(())
+    }
 }
diff --git a/tvix/derivation/src/tests/mod.rs b/tvix/derivation/src/tests/mod.rs
index 435d821440..1b55c6e3e7 100644
--- a/tvix/derivation/src/tests/mod.rs
+++ b/tvix/derivation/src/tests/mod.rs
@@ -31,6 +31,16 @@ fn check_serizaliation(path_to_drv_file: &str) {
 }
 
 #[test_resources("src/tests/derivation_tests/*.drv")]
+fn validate(path_to_drv_file: &str) {
+    let data = read_file(&format!("{}.json", path_to_drv_file));
+    let derivation: Derivation = serde_json::from_str(&data).expect("JSON was not well-formatted");
+
+    derivation
+        .validate()
+        .expect("derivation failed to validate")
+}
+
+#[test_resources("src/tests/derivation_tests/*.drv")]
 fn check_to_string(path_to_drv_file: &str) {
     let data = read_file(&format!("{}.json", path_to_drv_file));
     let derivation: Derivation = serde_json::from_str(&data).expect("JSON was not well-formatted");
diff --git a/tvix/derivation/src/validate.rs b/tvix/derivation/src/validate.rs
new file mode 100644
index 0000000000..e0f62a323f
--- /dev/null
+++ b/tvix/derivation/src/validate.rs
@@ -0,0 +1,104 @@
+use crate::{derivation::Derivation, write::DOT_FILE_EXT};
+use anyhow::bail;
+use tvix_store::nixpath::NixPath;
+
+impl Derivation {
+    /// validate ensures a Derivation struct is properly populated,
+    /// and returns an error if not.
+    /// TODO(flokli): make this proper errors
+    pub fn validate(&self) -> anyhow::Result<()> {
+        // Ensure the number of outputs is > 1
+        if self.outputs.is_empty() {
+            bail!("0 outputs");
+        }
+
+        // Validate all outputs
+        for (output_name, output) in &self.outputs {
+            if output_name.is_empty() {
+                bail!("output_name from outputs may not be empty")
+            }
+
+            if output.is_fixed() {
+                if self.outputs.len() != 1 {
+                    bail!("encountered fixed-output, but there's more than 1 output in total");
+                }
+                if output_name != "out" {
+                    bail!("the fixed-output output name must be called 'out'");
+                }
+
+                break;
+            }
+
+            output.validate()?;
+        }
+
+        // Validate all input_derivations
+        for (input_derivation_path, output_names) in &self.input_derivations {
+            // Validate input_derivation_path
+            NixPath::from_absolute_path(input_derivation_path)?;
+            if !input_derivation_path.ends_with(DOT_FILE_EXT) {
+                bail!(
+                    "derivation {} does not end with .drv",
+                    input_derivation_path
+                );
+            }
+
+            if output_names.is_empty() {
+                bail!(
+                    "output_names list for {} may not be empty",
+                    input_derivation_path
+                );
+            }
+
+            for (i, output_name) in output_names.iter().enumerate() {
+                if output_name.is_empty() {
+                    bail!(
+                        "output name entry for {} may not be empty",
+                        input_derivation_path
+                    )
+                }
+                // if i is at least 1, peek at the previous element to ensure output_names are sorted.
+                if i > 0 && (output_names[i - 1] >= *output_name) {
+                    bail!(
+                        "invalid input derivation output order: {} < {}",
+                        output_name,
+                        output_names[i - 1],
+                    );
+                }
+            }
+        }
+
+        // Validate all input_sources
+        for (i, input_source) in self.input_sources.iter().enumerate() {
+            NixPath::from_absolute_path(input_source)?;
+
+            if i > 0 && self.input_sources[i - 1] >= *input_source {
+                bail!(
+                    "invalid input source order: {} < {}",
+                    input_source,
+                    self.input_sources[i - 1],
+                );
+            }
+        }
+
+        // validate platform
+        if self.system.is_empty() {
+            bail!("required attribute 'platform' missing");
+        }
+
+        // validate builder
+        if self.builder.is_empty() {
+            bail!("required attribute 'builder' missing");
+        }
+
+        // validate env, none of the keys may be empty.
+        // We skip the `name` validation seen in go-nix.
+        for k in self.environment.keys() {
+            if k.is_empty() {
+                bail!("found empty environment variable key");
+            }
+        }
+
+        Ok(())
+    }
+}