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}