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