fud_core/
run.rs

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/// An error that arises while emitting the Ninja file.
10#[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
37/// Code to emit a Ninja `build` command.
38pub 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
60// TODO make this unnecessary...
61/// A simple `build` emitter that just runs a Ninja rule.
62pub 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
78/// Code to emit Ninja code at the setup stage.
79pub 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    /// Just print the plan for debugging purposes.
112    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    /// Print a GraphViz representation of the plan.
127    pub fn show_dot(self) {
128        println!("digraph plan {{");
129        println!("  rankdir=LR;");
130        println!("  node[shape=box];");
131
132        // Record the states and ops that are actually used in the plan.
133        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        // Show all states.
146        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        // Show all operations.
160        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    /// Print the `build.ninja` file to stdout.
172    pub fn emit_to_stdout(&self) -> EmitResult {
173        self.emit(std::io::stdout())
174    }
175
176    /// Ensure that a directory exists and write `build.ninja` inside it.
177    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    /// Emit `build.ninja` to a temporary directory and then actually execute ninja.
186    pub fn emit_and_run(&self, dir: &Utf8Path) -> EmitResult {
187        // Emit the Ninja file.
188        let stale_dir = dir.exists();
189        self.emit_to_dir(dir)?;
190
191        // Capture stdin.
192        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        // Run `ninja` in the working directory.
203        let mut cmd = Command::new(&self.global_config.ninja);
204        cmd.current_dir(dir);
205        if self.plan.stdout && !self.global_config.verbose {
206            // When we're printing to stdout, suppress Ninja's output by default.
207            cmd.stdout(std::process::Stdio::null());
208        }
209        cmd.status()?;
210
211        // Emit stdout.
212        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        // Remove the temporary directory unless it already existed at the start *or* the user specified `--keep`.
222        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        // Emit the setup for each operation used in the plan, only once.
237        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        // Emit the build commands for each step in the plan.
250        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        // Mark the last file as the default target.
264        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    /// Fetch a configuration value, or panic if it's missing.
290    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    /// Fetch a configuration value, using a default if it's missing.
297    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    /// Emit a Ninja variable declaration for `name` based on the configured value for `key`.
304    pub fn config_var(&mut self, name: &str, key: &str) -> EmitResult {
305        self.var(name, &self.config_val(key)?)?;
306        Ok(())
307    }
308
309    /// Emit a Ninja variable declaration for `name` based on the configured value for `key`, or a
310    /// default value if it's missing.
311    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    /// Emit a Ninja variable declaration.
321    pub fn var(&mut self, name: &str, value: &str) -> std::io::Result<()> {
322        writeln!(self.out, "{} = {}", name, value)
323    }
324
325    /// Emit a Ninja rule definition.
326    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    /// Emit a simple Ninja build command with one dependency.
332    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    /// Emit a Ninja build command.
342    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    /// Emit a Ninja comment.
368    pub fn comment(&mut self, text: &str) -> std::io::Result<()> {
369        writeln!(self.out, "# {}", text)?;
370        Ok(())
371    }
372
373    /// Add a file to the build directory.
374    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    /// Get a path to an external file. The input `path` may be relative to our original
381    /// invocation; we make it relative to the build directory so it can safely be used in the
382    /// Ninja file.
383    pub fn external_path(&self, path: &Utf8Path) -> Utf8PathBuf {
384        relative_path(path, &self.workdir)
385    }
386
387    /// Add a variable parameter to a rule or build command.
388    pub fn arg(&mut self, name: &str, value: &str) -> std::io::Result<()> {
389        writeln!(self.out, "  {} = {}", name, value)?;
390        Ok(())
391    }
392}