1use crate::builtins::DerivationError;
3use crate::snix_store_io::SnixStoreIO;
4use bstr::BString;
5use nix_compat::derivation::{Derivation, 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(drv: &mut Derivation, full_context: NixContext, known_paths: &KnownPaths) {
23 for element in full_context.iter() {
24 match element {
25 NixContextElement::Plain(source) => {
26 let sp = StorePathRef::from_absolute_path(source.as_bytes())
27 .expect("invalid store path")
28 .to_owned();
29 drv.input_sources.insert(sp);
30 }
31
32 NixContextElement::Single {
33 name,
34 derivation: derivation_str,
35 } => {
36 let (derivation, _rest) =
40 StorePath::from_absolute_path_full(derivation_str).expect("valid store path");
41
42 #[cfg(debug_assertions)]
43 assert!(
44 _rest.iter().next().is_none(),
45 "Extra path not empty for {derivation_str}"
46 );
47
48 let name: OutputName = name
49 .parse()
50 .expect("Snix bug: output name in context invalid");
51
52 match drv.input_derivations.entry(derivation.clone()) {
53 btree_map::Entry::Vacant(entry) => {
54 entry.insert(BTreeSet::from([name]));
55 }
56
57 btree_map::Entry::Occupied(mut entry) => {
58 entry.get_mut().insert(name);
59 }
60 }
61 }
62
63 NixContextElement::Derivation(drv_path) => {
64 let (derivation, _rest) =
65 StorePath::from_absolute_path_full(drv_path).expect("valid store path");
66
67 #[cfg(debug_assertions)]
68 assert!(
69 _rest.iter().next().is_none(),
70 "Extra path not empty for {drv_path}"
71 );
72
73 let output_names = known_paths
75 .get_drv_by_drvpath(&derivation.as_ref())
76 .expect("no known derivation associated to that derivation path")
77 .outputs
78 .keys();
79
80 match drv.input_derivations.entry(derivation.clone()) {
83 btree_map::Entry::Vacant(entry) => {
84 entry.insert(output_names.cloned().collect());
85 }
86
87 btree_map::Entry::Occupied(mut entry) => {
88 entry.get_mut().extend(output_names.cloned());
89 }
90 }
91
92 drv.input_sources.insert(derivation);
93 }
94 }
95 }
96}
97
98#[builtins(state = "Rc<SnixStoreIO>")]
99pub(crate) mod derivation_builtins {
100 use std::collections::BTreeMap;
101 use std::sync::Arc;
102
103 use bstr::ByteSlice;
104
105 use nix_compat::derivation::{Output, Outputs};
106 use nix_compat::nixhash::{HashAlgo, NixHash};
107 use nix_compat::store_path::hash_placeholder;
108 use snix_build_glue::builder;
109 use snix_eval::generators::Gen;
110 use snix_eval::{NixContext, NixContextElement, NixString, try_cek_to_value};
111
112 use crate::builtins::utils::{select_string, strong_importing_coerce_to_string};
113 use crate::fetchurl::fetchurl_derivation_to_fetch;
114
115 use super::*;
116
117 #[builtin("placeholder")]
118 async fn builtin_placeholder(co: GenCo, input: Value) -> Result<Value, ErrorKind> {
119 if input.is_catchable() {
120 return Ok(input);
121 }
122
123 let nix_string = input
124 .to_str()
125 .context("looking at output name in builtins.placeholder")?;
126 let output_name = nix_string.to_str()?;
127
128 let placeholder = hash_placeholder(output_name);
129
130 Ok(placeholder.into())
131 }
132
133 #[builtin("derivationStrict")]
138 async fn builtin_derivation_strict(
139 state: Rc<SnixStoreIO>,
140 co: GenCo,
141 input: Value,
142 ) -> Result<Value, ErrorKind> {
143 if input.is_catchable() {
144 return Ok(input);
145 }
146
147 let input = input.to_attrs()?;
148 let name = generators::request_force(&co, input.select_required("name")?.clone()).await;
149
150 if name.is_catchable() {
151 return Ok(name);
152 }
153
154 let name = name.to_str().context("determining derivation name")?;
155 if name.is_empty() {
156 return Err(ErrorKind::Abort("derivation has empty name".to_string()));
157 }
158 let name = name.to_str()?;
159
160 let mut drv = Derivation::default();
161
162 let mut input_context = NixContext::new();
163
164 fn insert_env(
167 drv: &mut Derivation,
168 k: &str, v: BString,
170 ) -> Result<(), DerivationError> {
171 if drv.environment.insert(k.into(), v).is_some() {
172 return Err(DerivationError::DuplicateEnvVar(k.into()));
173 }
174 Ok(())
175 }
176
177 let ignore_nulls = match input.select(IGNORE_NULLS) {
179 Some(b) => generators::request_force(&co, b.clone()).await.as_bool()?,
180 None => false,
181 };
182
183 let mut structured_attrs: Option<BTreeMap<&str, serde_json::Value>> =
187 match input.select(STRUCTURED_ATTRS_ENABLE_KEY) {
188 Some(b) => generators::request_force(&co, b.clone())
189 .await
190 .as_bool()?
191 .then_some(Default::default()),
192 None => None,
193 };
194
195 for (arg_name, arg_value) in input.iter_sorted() {
199 let arg_name = arg_name.to_str()?;
200 let value = generators::request_force(&co, arg_value.clone()).await;
202
203 if ignore_nulls && matches!(value, Value::Null) {
205 continue;
206 }
207
208 match arg_name {
209 "args" => {
212 for arg in value.to_list()? {
213 let s =
214 try_cek_to_value!(strong_importing_coerce_to_string(&co, arg).await);
215 input_context.mimic(&s);
216 drv.arguments.push(s.to_str()?.to_owned())
217 }
218 }
219
220 "outputs" => {
222 let outputs = value
223 .to_list()
224 .context("looking at the `outputs` parameter of the derivation")?;
225
226 let mut output_names = Vec::with_capacity(outputs.len());
227
228 for output in outputs {
229 let output_name = generators::request_force(&co, output)
230 .await
231 .to_str()
232 .context("determining output name")?;
233
234 input_context.mimic(&output_name);
235
236 let output_name: OutputName = output_name
237 .to_str()?
238 .parse()
239 .map_err(|err| ErrorKind::SnixError(Arc::new(err)))?;
240
241 output_names.push(output_name);
242 }
243 drv.outputs = Outputs::try_from_iter(
244 output_names
245 .iter()
246 .cloned()
247 .map(|name| (name, Output::default())),
248 )
249 .map_err(nix_compat::derivation::DerivationError::from)
250 .map_err(DerivationError::from)?;
251
252 match structured_attrs.as_mut() {
253 Some(structured_attrs) => {
255 let names = output_names.iter().map(ToString::to_string).collect();
256 structured_attrs.insert(arg_name, names);
257 }
258 None => {
260 insert_env(&mut drv, arg_name, output_names.join(" ").into())?;
261 }
262 }
263 }
266
267 "builder" | "system" => {
269 let val_str =
270 try_cek_to_value!(strong_importing_coerce_to_string(&co, value).await);
271 input_context.mimic(&val_str);
272
273 if arg_name == "builder" {
274 val_str.to_str()?.clone_into(&mut drv.builder);
275 } else {
276 val_str.to_str()?.clone_into(&mut drv.system);
277 }
278
279 if let Some(ref mut structured_attrs) = structured_attrs {
281 structured_attrs.insert(arg_name, val_str.to_str()?.to_owned().into());
283 } else {
284 insert_env(&mut drv, arg_name, val_str.as_bytes().into())?;
285 }
286 }
287
288 STRUCTURED_ATTRS_ENABLE_KEY if structured_attrs.is_some() => continue,
290
291 IGNORE_NULLS => continue,
293
294 _ => {
296 match structured_attrs {
297 Some(ref mut structured_attrs) => {
299 let val = generators::request_force(&co, value).await;
300 if val.is_catchable() {
301 return Ok(val);
302 }
303
304 let (val_json, context) = val.into_contextful_json(&co).await?;
305 input_context.extend(context);
306
307 structured_attrs.insert(arg_name, val_json);
309 }
310 None => {
312 if arg_name == builder::structured_attrs::JSON_KEY {
313 return Err(DerivationError::StructuredAttrsJsonKeyPresent.into());
314 }
315 let val_str = try_cek_to_value!(
316 strong_importing_coerce_to_string(&co, value).await
317 );
318 input_context.mimic(&val_str);
319
320 insert_env(&mut drv, arg_name, val_str.as_bytes().into())?;
321 }
322 }
323 }
324 }
325 }
326 {
330 let hash_str = try_cek_to_value!(
332 select_string(&co, &input, "outputHash")
333 .await
334 .context("evaluating the `outputHash` parameter")?
335 )
336 .filter(|s| !s.is_empty());
337
338 let hash_algo = try_cek_to_value!(
339 select_string(&co, &input, "outputHashAlgo")
340 .await
341 .context("evaluating the `outputHashAlgo` parameter")?
342 )
343 .filter(|s| !s.is_empty());
344
345 let hash_mode = try_cek_to_value!(
346 select_string(&co, &input, "outputHashMode")
347 .await
348 .context("evaluating the `outputHashMode` parameter")?
349 )
350 .filter(|s| !s.is_empty());
351
352 if let Some(hash_str) = hash_str {
354 if !drv.outputs.is_single() {
355 return Err(ErrorKind::SnixError(Arc::new(
356 DerivationError::ConflictingOutputTypes,
357 )));
358 }
359
360 let mode = hash_mode
362 .map(|s| s.parse().map_err(|err| ErrorKind::SnixError(Arc::new(err))))
363 .transpose()?
364 .unwrap_or_default();
365
366 let want_algo: Option<HashAlgo> = hash_algo
368 .map(|s| s.parse())
369 .transpose()
370 .map_err(|err| ErrorKind::SnixError(Arc::new(err)))?;
371
372 let hash = NixHash::from_str(&hash_str, want_algo)
374 .map_err(|err| ErrorKind::SnixError(Arc::new(err)))?;
375
376 if let Some(rest) = hash_str.strip_prefix(hash.algo().sri_prefix())
378 && data_encoding::BASE64.encode_len(hash.algo().digest_length()) != rest.len()
379 {
380 emit_warning_kind(&co, WarningKind::SRIHashWrongPadding).await;
381 }
382 drv.outputs = Outputs::from_fod_hash(OutputHash { mode, hash });
383 }
384 }
385
386 for output in drv.outputs.keys() {
391 if drv
392 .environment
393 .insert(output.to_string(), String::new().into())
394 .is_some()
395 {
396 emit_warning_kind(&co, WarningKind::ShadowedOutput(output.to_string())).await;
397 }
398 }
399
400 if let Some(structured_attrs) = structured_attrs {
401 drv.environment.insert(
403 builder::structured_attrs::JSON_KEY.to_string(),
404 BString::from(serde_json::to_string(&structured_attrs)?),
405 );
406 }
407
408 let mut known_paths = state.as_ref().build_state.known_paths.borrow_mut();
409 populate_inputs(&mut drv, input_context, &known_paths);
410
411 drv.validate().map_err(DerivationError::InvalidDerivation)?;
414
415 debug_assert!(
417 drv.outputs.values().all(|output| { output.path.is_none() }),
418 "outputs should still be unset"
419 );
420
421 drv.calculate_output_paths(
423 name,
424 &drv.hash_derivation_modulo(|drv_path| {
427 *known_paths
428 .get_hash_derivation_modulo(drv_path)
429 .unwrap_or_else(|| panic!("{drv_path} not found"))
430 }),
431 )
432 .map_err(DerivationError::InvalidDerivation)?;
433
434 let drv_path = drv
435 .calculate_derivation_path(name)
436 .map_err(DerivationError::InvalidDerivation)?;
437
438 let out = Value::Attrs(NixAttrs::from_iter(
440 drv.outputs
441 .iter()
442 .map(|(name, output)| {
443 (
444 name.into(),
445 NixString::new_context_from(
446 NixContextElement::Single {
447 name: name.into(),
448 derivation: drv_path.to_absolute_path(),
449 }
450 .into(),
451 output.path.as_ref().unwrap().to_absolute_path(),
452 ),
453 )
454 })
455 .chain(std::iter::once((
456 "drvPath".to_owned(),
457 NixString::new_context_from(
458 NixContextElement::Derivation(drv_path.to_absolute_path()).into(),
459 drv_path.to_absolute_path(),
460 ),
461 ))),
462 ));
463
464 if drv.builder == "builtin:fetchurl" {
467 let (name, fetch) = fetchurl_derivation_to_fetch(&drv)
468 .map_err(|e| ErrorKind::SnixError(Arc::from(e)))?;
469
470 known_paths
471 .add_fetch(fetch, &name)
472 .map_err(|e| ErrorKind::SnixError(Arc::from(e)))?;
473 }
474
475 known_paths.add_derivation(drv_path, drv);
477
478 Ok(out)
479 }
480}