use std::collections::HashMap;
use std::error::Error;
use std::io::Write;
use std::io;
use std::path::PathBuf;
use std::process::{Command, Stdio};
fn escape_path(word: &str) -> String {
word.replace("$ ", "$$ ").replace(' ', "$ ").replace(':', "$:")
}
pub struct NinjaWriter<T: Write> {
output: T
}
impl<T: Write> NinjaWriter<T> {
pub fn new(output: T) -> Self {
Self {
output
}
}
pub fn newline(&mut self) -> Result<(), io::Error> {
writeln!(self.output)
}
pub fn comment(&mut self, text: &str) -> Result<(), io::Error> {
writeln!(self.output, "# {}", text)
}
pub fn variable(&mut self, key: &str, value: &str, indent: usize) -> Result<(), io::Error> {
if value.is_empty() {
return Ok(())
}
writeln!(self.output, "{}{} = {}", self._indent(indent), key, value)
}
pub fn pool(&mut self, name: &str, depth: usize) -> Result<(), io::Error> {
writeln!(self.output, "pool {}", name)?;
self.variable("depth", &depth.to_string(), 1)
}
pub fn rule(&mut self, name: &str, command: &str, description: Option<&str>, depfile: Option<&str>, generator: Option<bool>, pool: Option<&str>, restat: Option<bool>, rspfile: Option<&str>, rspfile_content: Option<&str>, deps: Option<&str>) -> Result<(), io::Error> {
writeln!(self.output, "rule {}", name)?;
self.variable("command", command, 1)?;
if let Some(desc) = description {
self.variable("description", desc, 1)?;
}
if let Some(depfile) = depfile {
self.variable("depfile", depfile, 1)?;
}
if let Some(gen) = generator {
if gen {
self.variable("generator", "1", 1)?;
}
}
if let Some(pool) = pool {
self.variable("pool", pool, 1)?;
}
if let Some(restat) = restat {
if restat {
self.variable("restat", "1", 1)?;
}
}
if let Some(rspfile) = rspfile {
self.variable("rspfile", rspfile, 1)?;
}
if let Some(rspfile_content) = rspfile_content {
self.variable("rspfile_content", rspfile_content, 1)?;
}
if let Some(deps) = deps {
self.variable("deps", deps, 1)?;
}
Ok(())
}
pub fn build(&mut self, outputs: Vec<String>, rule: String, inputs: Vec<String>, mut implicit: Vec<String>, mut order_only: Vec<String>, variables: HashMap<String, String>, mut implicit_outputs: Vec<String>, pool: Option<String>, dyndep: Option<String>) -> Result<(), io::Error> {
let mut out_outputs: Vec<String> = outputs.iter().map(|u| escape_path(u)).collect();
let mut all_inputs: Vec<String> = inputs.iter().map(|u| escape_path(u)).collect();
if !implicit.is_empty() {
all_inputs.push("|".to_string());
all_inputs.append(&mut implicit.iter_mut().map(|u| escape_path(u)).collect());
}
if !order_only.is_empty() {
all_inputs.push("||".to_string());
all_inputs.append(&mut order_only.iter_mut().map(|u| escape_path(u)).collect());
}
if !implicit_outputs.is_empty() {
out_outputs.push("|".to_string());
out_outputs.append(&mut implicit_outputs.iter_mut().map(|u| escape_path(u)).collect());
}
all_inputs.insert(0, rule);
writeln!(self.output, "build {}: {}", out_outputs.join(" "), all_inputs.join(" "))?;
if let Some(pool) = pool {
self.variable("pool", &pool, 1)?;
}
if let Some(dyndep) = dyndep {
self.variable("dyndep", &dyndep, 1)?;
}
for (key, value) in variables {
self.variable(&key, &value, 1)?;
}
Ok(())
}
pub fn include(&mut self, path: &str) -> Result<(), io::Error> {
writeln!(self.output, "include {}", path)
}
pub fn subninja(&mut self, path: &str) -> Result<(), io::Error> {
writeln!(self.output, "subninja {}", path)
}
pub fn default(&mut self, paths: Vec<String>) -> Result<(), io::Error> {
writeln!(self.output, "default {}", paths.join(" "))
}
fn _indent(&self, num: usize) -> String {
" ".repeat(num)
}
}
pub fn exec_ninja(root: &PathBuf, args: Vec<String>) -> Result<(), Box<dyn Error>> {
let stat = Command::new("ninja")
.args(args)
.current_dir(root)
.stdout(Stdio::inherit())
.stderr(Stdio::inherit())
.spawn()?
.wait()?;
if stat.success() {
Ok(())
} else {
Err(format!("ninja exited with error: {}", stat.code().unwrap()).into())
}
}
pub fn exec(cmd: &str, dir: &PathBuf, args: Vec<String>) -> Result<(), Box<dyn Error>> {
let stat = Command::new(cmd)
.args(args)
.current_dir(dir)
.stdout(Stdio::inherit())
.stderr(Stdio::inherit())
.spawn()?
.wait()?;
if stat.success() {
Ok(())
} else {
Err(format!("{} exited with error: {}", cmd, stat.code().unwrap()).into())
}
}