use crate::config;
use crate::exec::{Driver, OpRef, Plan, SetupRef, StateRef};
use crate::utils::relative_path;
use camino::{Utf8Path, Utf8PathBuf};
use std::collections::{HashMap, HashSet};
use std::io::Write;
use std::process::Command;
#[derive(Debug)]
pub enum EmitError {
Io(std::io::Error),
MissingConfig(String),
}
impl From<std::io::Error> for EmitError {
fn from(e: std::io::Error) -> Self {
Self::Io(e)
}
}
impl std::fmt::Display for EmitError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match &self {
EmitError::Io(e) => write!(f, "{}", e),
EmitError::MissingConfig(s) => {
write!(f, "missing required config key: {}", s)
}
}
}
}
impl std::error::Error for EmitError {}
pub type EmitResult = std::result::Result<(), EmitError>;
pub trait EmitBuild {
fn build(
&self,
emitter: &mut Emitter,
input: &str,
output: &str,
) -> EmitResult;
}
pub type EmitBuildFn = fn(&mut Emitter, &str, &str) -> EmitResult;
impl EmitBuild for EmitBuildFn {
fn build(
&self,
emitter: &mut Emitter,
input: &str,
output: &str,
) -> EmitResult {
self(emitter, input, output)
}
}
pub struct EmitRuleBuild {
pub rule_name: String,
}
impl EmitBuild for EmitRuleBuild {
fn build(
&self,
emitter: &mut Emitter,
input: &str,
output: &str,
) -> EmitResult {
emitter.build(&self.rule_name, input, output)?;
Ok(())
}
}
pub trait EmitSetup {
fn setup(&self, emitter: &mut Emitter) -> EmitResult;
}
pub type EmitSetupFn = fn(&mut Emitter) -> EmitResult;
impl EmitSetup for EmitSetupFn {
fn setup(&self, emitter: &mut Emitter) -> EmitResult {
self(emitter)
}
}
pub struct Run<'a> {
pub driver: &'a Driver,
pub plan: Plan,
pub config_data: figment::Figment,
pub global_config: config::GlobalConfig,
}
impl<'a> Run<'a> {
pub fn new(driver: &'a Driver, plan: Plan) -> Self {
let config_data = config::load_config(&driver.name);
let global_config: config::GlobalConfig =
config_data.extract().expect("failed to load config");
Self {
driver,
plan,
config_data,
global_config,
}
}
pub fn show(self) {
if self.plan.stdin {
println!("(stdin) -> {}", self.plan.start);
} else {
println!("start: {}", self.plan.start);
}
for (op, file) in self.plan.steps {
println!("{}: {} -> {}", op, self.driver.ops[op].name, file);
}
if self.plan.stdout {
println!("-> (stdout)");
}
}
pub fn show_dot(self) {
println!("digraph plan {{");
println!(" rankdir=LR;");
println!(" node[shape=box];");
let mut states: HashMap<StateRef, String> = HashMap::new();
let mut ops: HashSet<OpRef> = HashSet::new();
let first_op = self.plan.steps[0].0;
states.insert(
self.driver.ops[first_op].input,
self.plan.start.to_string(),
);
for (op, file) in &self.plan.steps {
states.insert(self.driver.ops[*op].output, file.to_string());
ops.insert(*op);
}
for (state_ref, state) in self.driver.states.iter() {
print!(" {} [", state_ref);
if let Some(filename) = states.get(&state_ref) {
print!(
"label=\"{}\n{}\" penwidth=3 fillcolor=gray style=filled",
state.name, filename
);
} else {
print!("label=\"{}\"", state.name);
}
println!("];");
}
for (op_ref, op) in self.driver.ops.iter() {
print!(" {} -> {} [label=\"{}\"", op.input, op.output, op.name);
if ops.contains(&op_ref) {
print!(" penwidth=3");
}
println!("];");
}
println!("}}");
}
pub fn emit_to_stdout(&self) -> EmitResult {
self.emit(std::io::stdout())
}
pub fn emit_to_dir(&self, dir: &Utf8Path) -> EmitResult {
std::fs::create_dir_all(dir)?;
let ninja_path = dir.join("build.ninja");
let ninja_file = std::fs::File::create(ninja_path)?;
self.emit(ninja_file)
}
pub fn emit_and_run(&self, dir: &Utf8Path) -> EmitResult {
let stale_dir = dir.exists();
self.emit_to_dir(dir)?;
if self.plan.stdin {
let stdin_file = std::fs::File::create(
self.plan.workdir.join(&self.plan.start),
)?;
std::io::copy(
&mut std::io::stdin(),
&mut std::io::BufWriter::new(stdin_file),
)?;
}
let mut cmd = Command::new(&self.global_config.ninja);
cmd.current_dir(dir);
if self.plan.stdout && !self.global_config.verbose {
cmd.stdout(std::process::Stdio::null());
}
cmd.status()?;
if self.plan.stdout {
let stdout_file =
std::fs::File::open(self.plan.workdir.join(self.plan.end()))?;
std::io::copy(
&mut std::io::BufReader::new(stdout_file),
&mut std::io::stdout(),
)?;
}
if !self.global_config.keep_build_dir && !stale_dir {
std::fs::remove_dir_all(dir)?;
}
Ok(())
}
fn emit<T: Write + 'static>(&self, out: T) -> EmitResult {
let mut emitter = Emitter::new(
out,
self.config_data.clone(),
self.plan.workdir.clone(),
);
let mut done_setups = HashSet::<SetupRef>::new();
for (op, _) in &self.plan.steps {
for setup in &self.driver.ops[*op].setups {
if done_setups.insert(*setup) {
let setup = &self.driver.setups[*setup];
writeln!(emitter.out, "# {}", setup.name)?;
setup.emit.setup(&mut emitter)?;
writeln!(emitter.out)?;
}
}
}
emitter.comment("build targets")?;
let mut last_file = &self.plan.start;
for (op, out_file) in &self.plan.steps {
let op = &self.driver.ops[*op];
op.emit.build(
&mut emitter,
last_file.as_str(),
out_file.as_str(),
)?;
last_file = out_file;
}
writeln!(emitter.out)?;
writeln!(emitter.out, "default {}", last_file)?;
Ok(())
}
}
pub struct Emitter {
pub out: Box<dyn Write>,
pub config_data: figment::Figment,
pub workdir: Utf8PathBuf,
}
impl Emitter {
fn new<T: Write + 'static>(
out: T,
config_data: figment::Figment,
workdir: Utf8PathBuf,
) -> Self {
Self {
out: Box::new(out),
config_data,
workdir,
}
}
pub fn config_val(&self, key: &str) -> Result<String, EmitError> {
self.config_data
.extract_inner::<String>(key)
.map_err(|_| EmitError::MissingConfig(key.to_string()))
}
pub fn config_or(&self, key: &str, default: &str) -> String {
self.config_data
.extract_inner::<String>(key)
.unwrap_or_else(|_| default.into())
}
pub fn config_var(&mut self, name: &str, key: &str) -> EmitResult {
self.var(name, &self.config_val(key)?)?;
Ok(())
}
pub fn config_var_or(
&mut self,
name: &str,
key: &str,
default: &str,
) -> std::io::Result<()> {
self.var(name, &self.config_or(key, default))
}
pub fn var(&mut self, name: &str, value: &str) -> std::io::Result<()> {
writeln!(self.out, "{} = {}", name, value)
}
pub fn rule(&mut self, name: &str, command: &str) -> std::io::Result<()> {
writeln!(self.out, "rule {}", name)?;
writeln!(self.out, " command = {}", command)
}
pub fn build(
&mut self,
rule: &str,
input: &str,
output: &str,
) -> std::io::Result<()> {
self.build_cmd(&[output], rule, &[input], &[])
}
pub fn build_cmd(
&mut self,
targets: &[&str],
rule: &str,
deps: &[&str],
implicit_deps: &[&str],
) -> std::io::Result<()> {
write!(self.out, "build")?;
for target in targets {
write!(self.out, " {}", target)?;
}
write!(self.out, ": {}", rule)?;
for dep in deps {
write!(self.out, " {}", dep)?;
}
if !implicit_deps.is_empty() {
write!(self.out, " |")?;
for dep in implicit_deps {
write!(self.out, " {}", dep)?;
}
}
writeln!(self.out)?;
Ok(())
}
pub fn comment(&mut self, text: &str) -> std::io::Result<()> {
writeln!(self.out, "# {}", text)?;
Ok(())
}
pub fn add_file(&self, name: &str, contents: &[u8]) -> std::io::Result<()> {
let path = self.workdir.join(name);
std::fs::write(path, contents)?;
Ok(())
}
pub fn external_path(&self, path: &Utf8Path) -> Utf8PathBuf {
relative_path(path, &self.workdir)
}
pub fn arg(&mut self, name: &str, value: &str) -> std::io::Result<()> {
writeln!(self.out, " {} = {}", name, value)?;
Ok(())
}
}