From cc626d686cceed84e45d21bf32514a3a3f8e2b11 Mon Sep 17 00:00:00 2001 From: Florian Klink Date: Wed, 4 Jan 2023 13:38:28 +0100 Subject: feat(tvix/derivation): implement Derivation::validate() Change-Id: I87dfadda872439e108e5f678a5da63dd5b1915d1 Reviewed-on: https://cl.tvl.fyi/c/depot/+/7732 Reviewed-by: tazjin Tested-by: BuildkiteCI --- tvix/Cargo.lock | 1 + tvix/Cargo.nix | 83 +++++++++++++++++++++++++++++++ tvix/derivation/Cargo.toml | 1 + tvix/derivation/src/lib.rs | 4 +- tvix/derivation/src/output.rs | 6 +++ tvix/derivation/src/tests/mod.rs | 10 ++++ tvix/derivation/src/validate.rs | 104 +++++++++++++++++++++++++++++++++++++++ 7 files changed, 207 insertions(+), 2 deletions(-) create mode 100644 tvix/derivation/src/validate.rs diff --git a/tvix/Cargo.lock b/tvix/Cargo.lock index 9cc9ffb6d1ea..477b6173daa2 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 3511c5e74233..963b5dfae5b4 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"; @@ -1473,6 +1500,10 @@ rec { then lib.cleanSourceWith { filter = sourceFilter; src = ./derivation; } else ./derivation; dependencies = [ + { + name = "anyhow"; + packageId = "anyhow"; + } { name = "glob"; packageId = "glob"; @@ -1482,12 +1513,24 @@ rec { packageId = "serde"; features = [ "derive" ]; } + { + name = "sha2"; + packageId = "sha2"; + } + { + name = "tvix-store"; + packageId = "tvix-store"; + } ]; devDependencies = [ { name = "serde_json"; 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 ec998c446508..b63b3ab9fd57 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 a8360fbafc5e..f0a654e111a2 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 0d668c3e9952..2839110971d2 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 435d82144067..1b55c6e3e751 100644 --- a/tvix/derivation/src/tests/mod.rs +++ b/tvix/derivation/src/tests/mod.rs @@ -30,6 +30,16 @@ fn check_serizaliation(path_to_drv_file: &str) { assert_eq!(expected, serialized_derivation); } +#[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)); diff --git a/tvix/derivation/src/validate.rs b/tvix/derivation/src/validate.rs new file mode 100644 index 000000000000..e0f62a323fcf --- /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(()) + } +} -- cgit 1.4.1