Skip to main content

nix_compat/derivation/
builder.rs

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