about summary refs log tree commit diff
diff options
context:
space:
mode:
authorJürgen Hahn <juergen_hahn@gmx.net>2022-12-28T09·28+0100
committerjrhahn <mail.jhahn@gmail.com>2022-12-29T14·38+0000
commitbb185b2c6eb8c4e035cddb61be772169df1b0139 (patch)
tree0598b8a1e26d98274c93f52d01b53291e59adb91
parent42fe3941c2a02b4b1eb55dad4f83941a2708f77e (diff)
feat(tvix/derivation): serialize Nix Derivation r/5536
This adds a Derivation structure and allows to write it to a structure that implements std::fmt:Write.
The implementation is based on the go-nix version.

Change-Id: Ib54e1202b5c67f5d206b21bc109a751e971064cf
Reviewed-on: https://cl.tvl.fyi/c/depot/+/7659
Reviewed-by: flokli <flokli@flokli.de>
Reviewed-by: tazjin <tazjin@tvl.su>
Tested-by: BuildkiteCI
-rw-r--r--tvix/Cargo.lock10
-rw-r--r--tvix/Cargo.nix61
-rw-r--r--tvix/Cargo.toml1
-rw-r--r--tvix/derivation/Cargo.toml19
-rw-r--r--tvix/derivation/src/lib.rs216
-rw-r--r--tvix/derivation/src/tests/derivation_tests/0hm2f1psjpcwg8fijsmr4wwxrx59s092-bar.drv1
-rw-r--r--tvix/derivation/src/tests/derivation_tests/0hm2f1psjpcwg8fijsmr4wwxrx59s092-bar.drv.json23
-rw-r--r--tvix/derivation/src/tests/derivation_tests/292w8yzv5nn7nhdpxcs8b7vby2p27s09-nested-json.drv1
-rw-r--r--tvix/derivation/src/tests/derivation_tests/292w8yzv5nn7nhdpxcs8b7vby2p27s09-nested-json.drv.json19
-rw-r--r--tvix/derivation/src/tests/derivation_tests/4wvvbi4jwn0prsdxb7vs673qa5h9gr7x-foo.drv1
-rw-r--r--tvix/derivation/src/tests/derivation_tests/4wvvbi4jwn0prsdxb7vs673qa5h9gr7x-foo.drv.json23
-rw-r--r--tvix/derivation/src/tests/derivation_tests/52a9id8hx688hvlnz4d1n25ml1jdykz0-unicode.drv1
-rw-r--r--tvix/derivation/src/tests/derivation_tests/52a9id8hx688hvlnz4d1n25ml1jdykz0-unicode.drv.json19
-rw-r--r--tvix/derivation/src/tests/derivation_tests/9lj1lkjm2ag622mh4h9rpy6j607an8g2-structured-attrs.drv1
-rw-r--r--tvix/derivation/src/tests/derivation_tests/9lj1lkjm2ag622mh4h9rpy6j607an8g2-structured-attrs.drv.json16
-rw-r--r--tvix/derivation/src/tests/derivation_tests/ch49594n9avinrf8ip0aslidkc4lxkqv-foo.drv1
-rw-r--r--tvix/derivation/src/tests/derivation_tests/ch49594n9avinrf8ip0aslidkc4lxkqv-foo.drv.json23
-rw-r--r--tvix/derivation/src/tests/derivation_tests/h32dahq0bx5rp1krcdx3a53asj21jvhk-has-multi-out.drv1
-rw-r--r--tvix/derivation/src/tests/derivation_tests/h32dahq0bx5rp1krcdx3a53asj21jvhk-has-multi-out.drv.json23
-rw-r--r--tvix/derivation/src/tests/derivation_tests/ss2p4wmxijn652haqyd7dckxwl4c7hxx-bar.drv1
-rw-r--r--tvix/derivation/src/tests/derivation_tests/ss2p4wmxijn652haqyd7dckxwl4c7hxx-bar.drv.json23
-rw-r--r--tvix/derivation/src/tests/mod.rs32
22 files changed, 516 insertions, 0 deletions
diff --git a/tvix/Cargo.lock b/tvix/Cargo.lock
index c1303be31f91..a3b91e534f33 100644
--- a/tvix/Cargo.lock
+++ b/tvix/Cargo.lock
@@ -477,6 +477,16 @@ dependencies = [
 ]
 
 [[package]]
+name = "derivation"
+version = "0.1.0"
+dependencies = [
+ "glob",
+ "serde",
+ "serde_json",
+ "test-generator",
+]
+
+[[package]]
 name = "diff"
 version = "0.1.13"
 source = "registry+https://github.com/rust-lang/crates.io-index"
diff --git a/tvix/Cargo.nix b/tvix/Cargo.nix
index 7d3c4c869196..fdc93f248b2d 100644
--- a/tvix/Cargo.nix
+++ b/tvix/Cargo.nix
@@ -33,6 +33,16 @@ rec {
   # You can override the features with
   # workspaceMembers."${crateName}".build.override { features = [ "default" "feature1" ... ]; }.
   workspaceMembers = {
+    "derivation" = rec {
+      packageId = "derivation";
+      build = internal.buildRustCrateWithFeatures {
+        packageId = "derivation";
+      };
+
+      # Debug support which might change between releases.
+      # File a bug if you depend on any for non-debug work!
+      debug = internal.debugCrate { inherit packageId; };
+    };
     "nix-cli" = rec {
       packageId = "nix-cli";
       build = internal.buildRustCrateWithFeatures {
@@ -1434,6 +1444,47 @@ rec {
         ];
 
       };
+      "derivation" = rec {
+        crateName = "derivation";
+        version = "0.1.0";
+        edition = "2021";
+        # We can't filter paths with references in Nix 2.4
+        # See https://github.com/NixOS/nix/issues/5410
+        src =
+          if (lib.versionOlder builtins.nixVersion "2.4pre20211007")
+          then lib.cleanSourceWith { filter = sourceFilter; src = ./derivation; }
+          else ./derivation;
+        dependencies = [
+          {
+            name = "blake3";
+            packageId = "blake3";
+            features = [ "rayon" "std" ];
+          }
+          {
+            name = "maplit";
+            packageId = "maplit";
+          }
+          {
+            name = "prost";
+            packageId = "prost";
+          }
+          {
+            name = "tonic";
+            packageId = "tonic";
+          }
+        ];
+        buildDependencies = [
+          {
+            name = "prost-build";
+            packageId = "prost-build";
+          }
+          {
+            name = "tonic-build";
+            packageId = "tonic-build";
+          }
+        ];
+
+      };
       "diff" = rec {
         crateName = "diff";
         version = "0.1.13";
@@ -2657,6 +2708,16 @@ rec {
           "value-bag" = [ "dep:value-bag" ];
         };
       };
+      "maplit" = rec {
+        crateName = "maplit";
+        version = "1.0.2";
+        edition = "2015";
+        sha256 = "07b5kjnhrrmfhgqm9wprjw8adx6i225lqp49gasgqg74lahnabiy";
+        authors = [
+          "bluss"
+        ];
+
+      };
       "matchit" = rec {
         crateName = "matchit";
         version = "0.7.0";
diff --git a/tvix/Cargo.toml b/tvix/Cargo.toml
index 3d39be16a86e..e12976d1f09d 100644
--- a/tvix/Cargo.toml
+++ b/tvix/Cargo.toml
@@ -19,6 +19,7 @@
 
 members = [
   "cli",
+  "derivation",
   "eval",
   "eval/builtin-macros",
   "nar",
diff --git a/tvix/derivation/Cargo.toml b/tvix/derivation/Cargo.toml
new file mode 100644
index 000000000000..99dfbb3a104f
--- /dev/null
+++ b/tvix/derivation/Cargo.toml
@@ -0,0 +1,19 @@
+[package]
+name = "derivation"
+version = "0.1.0"
+edition = "2021"
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies]
+glob = "0.3.0"
+serde = { version = "1.0", features = ["derive"] }
+
+[dev-dependencies.test-generator]
+# This fork of test-generator adds support for cargo workspaces, see
+# also https://github.com/frehberg/test-generator/pull/14
+git = "https://github.com/JamesGuthrie/test-generator.git"
+rev = "82e799979980962aec1aa324ec6e0e4cad781f41"
+
+[dev-dependencies]
+serde_json = "1.0"
diff --git a/tvix/derivation/src/lib.rs b/tvix/derivation/src/lib.rs
new file mode 100644
index 000000000000..a0ddee410b80
--- /dev/null
+++ b/tvix/derivation/src/lib.rs
@@ -0,0 +1,216 @@
+use serde::{Deserialize, Serialize};
+use std::{collections::BTreeMap, fmt, fmt::Write};
+
+#[cfg(test)]
+mod tests;
+
+const DERIVATION_PREFIX: &str = "Derive";
+const PAREN_OPEN: char = '(';
+const PAREN_CLOSE: char = ')';
+const BRACKET_OPEN: char = '[';
+const BRACKET_CLOSE: char = ']';
+const COMMA: char = ',';
+const QUOTE: char = '"';
+
+const STRING_ESCAPER: [(char, &str); 5] = [
+    ('\\', "\\\\"),
+    ('\n', "\\n"),
+    ('\r', "\\r"),
+    ('\t', "\\t"),
+    ('\"', "\\\""),
+];
+
+fn default_resource() -> String {
+    "".to_string()
+}
+
+#[derive(Serialize, Deserialize)]
+pub struct Output {
+    path: String,
+    #[serde(default = "default_resource")]
+    hash_algorithm: String,
+    #[serde(default = "default_resource")]
+    hash: String,
+}
+
+#[derive(Serialize, Deserialize)]
+pub struct Derivation {
+    outputs: BTreeMap<String, Output>,
+    input_sources: Vec<String>,
+    input_derivations: BTreeMap<String, Vec<String>>,
+    platform: String,
+    builder: String,
+    arguments: Vec<String>,
+    environment: BTreeMap<String, String>,
+}
+
+fn escape_string(s: &String) -> String {
+    let mut s_replaced = s.clone();
+
+    for escape_sequence in STRING_ESCAPER {
+        s_replaced = s_replaced.replace(escape_sequence.0, escape_sequence.1);
+    }
+
+    return format!("\"{}\"", s_replaced);
+}
+
+fn write_array_elements(
+    writer: &mut impl Write,
+    quote: bool,
+    open: &str,
+    closing: &str,
+    elements: Vec<&String>,
+) -> Result<(), fmt::Error> {
+    writer.write_str(open)?;
+
+    for (index, element) in elements.iter().enumerate() {
+        if index > 0 {
+            writer.write_char(COMMA)?;
+        }
+
+        if quote {
+            writer.write_char(QUOTE)?;
+        }
+
+        writer.write_str(element)?;
+
+        if quote {
+            writer.write_char(QUOTE)?;
+        }
+    }
+
+    writer.write_str(closing)?;
+
+    return Ok(());
+}
+
+pub fn serialize_derivation(
+    derivation: Derivation,
+    writer: &mut impl Write,
+) -> Result<(), fmt::Error> {
+    writer.write_str(DERIVATION_PREFIX)?;
+    writer.write_char(PAREN_OPEN)?;
+
+    // Step 1: Write outputs
+    {
+        writer.write_char(BRACKET_OPEN)?;
+        for (ii, (output_name, output)) in derivation.outputs.iter().enumerate() {
+            if ii > 0 {
+                writer.write_char(COMMA)?;
+            }
+
+            // TODO(jrhahn) option to strip output
+            let elements = vec![
+                output_name,
+                &output.path,
+                &output.hash_algorithm,
+                &output.hash,
+            ];
+
+            write_array_elements(
+                writer,
+                true,
+                &PAREN_OPEN.to_string(),
+                &PAREN_CLOSE.to_string(),
+                elements,
+            )?
+        }
+        writer.write_char(BRACKET_CLOSE)?;
+    }
+
+    // Step 2: Write input_derivations
+    {
+        writer.write_char(COMMA)?;
+        writer.write_char(BRACKET_OPEN)?;
+
+        for (ii, (input_derivation_path, input_derivation)) in
+            derivation.input_derivations.iter().enumerate()
+        {
+            if ii > 0 {
+                writer.write_char(COMMA)?;
+            }
+
+            writer.write_char(PAREN_OPEN)?;
+            writer.write_char(QUOTE)?;
+            writer.write_str(input_derivation_path.as_str())?;
+            writer.write_char(QUOTE)?;
+            writer.write_char(COMMA)?;
+
+            write_array_elements(
+                writer,
+                true,
+                &BRACKET_OPEN.to_string(),
+                &BRACKET_CLOSE.to_string(),
+                input_derivation.iter().map(|s| s).collect(),
+            )?;
+
+            writer.write_char(PAREN_CLOSE)?;
+        }
+
+        writer.write_char(BRACKET_CLOSE)?;
+    }
+
+    // Step 3: Write input_sources
+    {
+        writer.write_char(COMMA)?;
+        write_array_elements(
+            writer,
+            true,
+            &BRACKET_OPEN.to_string(),
+            &BRACKET_CLOSE.to_string(),
+            derivation.input_sources.iter().map(|s| s).collect(),
+        )?;
+    }
+
+    // Step 4: Write platform
+    {
+        writer.write_char(COMMA)?;
+        writer.write_str(&escape_string(&derivation.platform).as_str())?;
+    }
+
+    // Step 5: Write builder
+    {
+        writer.write_char(COMMA)?;
+        writer.write_str(&escape_string(&derivation.builder).as_str())?;
+    }
+
+    // Step 6: Write arguments
+    {
+        writer.write_char(COMMA)?;
+        write_array_elements(
+            writer,
+            true,
+            &BRACKET_OPEN.to_string(),
+            &BRACKET_CLOSE.to_string(),
+            derivation.arguments.iter().map(|s| s).collect(),
+        )?;
+    }
+
+    // Step 7: Write env
+    {
+        writer.write_char(COMMA)?;
+        writer.write_char(BRACKET_OPEN)?;
+
+        for (ii, (key, environment)) in derivation.environment.iter().enumerate() {
+            if ii > 0 {
+                writer.write_char(COMMA)?;
+            }
+
+            // TODO(jrhahn) add strip option
+            write_array_elements(
+                writer,
+                false,
+                &PAREN_OPEN.to_string(),
+                &PAREN_CLOSE.to_string(),
+                vec![&escape_string(key), &escape_string(&environment)],
+            )?;
+        }
+
+        writer.write_char(BRACKET_CLOSE)?;
+    }
+
+    // Step 8: Close Derive call
+    writer.write_char(PAREN_CLOSE)?;
+
+    return Ok(());
+}
diff --git a/tvix/derivation/src/tests/derivation_tests/0hm2f1psjpcwg8fijsmr4wwxrx59s092-bar.drv b/tvix/derivation/src/tests/derivation_tests/0hm2f1psjpcwg8fijsmr4wwxrx59s092-bar.drv
new file mode 100644
index 000000000000..a4fea3c5f486
--- /dev/null
+++ b/tvix/derivation/src/tests/derivation_tests/0hm2f1psjpcwg8fijsmr4wwxrx59s092-bar.drv
@@ -0,0 +1 @@
+Derive([("out","/nix/store/4q0pg5zpfmznxscq3avycvf9xdvx50n3-bar","r:sha256","08813cbee9903c62be4c5027726a418a300da4500b2d369d3af9286f4815ceba")],[],[],":",":",[],[("builder",":"),("name","bar"),("out","/nix/store/4q0pg5zpfmznxscq3avycvf9xdvx50n3-bar"),("outputHash","08813cbee9903c62be4c5027726a418a300da4500b2d369d3af9286f4815ceba"),("outputHashAlgo","sha256"),("outputHashMode","recursive"),("system",":")])
\ No newline at end of file
diff --git a/tvix/derivation/src/tests/derivation_tests/0hm2f1psjpcwg8fijsmr4wwxrx59s092-bar.drv.json b/tvix/derivation/src/tests/derivation_tests/0hm2f1psjpcwg8fijsmr4wwxrx59s092-bar.drv.json
new file mode 100644
index 000000000000..abaaa8d4a6fc
--- /dev/null
+++ b/tvix/derivation/src/tests/derivation_tests/0hm2f1psjpcwg8fijsmr4wwxrx59s092-bar.drv.json
@@ -0,0 +1,23 @@
+ {
+  "outputs": {
+    "out": {
+      "path": "/nix/store/4q0pg5zpfmznxscq3avycvf9xdvx50n3-bar",
+      "hash_algorithm": "r:sha256",
+      "hash": "08813cbee9903c62be4c5027726a418a300da4500b2d369d3af9286f4815ceba"
+    }
+  },
+  "input_sources": [],
+  "input_derivations": {},
+  "platform": ":",
+  "builder": ":",
+  "arguments": [],
+  "environment": {
+    "builder": ":",
+    "name": "bar",
+    "out": "/nix/store/4q0pg5zpfmznxscq3avycvf9xdvx50n3-bar",
+    "outputHash": "08813cbee9903c62be4c5027726a418a300da4500b2d369d3af9286f4815ceba",
+    "outputHashAlgo": "sha256",
+    "outputHashMode": "recursive",
+    "system": ":"
+  }
+}
diff --git a/tvix/derivation/src/tests/derivation_tests/292w8yzv5nn7nhdpxcs8b7vby2p27s09-nested-json.drv b/tvix/derivation/src/tests/derivation_tests/292w8yzv5nn7nhdpxcs8b7vby2p27s09-nested-json.drv
new file mode 100644
index 000000000000..f0d9230a5a52
--- /dev/null
+++ b/tvix/derivation/src/tests/derivation_tests/292w8yzv5nn7nhdpxcs8b7vby2p27s09-nested-json.drv
@@ -0,0 +1 @@
+Derive([("out","/nix/store/pzr7lsd3q9pqsnb42r9b23jc5sh8irvn-nested-json","","")],[],[],":",":",[],[("builder",":"),("json","{\"hello\":\"moto\\n\"}"),("name","nested-json"),("out","/nix/store/pzr7lsd3q9pqsnb42r9b23jc5sh8irvn-nested-json"),("system",":")])
\ No newline at end of file
diff --git a/tvix/derivation/src/tests/derivation_tests/292w8yzv5nn7nhdpxcs8b7vby2p27s09-nested-json.drv.json b/tvix/derivation/src/tests/derivation_tests/292w8yzv5nn7nhdpxcs8b7vby2p27s09-nested-json.drv.json
new file mode 100644
index 000000000000..a1b763ec9449
--- /dev/null
+++ b/tvix/derivation/src/tests/derivation_tests/292w8yzv5nn7nhdpxcs8b7vby2p27s09-nested-json.drv.json
@@ -0,0 +1,19 @@
+{
+  "outputs": {
+    "out": {
+      "path": "/nix/store/pzr7lsd3q9pqsnb42r9b23jc5sh8irvn-nested-json"
+    }
+  },
+  "input_sources": [],
+  "input_derivations": {},
+  "platform": ":",
+  "builder": ":",
+  "arguments": [],
+  "environment": {
+    "builder": ":",
+    "json": "{\"hello\":\"moto\\n\"}",
+    "name": "nested-json",
+    "out": "/nix/store/pzr7lsd3q9pqsnb42r9b23jc5sh8irvn-nested-json",
+    "system": ":"
+  }
+}
diff --git a/tvix/derivation/src/tests/derivation_tests/4wvvbi4jwn0prsdxb7vs673qa5h9gr7x-foo.drv b/tvix/derivation/src/tests/derivation_tests/4wvvbi4jwn0prsdxb7vs673qa5h9gr7x-foo.drv
new file mode 100644
index 000000000000..a2cf9d31f92e
--- /dev/null
+++ b/tvix/derivation/src/tests/derivation_tests/4wvvbi4jwn0prsdxb7vs673qa5h9gr7x-foo.drv
@@ -0,0 +1 @@
+Derive([("out","/nix/store/5vyvcwah9l9kf07d52rcgdk70g2f4y13-foo","","")],[("/nix/store/0hm2f1psjpcwg8fijsmr4wwxrx59s092-bar.drv",["out"])],[],":",":",[],[("bar","/nix/store/4q0pg5zpfmznxscq3avycvf9xdvx50n3-bar"),("builder",":"),("name","foo"),("out","/nix/store/5vyvcwah9l9kf07d52rcgdk70g2f4y13-foo"),("system",":")])
\ No newline at end of file
diff --git a/tvix/derivation/src/tests/derivation_tests/4wvvbi4jwn0prsdxb7vs673qa5h9gr7x-foo.drv.json b/tvix/derivation/src/tests/derivation_tests/4wvvbi4jwn0prsdxb7vs673qa5h9gr7x-foo.drv.json
new file mode 100644
index 000000000000..f6bb5be234d1
--- /dev/null
+++ b/tvix/derivation/src/tests/derivation_tests/4wvvbi4jwn0prsdxb7vs673qa5h9gr7x-foo.drv.json
@@ -0,0 +1,23 @@
+{
+  "outputs": {
+    "out": {
+      "path": "/nix/store/5vyvcwah9l9kf07d52rcgdk70g2f4y13-foo"
+    }
+  },
+  "input_sources": [],
+  "input_derivations": {
+    "/nix/store/0hm2f1psjpcwg8fijsmr4wwxrx59s092-bar.drv": [
+      "out"
+    ]
+  },
+  "platform": ":",
+  "builder": ":",
+  "arguments": [],
+  "environment": {
+    "bar": "/nix/store/4q0pg5zpfmznxscq3avycvf9xdvx50n3-bar",
+    "builder": ":",
+    "name": "foo",
+    "out": "/nix/store/5vyvcwah9l9kf07d52rcgdk70g2f4y13-foo",
+    "system": ":"
+  }
+}
diff --git a/tvix/derivation/src/tests/derivation_tests/52a9id8hx688hvlnz4d1n25ml1jdykz0-unicode.drv b/tvix/derivation/src/tests/derivation_tests/52a9id8hx688hvlnz4d1n25ml1jdykz0-unicode.drv
new file mode 100644
index 000000000000..bbe88c02c739
--- /dev/null
+++ b/tvix/derivation/src/tests/derivation_tests/52a9id8hx688hvlnz4d1n25ml1jdykz0-unicode.drv
@@ -0,0 +1 @@
+Derive([("out","/nix/store/vgvdj6nf7s8kvfbl2skbpwz9kc7xjazc-unicode","","")],[],[],":",":",[],[("builder",":"),("letters","räksmörgås\nrødgrød med fløde\nLübeck\n肥猪\nこんにちは / 今日は\n🌮\n"),("name","unicode"),("out","/nix/store/vgvdj6nf7s8kvfbl2skbpwz9kc7xjazc-unicode"),("system",":")])
\ No newline at end of file
diff --git a/tvix/derivation/src/tests/derivation_tests/52a9id8hx688hvlnz4d1n25ml1jdykz0-unicode.drv.json b/tvix/derivation/src/tests/derivation_tests/52a9id8hx688hvlnz4d1n25ml1jdykz0-unicode.drv.json
new file mode 100644
index 000000000000..bf837b3e8665
--- /dev/null
+++ b/tvix/derivation/src/tests/derivation_tests/52a9id8hx688hvlnz4d1n25ml1jdykz0-unicode.drv.json
@@ -0,0 +1,19 @@
+{
+  "outputs": {
+    "out": {
+      "path": "/nix/store/vgvdj6nf7s8kvfbl2skbpwz9kc7xjazc-unicode"
+    }
+  },
+  "input_sources": [],
+  "input_derivations": {},
+  "platform": ":",
+  "builder": ":",
+  "arguments": [],
+  "environment": {
+    "builder": ":",
+    "letters": "räksmörgås\nrødgrød med fløde\nLübeck\n肥猪\nこんにちは / 今日は\n🌮\n",
+    "name": "unicode",
+    "out": "/nix/store/vgvdj6nf7s8kvfbl2skbpwz9kc7xjazc-unicode",
+    "system": ":"
+  }
+}
diff --git a/tvix/derivation/src/tests/derivation_tests/9lj1lkjm2ag622mh4h9rpy6j607an8g2-structured-attrs.drv b/tvix/derivation/src/tests/derivation_tests/9lj1lkjm2ag622mh4h9rpy6j607an8g2-structured-attrs.drv
new file mode 100644
index 000000000000..4b9338c0b953
--- /dev/null
+++ b/tvix/derivation/src/tests/derivation_tests/9lj1lkjm2ag622mh4h9rpy6j607an8g2-structured-attrs.drv
@@ -0,0 +1 @@
+Derive([("out","/nix/store/6a39dl014j57bqka7qx25k0vb20vkqm6-structured-attrs","","")],[],[],":",":",[],[("__json","{\"builder\":\":\",\"name\":\"structured-attrs\",\"system\":\":\"}"),("out","/nix/store/6a39dl014j57bqka7qx25k0vb20vkqm6-structured-attrs")])
\ No newline at end of file
diff --git a/tvix/derivation/src/tests/derivation_tests/9lj1lkjm2ag622mh4h9rpy6j607an8g2-structured-attrs.drv.json b/tvix/derivation/src/tests/derivation_tests/9lj1lkjm2ag622mh4h9rpy6j607an8g2-structured-attrs.drv.json
new file mode 100644
index 000000000000..97f99acdb140
--- /dev/null
+++ b/tvix/derivation/src/tests/derivation_tests/9lj1lkjm2ag622mh4h9rpy6j607an8g2-structured-attrs.drv.json
@@ -0,0 +1,16 @@
+{
+  "outputs": {
+    "out": {
+      "path": "/nix/store/6a39dl014j57bqka7qx25k0vb20vkqm6-structured-attrs"
+    }
+  },
+  "input_sources": [],
+  "input_derivations": {},
+  "platform": ":",
+  "builder": ":",
+  "arguments": [],
+  "environment": {
+    "__json": "{\"builder\":\":\",\"name\":\"structured-attrs\",\"system\":\":\"}",
+    "out": "/nix/store/6a39dl014j57bqka7qx25k0vb20vkqm6-structured-attrs"
+  }
+}
diff --git a/tvix/derivation/src/tests/derivation_tests/ch49594n9avinrf8ip0aslidkc4lxkqv-foo.drv b/tvix/derivation/src/tests/derivation_tests/ch49594n9avinrf8ip0aslidkc4lxkqv-foo.drv
new file mode 100644
index 000000000000..1699c2a75e48
--- /dev/null
+++ b/tvix/derivation/src/tests/derivation_tests/ch49594n9avinrf8ip0aslidkc4lxkqv-foo.drv
@@ -0,0 +1 @@
+Derive([("out","/nix/store/fhaj6gmwns62s6ypkcldbaj2ybvkhx3p-foo","","")],[("/nix/store/ss2p4wmxijn652haqyd7dckxwl4c7hxx-bar.drv",["out"])],[],":",":",[],[("bar","/nix/store/mp57d33657rf34lzvlbpfa1gjfv5gmpg-bar"),("builder",":"),("name","foo"),("out","/nix/store/fhaj6gmwns62s6ypkcldbaj2ybvkhx3p-foo"),("system",":")])
\ No newline at end of file
diff --git a/tvix/derivation/src/tests/derivation_tests/ch49594n9avinrf8ip0aslidkc4lxkqv-foo.drv.json b/tvix/derivation/src/tests/derivation_tests/ch49594n9avinrf8ip0aslidkc4lxkqv-foo.drv.json
new file mode 100644
index 000000000000..2d7dbeb75299
--- /dev/null
+++ b/tvix/derivation/src/tests/derivation_tests/ch49594n9avinrf8ip0aslidkc4lxkqv-foo.drv.json
@@ -0,0 +1,23 @@
+{
+  "outputs": {
+    "out": {
+      "path": "/nix/store/fhaj6gmwns62s6ypkcldbaj2ybvkhx3p-foo"
+    }
+  },
+  "input_sources": [],
+  "input_derivations": {
+    "/nix/store/ss2p4wmxijn652haqyd7dckxwl4c7hxx-bar.drv": [
+      "out"
+    ]
+  },
+  "platform": ":",
+  "builder": ":",
+  "arguments": [],
+  "environment": {
+    "bar": "/nix/store/mp57d33657rf34lzvlbpfa1gjfv5gmpg-bar",
+    "builder": ":",
+    "name": "foo",
+    "out": "/nix/store/fhaj6gmwns62s6ypkcldbaj2ybvkhx3p-foo",
+    "system": ":"
+  }
+}
diff --git a/tvix/derivation/src/tests/derivation_tests/h32dahq0bx5rp1krcdx3a53asj21jvhk-has-multi-out.drv b/tvix/derivation/src/tests/derivation_tests/h32dahq0bx5rp1krcdx3a53asj21jvhk-has-multi-out.drv
new file mode 100644
index 000000000000..523612238c76
--- /dev/null
+++ b/tvix/derivation/src/tests/derivation_tests/h32dahq0bx5rp1krcdx3a53asj21jvhk-has-multi-out.drv
@@ -0,0 +1 @@
+Derive([("lib","/nix/store/2vixb94v0hy2xc6p7mbnxxcyc095yyia-has-multi-out-lib","",""),("out","/nix/store/55lwldka5nyxa08wnvlizyqw02ihy8ic-has-multi-out","","")],[],[],":",":",[],[("builder",":"),("lib","/nix/store/2vixb94v0hy2xc6p7mbnxxcyc095yyia-has-multi-out-lib"),("name","has-multi-out"),("out","/nix/store/55lwldka5nyxa08wnvlizyqw02ihy8ic-has-multi-out"),("outputs","out lib"),("system",":")])
\ No newline at end of file
diff --git a/tvix/derivation/src/tests/derivation_tests/h32dahq0bx5rp1krcdx3a53asj21jvhk-has-multi-out.drv.json b/tvix/derivation/src/tests/derivation_tests/h32dahq0bx5rp1krcdx3a53asj21jvhk-has-multi-out.drv.json
new file mode 100644
index 000000000000..12cb3481980e
--- /dev/null
+++ b/tvix/derivation/src/tests/derivation_tests/h32dahq0bx5rp1krcdx3a53asj21jvhk-has-multi-out.drv.json
@@ -0,0 +1,23 @@
+{
+  "outputs": {
+    "lib": {
+      "path": "/nix/store/2vixb94v0hy2xc6p7mbnxxcyc095yyia-has-multi-out-lib"
+    },
+    "out": {
+      "path": "/nix/store/55lwldka5nyxa08wnvlizyqw02ihy8ic-has-multi-out"
+    }
+  },
+  "input_sources": [],
+  "input_derivations": {},
+  "platform": ":",
+  "builder": ":",
+  "arguments": [],
+  "environment": {
+    "builder": ":",
+    "lib": "/nix/store/2vixb94v0hy2xc6p7mbnxxcyc095yyia-has-multi-out-lib",
+    "name": "has-multi-out",
+    "out": "/nix/store/55lwldka5nyxa08wnvlizyqw02ihy8ic-has-multi-out",
+    "outputs": "out lib",
+    "system": ":"
+  }
+}
diff --git a/tvix/derivation/src/tests/derivation_tests/ss2p4wmxijn652haqyd7dckxwl4c7hxx-bar.drv b/tvix/derivation/src/tests/derivation_tests/ss2p4wmxijn652haqyd7dckxwl4c7hxx-bar.drv
new file mode 100644
index 000000000000..559e93ed0ed6
--- /dev/null
+++ b/tvix/derivation/src/tests/derivation_tests/ss2p4wmxijn652haqyd7dckxwl4c7hxx-bar.drv
@@ -0,0 +1 @@
+Derive([("out","/nix/store/mp57d33657rf34lzvlbpfa1gjfv5gmpg-bar","r:sha1","0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33")],[],[],":",":",[],[("builder",":"),("name","bar"),("out","/nix/store/mp57d33657rf34lzvlbpfa1gjfv5gmpg-bar"),("outputHash","0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33"),("outputHashAlgo","sha1"),("outputHashMode","recursive"),("system",":")])
\ No newline at end of file
diff --git a/tvix/derivation/src/tests/derivation_tests/ss2p4wmxijn652haqyd7dckxwl4c7hxx-bar.drv.json b/tvix/derivation/src/tests/derivation_tests/ss2p4wmxijn652haqyd7dckxwl4c7hxx-bar.drv.json
new file mode 100644
index 000000000000..e159a5d12eed
--- /dev/null
+++ b/tvix/derivation/src/tests/derivation_tests/ss2p4wmxijn652haqyd7dckxwl4c7hxx-bar.drv.json
@@ -0,0 +1,23 @@
+{
+  "outputs": {
+    "out": {
+      "path": "/nix/store/mp57d33657rf34lzvlbpfa1gjfv5gmpg-bar",
+      "hash_algorithm": "r:sha1",
+      "hash": "0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33"
+    }
+  },
+  "input_sources": [],
+  "input_derivations": {},
+  "platform": ":",
+  "builder": ":",
+  "arguments": [],
+  "environment": {
+    "builder": ":",
+    "name": "bar",
+    "out": "/nix/store/mp57d33657rf34lzvlbpfa1gjfv5gmpg-bar",
+    "outputHash": "0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33",
+    "outputHashAlgo": "sha1",
+    "outputHashMode": "recursive",
+    "system": ":"
+  }
+}
diff --git a/tvix/derivation/src/tests/mod.rs b/tvix/derivation/src/tests/mod.rs
new file mode 100644
index 000000000000..9c8ffd061c11
--- /dev/null
+++ b/tvix/derivation/src/tests/mod.rs
@@ -0,0 +1,32 @@
+use super::{serialize_derivation, Derivation};
+use std::fs::File;
+use std::io::Read;
+use std::path::Path;
+use test_generator::test_resources;
+
+fn read_file(path: &str) -> String {
+    let path = Path::new(path);
+    let mut file = File::open(path).unwrap();
+    let mut data = String::new();
+
+    file.read_to_string(&mut data).unwrap();
+
+    return data;
+}
+
+fn assert_derivation_ok(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");
+
+    let mut serialized_derivation = String::new();
+    serialize_derivation(derivation, &mut serialized_derivation).unwrap();
+
+    let expected = read_file(path_to_drv_file);
+
+    assert_eq!(expected, serialized_derivation);
+}
+
+#[test_resources("src/tests/derivation_tests/*.drv")]
+fn derivation_files_ok(path: &str) {
+    assert_derivation_ok(path);
+}