aboutsummaryrefslogtreecommitdiff
path: root/src/makefile/command_line.rs
blob: aaf964ae83e6adc909b660fb6eea8ca25021fc73 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
use std::env;
use std::fmt;
use std::io;
use std::process::{Command, ExitStatus};

use crate::makefile::target::Target;
use crate::makefile::token::{Token, TokenString};
use crate::makefile::Makefile;

// inspired by python's subprocess module
fn execute_command_line(command_line: &str, ignore_errors: bool) -> Result<ExitStatus, io::Error> {
    let (program, args) = if cfg!(windows) {
        let cmd = env::var("COMSPEC").unwrap_or_else(|_| "cmd.exe".into());
        let args = vec!["/c", command_line];
        (cmd, args)
    } else {
        let sh = env::var("SHELL").unwrap_or_else(|_| "/bin/sh".into());
        let args = if ignore_errors {
            vec!["-c", command_line]
        } else {
            vec!["-e", "-c", command_line]
        };
        (sh, args)
    };
    Command::new(program).args(args).status()
}

#[derive(PartialEq, Eq, Clone, Debug)]
pub struct CommandLine {
    /// If the command prefix contains a <hyphen-minus>, or the -i option is present, or
    /// the special target .IGNORE has either the current target as a prerequisite or has
    /// no prerequisites, any error found while executing the command shall be ignored.
    ignore_errors: bool,
    /// If the command prefix contains an at-sign and the make utility command line -n
    /// option is not specified, or the -s option is present, or the special target
    /// .SILENT has either the current target as a prerequisite or has no prerequisites,
    /// the command shall not be written to standard output before it is executed.
    silent: bool,
    /// If the command prefix contains a <plus-sign>, this indicates a makefile command
    /// line that shall be executed even if -n, -q, or -t is specified.
    always_execute: bool,
    execution_line: TokenString,
}

impl CommandLine {
    pub fn from(mut line: TokenString) -> Self {
        let mut ignore_errors = false;
        let mut silent = false;
        let mut always_execute = false;

        if let Token::Text(text) = line.first_token_mut() {
            let mut text_chars = text.chars().peekable();
            while let Some(x) = text_chars.next_if(|x| matches!(x, '-' | '@' | '+')) {
                match x {
                    '-' => ignore_errors = true,
                    '@' => silent = true,
                    '+' => always_execute = true,
                    _ => unreachable!(),
                }
            }
            *text = text_chars.collect();
        }

        Self {
            ignore_errors,
            silent,
            always_execute,
            execution_line: line,
        }
    }

    pub fn execute(&self, file: &Makefile, target: &Target) -> anyhow::Result<()> {
        let ignore_error = self.ignore_errors
            || file.args.ignore_errors
            || file.special_target_has_prereq(".IGNORE", &target.name);
        let silent = (self.silent && !file.args.dry_run)
            || file.args.silent
            || file.special_target_has_prereq(".SILENT", &target.name);

        let execution_line = file.expand_macros(&self.execution_line, Some(target))?;

        if !silent {
            println!("{}", execution_line);
        }

        let should_execute =
            self.always_execute || !(file.args.dry_run || file.args.question || file.args.touch);
        if !should_execute {
            return Ok(());
        }

        let return_value = execute_command_line(&execution_line, ignore_error);
        let errored = return_value.map_or(true, |status| !status.success());
        if errored {
            // apparently there was an error. do we care?
            if !ignore_error {
                anyhow::bail!("error from command execution!");
            }
        }

        Ok(())
    }
}

impl fmt::Display for CommandLine {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        if self.ignore_errors {
            write!(f, "-")?;
        }
        if self.silent {
            write!(f, "@")?;
        }
        if self.always_execute {
            write!(f, "+")?;
        }
        let execution_line = format!("{}", &self.execution_line);
        let execution_line = execution_line.replace("\n", "↵\n");
        write!(f, "{}", execution_line)?;
        Ok(())
    }
}