| // Copyright 2020 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| use { |
| crate::{ |
| cml, |
| error::{Error, Location}, |
| }, |
| serde_json::Value, |
| serde_json5, |
| std::{ |
| convert::TryInto, |
| fs, |
| io::{Read, Write}, |
| path::{Path, PathBuf}, |
| }, |
| }; |
| |
| /// Read a JSON or JSON5 file. |
| /// Attempts to parse as JSON first. |
| /// If this fails, attempts to parse as JSON5. |
| /// Parsing with serde_json5 is known to be much slower, so we try the faster |
| /// parser first. |
| pub fn json_or_json5_from_file(file: &PathBuf) -> Result<Value, Error> { |
| let mut buffer = String::new(); |
| fs::File::open(&file)?.read_to_string(&mut buffer)?; |
| |
| serde_json::from_str(&buffer).or_else(|_| { |
| // If JSON parsing fails, try JSON5 parsing (which is slower) |
| serde_json5::from_str(&buffer).map_err(|e| { |
| Error::parse( |
| format!("Couldn't read {:#?} as JSON: {}", file, e), |
| e.try_into().ok(), |
| Some(file.as_path()), |
| ) |
| }) |
| }) |
| } |
| |
| /// Write a depfile. |
| /// Given an output and its includes, writes a depfile in Make format. |
| /// If there is no output, deletes the potentially stale depfile. |
| pub fn write_depfile( |
| depfile: &PathBuf, |
| output: Option<&PathBuf>, |
| inputs: &Vec<PathBuf>, |
| ) -> Result<(), Error> { |
| if output.is_none() || inputs.is_empty() { |
| // A non-existent depfile is the same as an empty depfile |
| if depfile.exists() { |
| // Delete stale depfile |
| fs::remove_file(depfile)?; |
| } |
| } else if let Some(output_path) = output { |
| let depfile_contents = format!( |
| "{}:{}\n", |
| output_path.display(), |
| &inputs.iter().map(|i| format!(" {}", i.display())).collect::<String>() |
| ); |
| fs::OpenOptions::new() |
| .create(true) |
| .truncate(true) |
| .write(true) |
| .open(depfile)? |
| .write_all(depfile_contents.as_bytes())?; |
| } |
| Ok(()) |
| } |
| |
| /// Read .cmx file and parse into JSON object. |
| pub fn read_cmx(file: &Path) -> Result<serde_json::Value, Error> { |
| let mut buffer = String::new(); |
| fs::File::open(&file)?.read_to_string(&mut buffer)?; |
| serde_json::from_str(&buffer).map_err(|e| { |
| Error::parse(format!("Couldn't parse file {:?}: {}", file, e), None, Some(file)) |
| }) |
| } |
| |
| /// Read .cml file and parse into a cml::Document. |
| pub fn read_cml(file: &Path) -> Result<cml::Document, Error> { |
| let mut buffer = String::new(); |
| fs::File::open(&file) |
| .map_err(|e| { |
| Error::parse(format!("Couldn't read include {:?}: {}", file, e), None, Some(file)) |
| })? |
| .read_to_string(&mut buffer) |
| .map_err(|e| { |
| Error::parse(format!("Couldn't read include {:?}: {}", file, e), None, Some(file)) |
| })?; |
| serde_json5::from_str(&buffer).map_err(|e| { |
| let serde_json5::Error::Message { location, msg } = e; |
| let location = location.map(|l| Location { line: l.line, column: l.column }); |
| Error::parse(msg, location, Some(file)) |
| }) |
| } |
| |
| pub fn ensure_directory_exists(output: &PathBuf) -> Result<(), Error> { |
| if let Some(parent) = output.parent() { |
| if !parent.exists() { |
| std::fs::create_dir_all(parent)?; |
| } |
| } |
| Ok(()) |
| } |
| |
| #[cfg(test)] |
| mod tests { |
| use super::*; |
| use std::fs::File; |
| use std::io::Read; |
| use tempfile::TempDir; |
| |
| #[test] |
| fn test_write_depfile() { |
| let tmp_dir = TempDir::new().unwrap(); |
| let tmp_path = tmp_dir.path(); |
| let depfile = tmp_path.join("foo.d"); |
| let output = tmp_path.join("foo.cml"); |
| let includes = vec![tmp_path.join("bar.cml"), tmp_path.join("qux.cml")]; |
| write_depfile(&depfile, Some(&output), &includes).unwrap(); |
| |
| let mut depfile_contents = String::new(); |
| File::open(&depfile).unwrap().read_to_string(&mut depfile_contents).unwrap(); |
| assert_eq!( |
| depfile_contents, |
| format!("{tmp}/foo.cml: {tmp}/bar.cml {tmp}/qux.cml\n", tmp = tmp_path.display()) |
| ); |
| } |
| |
| #[test] |
| fn test_ensure_directory_exists() { |
| let tmp_dir = TempDir::new().unwrap(); |
| let tmp_path = tmp_dir.path(); |
| let nested_directory = tmp_path.join("foo/bar"); |
| let nested_file = nested_directory.join("qux.cml"); |
| assert!(!nested_directory.exists()); |
| ensure_directory_exists(&nested_file).unwrap(); |
| assert!(nested_directory.exists()); |
| // Operation is idempotent |
| ensure_directory_exists(&nested_file).unwrap(); |
| assert!(nested_directory.exists()); |
| } |
| } |