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}