Skip to main content

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}