From 09b30f895a4901557194179182840320c829b471 Mon Sep 17 00:00:00 2001 From: Florian Klink Date: Wed, 15 Mar 2023 00:40:10 +0100 Subject: refactor(tvix/nix-compat): simplify fingerprint calculation Instead of having two very similar match branches for the FOD and non- FOD case, detect the FOD case while looping over all outputs. In the case of anything other than recursive sha256 FODs, the fingerprint and output path calculation is exactly the same. Change-Id: Ieb6995653d008766e595cf29d7cd4fb1334e33dd Reviewed-on: https://cl.tvl.fyi/c/depot/+/8310 Tested-by: BuildkiteCI Reviewed-by: tazjin Autosubmit: flokli --- tvix/nix-compat/src/derivation/mod.rs | 130 ++++++++++++++-------------------- 1 file changed, 54 insertions(+), 76 deletions(-) diff --git a/tvix/nix-compat/src/derivation/mod.rs b/tvix/nix-compat/src/derivation/mod.rs index 06b7ba02f4d8..d5857c1a6954 100644 --- a/tvix/nix-compat/src/derivation/mod.rs +++ b/tvix/nix-compat/src/derivation/mod.rs @@ -236,86 +236,64 @@ impl Derivation { name: &str, derivation_or_fod_hash: &NixHash, ) -> Result<(), DerivationError> { - // Check if the Derivation is fixed output, because they cause - // different fingerprints to be hashed. - match self.get_fixed_output() { - None => { - // The fingerprint and hash differs per output - for (output_name, output) in self.outputs.iter_mut() { - // Assert that outputs are not yet populated, to avoid using this function wrongly. - // We don't also go over self.environment, but it's a sufficient - // footgun prevention mechanism. - assert!(output.path.is_empty()); - - // calculate the output_name_path, which is the part of the NixPath after the digest. - let mut output_path_name = name.to_string(); - if output_name != "out" { - output_path_name.push('-'); - output_path_name.push_str(output_name); - } - - let fp = &format!( - "output:{}:{}:{}:{}", - output_name, - derivation_or_fod_hash.to_nix_hash_string(), - store_path::STORE_DIR, - output_path_name, - ); - - let abs_store_path = - utils::build_store_path(false, fp, &output_path_name)?.to_absolute_path(); - - output.path = abs_store_path.clone(); - self.environment - .insert(output_name.to_string(), abs_store_path); + let num_outputs = self.outputs.len(); + // The fingerprint and hash differs per output + for (output_name, output) in self.outputs.iter_mut() { + // Assert that outputs are not yet populated, to avoid using this function wrongly. + // We don't also go over self.environment, but it's a sufficient + // footgun prevention mechanism. + assert!(output.path.is_empty()); + + // calculate the output_name_path, which is the part of the NixPath after the digest. + // It's the name, and (if it's the non-out output), the output name after a `-`. + let output_path_name = { + let mut output_path_name = name.to_string(); + if output_name != "out" { + output_path_name.push('-'); + output_path_name.push_str(output_name); } - } - Some((fixed_output_path, fixed_output_hash)) => { - // Assert that outputs are not yet populated, to avoid using this function wrongly. - // We don't also go over self.environment, but it's a sufficient - // footgun prevention mechanism. - assert!(fixed_output_path.is_empty()); - - let fp = { - let mut fp = String::new(); - // Fixed-output derivation. - // There's two different hashing strategies in place, - // depending on whether the hash is recursive AND sha256 or anything else. - // This code is _weird_ but it is what Nix is doing. See: - // https://github.com/NixOS/nix/blob/1385b2007804c8a0370f2a6555045a00e34b07c7/src/libstore/store-api.cc#L178-L196 - if let NixHashWithMode::Recursive(recursive_hash) = fixed_output_hash { - if recursive_hash.algo == HashAlgo::Sha256 { - fp.push_str(&format!("source:{}", recursive_hash.to_nix_hash_string())); - } else { - // This is similar to the FOD case, with an empty fixed_output_path. - fp.push_str(&format!( - "output:out:{}", - derivation_or_fod_hash.to_nix_hash_string(), - )); - } - } else { - // This is similar to the FOD case, with an empty fixed_output_path. - fp.push_str(&format!( - "output:out:{}", - derivation_or_fod_hash.to_nix_hash_string(), - )); + output_path_name + }; + + // In the case this is a fixed-output derivation AND the + // hash mode is recursive AND the hash algo is sha256, a + // custom fingerprint is used to calculate the output path. + // + // In all other cases, the fingerprint is derived from the + // derivation_or_fod_hash. + let custom_fp: Option = + if num_outputs == 1 && output_name == "out" && output.hash_with_mode.is_some() { + match &output.hash_with_mode { + Some(NixHashWithMode::Recursive(NixHash { + digest, + algo: HashAlgo::Sha256, + })) => Some(format!( + "source:sha256:{}:{}:{}", + data_encoding::HEXLOWER.encode(digest), + store_path::STORE_DIR, + output_path_name + )), + _ => None, } - fp.push_str(&format!(":{}:{}", store_path::STORE_DIR, name)); - fp + } else { + None }; - let abs_store_path = utils::build_store_path(false, &fp, name)?.to_absolute_path(); - - self.outputs.insert( - "out".to_string(), - Output { - path: abs_store_path.clone(), - hash_with_mode: Some(fixed_output_hash.clone()), - }, - ); - self.environment.insert("out".to_string(), abs_store_path); - } - }; + // If no custom_fp has been determined, use the default one. + let fp = custom_fp.unwrap_or(format!( + "output:{}:{}:{}:{}", + output_name, + derivation_or_fod_hash.to_nix_hash_string(), + store_path::STORE_DIR, + output_path_name, + )); + let abs_store_path = + utils::build_store_path(false, &fp, &output_path_name)?.to_absolute_path(); + + output.path = abs_store_path.clone(); + self.environment + .insert(output_name.to_string(), abs_store_path); + } Ok(()) } -- cgit 1.4.1