1use crate::builtins::DerivationError;
3use crate::snix_store_io::SnixStoreIO;
4use bstr::BString;
5use nix_compat::derivation::{DerivationBuilder, OutputHash, OutputName};
6use nix_compat::store_path::{StorePath, StorePathRef};
7use snix_build_glue::known_paths::KnownPaths;
8use snix_eval::builtin_macros::builtins;
9use snix_eval::generators::{self, GenCo, emit_warning_kind};
10use snix_eval::{
11 AddContext, ErrorKind, NixAttrs, NixContext, NixContextElement, Value, WarningKind,
12};
13use std::collections::{BTreeSet, btree_map};
14use std::rc::Rc;
15
16const IGNORE_NULLS: &str = "__ignoreNulls";
18pub const STRUCTURED_ATTRS_ENABLE_KEY: &str = "__structuredAttrs";
19
20fn populate_inputs(
23 drv: &mut DerivationBuilder,
24 full_context: NixContext,
25 known_paths: &KnownPaths,
26) {
27 for element in full_context.iter() {
28 match element {
29 NixContextElement::Plain(source) => {
30 let sp = StorePathRef::from_absolute_path(source.as_bytes())
31 .expect("invalid store path")
32 .to_owned();
33 drv.input_sources.insert(sp);
34 }
35
36 NixContextElement::Single {
37 name,
38 derivation: derivation_str,
39 } => {
40 let (derivation, _rest) =
44 StorePath::from_absolute_path_full(derivation_str).expect("valid store path");
45
46 #[cfg(debug_assertions)]
47 assert!(
48 _rest.iter().next().is_none(),
49 "Extra path not empty for {derivation_str}"
50 );
51
52 let name: OutputName = name
53 .parse()
54 .expect("Snix bug: output name in context invalid");
55
56 match drv.input_derivations.entry(derivation.clone()) {
57 btree_map::Entry::Vacant(entry) => {
58 entry.insert(BTreeSet::from([name]));
59 }
60
61 btree_map::Entry::Occupied(mut entry) => {
62 entry.get_mut().insert(name);
63 }
64 }
65 }
66
67 NixContextElement::Derivation(drv_path) => {
68 let (derivation, _rest) =
69 StorePath::from_absolute_path_full(drv_path).expect("valid store path");
70
71 #[cfg(debug_assertions)]
72 assert!(
73 _rest.iter().next().is_none(),
74 "Extra path not empty for {drv_path}"
75 );
76
77 let output_names = known_paths
79 .get_drv_by_drvpath(&derivation.as_ref())
80 .expect("no known derivation associated to that derivation path")
81 .outputs()
82 .names();
83
84 match drv.input_derivations.entry(derivation.clone()) {
87 btree_map::Entry::Vacant(entry) => {
88 entry.insert(output_names.cloned().collect());
89 }
90
91 btree_map::Entry::Occupied(mut entry) => {
92 entry.get_mut().extend(output_names.cloned());
93 }
94 }
95
96 drv.input_sources.insert(derivation);
97 }
98 }
99 }
100}
101
102#[builtins(state = "Rc<SnixStoreIO>")]
103pub(crate) mod derivation_builtins {
104 use std::collections::BTreeMap;
105 use std::sync::Arc;
106
107 use bstr::ByteSlice;
108
109 use nix_compat::derivation::OutputsBuilder;
110 use nix_compat::nixhash::{HashAlgo, NixHash};
111 use nix_compat::store_path::{StorePathName, hash_placeholder};
112 use snix_build_glue::builder;
113 use snix_eval::generators::Gen;
114 use snix_eval::{NixContext, NixContextElement, NixString, try_cek_to_value};
115
116 use crate::builtins::utils::{select_string, strong_importing_coerce_to_string};
117
118 use super::*;
119
120 #[builtin("placeholder")]
121 async fn builtin_placeholder(co: GenCo, input: Value) -> Result<Value, ErrorKind> {
122 if input.is_catchable() {
123 return Ok(input);
124 }
125
126 let nix_string = input
127 .to_str()
128 .context("looking at output name in builtins.placeholder")?;
129 let output_name = nix_string.to_str()?;
130
131 let placeholder = hash_placeholder(output_name);
132
133 Ok(placeholder.into())
134 }
135
136 #[builtin("derivationStrict")]
141 async fn builtin_derivation_strict(
142 state: Rc<SnixStoreIO>,
143 co: GenCo,
144 input: Value,
145 ) -> Result<Value, ErrorKind> {
146 if input.is_catchable() {
147 return Ok(input);
148 }
149
150 let input = input.to_attrs()?;
151 let name = generators::request_force(&co, input.select_required("name")?.clone()).await;
152
153 if name.is_catchable() {
154 return Ok(name);
155 }
156
157 let name = name.to_str().context("determining derivation name")?;
158 let drv_name = StorePathName::from_slice(&name).map_err(|err| {
159 DerivationError::InvalidDerivation(
160 nix_compat::derivation::DerivationError::InvalidDerivationName(
161 name.to_vec(),
162 err.into(),
163 ),
164 )
165 })?;
166
167 let mut drv = DerivationBuilder::default();
168
169 let mut input_context = NixContext::new();
170
171 fn insert_env(
174 drv: &mut DerivationBuilder,
175 k: &str, v: BString,
177 ) -> Result<(), DerivationError> {
178 if drv.environment.insert(k.into(), v).is_some() {
179 return Err(DerivationError::DuplicateEnvVar(k.into()));
180 }
181 Ok(())
182 }
183
184 let ignore_nulls = match input.select(IGNORE_NULLS) {
186 Some(b) => generators::request_force(&co, b.clone()).await.as_bool()?,
187 None => false,
188 };
189
190 let mut structured_attrs: Option<BTreeMap<&str, serde_json::Value>> =
194 match input.select(STRUCTURED_ATTRS_ENABLE_KEY) {
195 Some(b) => generators::request_force(&co, b.clone())
196 .await
197 .as_bool()?
198 .then_some(Default::default()),
199 None => None,
200 };
201
202 for (arg_name, arg_value) in input.iter_sorted() {
206 let arg_name = arg_name.to_str()?;
207 let value = generators::request_force(&co, arg_value.clone()).await;
209
210 if ignore_nulls && matches!(value, Value::Null) {
212 continue;
213 }
214
215 match arg_name {
216 "args" => {
219 for arg in value.to_list()? {
220 let s =
221 try_cek_to_value!(strong_importing_coerce_to_string(&co, arg).await);
222 input_context.mimic(&s);
223 drv.arguments.push(s.to_str()?.to_owned())
224 }
225 }
226
227 "outputs" => {
229 let outputs = value
230 .to_list()
231 .context("looking at the `outputs` parameter of the derivation")?;
232
233 let mut output_names = Vec::with_capacity(outputs.len());
234
235 for output in outputs {
236 let output_name = generators::request_force(&co, output)
237 .await
238 .to_str()
239 .context("determining output name")?;
240
241 input_context.mimic(&output_name);
242
243 let output_name: OutputName = output_name
244 .to_str()?
245 .parse()
246 .map_err(|err| ErrorKind::SnixError(Arc::new(err)))?;
247
248 output_names.push(output_name);
249 }
250 let mut outputs_set: BTreeSet<_> = output_names.iter().cloned().collect();
251 if outputs_set.len() != output_names.len() {
252 for output_name in output_names {
253 if !outputs_set.remove(&output_name) {
254 return Err(DerivationError::DuplicateOutput(output_name).into());
255 }
256 }
257 unreachable!("should have found the duplicate output name");
258 }
259 drv.outputs = OutputsBuilder::InputAddressed(outputs_set);
260
261 match structured_attrs.as_mut() {
262 Some(structured_attrs) => {
264 let names = output_names.iter().map(ToString::to_string).collect();
265 structured_attrs.insert(arg_name, names);
266 }
267 None => {
269 insert_env(&mut drv, arg_name, output_names.join(" ").into())?;
270 }
271 }
272 }
275
276 "builder" | "system" => {
278 let val_str =
279 try_cek_to_value!(strong_importing_coerce_to_string(&co, value).await);
280 input_context.mimic(&val_str);
281
282 if arg_name == "builder" {
283 val_str.to_str()?.clone_into(&mut drv.command);
284 } else {
285 val_str.to_str()?.clone_into(&mut drv.system);
286 }
287
288 if let Some(ref mut structured_attrs) = structured_attrs {
290 structured_attrs.insert(arg_name, val_str.to_str()?.to_owned().into());
292 } else {
293 insert_env(&mut drv, arg_name, val_str.as_bytes().into())?;
294 }
295 }
296
297 STRUCTURED_ATTRS_ENABLE_KEY if structured_attrs.is_some() => continue,
299
300 IGNORE_NULLS => continue,
302
303 _ => {
305 if arg_name == builder::structured_attrs::JSON_KEY {
306 return Err(DerivationError::StructuredAttrsJsonKeyPresent.into());
307 }
308
309 match structured_attrs {
310 Some(ref mut structured_attrs) => {
312 let val = generators::request_force(&co, value).await;
313 if val.is_catchable() {
314 return Ok(val);
315 }
316
317 let (val_json, context) = val.into_contextful_json(&co).await?;
318 input_context.extend(context);
319
320 structured_attrs.insert(arg_name, val_json);
322 }
323 None => {
325 let val_str = try_cek_to_value!(
326 strong_importing_coerce_to_string(&co, value).await
327 );
328 input_context.mimic(&val_str);
329
330 insert_env(&mut drv, arg_name, val_str.as_bytes().into())?;
331 }
332 }
333 }
334 }
335 }
336 {
340 let hash_str = try_cek_to_value!(
342 select_string(&co, &input, "outputHash")
343 .await
344 .context("evaluating the `outputHash` parameter")?
345 )
346 .filter(|s| !s.is_empty());
347
348 let hash_algo = try_cek_to_value!(
349 select_string(&co, &input, "outputHashAlgo")
350 .await
351 .context("evaluating the `outputHashAlgo` parameter")?
352 )
353 .filter(|s| !s.is_empty());
354
355 let hash_mode = try_cek_to_value!(
356 select_string(&co, &input, "outputHashMode")
357 .await
358 .context("evaluating the `outputHashMode` parameter")?
359 )
360 .filter(|s| !s.is_empty());
361
362 if let Some(hash_str) = hash_str {
364 if !drv.outputs.is_single() && !drv.outputs.contains(&OutputName::out()) {
365 return Err(ErrorKind::SnixError(Arc::new(
366 DerivationError::ConflictingOutputTypes,
367 )));
368 }
369
370 let mode = hash_mode
372 .map(|s| s.parse().map_err(|err| ErrorKind::SnixError(Arc::new(err))))
373 .transpose()?
374 .unwrap_or_default();
375
376 let want_algo: Option<HashAlgo> = hash_algo
378 .map(|s| s.parse())
379 .transpose()
380 .map_err(|err| ErrorKind::SnixError(Arc::new(err)))?;
381
382 let hash = NixHash::from_str(&hash_str, want_algo)
384 .map_err(|err| ErrorKind::SnixError(Arc::new(err)))?;
385
386 if let Some(rest) = hash_str.strip_prefix(hash.algo().sri_prefix())
388 && data_encoding::BASE64.encode_len(hash.algo().digest_length()) != rest.len()
389 {
390 emit_warning_kind(&co, WarningKind::SRIHashWrongPadding).await;
391 }
392 drv.outputs = OutputsBuilder::Fixed(OutputHash { mode, hash });
393 }
394 }
395
396 for output_name in drv.outputs.names() {
401 if drv.environment.remove(output_name.as_str()).is_some() {
402 emit_warning_kind(&co, WarningKind::ShadowedOutput(output_name.to_string())).await;
403 }
404 }
405
406 if let Some(structured_attrs) = structured_attrs {
407 drv.environment.insert(
409 builder::structured_attrs::JSON_KEY.to_string(),
410 BString::from(serde_json::to_string(&structured_attrs)?),
411 );
412 }
413
414 let mut known_paths = state.as_ref().build_state.known_paths.write();
415 populate_inputs(&mut drv, input_context, &known_paths);
416
417 let drv = known_paths
419 .add_derivation(drv_name, drv)
420 .map_err(|e| ErrorKind::SnixError(Arc::from(e)))?;
421
422 Ok(Value::Attrs(NixAttrs::from_iter(
424 drv.outputs()
425 .iter()
426 .map(|(output_name, path)| {
427 (
428 output_name.into(),
429 NixString::new_context_from(
430 NixContextElement::Single {
431 name: output_name.into(),
432 derivation: drv.drv_path().to_absolute_path(),
433 }
434 .into(),
435 path.to_absolute_path(),
436 ),
437 )
438 })
439 .chain(std::iter::once((
440 "drvPath".to_owned(),
441 NixString::new_context_from(
442 NixContextElement::Derivation(drv.drv_path().to_absolute_path()).into(),
443 drv.drv_path().to_absolute_path(),
444 ),
445 ))),
446 )))
447 }
448}