1use multimap::MultiMap;
3use std::collections::{BTreeMap, HashMap, HashSet};
4use std::fs::File;
5use std::io::prelude::*;
6use std::path::Path;
7use toml;
8
9use super::ConfigError;
10use types::*;
11#[derive(Clone, Debug, PartialEq)]
13pub struct FullFel4Manifest {
14 pub artifact_path: String,
15 pub target_specs_path: String,
16 pub selected_target: SupportedTarget,
17 pub selected_platform: SupportedPlatform,
18 pub targets: HashMap<SupportedTarget, FullFel4Target>,
19}
20
21#[derive(Clone, Debug, PartialEq)]
23pub struct FullFel4Target {
24 pub identity: SupportedTarget,
25 pub direct_properties: Vec<FlatTomlProperty>,
26 pub build_profile_properties: MultiMap<BuildProfile, FlatTomlProperty>,
27 pub platform_properties: MultiMap<SupportedPlatform, FlatTomlProperty>,
28}
29
30pub fn get_full_manifest<P: AsRef<Path>>(path: P) -> Result<FullFel4Manifest, ConfigError> {
32 let mut manifest_file = File::open(&path).map_err(|_| ConfigError::FileReadFailure)?;
33 let mut toml_string = String::new();
34 let _size = manifest_file
35 .read_to_string(&mut toml_string)
36 .map_err(|_| ConfigError::FileReadFailure)?;
37 parse_full_manifest(toml_string)
38}
39
40pub fn parse_full_manifest<S: AsRef<str>>(toml_string: S) -> Result<FullFel4Manifest, ConfigError> {
42 let manifest = toml_string
43 .as_ref()
44 .parse::<toml::Value>()
45 .map_err(|_| ConfigError::TomlParseFailure)?;
46 toml_to_full_manifest(&manifest)
47}
48
49#[derive(Clone, Debug, PartialEq)]
50struct Fel4Header {
51 pub artifact_path: String,
52 pub target_specs_path: String,
53 pub selected_target: SupportedTarget,
54 pub selected_platform: SupportedPlatform,
55}
56
57fn parse_fel4_header(raw: &toml::Value) -> Result<Fel4Header, ConfigError> {
59 let fel4_table = raw
60 .get("fel4")
61 .and_then(toml::Value::as_table)
62 .ok_or_else(|| ConfigError::MissingTable("fel4".into()))?;
63
64 has_only_approved_substructures(fel4_table, None)
65 .map_err(|name| ConfigError::UnexpectedStructure(format!("fel4.{}", name)))?;
66
67 let selected_target: SupportedTarget = fel4_table
68 .get("target")
69 .and_then(toml::Value::as_str)
70 .and_then(|s| if s.is_empty() { None } else { Some(s) })
71 .ok_or_else(|| ConfigError::MissingRequiredProperty("fel4".into(), "target".into()))?
72 .parse()
73 .map_err(|e| {
74 ConfigError::InvalidValueOption("target", SupportedTarget::target_names(), e)
75 })?;
76 let selected_platform: SupportedPlatform = fel4_table
77 .get("platform")
78 .and_then(toml::Value::as_str)
79 .and_then(|s| if s.is_empty() { None } else { Some(s) })
80 .ok_or_else(|| ConfigError::MissingRequiredProperty("fel4".into(), "platform".into()))?
81 .parse()
82 .map_err(|e| {
83 ConfigError::InvalidValueOption("platform", SupportedPlatform::platform_names(), e)
84 })?;
85
86 let artifact_path = fel4_table
87 .get("artifact-path")
88 .ok_or_else(|| ConfigError::MissingRequiredProperty("fel4".into(), "artifact-path".into()))
89 .and_then(|o| {
90 o.as_str()
91 .ok_or_else(|| ConfigError::NonStringProperty("artifact-path"))
92 })
93 .and_then(|s| {
94 if s.is_empty() {
95 Err(ConfigError::MissingRequiredProperty(
96 "fel4".into(),
97 "artifact-path".into(),
98 ))
99 } else {
100 Ok(s)
101 }
102 })?
103 .to_string();
104 let target_specs_path = fel4_table
105 .get("target-specs-path")
106 .ok_or_else(|| {
107 ConfigError::MissingRequiredProperty("fel4".into(), "target-specs-path".into())
108 })
109 .and_then(|o| {
110 o.as_str()
111 .ok_or_else(|| ConfigError::NonStringProperty("target-specs-path"))
112 })
113 .and_then(|s| {
114 if s.is_empty() {
115 Err(ConfigError::MissingRequiredProperty(
116 "fel4".into(),
117 "target-specs-path".into(),
118 ))
119 } else {
120 Ok(s)
121 }
122 })?
123 .to_string();
124 Ok(Fel4Header {
125 artifact_path,
126 target_specs_path,
127 selected_target,
128 selected_platform,
129 })
130}
131
132pub fn toml_to_full_manifest(raw: &toml::Value) -> Result<FullFel4Manifest, ConfigError> {
134 let Fel4Header {
135 artifact_path,
136 target_specs_path,
137 selected_target,
138 selected_platform,
139 } = parse_fel4_header(&raw)?;
140
141 let allowed_target_subtable_names: HashSet<String> = SupportedPlatform::platform_names()
143 .into_iter()
144 .chain(BuildProfile::build_profile_names().into_iter())
145 .collect();
146 let mut targets: HashMap<SupportedTarget, FullFel4Target> = HashMap::new();
147 for curr_target in SupportedTarget::targets() {
148 let curr_target_name = curr_target.full_name();
149 let curr_target_table = match raw.get(curr_target_name).and_then(toml::Value::as_table) {
150 None => continue,
151 Some(t) => t,
152 };
153 has_only_approved_substructures(curr_target_table, Some(&allowed_target_subtable_names))
154 .map_err(|prop_name| {
155 ConfigError::UnexpectedStructure(format!("{}.{}", curr_target_name, prop_name))
156 })?;
157
158 let mut build_profile_properties: MultiMap<BuildProfile, FlatTomlProperty> =
159 MultiMap::new();
160 for profile in BuildProfile::build_profiles() {
161 let profile_name = profile.full_name();
162 let properties = match curr_target_table
163 .get(profile_name)
164 .and_then(toml::Value::as_table)
165 {
166 None => continue,
167 Some(t) => extract_flat_properties(t).map_err(|prop_name| {
168 ConfigError::UnexpectedStructure(format!(
169 "{}.{}.{}",
170 curr_target_name, profile_name, prop_name
171 ))
172 })?,
173 };
174 build_profile_properties
175 .entry(profile)
176 .or_insert_vec(properties);
177 }
178 let mut platform_properties: MultiMap<SupportedPlatform, FlatTomlProperty> =
179 MultiMap::new();
180 for platform in SupportedPlatform::platforms() {
181 let platform_name = platform.full_name();
182 let properties = match curr_target_table
183 .get(platform_name)
184 .and_then(toml::Value::as_table)
185 {
186 None => continue,
187 Some(t) => extract_flat_properties(t).map_err(|prop_name| {
188 ConfigError::UnexpectedStructure(format!(
189 "{}.{}.{}",
190 curr_target_name, platform_name, prop_name
191 ))
192 })?,
193 };
194 platform_properties
195 .entry(platform)
196 .or_insert_vec(properties);
197 }
198
199 let table_minus_approved_subtables = curr_target_table
200 .iter()
201 .filter(|&(k, _)| !allowed_target_subtable_names.contains(&(*k).clone()))
202 .map(|(k, v)| (k.clone(), v.clone()))
203 .collect();
204 let direct_properties =
205 extract_flat_properties(&table_minus_approved_subtables).map_err(|prop_name| {
206 ConfigError::UnexpectedStructure(format!("{}.{}", curr_target_name, prop_name))
207 })?;
208
209 targets.insert(
210 curr_target,
211 FullFel4Target {
212 identity: curr_target,
213 direct_properties,
214 build_profile_properties,
215 platform_properties,
216 },
217 );
218 }
219
220 Ok(FullFel4Manifest {
221 artifact_path,
222 target_specs_path,
223 selected_target,
224 selected_platform,
225 targets,
226 })
227}
228
229fn has_only_approved_substructures(
230 map: &BTreeMap<String, toml::Value>,
231 approved_substructures: Option<&HashSet<String>>,
232) -> Result<(), String> {
233 for (k, v) in map {
234 match v {
235 &toml::Value::Array(_) | toml::Value::Table(_) => {
236 if let Some(substructure_whitelist) = approved_substructures {
237 if substructure_whitelist.contains(k) {
238 continue;
239 } else {
240 return Err(k.to_string());
241 }
242 } else {
243 return Err(k.to_string());
244 }
245 }
246 _ => continue,
247 }
248 }
249 Ok(())
250}
251
252fn extract_flat_properties(
253 table: &BTreeMap<String, toml::Value>,
254) -> Result<Vec<FlatTomlProperty>, String> {
255 let mut v = Vec::new();
256 for (prop_name, value) in table {
257 let flat_value = match value {
258 &toml::Value::String(ref v) => FlatTomlValue::String(v.to_string()),
259 &toml::Value::Integer(v) => FlatTomlValue::Integer(v),
260 &toml::Value::Float(v) => FlatTomlValue::Float(v),
261 &toml::Value::Boolean(v) => FlatTomlValue::Boolean(v),
262 &toml::Value::Datetime(ref v) => FlatTomlValue::Datetime(v.clone()),
263 &toml::Value::Array(_) | toml::Value::Table(_) => {
264 return Err(prop_name.to_string());
265 }
266 };
267 v.push(FlatTomlProperty::new(prop_name.to_string(), flat_value));
268 }
269 Ok(v)
270}
271#[cfg(test)]
272mod tests {
273 use super::*;
274 use std::path::PathBuf;
275
276 #[test]
277 fn bogus_file_unreadable() {
278 assert_eq!(
279 Err(ConfigError::FileReadFailure),
280 get_full_manifest(PathBuf::from("path/to/nowhere"))
281 );
282 }
283
284 #[test]
285 fn non_toml_file_unparseable() {
286 assert_eq!(
287 Err(ConfigError::TomlParseFailure),
288 parse_full_manifest("<hey>not toml</hey>")
289 );
290 }
291
292 #[test]
293 fn toml_file_without_fel4_table() {
294 assert_eq!(
295 Err(ConfigError::MissingTable("fel4".into())),
296 parse_full_manifest(
297 "just = true
298 some = \"unrelated property\""
299 )
300 );
301 }
302
303 #[test]
304 fn fel4_table_missing_target() {
305 assert_eq!(
306 Err(ConfigError::MissingRequiredProperty(
307 "fel4".into(),
308 "target".into()
309 )),
310 parse_full_manifest(
311 r#"[fel4]
312 wrong_properties = true"#
313 )
314 );
315 }
316
317 #[test]
318 fn fel4_table_invalid_target() {
319 assert_eq!(
320 Err(ConfigError::InvalidValueOption(
321 "target",
322 SupportedTarget::target_names(),
323 "wrong".into()
324 )),
325 parse_full_manifest(
326 r#"[fel4]
327 target = "wrong""#
328 )
329 );
330 }
331
332 #[test]
333 fn fel4_table_missing_platform() {
334 assert_eq!(
335 Err(ConfigError::MissingRequiredProperty(
336 "fel4".into(),
337 "platform".into()
338 )),
339 parse_full_manifest(
340 r#"[fel4]
341 target = "x86_64-sel4-fel4""#
342 )
343 );
344 }
345
346 #[test]
347 fn fel4_table_invalid_platform() {
348 assert_eq!(
349 Err(ConfigError::InvalidValueOption(
350 "platform",
351 SupportedPlatform::platform_names(),
352 "wrong".into()
353 )),
354 parse_full_manifest(
355 r#"[fel4]
356 target = "x86_64-sel4-fel4"
357 platform = "wrong""#
358 )
359 );
360 }
361
362 #[test]
363 fn fel4_table_missing_artifact_path() {
364 assert_eq!(
365 Err(ConfigError::MissingRequiredProperty(
366 "fel4".into(),
367 "artifact-path".into()
368 )),
369 parse_full_manifest(
370 r#"[fel4]
371 target = "x86_64-sel4-fel4"
372 platform = "pc99""#
373 )
374 );
375 }
376
377 #[test]
378 fn fel4_table_missing_target_specs_path() {
379 assert_eq!(
380 Err(ConfigError::MissingRequiredProperty(
381 "fel4".into(),
382 "target-specs-path".into()
383 )),
384 parse_full_manifest(
385 r#"[fel4]
386 target = "x86_64-sel4-fel4"
387 platform = "pc99"
388 artifact-path = "artifacts/path/nested""#
389 )
390 );
391 }
392
393 #[test]
394 fn wrong_type_target_specs_path_in_fel4() {
395 assert_eq!(
396 Err(ConfigError::NonStringProperty("target-specs-path")),
397 parse_full_manifest(
398 r#"[fel4]
399 target = "x86_64-sel4-fel4"
400 platform = "pc99"
401 artifact-path = "somewhere"
402 target-specs-path = true
403 "#
404 )
405 );
406 }
407
408 #[test]
409 fn wrong_type_artifact_path_in_fel4() {
410 assert_eq!(
411 Err(ConfigError::NonStringProperty("artifact-path")),
412 parse_full_manifest(
413 r#"[fel4]
414 target = "x86_64-sel4-fel4"
415 platform = "pc99"
416 artifact-path = true
417 target-specs-path = "where/are/rust/targets"
418 "#
419 )
420 );
421 }
422
423 #[test]
424 fn unexpected_structure_in_fel4() {
425 assert_eq!(
426 Err(ConfigError::UnexpectedStructure("fel4.custom".into())),
427 parse_full_manifest(
428 r#"[fel4]
429 target = "x86_64-sel4-fel4"
430 platform = "pc99"
431 artifact-path = "artifacts/path/nested"
432 target-specs-path = "where/are/rust/targets"
433 [fel4.custom]
434 SomeProp = "hello"
435 "#
436 )
437 );
438 }
439
440 #[test]
441 fn unexpected_structure_in_target() {
442 assert_eq!(
443 Err(ConfigError::UnexpectedStructure(
444 "x86_64-sel4-fel4.custom".into()
445 )),
446 parse_full_manifest(
447 r#"[fel4]
448 target = "x86_64-sel4-fel4"
449 platform = "pc99"
450 artifact-path = "artifacts/path/nested"
451 target-specs-path = "where/are/rust/targets"
452 [x86_64-sel4-fel4]
453 SomeProp = "hello"
454 [x86_64-sel4-fel4.custom]
455 NestedProp = true
456 "#
457 )
458 );
459 }
460
461 #[test]
462 fn unexpected_structure_in_target_platform() {
463 assert_eq!(
464 Err(ConfigError::UnexpectedStructure(
465 "x86_64-sel4-fel4.pc99.custom".into()
466 )),
467 parse_full_manifest(
468 r#"[fel4]
469 target = "x86_64-sel4-fel4"
470 platform = "pc99"
471 artifact-path = "artifacts/path/nested"
472 target-specs-path = "where/are/rust/targets"
473 [x86_64-sel4-fel4]
474 SomeProp = "hello"
475 [x86_64-sel4-fel4.pc99]
476 SomethingPlatformy = true
477 [x86_64-sel4-fel4.pc99.custom]
478 DeepNesting = true
479 "#
480 )
481 );
482 }
483
484 #[test]
485 fn unexpected_structure_in_target_build_profile() {
486 assert_eq!(
487 Err(ConfigError::UnexpectedStructure(
488 "x86_64-sel4-fel4.debug.custom".into()
489 )),
490 parse_full_manifest(
491 r#"[fel4]
492 target = "x86_64-sel4-fel4"
493 platform = "pc99"
494 artifact-path = "artifacts/path/nested"
495 target-specs-path = "where/are/rust/targets"
496 [x86_64-sel4-fel4]
497 SomeProp = "hello"
498 [x86_64-sel4-fel4.debug]
499 KernelPrinting = true
500 [x86_64-sel4-fel4.debug.custom]
501 DeepNesting = true
502 "#
503 )
504 );
505 }
506}