@@ -39,7 +39,12 @@ const DEFAULT_SHELL: &str = "/bin/sh";
3939/// The only way to create a Make is from a Makefile and a Config.
4040pub struct Make {
4141 macros : Vec < VariableDefinition > ,
42+ /// Target rules (non-special, non-inference).
43+ /// Invariant: inference rules are never stored here, so `first_target()`
44+ /// always returns a valid default target per POSIX.
4245 rules : Vec < Rule > ,
46+ /// Inference rules (e.g. `.c.o:`, `.txt.out:`).
47+ inference_rules : Vec < Rule > ,
4348 default_rule : Option < Rule > , // .DEFAULT
4449 pub config : Config ,
4550}
@@ -58,25 +63,84 @@ impl Make {
5863 }
5964
6065 pub fn first_target ( & self ) -> Result < & Target , ErrorCode > {
61- let rule = self . rules . first ( ) . ok_or ( NoTarget { target : None } ) ?;
66+ // Per POSIX: "the first target that make encounters that is not a special
67+ // target or an inference rule shall be used."
68+ // If there are no non-special, non-inference targets, fall back to the
69+ // first inference rule (which will scan CWD for matching files).
70+ let rule = self
71+ . rules
72+ . first ( )
73+ . or_else ( || self . inference_rules . first ( ) )
74+ . ok_or ( NoTarget { target : None } ) ?;
6275 rule. targets ( ) . next ( ) . ok_or ( NoTarget { target : None } )
6376 }
6477
78+ /// Finds a matching inference rule for the given target name.
79+ ///
80+ /// Per POSIX: the suffix of the target (.s1) is compared to .SUFFIXES.
81+ /// If found, inference rules are searched for the first .s2.s1 rule whose
82+ /// prerequisite file ($*.s2) exists.
83+ fn find_inference_rule ( & self , name : & str ) -> Option < & Rule > {
84+ let suffixes = self . config . rules . get ( ".SUFFIXES" ) ?;
85+
86+ // Find the target's suffix (.s1)
87+ let target_suffix = suffixes
88+ . iter ( )
89+ . filter ( |s| name. ends_with ( s. as_str ( ) ) )
90+ . max_by_key ( |s| s. len ( ) ) ?;
91+
92+ let stem = & name[ ..name. len ( ) - target_suffix. len ( ) ] ;
93+
94+ // Search inference rules for .s2.s1 where $*.s2 exists
95+ for rule in & self . inference_rules {
96+ let Some ( rule_target) = rule. targets ( ) . next ( ) else {
97+ continue ;
98+ } ;
99+ if let Target :: Inference { from, to, .. } = rule_target {
100+ let expected_suffix = format ! ( ".{}" , to) ;
101+ if expected_suffix == * target_suffix {
102+ let prereq_path = format ! ( "{}.{}" , stem, from) ;
103+ if std:: path:: Path :: new ( & prereq_path) . exists ( ) {
104+ return Some ( rule) ;
105+ }
106+ }
107+ }
108+ }
109+ None
110+ }
111+
65112 /// Builds the target with the given name.
66113 ///
67114 /// # Returns
68115 /// - Ok(true) if the target was built.
69116 /// - Ok(false) if the target was already up to date.
70117 /// - Err(_) if any errors occur.
71118 pub fn build_target ( & self , name : impl AsRef < str > ) -> Result < bool , ErrorCode > {
119+ // Search both regular rules and inference rules
72120 let rule = match self . rule_by_target_name ( & name) {
73121 Some ( rule) => rule,
74- None => match & self . default_rule {
122+ None => match self
123+ . inference_rules
124+ . iter ( )
125+ . find ( |rule| rule. targets ( ) . any ( |t| t. as_ref ( ) == name. as_ref ( ) ) )
126+ {
75127 Some ( rule) => rule,
76128 None => {
77- return Err ( NoTarget {
78- target : Some ( name. as_ref ( ) . to_string ( ) ) ,
79- } )
129+ // Per POSIX: "If a target exists and there is neither a target rule
130+ // nor an inference rule for the target, the target shall be considered
131+ // up-to-date."
132+ if get_modified_time ( & name) . is_some ( ) {
133+ return Ok ( false ) ;
134+ }
135+ // No rule and file doesn't exist - try .DEFAULT or fail
136+ match & self . default_rule {
137+ Some ( rule) => rule,
138+ None => {
139+ return Err ( NoTarget {
140+ target : Some ( name. as_ref ( ) . to_string ( ) ) ,
141+ } )
142+ }
143+ }
80144 }
81145 } ,
82146 } ;
@@ -111,6 +175,18 @@ impl Make {
111175 for prerequisite in & newer_prerequisites {
112176 self . build_target ( prerequisite) ?;
113177 }
178+
179+ // Per POSIX: "When no target rule with commands is found to update a
180+ // target, the inference rules shall be checked." If the matched target
181+ // rule has no recipes, look for a matching inference rule and run it
182+ // for this specific target instead.
183+ if rule. recipes ( ) . count ( ) == 0 {
184+ if let Some ( inference_rule) = self . find_inference_rule ( target. as_ref ( ) ) {
185+ inference_rule. run_for_target ( & self . config , & self . macros , target, up_to_date) ?;
186+ return Ok ( true ) ;
187+ }
188+ }
189+
114190 rule. run ( & self . config , & self . macros , target, up_to_date) ?;
115191
116192 Ok ( true )
@@ -184,32 +260,60 @@ impl TryFrom<(Makefile, Config)> for Make {
184260 type Error = ErrorCode ;
185261
186262 fn try_from ( ( makefile, config) : ( Makefile , Config ) ) -> Result < Self , Self :: Error > {
187- let mut rules = vec ! [ ] ;
188- let mut special_rules = vec ! [ ] ;
189- let mut inference_rules = vec ! [ ] ;
190-
191- for rule in makefile. rules ( ) {
192- let rule = Rule :: from ( rule) ;
263+ // Two-pass classification: .SUFFIXES must be processed before inference
264+ // rule classification so that user-defined suffixes (especially with -r)
265+ // are available when determining whether a rule like `.txt.out:` is an
266+ // inference rule.
267+
268+ let mut suffixes_rules = vec ! [ ] ;
269+ let mut remaining_parsed_rules = vec ! [ ] ;
270+
271+ // Pass 1: Separate .SUFFIXES rules from everything else and process
272+ // them immediately so config.rules[".SUFFIXES"] is populated.
273+ for parsed_rule in makefile. rules ( ) {
274+ let rule = Rule :: from ( parsed_rule) ;
193275 let Some ( target) = rule. targets ( ) . next ( ) else {
194276 return Err ( NoTarget { target : None } ) ;
195277 } ;
196-
197- if SpecialTarget :: try_from ( target. clone ( ) ) . is_ok ( ) {
198- special_rules. push ( rule) ;
199- } else if InferenceTarget :: try_from ( ( target. clone ( ) , config. clone ( ) ) ) . is_ok ( ) {
200- inference_rules. push ( rule) ;
278+ if let Ok ( SpecialTarget :: Suffixes ) = SpecialTarget :: try_from ( target. clone ( ) ) {
279+ suffixes_rules. push ( rule) ;
201280 } else {
202- rules . push ( rule) ;
281+ remaining_parsed_rules . push ( rule) ;
203282 }
204283 }
205284
285+ // Build the Make struct early so we can process .SUFFIXES via the
286+ // normal special_target::process path (which writes to make.config).
206287 let mut make = Self {
207- rules,
288+ rules : vec ! [ ] ,
289+ inference_rules : vec ! [ ] ,
208290 macros : makefile. variable_definitions ( ) . collect ( ) ,
209291 default_rule : None ,
210292 config,
211293 } ;
212294
295+ for rule in suffixes_rules {
296+ special_target:: process ( rule, & mut make) ?;
297+ }
298+
299+ // Pass 2: Classify remaining rules. Now make.config.rules[".SUFFIXES"]
300+ // contains both built-in (unless -r) and user-defined suffixes.
301+ let mut special_rules = vec ! [ ] ;
302+
303+ for rule in remaining_parsed_rules {
304+ let Some ( target) = rule. targets ( ) . next ( ) else {
305+ return Err ( NoTarget { target : None } ) ;
306+ } ;
307+
308+ if SpecialTarget :: try_from ( target. clone ( ) ) . is_ok ( ) {
309+ special_rules. push ( rule) ;
310+ } else if InferenceTarget :: try_from ( ( target. clone ( ) , make. config . clone ( ) ) ) . is_ok ( ) {
311+ make. inference_rules . push ( rule) ;
312+ } else {
313+ make. rules . push ( rule) ;
314+ }
315+ }
316+
213317 for rule in special_rules {
214318 special_target:: process ( rule, & mut make) ?;
215319 }
0 commit comments