blob: 69d0fb1e4a58f0c356161b55a545405be774f7f6 [file] [log] [blame]
// 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());
}
}