Skip to main content

snix_glue/builtins/
mod.rs

1//! Contains builtins that deal with the store or builder.
2
3use std::rc::Rc;
4
5use crate::snix_store_io::SnixStoreIO;
6
7mod derivation;
8mod errors;
9mod fetchers;
10mod import;
11mod utils;
12
13pub use errors::{DerivationError, FetcherError, ImportError};
14
15/// Adds derivation-related builtins to the passed [snix_eval::EvaluationBuilder]:
16///
17/// * `derivation`
18/// * `derivationStrict`
19/// * `toFile`
20///
21/// As they need to interact with `known_paths`, we also need to pass in
22/// `known_paths`.
23pub fn add_derivation_builtins<'co, 'ro, 'env, IO>(
24    eval_builder: snix_eval::EvaluationBuilder<'co, 'ro, 'env, IO>,
25    io: Rc<SnixStoreIO>,
26) -> snix_eval::EvaluationBuilder<'co, 'ro, 'env, IO> {
27    eval_builder
28        .add_builtins(derivation::derivation_builtins::builtins(Rc::clone(&io)))
29        // Add the actual `builtins.derivation` from compiled Nix code
30        .add_src_builtin("derivation", include_str!("derivation.nix"))
31}
32
33/// Adds fetcher builtins to the passed [snix_eval::EvaluationBuilder]:
34///
35/// * `fetchurl`
36/// * `fetchTarball`
37/// * `fetchGit`
38pub fn add_fetcher_builtins<'co, 'ro, 'env, IO>(
39    eval_builder: snix_eval::EvaluationBuilder<'co, 'ro, 'env, IO>,
40    io: Rc<SnixStoreIO>,
41) -> snix_eval::EvaluationBuilder<'co, 'ro, 'env, IO> {
42    eval_builder.add_builtins(fetchers::fetcher_builtins::builtins(Rc::clone(&io)))
43}
44
45/// Adds import-related builtins to the passed [snix_eval::EvaluationBuilder]:
46///
47///
48/// * `filterSource`
49/// * `path`
50/// * `storePath`
51///
52/// As they need to interact with the store implementation, we pass [`SnixStoreIO`].
53/// Due to #176, some IO still sidesteps `EvalIO` and accesses the filesystem directly.
54pub fn add_import_builtins<'co, 'ro, 'env, IO>(
55    eval_builder: snix_eval::EvaluationBuilder<'co, 'ro, 'env, IO>,
56    io: Rc<SnixStoreIO>,
57) -> snix_eval::EvaluationBuilder<'co, 'ro, 'env, IO> {
58    eval_builder.add_builtins(import::import_builtins(io))
59}
60
61#[cfg(test)]
62mod tests {
63    use std::{rc::Rc, sync::Arc};
64
65    use crate::snix_store_io::SnixStoreIO;
66
67    use super::{add_derivation_builtins, add_fetcher_builtins, add_import_builtins};
68    use clap::Parser;
69    use nix_compat::store_path::hash_placeholder;
70    use rstest::rstest;
71    use snix_build::buildservice::DummyBuildService;
72    use snix_eval::{EvalIO, EvaluationResult};
73    use snix_store::utils::{ServiceUrlsMemory, construct_services};
74
75    /// evaluates a given nix expression and returns the result.
76    /// Takes care of setting up the evaluator so it knows about the
77    // `derivation` builtin.
78    fn eval(str: &str) -> EvaluationResult {
79        // We assemble a complete store in memory.
80        let runtime = tokio::runtime::Runtime::new().expect("Failed to build a Tokio runtime");
81        let (blob_service, directory_service, path_info_service, nar_calculation_service) = runtime
82            .block_on(async {
83                construct_services(ServiceUrlsMemory::parse_from(std::iter::empty::<&str>())).await
84            })
85            .expect("Failed to construct store services in memory");
86
87        let io = Rc::new(SnixStoreIO::new(
88            blob_service,
89            directory_service,
90            path_info_service,
91            nar_calculation_service.into(),
92            Arc::<DummyBuildService>::default(),
93            runtime.handle().clone(),
94            Vec::new(),
95        ));
96
97        let mut eval_builder = snix_eval::Evaluation::builder(io.clone() as Rc<dyn EvalIO>);
98        eval_builder = add_derivation_builtins(eval_builder, Rc::clone(&io));
99        eval_builder = add_fetcher_builtins(eval_builder, Rc::clone(&io));
100        eval_builder = add_import_builtins(eval_builder, io);
101        let eval = eval_builder.build();
102
103        // run the evaluation itself.
104        eval.evaluate(str, None)
105    }
106
107    #[test]
108    fn builtins_placeholder_hashes() {
109        assert_eq!(
110            hash_placeholder("out").as_str(),
111            "/1rz4g4znpzjwh1xymhjpm42vipw92pr73vdgl6xs1hycac8kf2n9"
112        );
113
114        assert_eq!(
115            hash_placeholder("").as_str(),
116            "/171rf4jhx57xqz3p7swniwkig249cif71pa08p80mgaf0mqz5bmr"
117        );
118    }
119
120    /// constructs calls to builtins.derivation that should succeed, but produce warnings
121    #[rstest]
122    #[case::r_sha256_wrong_padding(r#"(builtins.derivation { name = "foo"; builder = "/bin/sh"; system = "x86_64-linux"; outputHashMode = "recursive"; outputHashAlgo = "sha256"; outputHash = "sha256-fgIr3TyFGDAXP5+qoAaiMKDg/a1MlT6Fv/S/DaA24S8===="; }).outPath"#, "/nix/store/xm1l9dx4zgycv9qdhcqqvji1z88z534b-foo")]
123    fn builtins_derivation_hash_wrong_padding_warn(
124        #[case] code: &str,
125        #[case] expected_path: &str,
126    ) {
127        let eval_result = eval(code);
128
129        let value = eval_result.value.expect("must succeed");
130
131        match value {
132            snix_eval::Value::String(s) => {
133                assert_eq!(*s, expected_path);
134            }
135            _ => panic!("unexpected value type: {value:?}"),
136        }
137
138        assert!(
139            !eval_result.warnings.is_empty(),
140            "warnings should not be empty"
141        );
142    }
143}