feat(tvix/derivation): implement Derivation::validate()

Change-Id: I87dfadda872439e108e5f678a5da63dd5b1915d1
Reviewed-on: https://cl.tvl.fyi/c/depot/+/7732
Reviewed-by: tazjin <tazjin@tvl.su>
Tested-by: BuildkiteCI
This commit is contained in:
Florian Klink 2023-01-04 13:38:28 +01:00 committed by flokli
parent 407a9cd90f
commit cc626d686c
7 changed files with 207 additions and 2 deletions

1
tvix/Cargo.lock generated
View file

@ -492,6 +492,7 @@ checksum = "23d8666cb01533c39dde32bcbab8e227b4ed6679b2c925eba05feabea39508fb"
name = "derivation"
version = "0.1.0"
dependencies = [
"anyhow",
"glob",
"serde",
"serde_json",

View file

@ -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";

View file

@ -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"

View file

@ -1,9 +1,9 @@
mod derivation;
mod nix_hash;
mod output;
mod string_escape;
mod validate;
mod write;
mod derivation;
#[cfg(test)]
mod tests;

View file

@ -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(())
}
}

View file

@ -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));

View file

@ -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(())
}
}