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 super::output::Output;
7use crate::derivation::OutputName;
8use crate::nixhash::Sha256;
9use crate::store_path::{StorePath, StorePathRef};
10use crate::{aterm::write_escaped, derivation::Derivation};
11use data_encoding::HEXLOWER;
12use smol_str::format_smolstr;
13
14use std::{collections::BTreeSet, io, io::Error, io::Write};
15
16pub const DERIVATION_PREFIX: &str = "Derive";
17pub const PAREN_OPEN: char = '(';
18pub const PAREN_CLOSE: char = ')';
19pub const BRACKET_OPEN: char = '[';
20pub const BRACKET_CLOSE: char = ']';
21pub const COMMA: char = ',';
22pub const QUOTE: char = '"';
23
24/// Something that can be written as ATerm.
25///
26/// Note that we mostly use explicit `write_*` calls
27/// instead since the serialization of the items depends on
28/// the context a lot.
29pub(super) trait AtermWriteable {
30    fn aterm_write(&self, writer: &mut impl io::Write) -> std::io::Result<()>;
31}
32
33impl<'a> AtermWriteable for &StorePathRef<'a> {
34    fn aterm_write(&self, writer: &mut impl Write) -> std::io::Result<()> {
35        write_char(writer, QUOTE)?;
36        write!(writer, "{}", self.as_absolute_path_fmt())?;
37        write_char(writer, QUOTE)?;
38        Ok(())
39    }
40}
41
42impl AtermWriteable for &StorePath {
43    fn aterm_write(&self, writer: &mut impl Write) -> std::io::Result<()> {
44        (&self.as_ref()).aterm_write(writer)
45    }
46}
47
48impl AtermWriteable for &String {
49    fn aterm_write(&self, writer: &mut impl Write) -> std::io::Result<()> {
50        write_field(writer, self, true)
51    }
52}
53impl AtermWriteable for &str {
54    fn aterm_write(&self, writer: &mut impl Write) -> std::io::Result<()> {
55        write_field(writer, self, true)
56    }
57}
58
59impl AtermWriteable for &[u8] {
60    fn aterm_write(&self, writer: &mut impl Write) -> std::io::Result<()> {
61        write_field(writer, HEXLOWER.encode(self), false)
62    }
63}
64
65impl AtermWriteable for [u8] {
66    fn aterm_write(&self, writer: &mut impl Write) -> std::io::Result<()> {
67        write_field(writer, HEXLOWER.encode(self), false)
68    }
69}
70
71impl AtermWriteable for Sha256 {
72    fn aterm_write(&self, writer: &mut impl Write) -> std::io::Result<()> {
73        write_field(writer, format_smolstr!("{self:x}").as_bytes(), false)
74    }
75}
76
77impl AtermWriteable for &OutputName {
78    fn aterm_write(&self, writer: &mut impl io::Write) -> std::io::Result<()> {
79        write_field(writer, self.as_ref(), false)
80    }
81}
82
83impl Derivation {
84    /// Like `serialize`, but allows replacing the input_derivations for hash calculations.
85    ///
86    /// This is used to render the ATerm representation of a Derivation "modulo
87    /// fixed-output derivations".
88    ///
89    /// The passed input_derivations MUST be sorted.
90    pub(super) fn serialize_with_replacements<'a, K, I>(
91        &self,
92        writer: &mut impl std::io::Write,
93        input_derivations_sorted: I,
94    ) -> Result<(), io::Error>
95    where
96        I: Iterator<Item = (K, &'a BTreeSet<OutputName>)>,
97        K: AtermWriteable,
98    {
99        writer.write_all(DERIVATION_PREFIX.as_bytes())?;
100        write_char(writer, PAREN_OPEN)?;
101
102        write_outputs(writer, &self.outputs)?;
103        write_char(writer, COMMA)?;
104
105        write_input_derivations(writer, input_derivations_sorted)?;
106        write_char(writer, COMMA)?;
107
108        write_input_sources(writer, &self.input_sources)?;
109        write_char(writer, COMMA)?;
110
111        write_system(writer, &self.system)?;
112        write_char(writer, COMMA)?;
113
114        write_builder(writer, &self.builder)?;
115        write_char(writer, COMMA)?;
116
117        write_arguments(writer, &self.arguments)?;
118        write_char(writer, COMMA)?;
119
120        write_environment(writer, &self.environment)?;
121
122        write_char(writer, PAREN_CLOSE)?;
123
124        Ok(())
125    }
126}
127
128// Writes a character to the writer.
129pub(crate) fn write_char(writer: &mut impl Write, c: char) -> io::Result<()> {
130    let mut buf = [0; 4];
131    let b = c.encode_utf8(&mut buf).as_bytes();
132    writer.write_all(b)
133}
134
135// Write a string `s` as a quoted field to the writer.
136// The `escape` argument controls whether escaping will be skipped.
137// This is the case if `s` is known to only contain characters that need no
138// escaping.
139pub(crate) fn write_field<S: AsRef<[u8]>>(
140    writer: &mut impl Write,
141    s: S,
142    escape: bool,
143) -> io::Result<()> {
144    write_char(writer, QUOTE)?;
145
146    if !escape {
147        writer.write_all(s.as_ref())?;
148    } else {
149        write_escaped(s, writer)?;
150    }
151
152    write_char(writer, QUOTE)?;
153
154    Ok(())
155}
156
157fn write_array_elements<S>(
158    writer: &mut impl Write,
159    elements: impl IntoIterator<Item = S>,
160) -> Result<(), io::Error>
161where
162    S: AtermWriteable,
163{
164    for (index, element) in elements.into_iter().enumerate() {
165        if index > 0 {
166            write_char(writer, COMMA)?;
167        }
168
169        element.aterm_write(writer)?;
170    }
171
172    Ok(())
173}
174
175fn write_outputs<'i, I>(writer: &mut impl Write, outputs: I) -> Result<(), io::Error>
176where
177    I: IntoIterator<Item = (&'i OutputName, &'i Output)>,
178{
179    write_char(writer, BRACKET_OPEN)?;
180    for (ii, (output_name, output)) in outputs.into_iter().enumerate() {
181        if ii > 0 {
182            write_char(writer, COMMA)?;
183        }
184
185        write_char(writer, PAREN_OPEN)?;
186
187        let path_str = output
188            .path
189            .as_ref()
190            .map(|sp| sp.to_absolute_path())
191            .unwrap_or_default();
192
193        if let Some(output_hash) = &output.output_hash {
194            write_array_elements(
195                writer,
196                [
197                    output_name.as_str(),
198                    &path_str,
199                    output_hash.as_mode_and_algo_str(),
200                    &data_encoding::HEXLOWER.encode(output_hash.hash.digest_as_bytes()),
201                ],
202            )?;
203        } else {
204            write_array_elements(writer, [output_name.as_str(), &path_str, "", ""])?;
205        };
206
207        write_char(writer, PAREN_CLOSE)?;
208    }
209    write_char(writer, BRACKET_CLOSE)?;
210
211    Ok(())
212}
213
214fn write_input_derivations<'a, I, K>(
215    writer: &mut impl Write,
216    input_derivations_sorted: I,
217) -> Result<(), io::Error>
218where
219    I: Iterator<Item = (K, &'a BTreeSet<OutputName>)>,
220    K: AtermWriteable,
221{
222    write_char(writer, BRACKET_OPEN)?;
223
224    for (ii, (k, output_names)) in input_derivations_sorted.enumerate() {
225        if ii > 0 {
226            write_char(writer, COMMA)?;
227        }
228
229        write_char(writer, PAREN_OPEN)?;
230        k.aterm_write(writer)?;
231        write_char(writer, COMMA)?;
232
233        write_char(writer, BRACKET_OPEN)?;
234        write_array_elements(writer, output_names)?;
235        write_char(writer, BRACKET_CLOSE)?;
236
237        write_char(writer, PAREN_CLOSE)?;
238    }
239
240    write_char(writer, BRACKET_CLOSE)?;
241
242    Ok(())
243}
244
245fn write_input_sources(
246    writer: &mut impl Write,
247    input_sources: &BTreeSet<StorePath>,
248) -> Result<(), io::Error> {
249    write_char(writer, BRACKET_OPEN)?;
250    write_array_elements(writer, input_sources)?;
251    write_char(writer, BRACKET_CLOSE)?;
252
253    Ok(())
254}
255
256fn write_system(writer: &mut impl Write, platform: &str) -> Result<(), Error> {
257    write_field(writer, platform, true)?;
258    Ok(())
259}
260
261fn write_builder(writer: &mut impl Write, builder: &str) -> Result<(), Error> {
262    write_field(writer, builder, true)?;
263    Ok(())
264}
265
266fn write_arguments(writer: &mut impl Write, arguments: &[String]) -> Result<(), io::Error> {
267    write_char(writer, BRACKET_OPEN)?;
268    write_array_elements(writer, arguments)?;
269    write_char(writer, BRACKET_CLOSE)?;
270
271    Ok(())
272}
273
274fn write_environment<E, K, V>(writer: &mut impl Write, environment: E) -> Result<(), io::Error>
275where
276    E: IntoIterator<Item = (K, V)>,
277    K: AsRef<[u8]>,
278    V: AsRef<[u8]>,
279{
280    write_char(writer, BRACKET_OPEN)?;
281
282    for (i, (k, v)) in environment.into_iter().enumerate() {
283        if i > 0 {
284            write_char(writer, COMMA)?;
285        }
286
287        write_char(writer, PAREN_OPEN)?;
288        write_field(writer, k, false)?;
289        write_char(writer, COMMA)?;
290        write_field(writer, v, true)?;
291        write_char(writer, PAREN_CLOSE)?;
292    }
293
294    write_char(writer, BRACKET_CLOSE)?;
295
296    Ok(())
297}