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