Skip to main content

nix_compat/derivation/
write.rs

1//! This module implements the serialisation of derivations into the
2//! [ATerm][] format used by C++ Nix.
3//!
4//! [ATerm]: http://program-transformation.org/Tools/ATermFormat.html
5
6use crate::aterm::write_escaped;
7use crate::derivation::{DerivationBuilder, OutputName, Outputs, OutputsBuilder};
8use crate::nixhash::Sha256;
9use crate::store_path::{StorePath, StorePathRef};
10use data_encoding::HEXLOWER;
11use smol_str::format_smolstr;
12
13use std::{collections::BTreeSet, io, io::Error, io::Write};
14
15pub const DERIVATION_PREFIX: &str = "Derive";
16pub const PAREN_OPEN: char = '(';
17pub const PAREN_CLOSE: char = ')';
18pub const BRACKET_OPEN: char = '[';
19pub const BRACKET_CLOSE: char = ']';
20pub const COMMA: char = ',';
21pub const QUOTE: char = '"';
22
23/// Something that can be written as ATerm.
24///
25/// Note that we mostly use explicit `write_*` calls
26/// instead since the serialization of the items depends on
27/// the context a lot.
28pub(super) trait AtermWriteable {
29    fn aterm_write(&self, writer: &mut impl io::Write) -> std::io::Result<()>;
30}
31impl<F: AtermWriteable> AtermWriteable for &F {
32    fn aterm_write(&self, writer: &mut impl io::Write) -> std::io::Result<()> {
33        (**self).aterm_write(writer)
34    }
35}
36
37impl<'a> AtermWriteable for StorePathRef<'a> {
38    fn aterm_write(&self, writer: &mut impl Write) -> std::io::Result<()> {
39        write_char(writer, QUOTE)?;
40        write!(writer, "{}", self.as_absolute_path_fmt())?;
41        write_char(writer, QUOTE)?;
42        Ok(())
43    }
44}
45
46impl AtermWriteable for StorePath {
47    fn aterm_write(&self, writer: &mut impl Write) -> std::io::Result<()> {
48        self.as_ref().aterm_write(writer)
49    }
50}
51
52impl AtermWriteable for String {
53    fn aterm_write(&self, writer: &mut impl Write) -> std::io::Result<()> {
54        write_field(writer, self, true)
55    }
56}
57
58impl AtermWriteable for &str {
59    fn aterm_write(&self, writer: &mut impl Write) -> std::io::Result<()> {
60        write_field(writer, self, true)
61    }
62}
63
64impl AtermWriteable for [u8] {
65    fn aterm_write(&self, writer: &mut impl Write) -> std::io::Result<()> {
66        write_field(writer, HEXLOWER.encode(self), false)
67    }
68}
69
70impl AtermWriteable for Sha256 {
71    fn aterm_write(&self, writer: &mut impl Write) -> std::io::Result<()> {
72        write_field(writer, format_smolstr!("{self:x}").as_bytes(), false)
73    }
74}
75
76impl AtermWriteable for OutputName {
77    fn aterm_write(&self, writer: &mut impl io::Write) -> std::io::Result<()> {
78        write_field(writer, self.as_ref(), false)
79    }
80}
81
82impl AtermWriteable for OutputsBuilder {
83    fn aterm_write(&self, writer: &mut impl io::Write) -> std::io::Result<()> {
84        write_char(writer, BRACKET_OPEN)?;
85        match self {
86            OutputsBuilder::Fixed(output_hash) => {
87                write_char(writer, PAREN_OPEN)?;
88
89                write_array_elements(
90                    writer,
91                    [
92                        OutputName::out().as_str(),
93                        "",
94                        output_hash.as_mode_and_algo_str(),
95                        &data_encoding::HEXLOWER.encode(output_hash.hash.digest_as_bytes()),
96                    ],
97                )?;
98
99                write_char(writer, PAREN_CLOSE)?;
100            }
101            OutputsBuilder::InputAddressed(output_names) => {
102                for (ii, output_name) in output_names.iter().enumerate() {
103                    if ii > 0 {
104                        write_char(writer, COMMA)?;
105                    }
106
107                    write_char(writer, PAREN_OPEN)?;
108                    write_array_elements(writer, [output_name.as_str(), "", "", ""])?;
109                    write_char(writer, PAREN_CLOSE)?;
110                }
111            }
112        }
113        write_char(writer, BRACKET_CLOSE)?;
114
115        Ok(())
116    }
117}
118
119impl AtermWriteable for Outputs {
120    fn aterm_write(&self, writer: &mut impl io::Write) -> std::io::Result<()> {
121        write_char(writer, BRACKET_OPEN)?;
122        if let Some((output_hash, store_path)) = self.as_fixed_output() {
123            let path_str = store_path.to_absolute_path();
124
125            write_char(writer, PAREN_OPEN)?;
126            write_array_elements(
127                writer,
128                [
129                    OutputName::out().as_str(),
130                    &path_str,
131                    output_hash.as_mode_and_algo_str(),
132                    &data_encoding::HEXLOWER.encode(output_hash.hash.digest_as_bytes()),
133                ],
134            )?;
135            write_char(writer, PAREN_CLOSE)?;
136        } else {
137            for (ii, (output_name, store_path)) in self.iter().enumerate() {
138                let path_str = store_path.to_absolute_path();
139                if ii > 0 {
140                    write_char(writer, COMMA)?;
141                }
142
143                write_char(writer, PAREN_OPEN)?;
144                write_array_elements(writer, [output_name.as_str(), &path_str, "", ""])?;
145                write_char(writer, PAREN_CLOSE)?;
146            }
147        }
148        write_char(writer, BRACKET_CLOSE)?;
149
150        Ok(())
151    }
152}
153
154impl DerivationBuilder {
155    /// Like `serialize`, but allows replacing the environment, outputs and input_derivations for hash calculations.
156    ///
157    /// This is used to render the ATerm representation of a Derivation "modulo
158    /// fixed-output derivations".
159    ///
160    /// The passed environment, outputs and input_derivations MUST be sorted.
161    pub(super) fn serialize_with_replacements<'a, E, EK, EV, O, I, ID>(
162        &self,
163        writer: &mut impl std::io::Write,
164        environment: E,
165        outputs: O,
166        input_derivations_sorted: I,
167    ) -> Result<(), io::Error>
168    where
169        E: IntoIterator<Item = (EK, EV)>,
170        EK: AsRef<[u8]>,
171        EV: AsRef<[u8]>,
172        O: AtermWriteable,
173        I: Iterator<Item = (ID, &'a BTreeSet<OutputName>)>,
174        ID: AtermWriteable,
175    {
176        writer.write_all(DERIVATION_PREFIX.as_bytes())?;
177        write_char(writer, PAREN_OPEN)?;
178
179        outputs.aterm_write(writer)?;
180        write_char(writer, COMMA)?;
181
182        write_input_derivations(writer, input_derivations_sorted)?;
183        write_char(writer, COMMA)?;
184
185        write_input_sources(writer, &self.input_sources)?;
186        write_char(writer, COMMA)?;
187
188        write_system(writer, &self.system)?;
189        write_char(writer, COMMA)?;
190
191        write_builder(writer, &self.command)?;
192        write_char(writer, COMMA)?;
193
194        write_arguments(writer, &self.arguments)?;
195        write_char(writer, COMMA)?;
196
197        write_environment(writer, environment)?;
198
199        write_char(writer, PAREN_CLOSE)?;
200
201        Ok(())
202    }
203}
204
205/// Writes a character to the writer.
206pub(crate) fn write_char(writer: &mut impl Write, c: char) -> io::Result<()> {
207    let mut buf = [0; 4];
208    let b = c.encode_utf8(&mut buf).as_bytes();
209    writer.write_all(b)
210}
211
212/// Write a string `s` as a quoted field to the writer.
213/// The `escape` argument controls whether escaping will be skipped.
214/// This is the case if `s` is known to only contain characters that need no
215/// escaping.
216pub(crate) fn write_field<S: AsRef<[u8]>>(
217    writer: &mut impl Write,
218    s: S,
219    escape: bool,
220) -> io::Result<()> {
221    write_char(writer, QUOTE)?;
222
223    if !escape {
224        writer.write_all(s.as_ref())?;
225    } else {
226        write_escaped(s, writer)?;
227    }
228
229    write_char(writer, QUOTE)?;
230
231    Ok(())
232}
233
234fn write_array_elements<S>(
235    writer: &mut impl Write,
236    elements: impl IntoIterator<Item = S>,
237) -> Result<(), io::Error>
238where
239    S: AtermWriteable,
240{
241    for (index, element) in elements.into_iter().enumerate() {
242        if index > 0 {
243            write_char(writer, COMMA)?;
244        }
245
246        element.aterm_write(writer)?;
247    }
248
249    Ok(())
250}
251
252fn write_input_derivations<'a, I, K>(
253    writer: &mut impl Write,
254    input_derivations_sorted: I,
255) -> Result<(), io::Error>
256where
257    I: Iterator<Item = (K, &'a BTreeSet<OutputName>)>,
258    K: AtermWriteable,
259{
260    write_char(writer, BRACKET_OPEN)?;
261
262    for (ii, (k, output_names)) in input_derivations_sorted.enumerate() {
263        if ii > 0 {
264            write_char(writer, COMMA)?;
265        }
266
267        write_char(writer, PAREN_OPEN)?;
268        k.aterm_write(writer)?;
269        write_char(writer, COMMA)?;
270
271        write_char(writer, BRACKET_OPEN)?;
272        write_array_elements(writer, output_names)?;
273        write_char(writer, BRACKET_CLOSE)?;
274
275        write_char(writer, PAREN_CLOSE)?;
276    }
277
278    write_char(writer, BRACKET_CLOSE)?;
279
280    Ok(())
281}
282
283fn write_input_sources(
284    writer: &mut impl Write,
285    input_sources: &BTreeSet<StorePath>,
286) -> Result<(), io::Error> {
287    write_char(writer, BRACKET_OPEN)?;
288    write_array_elements(writer, input_sources)?;
289    write_char(writer, BRACKET_CLOSE)?;
290
291    Ok(())
292}
293
294fn write_system(writer: &mut impl Write, platform: &str) -> Result<(), Error> {
295    write_field(writer, platform, true)?;
296    Ok(())
297}
298
299fn write_builder(writer: &mut impl Write, builder: &str) -> Result<(), Error> {
300    write_field(writer, builder, true)?;
301    Ok(())
302}
303
304fn write_arguments(writer: &mut impl Write, arguments: &[String]) -> Result<(), io::Error> {
305    write_char(writer, BRACKET_OPEN)?;
306    write_array_elements(writer, arguments)?;
307    write_char(writer, BRACKET_CLOSE)?;
308
309    Ok(())
310}
311
312fn write_environment<E, K, V>(writer: &mut impl Write, environment: E) -> Result<(), io::Error>
313where
314    E: IntoIterator<Item = (K, V)>,
315    K: AsRef<[u8]>,
316    V: AsRef<[u8]>,
317{
318    write_char(writer, BRACKET_OPEN)?;
319
320    for (i, (k, v)) in environment.into_iter().enumerate() {
321        if i > 0 {
322            write_char(writer, COMMA)?;
323        }
324
325        write_char(writer, PAREN_OPEN)?;
326        write_field(writer, k, false)?;
327        write_char(writer, COMMA)?;
328        write_field(writer, v, true)?;
329        write_char(writer, PAREN_CLOSE)?;
330    }
331
332    write_char(writer, BRACKET_CLOSE)?;
333
334    Ok(())
335}
336
337/// Returns an iterator combining two other iterators (sorted by K).
338///
339/// If K is found in the shadow iterator (B), the element will be yielded from B,
340/// else from A.
341///
342/// This allows computing environment variables without having to clone and
343/// mutate the entire environment variables struct (see [DerivationBuilder::hash_derivation_modulo]).
344pub(super) fn shadow<A, B, K, V>(main: A, shadow: B) -> ShadowIter<A::IntoIter, B::IntoIter>
345where
346    K: Ord,
347    A: IntoIterator<Item = (K, V)>,
348    B: IntoIterator<Item = (K, V)>,
349{
350    ShadowIter {
351        main: main.into_iter().peekable(),
352        shadow: shadow.into_iter().peekable(),
353    }
354}
355
356/// Iterator for the [shadow] function.
357pub(super) struct ShadowIter<A: Iterator, B: Iterator> {
358    main: std::iter::Peekable<A>,
359    shadow: std::iter::Peekable<B>,
360}
361
362impl<A, B, K, V> Iterator for ShadowIter<A, B>
363where
364    K: Ord,
365    A: Iterator<Item = (K, V)>,
366    B: Iterator<Item = (K, V)>,
367{
368    type Item = (K, V);
369
370    fn next(&mut self) -> Option<Self::Item> {
371        match (self.main.peek(), self.shadow.peek()) {
372            (Some((main_k, _)), Some((shadow_k, _))) if main_k < shadow_k => self.main.next(),
373            (Some((main_k, _)), Some((shadow_k, _))) if main_k == shadow_k => {
374                self.main.next();
375                self.shadow.next()
376            }
377            (_, Some(_)) => self.shadow.next(),
378            (Some(_), None) => self.main.next(),
379            (None, None) => None,
380        }
381    }
382}