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, 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: &str,
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) -> &str {
167        // drv_path MUST end in .drv so this cannot panic
168        self.drv_path.name().strip_suffix(".drv").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>(self, name: &str, lookup_fn: L) -> Result<Derivation, DerivationError>
239    where
240        L: HashDerivationModuloLookup,
241    {
242        let drv_path = self.calculate_derivation_path(name)?;
243        let drv = Derivation {
244            drv_path,
245            inner: self,
246        };
247        let outputs = drv.recalculate_outputs(lookup_fn)?;
248        if drv.outputs != outputs {
249            return Err(DerivationError::InvalidOutputs(
250                outputs::OutputsError::VerificationError(),
251            ));
252        }
253        Ok(drv)
254    }
255
256    /// write the Derivation to the given [std::io::Write], in ATerm format.
257    ///
258    /// The only errors returned are from the passed in writer.
259    pub fn serialize(&self, writer: &mut impl std::io::Write) -> Result<(), io::Error> {
260        self.builder.serialize_with_replacements(
261            writer,
262            &self.builder.environment,
263            &self.outputs,
264            self.builder.input_derivations.iter(),
265        )
266    }
267
268    /// return the ATerm serialization.
269    pub fn to_aterm_bytes(&self) -> Vec<u8> {
270        let mut buf = Vec::new();
271        self.serialize(&mut buf).unwrap();
272        buf
273    }
274
275    /// Parse an Derivation in ATerm serialization, and validate it passes our
276    /// set of [validations].
277    /// This one does not validate output store paths to be calculated correctly.
278    ///
279    /// [validations]: DerivationBuilder#validation
280    pub fn from_aterm_bytes(b: &[u8]) -> Result<Self, parser::Error<&[u8]>> {
281        parser::parse(b)
282    }
283
284    /// Calculate and return the drv path of this `UnverifiedDerivation`.
285    ///
286    /// The drv path is calculated by invoking [`store_path::build_text_path`], using
287    /// the provided `name` with a `.drv` suffix added. All [`input_sources`] and
288    /// keys of [`input_derivations`] are used as references. And the ATerm string of
289    /// the `UnverifiedDerivation` is used as content.
290    ///
291    /// [`input_sources`]: UnverifiedDerivation::input_sources
292    /// [`input_derivations`]: UnverifiedDerivation::input_derivations
293    pub fn calculate_derivation_path(&self, name: &str) -> Result<StorePath, DerivationError> {
294        // collect the list of paths from input_sources AND input_derivations
295        // into a sorted list of references.
296        let mut references: BTreeSet<StorePathRef> = self
297            .builder
298            .input_derivations
299            .keys()
300            .map(StorePath::as_ref)
301            .collect();
302        references.extend(self.builder.input_sources.iter().map(StorePath::as_ref));
303
304        let drv_name = format!("{}.drv", name);
305        store_path::build_text_path(
306            // append .drv to the name
307            &drv_name,
308            self.to_aterm_bytes(),
309            references,
310        )
311        .map_err(|err| DerivationError::InvalidDerivationName(drv_name.to_string(), err))
312        .map(|sp| sp.to_owned())
313    }
314
315    /// Returns the FOD digest, if the derivation is fixed-output, or None if
316    /// it's not.
317    pub fn fod_digest(&self) -> Option<Sha256> {
318        let (out_output_hash, out_output_path) = self.outputs.as_fixed_output()?;
319
320        Some(store_path::fod_digest(
321            out_output_hash.mode == OutputHashMode::Recursive,
322            &out_output_hash.hash,
323            Some(out_output_path.as_ref()),
324        ))
325    }
326
327    /// Calculates the [hash derivation module] of this `UnverifiedDerivation`.
328    ///
329    /// [hash derivation module]: nix_compat::derivation#hash-derivation-module
330    pub fn hash_derivation_modulo<L>(&self, lookup: L) -> Result<Sha256, DerivationError>
331    where
332        L: HashDerivationModuloLookup,
333    {
334        // Fixed-output derivations return a fixed hash.
335        if let Some(hdm) = self.fod_digest() {
336            return Ok(hdm);
337        }
338
339        // Non-Fixed-output derivations return the sha256 digest of the ATerm
340        // notation, but with all input_derivation paths replaced by a recursive
341        // call to this function.
342        // We call [hdm_lookup] rather than recursing
343        // ourselves, so callers can precompute this.
344        self.builder.hash_derivation_modulo_with_outputs(
345            &self.builder.environment,
346            &self.outputs,
347            lookup,
348        )
349    }
350
351    /// Recalculate and return the [`Outputs`] of this `UnverifiedDerivation`.
352    pub fn recalculate_outputs<L>(&self, name: &str, lookup: L) -> Result<Outputs, DerivationError>
353    where
354        L: HashDerivationModuloLookup,
355    {
356        self.builder.calculate_outputs(name, lookup)
357    }
358
359    /// Split this `UnverifiedDerivation` into its constituent parts.
360    pub fn into_parts(mut self) -> (DerivationBuilder, Outputs) {
361        for output_name in self.outputs.names() {
362            self.builder.environment.remove(output_name.as_str());
363        }
364        (self.builder, self.outputs)
365    }
366
367    /// Return command line arguments to builder
368    pub fn arguments(&self) -> &[String] {
369        &self.builder.arguments
370    }
371
372    /// Return builder to execute.
373    ///
374    /// This is usually a path to `bash`.
375    ///
376    /// **NOTE:** This is called `builder` in cppnix and in Nix code.
377    pub fn command(&self) -> &str {
378        &self.builder.command
379    }
380
381    /// Return environment variables to include in build environmennt.
382    pub fn environment(&self) -> &BTreeMap<String, BString> {
383        &self.builder.environment
384    }
385
386    /// Map from drv path to output names used from this derivation.
387    pub fn input_derivations(&self) -> &BTreeMap<StorePath, BTreeSet<OutputName>> {
388        &self.builder.input_derivations
389    }
390
391    /// Return plain store paths of additional inputs.
392    pub fn input_sources(&self) -> &BTreeSet<StorePath> {
393        &self.builder.input_sources
394    }
395
396    /// Return the outputs of this `UnverifiedDerivation`.
397    pub fn outputs(&self) -> &Outputs {
398        &self.outputs
399    }
400
401    /// Return system used to build this derivation.
402    pub fn system(&self) -> &str {
403        &self.builder.system
404    }
405}
406
407impl PartialEq<UnverifiedDerivation> for Derivation {
408    fn eq(&self, other: &UnverifiedDerivation) -> bool {
409        self.as_unverified() == other
410    }
411}
412
413impl PartialEq<&UnverifiedDerivation> for Derivation {
414    fn eq(&self, other: &&UnverifiedDerivation) -> bool {
415        self.as_unverified() == *other
416    }
417}
418
419impl PartialEq<&Derivation> for UnverifiedDerivation {
420    fn eq(&self, other: &&Derivation) -> bool {
421        other.as_unverified() == self
422    }
423}
424
425impl PartialEq<Derivation> for UnverifiedDerivation {
426    fn eq(&self, other: &Derivation) -> bool {
427        other.as_unverified() == self
428    }
429}
430
431#[cfg(feature = "serde")]
432mod serde_impl {
433    use std::collections::{BTreeMap, BTreeSet};
434
435    use bstr::BString;
436
437    use crate::derivation::{
438        Derivation, DerivationBuilder, OutputName, Outputs, UnverifiedDerivation,
439    };
440    use crate::store_path::StorePath;
441
442    impl serde::Serialize for Derivation {
443        fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
444        where
445            S: serde::Serializer,
446        {
447            serde::Serialize::serialize(self.as_unverified(), serializer)
448        }
449    }
450
451    impl serde::Serialize for UnverifiedDerivation {
452        fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
453        where
454            S: serde::Serializer,
455        {
456            use serde::ser::SerializeMap;
457
458            let mut map = serializer.serialize_map(Some(7))?;
459            map.serialize_entry("args", self.arguments())?;
460            map.serialize_entry("builder", self.command())?;
461            map.serialize_entry("env", self.environment())?;
462            map.serialize_entry("inputDrvs", self.input_derivations())?;
463            map.serialize_entry("inputSrcs", self.input_sources())?;
464            map.serialize_entry("outputs", self.outputs())?;
465            map.serialize_entry("system", self.system())?;
466            map.end()
467        }
468    }
469
470    #[derive(serde::Deserialize)]
471    #[serde(rename_all = "camelCase")]
472    struct Helper {
473        args: Vec<String>,
474        builder: String,
475        env: BTreeMap<String, BString>,
476        input_drvs: BTreeMap<StorePath, BTreeSet<OutputName>>,
477        input_srcs: BTreeSet<StorePath>,
478        outputs: Outputs,
479        system: String,
480    }
481
482    impl<'d> serde::Deserialize<'d> for UnverifiedDerivation {
483        fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
484        where
485            D: serde::Deserializer<'d>,
486        {
487            let h = Helper::deserialize(deserializer)?;
488            let outputs = h.outputs;
489            let builder = DerivationBuilder {
490                arguments: h.args,
491                command: h.builder,
492                environment: h.env,
493                input_derivations: h.input_drvs,
494                input_sources: h.input_srcs,
495                outputs: outputs.clone().into_builder(),
496                system: h.system,
497            };
498            Ok(UnverifiedDerivation { builder, outputs })
499        }
500    }
501}
502
503#[cfg(feature = "async")]
504#[allow(dead_code)]
505trait DerivationAsyncExt: Sized {
506    /// Parse an Derivation in ATerm serialization, and validate it passes
507    /// our set of validations, from a asynchronous buffered reader.
508    /// This is a streaming variant of [Derivation::from_aterm_bytes].
509    async fn from_streaming_aterm_bytes<R>(reader: R) -> Result<Self, parser::Error<Vec<u8>>>
510    where
511        R: tokio::io::AsyncBufRead + Unpin + Send;
512}
513
514#[cfg(feature = "async")]
515impl DerivationAsyncExt for UnverifiedDerivation {
516    async fn from_streaming_aterm_bytes<R>(
517        mut reader: R,
518    ) -> Result<UnverifiedDerivation, parser::Error<Vec<u8>>>
519    where
520        R: tokio::io::AsyncBufRead + Unpin + Send,
521    {
522        use tokio::io::AsyncBufReadExt;
523        let mut buffer = Vec::new();
524        loop {
525            let rest = reader.fill_buf().await.unwrap();
526            let length = rest.len();
527
528            // We reached EOF, we can stop and return incompleteness.
529            if length == 0 {
530                return Err(ParserError::Incomplete);
531            }
532
533            buffer.extend_from_slice(rest);
534
535            // Parse the so-far internal buffer of reader.
536            match parser::parse_streaming(&buffer) {
537                (Err(parser::Error::Incomplete), _) => {
538                    reader.consume(length);
539                    continue;
540                }
541                (Ok(derivation), leftover) => {
542                    // We cannot inline it in the next call because `reader` is mutably borrowed
543                    // and has a relationship with the lifetime of `leftover`.
544                    let leftover_length = leftover.len();
545
546                    // Well, if we already had consumed the leftovers of the past fetch
547                    // while believing we were just parsing incomplete ATerm, there's nothing
548                    // we can do about it. The protocol is made this way.
549                    if length >= leftover_length {
550                        // We still have leftover, let's not consume it.
551                        // It's not for us.
552                        reader.consume(length - leftover_length);
553                    }
554                    return Ok(derivation);
555                }
556                (Err(e), _) => {
557                    return Err(e.into());
558                }
559            }
560        }
561    }
562}