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}