feat(tvix/nix-compat): Use StorePath in Output

https: //b.tvl.fyi/issues/264
Change-Id: Icb09be9643245cc68d09f01d7723af2d44d6bd1a
Reviewed-on: https://cl.tvl.fyi/c/depot/+/11001
Autosubmit: Peter Kolloch <info@eigenvalue.net>
Reviewed-by: flokli <flokli@flokli.de>
Tested-by: BuildkiteCI
This commit is contained in:
Peter Kolloch 2024-02-21 18:31:35 +07:00 committed by clbot
parent 035f617b7f
commit fde488ec6d
11 changed files with 119 additions and 82 deletions

View file

@ -116,7 +116,7 @@ fn handle_fixed_output(
drv.outputs.insert( drv.outputs.insert(
"out".to_string(), "out".to_string(),
Output { Output {
path: "".to_string(), path: None,
ca_hash: match hash_mode_str.as_deref() { ca_hash: match hash_mode_str.as_deref() {
None | Some("flat") => Some(nixhash::CAHash::Flat(nixhash)), None | Some("flat") => Some(nixhash::CAHash::Flat(nixhash)),
Some("recursive") => Some(nixhash::CAHash::Nar(nixhash)), Some("recursive") => Some(nixhash::CAHash::Nar(nixhash)),
@ -486,7 +486,7 @@ pub(crate) mod derivation_builtins {
( (
name.clone(), name.clone(),
( (
output.path, output.path.unwrap().to_absolute_path(),
Some( Some(
NixContextElement::Single { NixContextElement::Single {
name, name,

View file

@ -74,14 +74,8 @@ impl KnownPaths {
// For all output paths, update our lookup table. // For all output paths, update our lookup table.
// We only write into the lookup table once. // We only write into the lookup table once.
for output in drv.outputs.values() { for output in drv.outputs.values() {
// We assume derivations to be passed validated, so ignoring rest
// and expecting parsing is ok.
// TODO: b/264
let (output_path, _rest) =
StorePath::from_absolute_path_full(&output.path).expect("parse output path");
self.outputs_to_drvpath self.outputs_to_drvpath
.entry(output_path) .entry(output.path.as_ref().expect("missing store path").clone())
.or_insert(drv_path.to_owned()); .or_insert(drv_path.to_owned());
} }

View file

@ -52,7 +52,7 @@ pub(crate) fn derivation_to_build_request(
let mut output_paths: Vec<String> = derivation let mut output_paths: Vec<String> = derivation
.outputs .outputs
.values() .values()
.map(|e| e.path[1..].to_owned()) .map(|e| e.path_str()[1..].to_owned())
.collect(); .collect();
// Sort the outputs. We can use sort_unstable, as these are unique strings. // Sort the outputs. We can use sort_unstable, as these are unique strings.

View file

@ -4,10 +4,7 @@ use async_recursion::async_recursion;
use bytes::Bytes; use bytes::Bytes;
use futures::Stream; use futures::Stream;
use futures::{StreamExt, TryStreamExt}; use futures::{StreamExt, TryStreamExt};
use nix_compat::{ use nix_compat::{nixhash::CAHash, store_path::StorePath};
nixhash::CAHash,
store_path::{StorePath, StorePathRef},
};
use std::{ use std::{
cell::RefCell, cell::RefCell,
collections::BTreeSet, collections::BTreeSet,
@ -153,16 +150,14 @@ impl TvixStoreIO {
let output_paths: Vec<StorePath> = output_names let output_paths: Vec<StorePath> = output_names
.iter() .iter()
.map(|output_name| { .map(|output_name| {
let output_path = &input_drv input_drv
.outputs .outputs
.get(output_name) .get(output_name)
.expect("missing output_name") .expect("missing output_name")
.path; .path
.as_ref()
// since Derivation is validated, we this can be parsed. .expect("missing output path")
StorePathRef::from_absolute_path(output_path.as_bytes()) .clone()
.expect("invalid output path")
.to_owned()
}) })
.collect(); .collect();
// For each output, ask for the castore node. // For each output, ask for the castore node.

View file

@ -53,6 +53,8 @@ pub enum DerivationError {
pub enum OutputError { pub enum OutputError {
#[error("Invalid output path {0}: {1}")] #[error("Invalid output path {0}: {1}")]
InvalidOutputPath(String, store_path::Error), InvalidOutputPath(String, store_path::Error),
#[error("Missing output path")]
MissingOutputPath,
#[error("Invalid CAHash: {:?}", .0)] #[error("Invalid CAHash: {:?}", .0)]
InvalidCAHash(CAHash), InvalidCAHash(CAHash),
} }

View file

@ -161,7 +161,13 @@ impl Derivation {
"fixed:out:{}{}:{}", "fixed:out:{}{}:{}",
ca_kind_prefix(ca_hash), ca_kind_prefix(ca_hash),
ca_hash.digest().to_nix_hex_string(), ca_hash.digest().to_nix_hex_string(),
out_output.path out_output
.path
.as_ref()
.map(StorePath::to_absolute_path)
.as_ref()
.map(|s| s as &str)
.unwrap_or(""),
)) ))
.finalize() .finalize()
.into(), .into(),
@ -239,7 +245,7 @@ impl Derivation {
// Assert that outputs are not yet populated, to avoid using this function wrongly. // 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 // We don't also go over self.environment, but it's a sufficient
// footgun prevention mechanism. // footgun prevention mechanism.
assert!(output.path.is_empty()); assert!(output.path.is_none());
let path_name = output_path_name(name, output_name); let path_name = output_path_name(name, output_name);
@ -258,7 +264,7 @@ impl Derivation {
})? })?
}; };
output.path = abs_store_path.to_absolute_path(); output.path = Some(abs_store_path.to_owned());
self.environment.insert( self.environment.insert(
output_name.to_string(), output_name.to_string(),
abs_store_path.to_absolute_path().into(), abs_store_path.to_absolute_path().into(),

View file

@ -1,14 +1,16 @@
use crate::derivation::OutputError;
use crate::nixhash::CAHash; use crate::nixhash::CAHash;
use crate::store_path::StorePathRef; use crate::store_path::StorePathRef;
use crate::{derivation::OutputError, store_path::StorePath};
use serde::de::Unexpected;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use serde_json::Map; use serde_json::Map;
use std::borrow::Cow;
/// References the derivation output. /// References the derivation output.
#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize)] #[derive(Clone, Debug, Default, Eq, PartialEq, Serialize)]
pub struct Output { pub struct Output {
/// Store path of build result. /// Store path of build result.
pub path: String, pub path: Option<StorePath>,
#[serde(flatten)] #[serde(flatten)]
pub ca_hash: Option<CAHash>, // we can only represent a subset here. pub ca_hash: Option<CAHash>, // we can only represent a subset here.
@ -20,18 +22,21 @@ impl<'de> Deserialize<'de> for Output {
D: serde::Deserializer<'de>, D: serde::Deserializer<'de>,
{ {
let fields = Map::deserialize(deserializer)?; let fields = Map::deserialize(deserializer)?;
let path: &str = fields
.get("path")
.ok_or(serde::de::Error::missing_field(
"`path` is missing but required for outputs",
))?
.as_str()
.ok_or(serde::de::Error::invalid_type(
serde::de::Unexpected::Other("certainly not a string"),
&"a string",
))?;
let path = StorePathRef::from_absolute_path(path.as_bytes())
.map_err(|_| serde::de::Error::invalid_value(Unexpected::Str(path), &"StorePath"))?;
Ok(Self { Ok(Self {
path: fields path: Some(path.to_owned()),
.get("path")
.ok_or(serde::de::Error::missing_field(
"`path` is missing but required for outputs",
))?
.as_str()
.ok_or(serde::de::Error::invalid_type(
serde::de::Unexpected::Other("certainly not a string"),
&"a string",
))?
.to_owned(),
ca_hash: CAHash::from_map::<D>(&fields)?, ca_hash: CAHash::from_map::<D>(&fields)?,
}) })
} }
@ -42,6 +47,14 @@ impl Output {
self.ca_hash.is_some() self.ca_hash.is_some()
} }
/// The output path as a string -- use `""` to indicate an unset output path.
pub fn path_str(&self) -> Cow<str> {
match &self.path {
None => Cow::Borrowed(""),
Some(path) => Cow::Owned(path.to_absolute_path()),
}
}
pub fn validate(&self, validate_output_paths: bool) -> Result<(), OutputError> { pub fn validate(&self, validate_output_paths: bool) -> Result<(), OutputError> {
if let Some(fixed_output_hash) = &self.ca_hash { if let Some(fixed_output_hash) = &self.ca_hash {
match fixed_output_hash { match fixed_output_hash {
@ -52,10 +65,8 @@ impl Output {
} }
} }
if validate_output_paths { if validate_output_paths && self.path.is_none() {
if let Err(e) = StorePathRef::from_absolute_path(self.path.as_bytes()) { return Err(OutputError::MissingOutputPath);
return Err(OutputError::InvalidOutputPath(self.path.to_string(), e));
}
} }
Ok(()) Ok(())
} }
@ -67,7 +78,7 @@ impl Output {
fn deserialize_valid_input_addressed_output() { fn deserialize_valid_input_addressed_output() {
let json_bytes = r#" let json_bytes = r#"
{ {
"path": "/nix/store/blablabla" "path": "/nix/store/00bgd045z0d4icpbc2yyz4gx48ak44la-net-tools-1.60_p20170221182432"
}"#; }"#;
let output: Output = serde_json::from_str(json_bytes).expect("must parse"); let output: Output = serde_json::from_str(json_bytes).expect("must parse");
@ -80,7 +91,7 @@ fn deserialize_valid_input_addressed_output() {
fn deserialize_valid_fixed_output() { fn deserialize_valid_fixed_output() {
let json_bytes = r#" let json_bytes = r#"
{ {
"path": "/nix/store/blablablabla", "path": "/nix/store/00bgd045z0d4icpbc2yyz4gx48ak44la-net-tools-1.60_p20170221182432",
"hash": "08813cbee9903c62be4c5027726a418a300da4500b2d369d3af9286f4815ceba", "hash": "08813cbee9903c62be4c5027726a418a300da4500b2d369d3af9286f4815ceba",
"hashAlgo": "r:sha256" "hashAlgo": "r:sha256"
}"#; }"#;
@ -95,7 +106,7 @@ fn deserialize_valid_fixed_output() {
fn deserialize_with_error_invalid_hash_encoding_fixed_output() { fn deserialize_with_error_invalid_hash_encoding_fixed_output() {
let json_bytes = r#" let json_bytes = r#"
{ {
"path": "/nix/store/blablablabla", "path": "/nix/store/00bgd045z0d4icpbc2yyz4gx48ak44la-net-tools-1.60_p20170221182432",
"hash": "IAMNOTVALIDNIXBASE32", "hash": "IAMNOTVALIDNIXBASE32",
"hashAlgo": "r:sha256" "hashAlgo": "r:sha256"
}"#; }"#;
@ -110,7 +121,7 @@ fn deserialize_with_error_invalid_hash_encoding_fixed_output() {
fn deserialize_with_error_invalid_hash_algo_fixed_output() { fn deserialize_with_error_invalid_hash_algo_fixed_output() {
let json_bytes = r#" let json_bytes = r#"
{ {
"path": "/nix/store/blablablabla", "path": "/nix/store/00bgd045z0d4icpbc2yyz4gx48ak44la-net-tools-1.60_p20170221182432",
"hash": "08813cbee9903c62be4c5027726a418a300da4500b2d369d3af9286f4815ceba", "hash": "08813cbee9903c62be4c5027726a418a300da4500b2d369d3af9286f4815ceba",
"hashAlgo": "r:sha1024" "hashAlgo": "r:sha1024"
}"#; }"#;
@ -125,7 +136,7 @@ fn deserialize_with_error_invalid_hash_algo_fixed_output() {
fn deserialize_with_error_missing_hash_algo_fixed_output() { fn deserialize_with_error_missing_hash_algo_fixed_output() {
let json_bytes = r#" let json_bytes = r#"
{ {
"path": "/nix/store/blablablabla", "path": "/nix/store/00bgd045z0d4icpbc2yyz4gx48ak44la-net-tools-1.60_p20170221182432",
"hash": "08813cbee9903c62be4c5027726a418a300da4500b2d369d3af9286f4815ceba", "hash": "08813cbee9903c62be4c5027726a418a300da4500b2d369d3af9286f4815ceba",
}"#; }"#;
let output: Result<Output, _> = serde_json::from_str(json_bytes); let output: Result<Output, _> = serde_json::from_str(json_bytes);
@ -139,7 +150,7 @@ fn deserialize_with_error_missing_hash_algo_fixed_output() {
fn deserialize_with_error_missing_hash_fixed_output() { fn deserialize_with_error_missing_hash_fixed_output() {
let json_bytes = r#" let json_bytes = r#"
{ {
"path": "/nix/store/blablablabla", "path": "/nix/store/00bgd045z0d4icpbc2yyz4gx48ak44la-net-tools-1.60_p20170221182432",
"hashAlgo": "r:sha1024" "hashAlgo": "r:sha1024"
}"#; }"#;
let output: Result<Output, _> = serde_json::from_str(json_bytes); let output: Result<Output, _> = serde_json::from_str(json_bytes);

View file

@ -97,7 +97,13 @@ fn parse_output(i: &[u8]) -> NomResult<&[u8], (String, Output)> {
Ok(hash_with_mode) => Ok(( Ok(hash_with_mode) => Ok((
output_name, output_name,
Output { Output {
path: output_path, // TODO: Check if allowing empty paths here actually makes sense
// or we should make this code stricter.
path: if output_path.is_empty() {
None
} else {
Some(string_to_store_path(i, output_path)?)
},
ca_hash: hash_with_mode, ca_hash: hash_with_mode,
}, },
)), )),
@ -164,21 +170,7 @@ fn parse_input_derivations(i: &[u8]) -> NomResult<&[u8], BTreeMap<StorePath, BTr
new_output_names.insert(output_name); new_output_names.insert(output_name);
} }
#[cfg(debug_assertions)] let input_derivation: StorePath = string_to_store_path(i, input_derivation)?;
let input_derivation_str = input_derivation.clone();
let input_derivation: StorePath =
StorePathRef::from_absolute_path(input_derivation.as_bytes())
.map_err(|e: store_path::Error| {
nom::Err::Failure(NomError {
input: i,
code: e.into(),
})
})?
.to_owned();
#[cfg(debug_assertions)]
assert_eq!(input_derivation_str, input_derivation.to_absolute_path());
input_derivations.insert(input_derivation, new_output_names); input_derivations.insert(input_derivation, new_output_names);
} }
@ -191,14 +183,7 @@ fn parse_input_sources(i: &[u8]) -> NomResult<&[u8], BTreeSet<StorePath>> {
let mut input_sources: BTreeSet<_> = BTreeSet::new(); let mut input_sources: BTreeSet<_> = BTreeSet::new();
for input_source in input_sources_lst.into_iter() { for input_source in input_sources_lst.into_iter() {
let input_source: StorePath = StorePathRef::from_absolute_path(input_source.as_bytes()) let input_source: StorePath = string_to_store_path(i, input_source)?;
.map_err(|e: store_path::Error| {
nom::Err::Failure(NomError {
input: i,
code: e.into(),
})
})?
.to_owned();
if input_sources.contains(&input_source) { if input_sources.contains(&input_source) {
return Err(nom::Err::Failure(NomError { return Err(nom::Err::Failure(NomError {
input: i, input: i,
@ -212,6 +197,28 @@ fn parse_input_sources(i: &[u8]) -> NomResult<&[u8], BTreeSet<StorePath>> {
Ok((i, input_sources)) Ok((i, input_sources))
} }
fn string_to_store_path(
i: &[u8],
path_str: String,
) -> Result<StorePath, nom::Err<NomError<&[u8]>>> {
#[cfg(debug_assertions)]
let path_str2 = path_str.clone();
let path: StorePath = StorePathRef::from_absolute_path(path_str.as_bytes())
.map_err(|e: store_path::Error| {
nom::Err::Failure(NomError {
input: i,
code: e.into(),
})
})?
.to_owned();
#[cfg(debug_assertions)]
assert_eq!(path_str2, path.to_absolute_path());
Ok(path)
}
pub fn parse_derivation(i: &[u8]) -> NomResult<&[u8], Derivation> { pub fn parse_derivation(i: &[u8]) -> NomResult<&[u8], Derivation> {
use nom::Parser; use nom::Parser;
preceded( preceded(
@ -343,15 +350,24 @@ mod tests {
b.insert( b.insert(
"lib".to_string(), "lib".to_string(),
Output { Output {
path: "/nix/store/2vixb94v0hy2xc6p7mbnxxcyc095yyia-has-multi-out-lib" path: Some(
.to_string(), StorePath::from_bytes(
b"2vixb94v0hy2xc6p7mbnxxcyc095yyia-has-multi-out-lib",
)
.unwrap(),
),
ca_hash: None, ca_hash: None,
}, },
); );
b.insert( b.insert(
"out".to_string(), "out".to_string(),
Output { Output {
path: "/nix/store/55lwldka5nyxa08wnvlizyqw02ihy8ic-has-multi-out".to_string(), path: Some(
StorePath::from_bytes(
b"55lwldka5nyxa08wnvlizyqw02ihy8ic-has-multi-out".as_bytes(),
)
.unwrap(),
),
ca_hash: None, ca_hash: None,
}, },
); );
@ -506,14 +522,17 @@ mod tests {
#[test_case( #[test_case(
br#"("out","/nix/store/5vyvcwah9l9kf07d52rcgdk70g2f4y13-foo","","")"#, br#"("out","/nix/store/5vyvcwah9l9kf07d52rcgdk70g2f4y13-foo","","")"#,
("out".to_string(), Output { ("out".to_string(), Output {
path: "/nix/store/5vyvcwah9l9kf07d52rcgdk70g2f4y13-foo".to_string(), path: Some(
StorePathRef::from_absolute_path("/nix/store/5vyvcwah9l9kf07d52rcgdk70g2f4y13-foo".as_bytes()).unwrap().to_owned()),
ca_hash: None ca_hash: None
}); "simple" }); "simple"
)] )]
#[test_case( #[test_case(
br#"("out","/nix/store/4q0pg5zpfmznxscq3avycvf9xdvx50n3-bar","r:sha256","08813cbee9903c62be4c5027726a418a300da4500b2d369d3af9286f4815ceba")"#, br#"("out","/nix/store/4q0pg5zpfmznxscq3avycvf9xdvx50n3-bar","r:sha256","08813cbee9903c62be4c5027726a418a300da4500b2d369d3af9286f4815ceba")"#,
("out".to_string(), Output { ("out".to_string(), Output {
path: "/nix/store/4q0pg5zpfmznxscq3avycvf9xdvx50n3-bar".to_string(), path: Some(
StorePathRef::from_absolute_path(
"/nix/store/4q0pg5zpfmznxscq3avycvf9xdvx50n3-bar".as_bytes()).unwrap().to_owned()),
ca_hash: Some(from_algo_and_mode_and_digest("r:sha256", ca_hash: Some(from_algo_and_mode_and_digest("r:sha256",
data_encoding::HEXLOWER.decode(b"08813cbee9903c62be4c5027726a418a300da4500b2d369d3af9286f4815ceba").unwrap() ).unwrap()), data_encoding::HEXLOWER.decode(b"08813cbee9903c62be4c5027726a418a300da4500b2d369d3af9286f4815ceba").unwrap() ).unwrap()),
}); "fod" }); "fod"

View file

@ -170,7 +170,7 @@ fn derivation_with_trimmed_output_paths(derivation: &Derivation) -> Derivation {
trimmed_outputs.insert( trimmed_outputs.insert(
output_name.to_string(), output_name.to_string(),
Output { Output {
path: "".to_string(), path: None,
..output.clone() ..output.clone()
}, },
); );
@ -314,7 +314,7 @@ fn output_path_construction() {
bar_drv.outputs.insert( bar_drv.outputs.insert(
"out".to_string(), "out".to_string(),
Output { Output {
path: "".to_string(), // will be calculated path: None, // will be calculated
ca_hash: Some(crate::nixhash::CAHash::Nar( ca_hash: Some(crate::nixhash::CAHash::Nar(
crate::nixhash::from_algo_and_digest( crate::nixhash::from_algo_and_digest(
crate::nixhash::HashAlgo::Sha256, crate::nixhash::HashAlgo::Sha256,
@ -365,7 +365,16 @@ fn output_path_construction() {
// assemble foo env // assemble foo env
let foo_env = &mut foo_drv.environment; let foo_env = &mut foo_drv.environment;
foo_env.insert("bar".to_string(), bar_output_path.to_owned().into()); // foo_env.insert("bar".to_string(), StorePathRef:: bar_output_path.to_owned().try_into().unwrap());
foo_env.insert(
"bar".to_string(),
bar_output_path
.as_ref()
.unwrap()
.to_absolute_path()
.as_bytes()
.into(),
);
foo_env.insert("builder".to_string(), ":".into()); foo_env.insert("builder".to_string(), ":".into());
foo_env.insert("name".to_string(), "foo".into()); foo_env.insert("name".to_string(), "foo".into());
foo_env.insert("out".to_string(), "".into()); // will be calculated foo_env.insert("out".to_string(), "".into()); // will be calculated
@ -375,7 +384,7 @@ fn output_path_construction() {
foo_drv.outputs.insert( foo_drv.outputs.insert(
"out".to_string(), "out".to_string(),
Output { Output {
path: "".to_string(), // will be calculated path: None, // will be calculated
ca_hash: None, ca_hash: None,
}, },
); );

View file

@ -123,7 +123,7 @@ mod test {
outputs.insert( outputs.insert(
"out".to_string(), "out".to_string(),
Output { Output {
path: "".to_string(), path: None,
ca_hash: Some(CAHash::Text([0; 32])), // This is disallowed ca_hash: Some(CAHash::Text([0; 32])), // This is disallowed
}, },
); );

View file

@ -132,7 +132,8 @@ pub(crate) fn write_outputs(
write_char(writer, PAREN_OPEN)?; write_char(writer, PAREN_OPEN)?;
let mut elements: Vec<&str> = vec![output_name, &output.path]; let path_str = output.path_str();
let mut elements: Vec<&str> = vec![output_name, &path_str];
let (mode_and_algo, digest) = match &output.ca_hash { let (mode_and_algo, digest) = match &output.ca_hash {
Some(ca_hash) => ( Some(ca_hash) => (