1use crate::config;
2use crate::exec::{Driver, OpRef, Plan, SetupRef, StateRef};
3use crate::utils::relative_path;
4use camino::{Utf8Path, Utf8PathBuf};
5use std::collections::{HashMap, HashSet};
6use std::io::Write;
7use std::process::Command;
8
9#[derive(Debug)]
11pub enum EmitError {
12 Io(std::io::Error),
13 MissingConfig(String),
14}
15
16impl From<std::io::Error> for EmitError {
17 fn from(e: std::io::Error) -> Self {
18 Self::Io(e)
19 }
20}
21
22impl std::fmt::Display for EmitError {
23 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
24 match &self {
25 EmitError::Io(e) => write!(f, "{}", e),
26 EmitError::MissingConfig(s) => {
27 write!(f, "missing required config key: {}", s)
28 }
29 }
30 }
31}
32
33impl std::error::Error for EmitError {}
34
35pub type EmitResult = std::result::Result<(), EmitError>;
36
37pub trait EmitBuild {
39 fn build(
40 &self,
41 emitter: &mut Emitter,
42 input: &str,
43 output: &str,
44 ) -> EmitResult;
45}
46
47pub type EmitBuildFn = fn(&mut Emitter, &str, &str) -> EmitResult;
48
49impl EmitBuild for EmitBuildFn {
50 fn build(
51 &self,
52 emitter: &mut Emitter,
53 input: &str,
54 output: &str,
55 ) -> EmitResult {
56 self(emitter, input, output)
57 }
58}
59
60pub struct EmitRuleBuild {
63 pub rule_name: String,
64}
65
66impl EmitBuild for EmitRuleBuild {
67 fn build(
68 &self,
69 emitter: &mut Emitter,
70 input: &str,
71 output: &str,
72 ) -> EmitResult {
73 emitter.build(&self.rule_name, input, output)?;
74 Ok(())
75 }
76}
77
78pub trait EmitSetup {
80 fn setup(&self, emitter: &mut Emitter) -> EmitResult;
81}
82
83pub type EmitSetupFn = fn(&mut Emitter) -> EmitResult;
84
85impl EmitSetup for EmitSetupFn {
86 fn setup(&self, emitter: &mut Emitter) -> EmitResult {
87 self(emitter)
88 }
89}
90
91pub struct Run<'a> {
92 pub driver: &'a Driver,
93 pub plan: Plan,
94 pub config_data: figment::Figment,
95 pub global_config: config::GlobalConfig,
96}
97
98impl<'a> Run<'a> {
99 pub fn new(driver: &'a Driver, plan: Plan) -> Self {
100 let config_data = config::load_config(&driver.name);
101 let global_config: config::GlobalConfig =
102 config_data.extract().expect("failed to load config");
103 Self {
104 driver,
105 plan,
106 config_data,
107 global_config,
108 }
109 }
110
111 pub fn show(self) {
113 if self.plan.stdin {
114 println!("(stdin) -> {}", self.plan.start);
115 } else {
116 println!("start: {}", self.plan.start);
117 }
118 for (op, file) in self.plan.steps {
119 println!("{}: {} -> {}", op, self.driver.ops[op].name, file);
120 }
121 if self.plan.stdout {
122 println!("-> (stdout)");
123 }
124 }
125
126 pub fn show_dot(self) {
128 println!("digraph plan {{");
129 println!(" rankdir=LR;");
130 println!(" node[shape=box];");
131
132 let mut states: HashMap<StateRef, String> = HashMap::new();
134 let mut ops: HashSet<OpRef> = HashSet::new();
135 let first_op = self.plan.steps[0].0;
136 states.insert(
137 self.driver.ops[first_op].input,
138 self.plan.start.to_string(),
139 );
140 for (op, file) in &self.plan.steps {
141 states.insert(self.driver.ops[*op].output, file.to_string());
142 ops.insert(*op);
143 }
144
145 for (state_ref, state) in self.driver.states.iter() {
147 print!(" {} [", state_ref);
148 if let Some(filename) = states.get(&state_ref) {
149 print!(
150 "label=\"{}\n{}\" penwidth=3 fillcolor=gray style=filled",
151 state.name, filename
152 );
153 } else {
154 print!("label=\"{}\"", state.name);
155 }
156 println!("];");
157 }
158
159 for (op_ref, op) in self.driver.ops.iter() {
161 print!(" {} -> {} [label=\"{}\"", op.input, op.output, op.name);
162 if ops.contains(&op_ref) {
163 print!(" penwidth=3");
164 }
165 println!("];");
166 }
167
168 println!("}}");
169 }
170
171 pub fn emit_to_stdout(&self) -> EmitResult {
173 self.emit(std::io::stdout())
174 }
175
176 pub fn emit_to_dir(&self, dir: &Utf8Path) -> EmitResult {
178 std::fs::create_dir_all(dir)?;
179 let ninja_path = dir.join("build.ninja");
180 let ninja_file = std::fs::File::create(ninja_path)?;
181
182 self.emit(ninja_file)
183 }
184
185 pub fn emit_and_run(&self, dir: &Utf8Path) -> EmitResult {
187 let stale_dir = dir.exists();
189 self.emit_to_dir(dir)?;
190
191 if self.plan.stdin {
193 let stdin_file = std::fs::File::create(
194 self.plan.workdir.join(&self.plan.start),
195 )?;
196 std::io::copy(
197 &mut std::io::stdin(),
198 &mut std::io::BufWriter::new(stdin_file),
199 )?;
200 }
201
202 let mut cmd = Command::new(&self.global_config.ninja);
204 cmd.current_dir(dir);
205 if self.plan.stdout && !self.global_config.verbose {
206 cmd.stdout(std::process::Stdio::null());
208 }
209 cmd.status()?;
210
211 if self.plan.stdout {
213 let stdout_file =
214 std::fs::File::open(self.plan.workdir.join(self.plan.end()))?;
215 std::io::copy(
216 &mut std::io::BufReader::new(stdout_file),
217 &mut std::io::stdout(),
218 )?;
219 }
220
221 if !self.global_config.keep_build_dir && !stale_dir {
223 std::fs::remove_dir_all(dir)?;
224 }
225
226 Ok(())
227 }
228
229 fn emit<T: Write + 'static>(&self, out: T) -> EmitResult {
230 let mut emitter = Emitter::new(
231 out,
232 self.config_data.clone(),
233 self.plan.workdir.clone(),
234 );
235
236 let mut done_setups = HashSet::<SetupRef>::new();
238 for (op, _) in &self.plan.steps {
239 for setup in &self.driver.ops[*op].setups {
240 if done_setups.insert(*setup) {
241 let setup = &self.driver.setups[*setup];
242 writeln!(emitter.out, "# {}", setup.name)?;
243 setup.emit.setup(&mut emitter)?;
244 writeln!(emitter.out)?;
245 }
246 }
247 }
248
249 emitter.comment("build targets")?;
251 let mut last_file = &self.plan.start;
252 for (op, out_file) in &self.plan.steps {
253 let op = &self.driver.ops[*op];
254 op.emit.build(
255 &mut emitter,
256 last_file.as_str(),
257 out_file.as_str(),
258 )?;
259 last_file = out_file;
260 }
261 writeln!(emitter.out)?;
262
263 writeln!(emitter.out, "default {}", last_file)?;
265
266 Ok(())
267 }
268}
269
270pub struct Emitter {
271 pub out: Box<dyn Write>,
272 pub config_data: figment::Figment,
273 pub workdir: Utf8PathBuf,
274}
275
276impl Emitter {
277 fn new<T: Write + 'static>(
278 out: T,
279 config_data: figment::Figment,
280 workdir: Utf8PathBuf,
281 ) -> Self {
282 Self {
283 out: Box::new(out),
284 config_data,
285 workdir,
286 }
287 }
288
289 pub fn config_val(&self, key: &str) -> Result<String, EmitError> {
291 self.config_data
292 .extract_inner::<String>(key)
293 .map_err(|_| EmitError::MissingConfig(key.to_string()))
294 }
295
296 pub fn config_or(&self, key: &str, default: &str) -> String {
298 self.config_data
299 .extract_inner::<String>(key)
300 .unwrap_or_else(|_| default.into())
301 }
302
303 pub fn config_var(&mut self, name: &str, key: &str) -> EmitResult {
305 self.var(name, &self.config_val(key)?)?;
306 Ok(())
307 }
308
309 pub fn config_var_or(
312 &mut self,
313 name: &str,
314 key: &str,
315 default: &str,
316 ) -> std::io::Result<()> {
317 self.var(name, &self.config_or(key, default))
318 }
319
320 pub fn var(&mut self, name: &str, value: &str) -> std::io::Result<()> {
322 writeln!(self.out, "{} = {}", name, value)
323 }
324
325 pub fn rule(&mut self, name: &str, command: &str) -> std::io::Result<()> {
327 writeln!(self.out, "rule {}", name)?;
328 writeln!(self.out, " command = {}", command)
329 }
330
331 pub fn build(
333 &mut self,
334 rule: &str,
335 input: &str,
336 output: &str,
337 ) -> std::io::Result<()> {
338 self.build_cmd(&[output], rule, &[input], &[])
339 }
340
341 pub fn build_cmd(
343 &mut self,
344 targets: &[&str],
345 rule: &str,
346 deps: &[&str],
347 implicit_deps: &[&str],
348 ) -> std::io::Result<()> {
349 write!(self.out, "build")?;
350 for target in targets {
351 write!(self.out, " {}", target)?;
352 }
353 write!(self.out, ": {}", rule)?;
354 for dep in deps {
355 write!(self.out, " {}", dep)?;
356 }
357 if !implicit_deps.is_empty() {
358 write!(self.out, " |")?;
359 for dep in implicit_deps {
360 write!(self.out, " {}", dep)?;
361 }
362 }
363 writeln!(self.out)?;
364 Ok(())
365 }
366
367 pub fn comment(&mut self, text: &str) -> std::io::Result<()> {
369 writeln!(self.out, "# {}", text)?;
370 Ok(())
371 }
372
373 pub fn add_file(&self, name: &str, contents: &[u8]) -> std::io::Result<()> {
375 let path = self.workdir.join(name);
376 std::fs::write(path, contents)?;
377 Ok(())
378 }
379
380 pub fn external_path(&self, path: &Utf8Path) -> Utf8PathBuf {
384 relative_path(path, &self.workdir)
385 }
386
387 pub fn arg(&mut self, name: &str, value: &str) -> std::io::Result<()> {
389 writeln!(self.out, " {} = {}", name, value)?;
390 Ok(())
391 }
392}