rix/
derivations.rs

1use crate::parsers::derivations::parse_derivation;
2use serde::{Deserialize, Serialize};
3use std::collections::{BTreeMap, BTreeSet};
4use std::fs;
5use std::io::Write;
6
7#[derive(Deserialize, Serialize, Debug, PartialEq)]
8#[serde(rename_all = "camelCase")]
9pub struct Derivation {
10    pub args: Vec<String>,
11    pub builder: String,
12    pub env: BTreeMap<String, String>,
13    pub input_drvs: BTreeMap<String, BTreeSet<String>>,
14    pub input_srcs: BTreeSet<String>,
15    pub outputs: BTreeMap<String, DerivationOutput>,
16    pub system: String,
17}
18
19#[derive(Deserialize, Serialize, Debug, PartialEq)]
20#[serde(rename_all = "camelCase")]
21pub struct DerivationOutput {
22    pub hash: Option<String>,
23    pub hash_algo: Option<String>,
24    pub path: String,
25}
26
27pub fn load_derivation(drv_path: &str) -> Result<Derivation, String> {
28    let content = fs::read_to_string(drv_path)
29        .map_err(|err| format!("Failed to read '{}': {}", drv_path, err))?;
30    parse_derivation(&content)
31        .map(|(_, derivation)| derivation)
32        .map_err(|err| format!("Failed to parse '{}': {}", drv_path, err))
33}
34
35pub fn save_derivation(writer: &mut impl Write, derivation: &Derivation) -> std::io::Result<()> {
36    write!(writer, "Derive(")?;
37    write_outputs(writer, &derivation.outputs)?;
38    write!(writer, ",")?;
39    write_input_drvs(writer, &derivation.input_drvs)?;
40    write!(writer, ",")?;
41    write_iter(writer, &mut derivation.input_srcs.iter(), write_string)?;
42    write!(writer, ",")?;
43    write_string(writer, &derivation.system)?;
44    write!(writer, ",")?;
45    write_string(writer, &derivation.builder)?;
46    write!(writer, ",")?;
47    write_iter(writer, &mut derivation.args.iter(), write_string)?;
48    write!(writer, ",")?;
49    write_iter(
50        writer,
51        &mut derivation.env.iter(),
52        |writer, (key, value)| {
53            write!(writer, "(")?;
54            write_string(writer, key)?;
55            write!(writer, ",")?;
56            write_string(writer, value)?;
57            write!(writer, ")")
58        },
59    )?;
60    write!(writer, ")")
61}
62
63fn write_outputs(
64    writer: &mut impl Write,
65    outputs: &BTreeMap<String, DerivationOutput>,
66) -> std::io::Result<()> {
67    write_iter(writer, &mut outputs.iter(), |writer, entry| {
68        write_output(writer, entry.0, entry.1)
69    })
70}
71
72fn write_input_drvs(
73    writer: &mut impl Write,
74    input_drvs: &BTreeMap<String, BTreeSet<String>>,
75) -> std::io::Result<()> {
76    write_iter(writer, &mut input_drvs.iter(), |writer, entry| {
77        let (drv_path, drv_outputs) = entry;
78        write!(writer, "(")?;
79        write_string(writer, &drv_path)?;
80        write!(writer, ",")?;
81        write_iter(writer, &mut drv_outputs.iter(), write_string)?;
82        write!(writer, ")")
83    })
84}
85
86fn write_iter<W, T, F>(
87    writer: &mut W,
88    iter: &mut impl Iterator<Item = T>,
89    write_value: F,
90) -> std::io::Result<()>
91where
92    W: Write,
93    F: Fn(&mut W, T) -> std::io::Result<()>,
94{
95    write!(writer, "[")?;
96    if let Some(entry) = iter.next() {
97        write_value(writer, entry)?;
98    }
99    while let Some(entry) = iter.next() {
100        write!(writer, ",")?;
101        write_value(writer, entry)?;
102    }
103    write!(writer, "]")?;
104    Ok(())
105}
106
107fn write_output(
108    writer: &mut impl Write,
109    output_name: &String,
110    output: &DerivationOutput,
111) -> std::io::Result<()> {
112    write!(writer, "(")?;
113    write_string(writer, &output_name)?;
114    write!(writer, ",")?;
115    write_string(writer, &output.path)?;
116    write!(writer, ",")?;
117    write_string(writer, output.hash_algo.as_ref().unwrap_or(&String::new()))?;
118    write!(writer, ",")?;
119    write_string(writer, output.hash.as_ref().unwrap_or(&String::new()))?;
120    write!(writer, ")")
121}
122
123fn write_string(writer: &mut impl Write, string: &String) -> std::io::Result<()> {
124    let mut escaped_string = String::with_capacity(2 * string.capacity());
125    for character in string.chars() {
126        match character {
127            '\t' => escaped_string.push_str("\\t"),
128            '\n' => escaped_string.push_str("\\n"),
129            '\r' => escaped_string.push_str("\\r"),
130            '\\' => escaped_string.push_str("\\\\"),
131            '"' => escaped_string.push_str("\\\""),
132            character => escaped_string.push(character),
133        }
134    }
135    write!(writer, "\"{}\"", escaped_string)
136}
137
138#[cfg(test)]
139mod tests {
140    use super::*;
141    use std::fs::File;
142    use tempfile::tempdir;
143
144    #[test]
145    fn test_save_and_load() {
146        let tmp_dir = tempdir().unwrap();
147        let derivation = sample_derivation();
148        let derivation_path = tmp_dir.path().join("foo.drv");
149        let mut derivation_file = File::create(&derivation_path).unwrap();
150        save_derivation(&mut derivation_file, &derivation).unwrap();
151        let derivation_from_file = load_derivation(&derivation_path.to_str().unwrap()).unwrap();
152        assert_eq!(derivation_from_file, derivation);
153    }
154
155    #[test]
156    fn test_save_and_load_json() {
157        let derivation = sample_derivation();
158        let derivation_json_str = serde_json::to_string(&derivation).unwrap();
159        let derivation_from_json: Derivation = serde_json::from_str(&derivation_json_str).unwrap();
160        assert_eq!(derivation, derivation_from_json);
161    }
162
163    fn sample_derivation() -> Derivation {
164        Derivation {
165            args: vec!["foo".to_owned(), "bar".to_owned()],
166            builder: "foo.sh".to_owned(),
167            env: BTreeMap::from([("var1".to_owned(), "val1".to_owned())]),
168            input_drvs: BTreeMap::from([(
169                "foo.drv".to_owned(),
170                BTreeSet::from(["out".to_owned()]),
171            )]),
172            input_srcs: BTreeSet::from(["/foo.txt".to_owned()]),
173            outputs: BTreeMap::from([(
174                "out".to_owned(),
175                DerivationOutput {
176                    hash: None,
177                    hash_algo: Some("foo".to_owned()),
178                    path: "/foo.out".to_owned(),
179                },
180            )]),
181            system: "foo-x64".to_owned(),
182        }
183    }
184}