Skip to main content

nix_compat/derivation/
builder.rs

1use std::collections::{BTreeMap, BTreeSet};
2
3use bstr::BString;
4use tracing::warn;
5
6use crate::derivation::outputs::OutputsBuilder;
7use crate::derivation::write::{AtermWriteable, shadow};
8use crate::derivation::{Derivation, UnverifiedDerivation};
9use crate::derivation::{
10    DerivationError, HashDerivationModuloLookup, OutputHashMode, OutputName, Outputs,
11    outputs::OutputsError,
12};
13use crate::nixhash::{Sha256, Sha256Digester};
14use crate::store_path::{self, StorePath, StorePathName};
15
16/// Builder for [`Derivation`] and [`UnverifiedDerivation`].
17///
18/// This helps to build either a `Derivation` or a `UnverifiedDerivation` from
19/// its component fields.
20///
21/// Since both `Derivation` and `UnverifiedDerivation` are immutable
22/// this builder is needed to construct them from scratch.
23///
24/// The fields are public so that they can be mutated without any getters and setters.
25/// The fields don't know about output paths for a derivation, only output names
26/// and whether it's a FOD.
27///
28/// Once the fields are populated:
29///  - [`build`] returns a fully validated [`Derivation`].
30///    It takes the derivation name and the HDM lookup function, calculating output paths on its own.
31///  - [`build_unverified`] returns a [`UnverifiedDerivation`]
32///    It takes [Outputs], which can be constructed using the constructors there.
33///
34/// ## Validation
35///
36/// When building a [`Derivation`] or an [`UnverifiedDerivation`] the following things are checked:
37/// - [`StorePath`] of input derivations end in `.drv`.
38/// - `system` is not empty.
39/// - `builder` is not empty.
40/// - no environment variable name is empty.
41///
42/// In addition, if producing a [Derivation], all of its invariants are checked as well.
43///
44/// [`StorePath`]: nix_compat::store_path::StorePath
45/// [`build`]: Self::build
46/// [`build_unverified`]: Self::build_unverified
47#[derive(Clone, Debug, Default, Eq, PartialEq)]
48#[cfg_attr(feature = "serde", derive(serde::Deserialize))]
49pub struct DerivationBuilder {
50    /// Command line arguments to builder
51    #[cfg_attr(feature = "serde", serde(rename = "args"))]
52    pub arguments: Vec<String>,
53
54    /// Command to execute. This is usually a path to `bash`.
55    ///
56    /// **NOTE:** This is called `builder` in cppnix and in Nix code.
57    pub command: String,
58
59    /// Environment variables to include in build environmennt.
60    #[cfg_attr(feature = "serde", serde(rename = "env"))]
61    pub environment: BTreeMap<String, BString>,
62
63    /// Map from drv path to output names used from this derivation.
64    #[cfg_attr(feature = "serde", serde(rename = "inputDrvs"))]
65    pub input_derivations: BTreeMap<StorePath, BTreeSet<OutputName>>,
66
67    /// Plain store paths of additional inputs.
68    #[cfg_attr(feature = "serde", serde(rename = "inputSrcs"))]
69    pub input_sources: BTreeSet<StorePath>,
70
71    /// Builder for the outputs of this derivation.
72    pub outputs: OutputsBuilder,
73
74    /// System used to build this derivation.
75    pub system: String,
76}
77
78impl DerivationBuilder {
79    /// Calculate HDM with provided environment variable and output replacements.
80    pub(super) fn hash_derivation_modulo_with_outputs<O, L, E, EK, EV>(
81        &self,
82        environment: E,
83        outputs: &O,
84        lookup: L,
85    ) -> Result<Sha256, DerivationError>
86    where
87        E: IntoIterator<Item = (EK, EV)>,
88        EK: AsRef<[u8]>,
89        EV: AsRef<[u8]>,
90        O: AtermWriteable,
91        L: HashDerivationModuloLookup,
92    {
93        // For each input_derivation, look up the hash derivation modulo,
94        // and replace the derivation path with the hash_derivation_modulo.
95        let mut replacements = BTreeMap::<Sha256, BTreeSet<OutputName>>::new();
96        for (drv_path, output_names) in &self.input_derivations {
97            let hdm = lookup
98                .lookup_hdm(&drv_path.as_ref())
99                .ok_or_else(|| DerivationError::MissingInputDerivation(drv_path.clone()))?;
100
101            replacements
102                .entry(hdm)
103                .or_default()
104                .extend(output_names.iter().cloned());
105        }
106
107        let mut hasher = Sha256Digester::new();
108        let _ = self.serialize_with_replacements(
109            &mut hasher,
110            environment,
111            outputs,
112            replacements.iter(),
113        );
114
115        Ok(hasher.finalize())
116    }
117
118    fn hash_derivation_modulo<L>(&self, lookup: L) -> Result<Sha256, DerivationError>
119    where
120        L: HashDerivationModuloLookup,
121    {
122        // In order to generate the correct ATerm, the environment variables that
123        // correspond to each output need to be blank, but since `environment` is
124        // a public field we can't gurantee this. So instead make a special
125        // iterator where the blank entries hide the actual contents in
126        // `environment`.
127        let environment = shadow(
128            self.environment
129                .iter()
130                .map(|(k, v)| (k.as_str(), v.as_slice())),
131            self.outputs.names().map(|name| (name.as_str(), &b""[..])),
132        );
133        self.hash_derivation_modulo_with_outputs(environment, &self.outputs, lookup)
134    }
135
136    /// Calculate and return [`Outputs`] for this `DerivationBuilder`.
137    ///
138    /// This will also ensure that the [`OutputsBuilder`] is valid, and that
139    /// the provided `name` can be combined with the name of the output to
140    /// produce a valid [`StorePath`].
141    ///
142    /// To calculate the outputs paths for input addressed outputs we
143    /// need to lookup the [hash derivation modulo] of all input
144    /// derivations, which is done using the provided
145    /// [`HashDerivationModuloLookup`].
146    ///
147    /// Internally this calls [`store_path::build_ca_path`] or
148    /// [`store_path::build_output_path`], depending on output type, to actually
149    /// make the [`StorePath`].
150    ///
151    /// [hash derivation modulo]: nix_compat::derivation#hash-derivation-modulo
152    pub fn calculate_outputs<L>(
153        &self,
154        name: &StorePathName,
155        lookup: L,
156    ) -> Result<Outputs, DerivationError>
157    where
158        L: HashDerivationModuloLookup,
159    {
160        match &self.outputs {
161            OutputsBuilder::Fixed(output_hash) => {
162                // For fixed output derivation we use [build_ca_path], otherwise we
163                // use [build_output_path] with [hash_derivation_modulo].
164                let store_path = store_path::build_ca_path(
165                    name,
166                    output_hash.mode == OutputHashMode::Recursive,
167                    &output_hash.hash,
168                    [],
169                    false,
170                );
171                Ok(Outputs::fixed_output(
172                    output_hash.clone(),
173                    store_path.to_owned(),
174                ))
175            }
176            OutputsBuilder::InputAddressed(output_names) if output_names.is_empty() => {
177                Err(OutputsError::NoOutputs().into())
178            }
179            OutputsBuilder::InputAddressed(output_names) => {
180                let hash_derivation_modulo = self.hash_derivation_modulo(lookup)?;
181                let mut outputs = BTreeMap::new();
182                for output_name in output_names.iter() {
183                    // Assemble the name, which is either the drv-name suffixed `-{output_name}`,
184                    // except in the `out` case, where it's omitted.
185                    let name = output_name.build_store_path_name(name).map_err(|e| {
186                        OutputsError::InvalidOutputDerivationPath(name.to_string(), e.into())
187                    })?;
188
189                    // use [build_output_path] with [hash_derivation_modulo].
190                    let store_path =
191                        store_path::build_output_path(&name, &hash_derivation_modulo, output_name);
192                    if outputs
193                        .insert(output_name.clone(), store_path.to_owned())
194                        .is_some()
195                    {
196                        return Err(OutputsError::DuplicateOutputName(output_name.clone()).into());
197                    }
198                }
199                Outputs::input_addressed_from_iter(outputs).map_err(From::from)
200            }
201        }
202    }
203
204    /// Consume this builder and return the built [`Derivation`] if possible.
205    ///
206    /// This will build the [validated] and full [`Derivation`] using the data from
207    /// this `DerivationBuilder`, the provided `name` and provided
208    /// [`HashDerivationModuloLookup`].
209    ///
210    /// [validated]: #validation
211    pub fn build<L>(self, name: &StorePathName, lookup: L) -> Result<Derivation, DerivationError>
212    where
213        L: HashDerivationModuloLookup,
214    {
215        let outputs = self.calculate_outputs(name, lookup)?;
216
217        let inner = self.build_unverified(outputs)?;
218        let drv_path = inner.calculate_derivation_path(name)?;
219        Ok(Derivation { drv_path, inner })
220    }
221
222    /// Consume this builder and return a [`UnverifiedDerivation`] with the provided `outputs`.
223    ///
224    /// This will [validate] and update environment variables for outputs to their store paths.
225    ///
226    /// [validate]: #validation
227    pub fn build_unverified(
228        mut self,
229        outputs: Outputs,
230    ) -> Result<UnverifiedDerivation, DerivationError> {
231        // Set environment variables corresponding to outputs to the store path of the outputs.
232        for (output_name, store_path) in outputs.iter() {
233            if self
234                .environment
235                .insert(
236                    output_name.to_string(),
237                    store_path.to_absolute_path().into(),
238                )
239                .is_some()
240            {
241                warn!(output.name = %output_name, "this derivation's environment shadows the output name {output_name}");
242            }
243        }
244        self.validate()?;
245
246        // Replace outputs builder if it doesn't match the provided outputs
247        if self.outputs != outputs {
248            self.outputs = outputs.clone().into_builder();
249        }
250        Ok(UnverifiedDerivation {
251            builder: self,
252            outputs,
253        })
254    }
255
256    /// [Validate] the fields of this builder.
257    ///
258    /// [Validate]: #validation
259    fn validate(&self) -> Result<(), DerivationError> {
260        // Validate all input_derivation paths to end with .drv.
261        // The output names are already validated as we're using the OutputName type.
262        for input_derivation_path in self.input_derivations.keys() {
263            if !input_derivation_path.name().is_drv() {
264                return Err(DerivationError::InvalidInputDerivationPrefix(
265                    input_derivation_path.to_string(),
266                ));
267            }
268        }
269
270        // validate platform
271        if self.system.is_empty() {
272            return Err(DerivationError::InvalidPlatform(self.system.to_string()));
273        }
274
275        // validate command
276        if self.command.is_empty() {
277            return Err(DerivationError::InvalidCommand(self.command.to_string()));
278        }
279
280        // validate env, none of the keys may be empty.
281        for k in self.environment.keys() {
282            if k.is_empty() {
283                return Err(DerivationError::InvalidEnvironmentKey(k.to_string()));
284            }
285        }
286
287        Ok(())
288    }
289}