1mod opts;
2mod rule_map;
3
4pub use opts::Opts;
5
6use std::io::{BufRead, BufReader};
7use std::path::PathBuf;
8use std::time::{Duration, SystemTime, UNIX_EPOCH};
9use std::{fs, fs::File};
10
11use crate::context::Context;
12use crate::error::MakeError;
13use crate::expand::expand;
14use crate::logger::Logger;
15use crate::vars::Vars;
16
17use rule_map::{Rule, RuleMap};
18
19const COMMENT_INDICATOR: char = '#';
20
21#[derive(Debug)]
34pub struct Makefile<L: Logger> {
35 pub opts: Opts,
36 pub logger: Box<L>,
37
38 rule_map: RuleMap,
39 default_target: Option<String>,
40
41 pub vars: Vars,
43 current_rule: Option<Rule>,
44 context: Context,
45}
46
47impl<L: Logger> Makefile<L> {
48 pub fn new(path: PathBuf, opts: Opts, logger: Box<L>, vars: Vars) -> Result<Self, MakeError> {
50 let mut makefile = Self {
52 opts,
53 logger: logger,
54 rule_map: RuleMap::new(),
55 default_target: None,
56 vars: vars,
57 current_rule: None,
58 context: path.clone().into(),
59 };
60
61 let file = File::open(&path).map_err(|e| {
63 MakeError::new(format!("Could not read makefile ({}).", e), path.into())
64 })?;
65 makefile.parse(BufReader::new(file))?;
66
67 Ok(makefile)
68 }
69
70 fn parse<R: BufRead>(&mut self, stream: R) -> Result<(), MakeError> {
73 self.current_rule = None;
74
75 for (i, result) in stream.lines().enumerate() {
76 self.context.line_index = Some(i);
78 let line = result.map_err(|e| MakeError::new(e.to_string(), self.context.clone()))?;
79 self.context.content = Some(line.clone());
80
81 self.parse_line(line)?;
83 }
84
85 self.parse_line("".to_string())?;
88 self.parse_line("".to_string())?;
89
90 Ok(())
91 }
92
93 fn parse_line(&mut self, line: String) -> Result<(), MakeError> {
98 let recipe_prefix = &self.vars.get(".RECIPEPREFIX").value;
100 if line.starts_with(recipe_prefix) {
101 match &mut self.current_rule {
103 None => return Err(MakeError::new("recipe without rule", self.context.clone())),
104 Some(r) => {
105 let cmd = line
107 .strip_prefix(recipe_prefix)
108 .expect("line known to start with a recipe prefix")
109 .trim()
110 .to_string();
111
112 if !cmd.is_empty() {
113 r.recipe.push(
114 expand(cmd.as_str(), &self.vars)
115 .map_err(|e| MakeError::new(e, self.context.clone()))?,
116 );
117 }
118 }
119 }
120 return Ok(());
121 }
122
123 if let Some(rule) = self.current_rule.take() {
125 if self.default_target.is_none() {
127 for target in rule.targets.iter() {
128 if self.default_target.is_none() && !target.starts_with('.') {
130 self.default_target = Some(target.clone());
131 }
132 }
133 }
134
135 self.rule_map.insert(rule, &self.logger)?;
137 }
138
139 let trimmed_line = line.trim();
141 if trimmed_line.starts_with(COMMENT_INDICATOR) || trimmed_line.is_empty() {
142 return Ok(());
143 }
144
145 if let Some((targets, mut deps)) = line.split_once(':') {
147 let mut double_colon = false;
150 if let Some(ch) = deps.chars().next() {
151 if ch == ':' {
152 deps = &deps[1..];
153 double_colon = true;
154 }
155 }
156
157 let rule = deps.split_once(';').map(|(d, r)| {
160 deps = d;
161 r
162 });
163
164 self.current_rule = Some(Rule {
165 targets: expand(targets, &self.vars)
166 .map_err(|e| MakeError::new(e, self.context.clone()))?
167 .split_whitespace()
168 .map(|s| s.to_string())
169 .collect(),
170 prerequisites: expand(deps, &self.vars)
171 .map_err(|e| MakeError::new(e, self.context.clone()))?
172 .split_whitespace()
173 .map(|s| s.to_string())
174 .collect(),
175 recipe: vec![],
176 context: self.context.clone(),
177 double_colon,
178 });
179
180 if let Some(r) = rule {
182 self.parse_line(format!("{}{}", self.vars.get(".RECIPEPREFIX").value, r))?;
183 }
184
185 return Ok(());
186 }
187
188 if let Some((k, v)) = line.split_once('=') {
190 if let Err(e) = self.vars.set(
191 k,
192 &expand(v.trim_start(), &self.vars)
193 .map_err(|e| MakeError::new(e, self.context.clone()))?,
194 false,
195 ) {
196 return Err(MakeError::new(e, self.context.clone()));
197 };
198 return Ok(());
199 }
200
201 Err(MakeError::new("Invalid line type.", self.context.clone()))
203 }
204
205 pub fn execute(&self, mut targets: Vec<String>) -> Result<(), MakeError> {
207 if targets.is_empty() {
209 match &self.default_target {
210 None => {
211 return Err(MakeError::new(
212 "No target specified and no default target found.",
213 Context::new(),
214 ))
215 }
216 Some(t) => targets.push(t.clone()),
217 }
218 }
219
220 for target in targets {
221 self.rule_map.execute(self, &target)?;
222 }
223
224 Ok(())
225 }
226
227 fn get_mtime(&self, file: &String) -> Option<SystemTime> {
234 match fs::metadata(file) {
235 Ok(metadata) => {
236 if self.opts.old_file.contains(file) {
237 Some(UNIX_EPOCH)
238 } else if self.opts.new_file.contains(file) {
239 Some(SystemTime::now() + Duration::from_secs(365 * 24 * 60 * 60))
241 } else {
242 metadata.modified().ok()
243 }
244 }
245 Err(_) => None,
246 }
247 }
248}