nix_compat/derivation/mod.rs
1#![deny(missing_docs)]
2#![deny(missing_debug_implementations)]
3
4//! Nix derivations
5//!
6//! This module allows you to construct, store, parse, … Nix derivations.
7//!
8//! There's two major types representing Derivations,
9//! [UnverifiedDerivation] and [Derivation].
10//!
11//! They can be constructed either from parsing ATerm
12//! (using [Derivation::from_aterm_bytes] or [UnverifiedDerivation::from_aterm_bytes] respectively),
13//! deserializing through serde, or using [DerivationBuilder].
14//!
15//! Contrary to [UnverifiedDerivation], [Derivation] can only be constructed when it's certain
16//! that the output path calculation is done correctly.
17//! So the name and a lookup function for the [hash derivation modulos] needs to
18//! be passed in whenever this type is constructed.
19//!
20//! [hash derivation modulos]: HashDerivationModuloLookup
21//!
22//!
23//! # Hash Derivation Modulo
24//!
25//! In order to ensure that the output paths of input addressed derivations
26//! are derived from the inputs of the derivation, a SHA256, calculated based
27//! on the type of the input derivation, is used. This digest is called the
28//! Hash Derivation Modulo (or HDM for short) and is used recursively
29//! to capture the entire input closure in a kind of merkle tree.
30//!
31//! Generally HDM comes in three flavors: [Fixed], [Derivation Input] and [Derivation Output].
32//! These are described in more detail below.
33//!
34//! ## Fixed
35//!
36//! The Fixed HDM (also called the FOD digest) is used for Fixed Output Derivations and
37//! form the leafs of the derivation merkle tree.
38//!
39//! Unlike the flavors of HDM described later, this one is not based on the ATerm
40//! representation of the derivation. It is instead calculated based on the [`OutputHash`]
41//! and [`StorePath`] of the derivation output.
42//!
43//! The following code illustrates the format:
44//!
45//! ```
46//! # use nix_compat::nixhash::NixHash;
47//! # use nix_compat::format_sha256;
48//! # let is_recursive = true;
49//! # let nix_hash = NixHash::Sha1(hex_literal::hex!("0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33"));
50//! let rec = if is_recursive { "r:" } else { "" };
51//! let algo = nix_hash.algo();
52//! let digest = data_encoding::HEXLOWER.encode(nix_hash.digest_as_bytes());
53//! // The output path of the derivation
54//! let fod_output_path = "/nix/store/mp57d33657rf34lzvlbpfa1gjfv5gmpg-bar";
55//! let hdm = format_sha256!("fixed:out:{rec}{algo}:{digest}:{fod_output_path}");
56//! ```
57//!
58//! This is the hash returned by [`UnverifiedDerivation::fod_digest`].
59//!
60//!
61//! ## Derivation Input
62//!
63//! The Derivation Input HDM is calculated for Input Addressed derivations and used by
64//! dependent derivations in their own HDM calculations to form a merkle tree.
65//!
66//! This HDM is a SHA256 digest of a special version of the ATerm serialized output of the
67//! derivation. In this version of the ATerm serialization, where normally the [`StorePath`]
68//! of input derivations would be written, it has instead been replaced with the
69//! [Derivation Input] HDM or the [Fixed] HDM (depending on what type of derivation it is).
70//!
71//! This is the hash used returned by [`UnverifiedDerivation::hash_derivation_modulo`].
72//!
73//!
74//! ## Derivation Output
75//!
76//! When calculating the HDM we want to use in the creation of output paths we
77//! don't have the output paths yet. So instead of SHA256 digesting the same ATerm format as
78//! used by [Derivation Input], we additionally replace the output paths, as well as their
79//! corresponding environment variables, in the ATerm serialization with empty strings.
80//!
81//! This is the hash used internally by [`DerivationBuilder::calculate_outputs`].
82//!
83//! [Fixed]: #fixed
84//! [Derivation Input]: #derivation-input
85//! [Derivation Output]: #derivation-output
86use crate::nixhash::Sha256;
87use crate::store_path::{self, StorePath, StorePathName, StorePathRef};
88use bstr::BString;
89use std::collections::{BTreeMap, BTreeSet};
90use std::io;
91
92mod errors;
93mod output;
94mod output_name;
95pub mod outputs;
96mod parse_error;
97mod parser;
98mod write;
99
100mod builder;
101mod hdm_lookup;
102#[cfg(test)]
103mod tests;
104
105// Public API of the crate.
106pub use crate::nixhash::{CAHash, NixHash};
107pub use builder::DerivationBuilder;
108pub use errors::DerivationError;
109pub use hdm_lookup::{HashDerivationModuloLookup, lookup_fn};
110pub use output::{OutputHash, OutputHashMode};
111pub use output_name::{OutputName, ParseOutputNameError};
112#[doc(inline)]
113pub use outputs::{Outputs, OutputsBuilder, UnverifiedOutputsBuilder};
114pub use parser::Error as ParserError;
115
116/// A verified derivation with its [`StorePath`].
117///
118/// A [`Derivation`] can only be created by also verifying that all its data
119/// is valid and that its output paths and [`StorePath`] matches that data
120/// and the [hash derivation modulo] of its dependencies.
121///
122/// [hash derivation modulo]: nix_compat::derivation#hash-derivation-modulo
123#[derive(Clone, Debug, Eq, PartialEq)]
124pub struct Derivation {
125 drv_path: StorePath,
126 inner: UnverifiedDerivation,
127}
128
129impl Derivation {
130 /// Parse a `Derivation` in ATerm serialization, and verify that it is valid
131 /// and that its output paths have been calculated correctly.
132 ///
133 /// Use [UnverifiedDerivation::from_aterm_bytes] to parse without verifying this.
134 pub fn from_aterm_bytes<'i, L>(
135 name: &StorePathName,
136 lookup_fn: L,
137 b: &'i [u8],
138 ) -> Result<Derivation, parser::Error<&'i [u8]>>
139 where
140 L: HashDerivationModuloLookup,
141 {
142 let drv = UnverifiedDerivation::from_aterm_bytes(b)?;
143 drv.verify(name, lookup_fn)
144 .map_err(parser::Error::Validation)
145 }
146
147 /// Split this `Derivation` into its constituent parts.
148 pub fn into_parts(self) -> (StorePath, DerivationBuilder, Outputs) {
149 let (builder, outputs) = self.inner.into_parts();
150 (self.drv_path, builder, outputs)
151 }
152
153 /// Return this [`Derivation`] as a [`UnverifiedDerivation`].
154 pub fn as_unverified(&self) -> &UnverifiedDerivation {
155 &self.inner
156 }
157
158 /// Convert this [`Derivation`] into a [`UnverifiedDerivation`].
159 pub fn into_unverified(self) -> UnverifiedDerivation {
160 self.inner
161 }
162
163 /// Return name of this derivation.
164 ///
165 /// This is the name of the store path with the suffix `.drv` stripped.
166 pub fn name(&self) -> &StorePathName {
167 // drv_path MUST end in .drv so this cannot panic
168 self.drv_path.name().strip_drv_ext().unwrap()
169 }
170
171 /// Return [`StorePath`] of this derivation.
172 pub fn drv_path(&self) -> &StorePath {
173 &self.drv_path
174 }
175
176 /// Recalculate and return the derivation [`StorePath`].
177 pub fn recalculate_derivation_path(&self) -> Result<StorePath, DerivationError> {
178 self.inner.calculate_derivation_path(self.name())
179 }
180
181 /// Recalculate and return the [`Outputs`] of this `Derivation`.
182 pub fn recalculate_outputs<L>(&self, lookup: L) -> Result<Outputs, DerivationError>
183 where
184 L: HashDerivationModuloLookup,
185 {
186 self.inner.recalculate_outputs(self.name(), lookup)
187 }
188}
189
190impl std::ops::Deref for Derivation {
191 type Target = UnverifiedDerivation;
192
193 fn deref(&self) -> &Self::Target {
194 self.as_unverified()
195 }
196}
197
198impl AsRef<UnverifiedDerivation> for Derivation {
199 fn as_ref(&self) -> &UnverifiedDerivation {
200 self.as_unverified()
201 }
202}
203
204impl From<Derivation> for UnverifiedDerivation {
205 fn from(value: Derivation) -> Self {
206 value.into_unverified()
207 }
208}
209
210/// A derivation with its output paths filled out but not verified.
211///
212/// This is used for working with serialized versions of the derivation
213/// without having to go through the [hash derivation modulo] verification
214/// required by [`Derivation`].
215///
216/// [`UnverifiedDerivation`] has getters for values, is not mutable and does
217/// have output paths defined using [`Outputs`]. Those output paths are
218/// possibly loaded from somewhere else and are not guaranteed to have been
219/// verified to match the content of the derivation. When a derivation is
220/// deserialized from either ATerm format or via serde this is the returned type.
221///
222/// [hash derivation modulo]: nix_compat::derivation#hash-derivation-modulo
223#[derive(Clone, Debug, Eq, PartialEq)]
224pub struct UnverifiedDerivation {
225 builder: DerivationBuilder,
226 outputs: Outputs,
227}
228
229impl UnverifiedDerivation {
230 /// Verify and consume this `UnverifiedDerivation` and return the [`Derivation`].
231 ///
232 /// It requires the name and lookup function to be passed in,
233 /// as it verifies the outputs to have been [calculated correctly]
234 /// and also [calculates the drvPath].
235 ///
236 /// [calculated correctly]: Self::recalculate_outputs
237 /// [calculates the drvPath]: Self::calculate_derivation_path
238 pub fn verify<L>(
239 self,
240 name: &StorePathName,
241 lookup_fn: L,
242 ) -> Result<Derivation, DerivationError>
243 where
244 L: HashDerivationModuloLookup,
245 {
246 let drv_path = self.calculate_derivation_path(name)?;
247 let drv = Derivation {
248 drv_path,
249 inner: self,
250 };
251 let outputs = drv.recalculate_outputs(lookup_fn)?;
252 if drv.outputs != outputs {
253 return Err(DerivationError::InvalidOutputs(
254 outputs::OutputsError::VerificationError(),
255 ));
256 }
257 Ok(drv)
258 }
259
260 /// write the Derivation to the given [std::io::Write], in ATerm format.
261 ///
262 /// The only errors returned are from the passed in writer.
263 pub fn serialize(&self, writer: &mut impl std::io::Write) -> Result<(), io::Error> {
264 self.builder.serialize_with_replacements(
265 writer,
266 &self.builder.environment,
267 &self.outputs,
268 self.builder.input_derivations.iter(),
269 )
270 }
271
272 /// return the ATerm serialization.
273 pub fn to_aterm_bytes(&self) -> Vec<u8> {
274 let mut buf = Vec::new();
275 self.serialize(&mut buf).unwrap();
276 buf
277 }
278
279 /// Parse an Derivation in ATerm serialization, and validate it passes our
280 /// set of [validations].
281 /// This one does not validate output store paths to be calculated correctly.
282 ///
283 /// [validations]: DerivationBuilder#validation
284 pub fn from_aterm_bytes(b: &[u8]) -> Result<Self, parser::Error<&[u8]>> {
285 parser::parse(b)
286 }
287
288 /// Calculate and return the drv path of this `UnverifiedDerivation`.
289 ///
290 /// The drv path is calculated by invoking [`store_path::build_text_path`], using
291 /// the provided `name` with a `.drv` suffix added. All [`input_sources`] and
292 /// keys of [`input_derivations`] are used as references. And the ATerm string of
293 /// the `UnverifiedDerivation` is used as content.
294 ///
295 /// [`input_sources`]: UnverifiedDerivation::input_sources
296 /// [`input_derivations`]: UnverifiedDerivation::input_derivations
297 pub fn calculate_derivation_path(
298 &self,
299 name: &StorePathName,
300 ) -> Result<StorePath, DerivationError> {
301 // collect the list of paths from input_sources AND input_derivations
302 // into a sorted list of references.
303 let mut references: BTreeSet<StorePathRef> = self
304 .builder
305 .input_derivations
306 .keys()
307 .map(StorePath::as_ref)
308 .collect();
309 references.extend(self.builder.input_sources.iter().map(StorePath::as_ref));
310
311 let drv_name = name.build_drv_name().map_err(|err| {
312 DerivationError::InvalidDerivationName(format!("{}.drv", name).into_bytes(), err.into())
313 })?;
314 Ok(store_path::build_text_path(
315 // append .drv to the name
316 &drv_name,
317 self.to_aterm_bytes(),
318 references,
319 )
320 .to_owned())
321 }
322
323 /// Returns the FOD digest, if the derivation is fixed-output, or None if
324 /// it's not.
325 pub fn fod_digest(&self) -> Option<Sha256> {
326 let (out_output_hash, out_output_path) = self.outputs.as_fixed_output()?;
327
328 Some(store_path::fod_digest(
329 out_output_hash.mode == OutputHashMode::Recursive,
330 &out_output_hash.hash,
331 Some(out_output_path.as_ref()),
332 ))
333 }
334
335 /// Calculates the [hash derivation module] of this `UnverifiedDerivation`.
336 ///
337 /// [hash derivation module]: nix_compat::derivation#hash-derivation-module
338 pub fn hash_derivation_modulo<L>(&self, lookup: L) -> Result<Sha256, DerivationError>
339 where
340 L: HashDerivationModuloLookup,
341 {
342 // Fixed-output derivations return a fixed hash.
343 if let Some(hdm) = self.fod_digest() {
344 return Ok(hdm);
345 }
346
347 // Non-Fixed-output derivations return the sha256 digest of the ATerm
348 // notation, but with all input_derivation paths replaced by a recursive
349 // call to this function.
350 // We call [hdm_lookup] rather than recursing
351 // ourselves, so callers can precompute this.
352 self.builder.hash_derivation_modulo_with_outputs(
353 &self.builder.environment,
354 &self.outputs,
355 lookup,
356 )
357 }
358
359 /// Recalculate and return the [`Outputs`] of this `UnverifiedDerivation`.
360 pub fn recalculate_outputs<L>(
361 &self,
362 name: &StorePathName,
363 lookup: L,
364 ) -> Result<Outputs, DerivationError>
365 where
366 L: HashDerivationModuloLookup,
367 {
368 self.builder.calculate_outputs(name, lookup)
369 }
370
371 /// Split this `UnverifiedDerivation` into its constituent parts.
372 pub fn into_parts(mut self) -> (DerivationBuilder, Outputs) {
373 for output_name in self.outputs.names() {
374 self.builder.environment.remove(output_name.as_str());
375 }
376 (self.builder, self.outputs)
377 }
378
379 /// Return command line arguments to builder
380 pub fn arguments(&self) -> &[String] {
381 &self.builder.arguments
382 }
383
384 /// Return builder to execute.
385 ///
386 /// This is usually a path to `bash`.
387 ///
388 /// **NOTE:** This is called `builder` in cppnix and in Nix code.
389 pub fn command(&self) -> &str {
390 &self.builder.command
391 }
392
393 /// Return environment variables to include in build environmennt.
394 pub fn environment(&self) -> &BTreeMap<String, BString> {
395 &self.builder.environment
396 }
397
398 /// Map from drv path to output names used from this derivation.
399 pub fn input_derivations(&self) -> &BTreeMap<StorePath, BTreeSet<OutputName>> {
400 &self.builder.input_derivations
401 }
402
403 /// Return plain store paths of additional inputs.
404 pub fn input_sources(&self) -> &BTreeSet<StorePath> {
405 &self.builder.input_sources
406 }
407
408 /// Return the outputs of this `UnverifiedDerivation`.
409 pub fn outputs(&self) -> &Outputs {
410 &self.outputs
411 }
412
413 /// Return system used to build this derivation.
414 pub fn system(&self) -> &str {
415 &self.builder.system
416 }
417}
418
419impl PartialEq<UnverifiedDerivation> for Derivation {
420 fn eq(&self, other: &UnverifiedDerivation) -> bool {
421 self.as_unverified() == other
422 }
423}
424
425impl PartialEq<&UnverifiedDerivation> for Derivation {
426 fn eq(&self, other: &&UnverifiedDerivation) -> bool {
427 self.as_unverified() == *other
428 }
429}
430
431impl PartialEq<&Derivation> for UnverifiedDerivation {
432 fn eq(&self, other: &&Derivation) -> bool {
433 other.as_unverified() == self
434 }
435}
436
437impl PartialEq<Derivation> for UnverifiedDerivation {
438 fn eq(&self, other: &Derivation) -> bool {
439 other.as_unverified() == self
440 }
441}
442
443#[cfg(feature = "serde")]
444mod serde_impl {
445 use std::collections::{BTreeMap, BTreeSet};
446
447 use bstr::BString;
448
449 use crate::derivation::{
450 Derivation, DerivationBuilder, OutputName, Outputs, UnverifiedDerivation,
451 };
452 use crate::store_path::StorePath;
453
454 impl serde::Serialize for Derivation {
455 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
456 where
457 S: serde::Serializer,
458 {
459 serde::Serialize::serialize(self.as_unverified(), serializer)
460 }
461 }
462
463 impl serde::Serialize for UnverifiedDerivation {
464 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
465 where
466 S: serde::Serializer,
467 {
468 use serde::ser::SerializeMap;
469
470 let mut map = serializer.serialize_map(Some(7))?;
471 map.serialize_entry("args", self.arguments())?;
472 map.serialize_entry("builder", self.command())?;
473 map.serialize_entry("env", self.environment())?;
474 map.serialize_entry("inputDrvs", self.input_derivations())?;
475 map.serialize_entry("inputSrcs", self.input_sources())?;
476 map.serialize_entry("outputs", self.outputs())?;
477 map.serialize_entry("system", self.system())?;
478 map.end()
479 }
480 }
481
482 #[derive(serde::Deserialize)]
483 #[serde(rename_all = "camelCase")]
484 struct Helper {
485 args: Vec<String>,
486 builder: String,
487 env: BTreeMap<String, BString>,
488 input_drvs: BTreeMap<StorePath, BTreeSet<OutputName>>,
489 input_srcs: BTreeSet<StorePath>,
490 outputs: Outputs,
491 system: String,
492 }
493
494 impl<'d> serde::Deserialize<'d> for UnverifiedDerivation {
495 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
496 where
497 D: serde::Deserializer<'d>,
498 {
499 let h = Helper::deserialize(deserializer)?;
500 let outputs = h.outputs;
501 let builder = DerivationBuilder {
502 arguments: h.args,
503 command: h.builder,
504 environment: h.env,
505 input_derivations: h.input_drvs,
506 input_sources: h.input_srcs,
507 outputs: outputs.clone().into_builder(),
508 system: h.system,
509 };
510 Ok(UnverifiedDerivation { builder, outputs })
511 }
512 }
513}
514
515#[cfg(feature = "async")]
516#[allow(dead_code)]
517trait DerivationAsyncExt: Sized {
518 /// Parse an Derivation in ATerm serialization, and validate it passes
519 /// our set of validations, from a asynchronous buffered reader.
520 /// This is a streaming variant of [Derivation::from_aterm_bytes].
521 async fn from_streaming_aterm_bytes<R>(reader: R) -> Result<Self, parser::Error<Vec<u8>>>
522 where
523 R: tokio::io::AsyncBufRead + Unpin + Send;
524}
525
526#[cfg(feature = "async")]
527impl DerivationAsyncExt for UnverifiedDerivation {
528 async fn from_streaming_aterm_bytes<R>(
529 mut reader: R,
530 ) -> Result<UnverifiedDerivation, parser::Error<Vec<u8>>>
531 where
532 R: tokio::io::AsyncBufRead + Unpin + Send,
533 {
534 use tokio::io::AsyncBufReadExt;
535 let mut buffer = Vec::new();
536 loop {
537 let rest = reader.fill_buf().await.unwrap();
538 let length = rest.len();
539
540 // We reached EOF, we can stop and return incompleteness.
541 if length == 0 {
542 return Err(ParserError::Incomplete);
543 }
544
545 buffer.extend_from_slice(rest);
546
547 // Parse the so-far internal buffer of reader.
548 match parser::parse_streaming(&buffer) {
549 (Err(parser::Error::Incomplete), _) => {
550 reader.consume(length);
551 continue;
552 }
553 (Ok(derivation), leftover) => {
554 // We cannot inline it in the next call because `reader` is mutably borrowed
555 // and has a relationship with the lifetime of `leftover`.
556 let leftover_length = leftover.len();
557
558 // Well, if we already had consumed the leftovers of the past fetch
559 // while believing we were just parsing incomplete ATerm, there's nothing
560 // we can do about it. The protocol is made this way.
561 if length >= leftover_length {
562 // We still have leftover, let's not consume it.
563 // It's not for us.
564 reader.consume(length - leftover_length);
565 }
566 return Ok(derivation);
567 }
568 (Err(e), _) => {
569 return Err(e.into());
570 }
571 }
572 }
573 }
574}