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}