commit 7ffaa2c

shrub  ·  2026-08-28 10:38:00 +0000 UTC
parent 8c91ec0
be more accurate to GNU make in terms of implicit rules and default variable initialization, as well as how we handle .POSIX in gnu mode
14 files changed,  +244, -58
+96, -39
  1@@ -19,11 +19,11 @@ seeddefaults(struct Env *env, const struct DefaultVar *vars)
  2 	size_t i;
  3 
  4 	for (i = 0; vars[i].name; i++)
  5-		envsetvar(env, vars[i].name, xstrdup(vars[i].val), 1, ORIGIN_DEFAULT, 0);
  6+		envsetvar(env, vars[i].name, xstrdup(vars[i].val), 0, ORIGIN_DEFAULT, 0);
  7 }
  8 
  9-/* rules shared by all dialects except the .c.o rule, which differs */
 10-#define IMPRULES_COMMON \
 11+/* posix default inference rules in both 08 and 24 */
 12+#define IMPRULES_POSIX_COMMON \
 13 	".c:\n" \
 14 	"\t$(CC) $(CFLAGS) $(LDFLAGS) -o $@ $<\n" \
 15 	".sh:\n" \
 16@@ -49,7 +49,7 @@ seeddefaults(struct Env *env, const struct DefaultVar *vars)
 17 /* posix 2008 includes fortran; posix 2024 dropped it */
 18 #define IMPRULES_POSIX_2008 \
 19 	".SUFFIXES: .o .c .f .y .l .sh\n" \
 20-	IMPRULES_COMMON \
 21+	IMPRULES_POSIX_COMMON \
 22 	".f:\n" \
 23 	"\t$(FC) $(FFLAGS) $(LDFLAGS) -o $@ $<\n" \
 24 	".c.o:\n" \
 25@@ -59,73 +59,92 @@ seeddefaults(struct Env *env, const struct DefaultVar *vars)
 26 
 27 #define IMPRULES_POSIX_2024 \
 28 	".SUFFIXES: .o .c .y .l .sh\n" \
 29-	IMPRULES_COMMON \
 30+	IMPRULES_POSIX_COMMON \
 31 	".c.o:\n" \
 32 	"\t$(CC) $(CFLAGS) -c $<\n"
 33 
 34 #define IMPRULES_GNU \
 35 	".SUFFIXES: .o .c .f .y .l .sh\n" \
 36-	IMPRULES_COMMON \
 37+	".c:\n" \
 38+	"\t$(LINK.c) $^ $(LOADLIBES) $(LDLIBS) -o $@\n" \
 39+	".sh:\n" \
 40+	"\tcp $< $@\n" \
 41+	"\tchmod a+x $@\n" \
 42+	".y.o:\n" \
 43+	"\t$(YACC.y) $<\n" \
 44+	"\t$(COMPILE.c) y.tab.c\n" \
 45+	"\trm -f y.tab.c\n" \
 46+	"\tmv y.tab.o $@\n" \
 47+	".l.o:\n" \
 48+	"\t$(LEX.l) $<\n" \
 49+	"\t$(COMPILE.c) lex.yy.c\n" \
 50+	"\trm -f lex.yy.c\n" \
 51+	"\tmv lex.yy.o $@\n" \
 52+	".y.c:\n" \
 53+	"\t$(YACC.y) $<\n" \
 54+	"\tmv y.tab.c $@\n" \
 55+	".l.c:\n" \
 56+	"\t$(LEX.l) $< > $@\n" \
 57 	".f:\n" \
 58-	"\t$(FC) $(FFLAGS) $(LDFLAGS) -o $@ $<\n" \
 59+	"\t$(LINK.f) $^ $(LOADLIBES) $(LDLIBS) -o $@\n" \
 60 	".c.o:\n" \
 61-	"\t$(CC) $(CPPFLAGS) $(CFLAGS) -c -o $@ $<\n" \
 62+	"\t$(COMPILE.c) $(OUTPUT_OPTION) $<\n" \
 63 	".f.o:\n" \
 64-	"\t$(FC) $(FFLAGS) -c -o $@ $<\n"
 65+	"\t$(COMPILE.f) $(OUTPUT_OPTION) $<\n"
 66 
 67 #define IMPPATRULES_GNU \
 68 	"%: %.o\n" \
 69-	"\t$(CC) $(CFLAGS) $(CPPFLAGS) $(LDFLAGS) $^ $(LOADLIBES) $(LDLIBS) -o $@\n" \
 70+	"\t$(LINK.o) $^ $(LOADLIBES) $(LDLIBS) -o $@\n" \
 71 	"%: %.c\n" \
 72-	"\t$(CC) $(CFLAGS) $(CPPFLAGS) $(LDFLAGS) $^ $(LOADLIBES) $(LDLIBS) -o $@\n" \
 73+	"\t$(LINK.c) $^ $(LOADLIBES) $(LDLIBS) -o $@\n" \
 74 	"%.o: %.c\n" \
 75-	"\t$(CC) $(CPPFLAGS) $(CFLAGS) -c -o $@ $<\n" \
 76+	"\t$(COMPILE.c) $(OUTPUT_OPTION) $<\n" \
 77 	"%: %.cc\n" \
 78-	"\t$(CXX) $(CXXFLAGS) $(CPPFLAGS) $(LDFLAGS) $^ $(LOADLIBES) $(LDLIBS) -o $@\n" \
 79+	"\t$(LINK.cc) $^ $(LOADLIBES) $(LDLIBS) -o $@\n" \
 80 	"%.o: %.cc\n" \
 81-	"\t$(CXX) $(CPPFLAGS) $(CXXFLAGS) -c -o $@ $<\n" \
 82+	"\t$(COMPILE.cc) $(OUTPUT_OPTION) $<\n" \
 83 	"%: %.C\n" \
 84-	"\t$(CXX) $(CXXFLAGS) $(CPPFLAGS) $(LDFLAGS) $^ $(LOADLIBES) $(LDLIBS) -o $@\n" \
 85+	"\t$(LINK.C) $^ $(LOADLIBES) $(LDLIBS) -o $@\n" \
 86 	"%.o: %.C\n" \
 87-	"\t$(CXX) $(CPPFLAGS) $(CXXFLAGS) -c -o $@ $<\n" \
 88+	"\t$(COMPILE.C) $(OUTPUT_OPTION) $<\n" \
 89 	"%: %.cpp\n" \
 90-	"\t$(CXX) $(CXXFLAGS) $(CPPFLAGS) $(LDFLAGS) $^ $(LOADLIBES) $(LDLIBS) -o $@\n" \
 91+	"\t$(LINK.cpp) $^ $(LOADLIBES) $(LDLIBS) -o $@\n" \
 92 	"%.o: %.cpp\n" \
 93-	"\t$(CXX) $(CPPFLAGS) $(CXXFLAGS) -c -o $@ $<\n" \
 94+	"\t$(COMPILE.cpp) $(OUTPUT_OPTION) $<\n" \
 95 	"%: %.p\n" \
 96-	"\t$(FC) $(FFLAGS) $(LDFLAGS) $^ $(LOADLIBES) $(LDLIBS) -o $@\n" \
 97+	"\t$(LINK.p) $^ $(LOADLIBES) $(LDLIBS) -o $@\n" \
 98 	"%.o: %.p\n" \
 99-	"\t$(FC) $(FFLAGS) -c -o $@ $<\n" \
100+	"\t$(COMPILE.p) $(OUTPUT_OPTION) $<\n" \
101 	"%: %.f\n" \
102-	"\t$(FC) $(FFLAGS) $(LDFLAGS) $^ $(LOADLIBES) $(LDLIBS) -o $@\n" \
103+	"\t$(LINK.f) $^ $(LOADLIBES) $(LDLIBS) -o $@\n" \
104 	"%.o: %.f\n" \
105-	"\t$(FC) $(FFLAGS) -c -o $@ $<\n" \
106+	"\t$(COMPILE.f) $(OUTPUT_OPTION) $<\n" \
107 	"%: %.F\n" \
108-	"\t$(FC) $(FFLAGS) $(CPPFLAGS) $(LDFLAGS) $^ $(LOADLIBES) $(LDLIBS) -o $@\n" \
109+	"\t$(LINK.F) $^ $(LOADLIBES) $(LDLIBS) -o $@\n" \
110 	"%.o: %.F\n" \
111-	"\t$(FC) $(CPPFLAGS) $(FFLAGS) -c -o $@ $<\n" \
112+	"\t$(COMPILE.F) $(OUTPUT_OPTION) $<\n" \
113 	"%.f: %.F\n" \
114-	"\t$(FC) $(CPPFLAGS) $(FFLAGS) -o $@ -c $<\n" \
115+	"\t$(PREPROCESS.F) $(OUTPUT_OPTION) $<\n" \
116 	"%: %.m\n" \
117-	"\t$(OBJC) $(OBJCFLAGS) $(CPPFLAGS) $(LDFLAGS) $^ $(LOADLIBES) $(LDLIBS) -o $@\n" \
118+	"\t$(LINK.m) $^ $(LOADLIBES) $(LDLIBS) -o $@\n" \
119 	"%.o: %.m\n" \
120-	"\t$(OBJC) $(CPPFLAGS) $(OBJCFLAGS) -c -o $@ $<\n" \
121+	"\t$(COMPILE.m) $(OUTPUT_OPTION) $<\n" \
122 	"%: %.r\n" \
123-	"\t$(FC) $(FFLAGS) $(RFLAGS) $(LDFLAGS) $^ $(LOADLIBES) $(LDLIBS) -o $@\n" \
124+	"\t$(LINK.r) $^ $(LOADLIBES) $(LDLIBS) -o $@\n" \
125 	"%.o: %.r\n" \
126-	"\t$(FC) $(FFLAGS) $(RFLAGS) -c -o $@ $<\n" \
127+	"\t$(COMPILE.r) $(OUTPUT_OPTION) $<\n" \
128 	"%.f: %.r\n" \
129-	"\t$(FC) $(FFLAGS) $(RFLAGS) -F $< > $@\n" \
130+	"\t$(PREPROCESS.r) $(OUTPUT_OPTION) $<\n" \
131 	"%: %.s\n" \
132-	"\t$(AS) $(ASFLAGS) $(LDFLAGS) $^ $(LOADLIBES) $(LDLIBS) -o $@\n" \
133+	"\t$(LINK.s) $^ $(LOADLIBES) $(LDLIBS) -o $@\n" \
134 	"%.o: %.s\n" \
135-	"\t$(AS) $(ASFLAGS) -o $@ $<\n" \
136+	"\t$(COMPILE.s) -o $@ $<\n" \
137 	"%: %.S\n" \
138-	"\t$(CC) $(ASFLAGS) $(CPPFLAGS) $(LDFLAGS) $^ $(LOADLIBES) $(LDLIBS) -o $@\n" \
139+	"\t$(LINK.S) $^ $(LOADLIBES) $(LDLIBS) -o $@\n" \
140 	"%.o: %.S\n" \
141-	"\t$(CC) $(ASFLAGS) $(CPPFLAGS) -c -o $@ $<\n" \
142+	"\t$(COMPILE.S) -o $@ $<\n" \
143 	"%.s: %.S\n" \
144-	"\t$(CC) $(ASFLAGS) $(CPPFLAGS) -E $< > $@\n"
145+	"\t$(PREPROCESS.S) $< > $@\n"
146 
147 static int
148 loadimprules(struct SufRules *rules, const char *src)
149@@ -146,7 +165,7 @@ loadimprules(struct SufRules *rules, const char *src)
150 			addsufs(&rules->active, &node->data.rule.prereqs);
151 			continue;
152 		}
153-		(void)collectsufrule(rules, &node->data.rule);
154+		(void)collectsufrule(rules, &node->data.rule, 1);
155 	}
156 	freeast(&ast);
157 	return 0;
158@@ -208,6 +227,8 @@ seedenv(struct Env *env, int isposix, int envoverride, enum ShinMode mode)
159 		{"CPP", "$(CC) -E"},
160 		{"FC", "fort77"},
161 		{"FFLAGS", ""},
162+		{"PC", "pc"},
163+		{"OBJC", "cc"},
164 		{"AR", "ar"},
165 		{"ARFLAGS", "-rv"},
166 		{"RANLIB", "ranlib"},
167@@ -216,6 +237,35 @@ seedenv(struct Env *env, int isposix, int envoverride, enum ShinMode mode)
168 		{"GFLAGS", ""},
169 		{"LD", "ld"},
170 		{"LDFLAGS", ""},
171+		{"OUTPUT_OPTION", "-o $@"},
172+		{"COMPILE.c", "$(CC) $(CFLAGS) $(CPPFLAGS) $(TARGET_ARCH) -c"},
173+		{"LINK.c", "$(CC) $(CFLAGS) $(CPPFLAGS) $(LDFLAGS) $(TARGET_ARCH)"},
174+		{"COMPILE.cc", "$(CXX) $(CXXFLAGS) $(CPPFLAGS) $(TARGET_ARCH) -c"},
175+		{"LINK.cc", "$(CXX) $(CXXFLAGS) $(CPPFLAGS) $(LDFLAGS) $(TARGET_ARCH)"},
176+		{"COMPILE.C", "$(COMPILE.cc)"},
177+		{"LINK.C", "$(LINK.cc)"},
178+		{"COMPILE.cpp", "$(COMPILE.cc)"},
179+		{"LINK.cpp", "$(LINK.cc)"},
180+		{"COMPILE.p", "$(PC) $(PFLAGS) $(CPPFLAGS) $(TARGET_ARCH) -c"},
181+		{"LINK.p", "$(PC) $(PFLAGS) $(CPPFLAGS) $(LDFLAGS) $(TARGET_ARCH)"},
182+		{"COMPILE.f", "$(FC) $(FFLAGS) $(TARGET_ARCH) -c"},
183+		{"LINK.f", "$(FC) $(FFLAGS) $(LDFLAGS) $(TARGET_ARCH)"},
184+		{"COMPILE.F", "$(FC) $(FFLAGS) $(CPPFLAGS) $(TARGET_ARCH) -c"},
185+		{"LINK.F", "$(FC) $(FFLAGS) $(CPPFLAGS) $(LDFLAGS) $(TARGET_ARCH)"},
186+		{"PREPROCESS.F", "$(FC) $(FFLAGS) $(CPPFLAGS) $(TARGET_ARCH) -F"},
187+		{"COMPILE.m", "$(OBJC) $(OBJCFLAGS) $(CPPFLAGS) $(TARGET_ARCH) -c"},
188+		{"LINK.m", "$(OBJC) $(OBJCFLAGS) $(CPPFLAGS) $(LDFLAGS) $(TARGET_ARCH)"},
189+		{"COMPILE.r", "$(FC) $(FFLAGS) $(RFLAGS) $(TARGET_ARCH) -c"},
190+		{"LINK.r", "$(FC) $(FFLAGS) $(RFLAGS) $(LDFLAGS) $(TARGET_ARCH)"},
191+		{"PREPROCESS.r", "$(FC) $(FFLAGS) $(RFLAGS) $(TARGET_ARCH) -F"},
192+		{"COMPILE.s", "$(AS) $(ASFLAGS) $(TARGET_MACH)"},
193+		{"LINK.s", "$(CC) $(ASFLAGS) $(LDFLAGS) $(TARGET_MACH)"},
194+		{"COMPILE.S", "$(CC) $(ASFLAGS) $(CPPFLAGS) $(TARGET_MACH) -c"},
195+		{"LINK.S", "$(CC) $(ASFLAGS) $(CPPFLAGS) $(LDFLAGS) $(TARGET_MACH)"},
196+		{"PREPROCESS.S", "$(CPP) $(CPPFLAGS)"},
197+		{"LINK.o", "$(CC) $(LDFLAGS) $(TARGET_ARCH)"},
198+		{"LEX.l", "$(LEX) $(LFLAGS) -t"},
199+		{"YACC.y", "$(YACC) $(YFLAGS)"},
200 		{"LEX", "lex"},
201 		{"LFLAGS", ""},
202 		{"SCCSFLAGS", ""},
203@@ -235,11 +285,18 @@ seedenv(struct Env *env, int isposix, int envoverride, enum ShinMode mode)
204 	if (mode == MODE_POSIX_2024) {
205 		seeddefaults(env, posix);
206 		seeddefaults(env, posix2024);
207-	} else if (mode == MODE_POSIX_2008 || isposix) {
208+	} else if (mode == MODE_POSIX_2008) {
209 		seeddefaults(env, posix);
210 		seeddefaults(env, posix2008);
211 	} else {
212 		seeddefaults(env, gnu);
213+		if (isposix) {
214+			/* GNU make keeps the whole gnu variable set under .POSIX
215+			 * then replaces only defaults for variables specifically specified
216+			 * by posix */
217+			seeddefaults(env, posix);
218+			seeddefaults(env, posix2008);
219+		}
220 		cwd = getcwddup();
221 		/*TODO: do this properly, this only expands at evaluation time,
222 		 * unsure about behaviour under directory change condition or
223@@ -265,13 +322,13 @@ seedenv(struct Env *env, int isposix, int envoverride, enum ShinMode mode)
224 }
225 
226 void
227-imprules(struct SufRules *rules, int posix, enum ShinMode mode)
228+imprules(struct SufRules *rules, enum ShinMode mode)
229 {
230 	const char *src;
231 
232 	if (mode == MODE_POSIX_2024)
233 		src = IMPRULES_POSIX_2024;
234-	else if (mode == MODE_POSIX_2008 || posix)
235+	else if (mode == MODE_POSIX_2008)
236 		src = IMPRULES_POSIX_2008;
237 	else
238 		src = IMPRULES_GNU;
+5, -3
 1@@ -140,7 +140,8 @@ pattargetexists(const struct Graph *graph, const char *name)
 2 }
 3 
 4 static int
 5-patruleviable(const struct PatRule *rule, const struct Graph *graph, const char *stem)
 6+patruleviable(const struct PatRule *rule, const struct Graph *graph,
 7+              const struct Target *target, const char *stem)
 8 {
 9 	size_t i;
10 
11@@ -149,7 +150,8 @@ patruleviable(const struct PatRule *rule, const struct Graph *graph, const char
12 		int ok;
13 
14 		s = patapplystem(rule->prereqs.v[i], stem);
15-		ok = pattargetexists(graph, s);
16+		ok = pattargetexists(graph, s) || hasword(&target->prereqs, s) ||
17+		     hasword(&target->order_only, s);
18 		free(s);
19 		if (!ok)
20 			return 0;
21@@ -170,7 +172,7 @@ instpatrule(const struct PatRules *rules, const struct Graph *graph, struct Targ
22 		stem = patmatchstem(rules->v[i].target, t->name);
23 		if (!stem)
24 			continue;
25-		if (!patruleviable(&rules->v[i], graph, stem)) {
26+		if (!patruleviable(&rules->v[i], graph, t, stem)) {
27 			free(stem);
28 			continue;
29 		}
+14, -9
 1@@ -304,7 +304,7 @@ buildgraph(const struct RuleSet *ruleset, const struct StrList *goals, struct Gr
 2 	ctx.env = &gs.env;
 3 	ctx.export_all = ruleset->export_all;
 4 	ctx.mode = mode;
 5-	imprules(&gs.sufs, ruleset->posix, mode);
 6+	imprules(&gs.sufs, mode);
 7 
 8 	for (i = 0; i < ruleset->nvars; i++)
 9 		evalassign(&ctx, &ruleset->vars[i]);
10@@ -332,7 +332,7 @@ buildgraph(const struct RuleSet *ruleset, const struct StrList *goals, struct Gr
11 
12 		if (issufrule(&gs.rules[i]))
13 			continue;
14-		if (collectsufrule(&gs.sufs, &gs.rules[i]))
15+		if (collectsufrule(&gs.sufs, &gs.rules[i], 0))
16 			continue;
17 		/* pattern rules (%.o: %.c) are a gnu extension */
18 		if (gs.mode == MODE_GNU)
19@@ -405,19 +405,24 @@ buildgraph(const struct RuleSet *ruleset, const struct StrList *goals, struct Gr
20 				targetctx.mode = gs.mode;
21 				targetenv(&gs, &targetctx, &t->env, t->name);
22 				/* rule precedence in GNU mode:
23-				 * 1) user defined pattern rules
24-				 * 2) suffix rules 
25-				 * 3) builtin pattern rules
26+				 * 1) user-defined pattern rules
27+				 * 2) user-defined suffix rules
28+				 * 3) built-in pattern rules
29+				 * 4) built-in suffix rules
30 				 *
31-				 * we make sure local suffix compile recipes dont get overshadowed
32-				 * by GNU builtins like %.o: %.c (dropbear fails without this). */
33+				 * this makes sure we keep local suffix compile recipes ahead of GNU builtins,
34+				 * while allowing builtins such as %: %.o to beat the built-in .c
35+				 * link rule when we are interpreting a GNU makefile which matches what gmake does. */
36 				if (gs.mode == MODE_GNU)
37 					instpatrule(&gs.patterns, graph, t, &targetctx, 0);
38 				matched = t->recipes.n > 0;
39 				if (!matched) {
40-					instsufrule(&gs.sufs, graph, t, &targetctx);
41-					if (gs.mode == MODE_GNU && t->recipes.n == 0)
42+					instsufrule(&gs.sufs, graph, t, &targetctx, 0);
43+					if (gs.mode == MODE_GNU && t->recipes.n == 0) {
44 						instpatrule(&gs.patterns, graph, t, &targetctx, 1);
45+					}
46+					if (t->recipes.n == 0)
47+						instsufrule(&gs.sufs, graph, t, &targetctx, 1);
48 					if (t->recipes.n == 0)
49 						instdefaultrule(&gs, t, &targetctx);
50 				}
+6, -3
 1@@ -10,6 +10,7 @@
 2 struct SingleSufRule {
 3 	char *from;
 4 	struct RecipeList recipes;
 5+	int builtin;
 6 };
 7 
 8 struct SuffixList {
 9@@ -22,6 +23,7 @@ struct SufRule {
10 	char *from;
11 	char *to;
12 	struct RecipeList recipes;
13+	int builtin;
14 };
15 
16 /*holds all stored suffix rules*/
17@@ -38,13 +40,14 @@ struct PatRules;
18 int issinglesuf(const char *s, char **from);
19 int issuf(const char *s, char **from, char **to);
20 void seedenv(struct Env *env, int posix, int envoverride, enum ShinMode mode);
21-void imprules(struct SufRules *rules, int posix, enum ShinMode mode);
22+void imprules(struct SufRules *rules, enum ShinMode mode);
23 int imppatrules(struct PatRules *rules, enum ShinMode mode);
24-int collectsufrule(struct SufRules *rules, const struct RuleNode *rule);
25+int collectsufrule(struct SufRules *rules, const struct RuleNode *rule, int builtin);
26 int instsufrule(const struct SufRules *rules,
27                 const struct Graph *graph,
28                 struct Target *t,
29-                struct EvalCtx *ctx);
30+                struct EvalCtx *ctx,
31+                int allow_builtin);
32 int issufrule(const struct RuleNode *rule);
33 void addsufs(struct SuffixList *list, const struct StrList *sufs);
34 void clearsufs(struct SuffixList *list);
+13, -4
 1@@ -106,7 +106,7 @@ issuf(const char *s, char **from, char **to)
 2 {
 3 	const char *mid;
 4 
 5-	if (!s || s[0] != '.')
 6+	if (!s || s[0] != '.' || strchr(s, '/'))
 7 		return 0;
 8 	mid = strchr(s + 1, '.');
 9 	if (!mid || mid == s + 1 || !mid[1] || strchr(mid + 1, '.'))
10@@ -119,7 +119,7 @@ issuf(const char *s, char **from, char **to)
11 int
12 issinglesuf(const char *s, char **from)
13 {
14-	if (!s || s[0] != '.')
15+	if (!s || s[0] != '.' || strchr(s, '/'))
16 		return 0;
17 	if (!s[1] || strchr(s + 1, '.'))
18 		return 0;
19@@ -248,7 +248,7 @@ clearsufs(struct SuffixList *list)
20 }
21 
22 int
23-collectsufrule(struct SufRules *rules, const struct RuleNode *rule)
24+collectsufrule(struct SufRules *rules, const struct RuleNode *rule, int builtin)
25 {
26 	size_t i;
27 
28@@ -281,6 +281,7 @@ collectsufrule(struct SufRules *rules, const struct RuleNode *rule)
29 					freerecipes(&rules->singlesuf[k].recipes);
30 					memset(&rules->singlesuf[k].recipes, 0, sizeof(rules->singlesuf[k].recipes));
31 					addrecipes(&rules->singlesuf[k].recipes, &rule->recipes);
32+					rules->singlesuf[k].builtin = builtin;
33 					goto next_target;
34 				}
35 			}
36@@ -290,6 +291,7 @@ collectsufrule(struct SufRules *rules, const struct RuleNode *rule)
37 			memset(sr, 0, sizeof(*sr));
38 			sr->from = from;
39 			addrecipes(&sr->recipes, &rule->recipes);
40+			sr->builtin = builtin;
41 			continue;
42 		}
43 		issuf(rule->targets.v[i], &from, &to);
44@@ -302,6 +304,7 @@ collectsufrule(struct SufRules *rules, const struct RuleNode *rule)
45 				freerecipes(&rules->sufs[k].recipes);
46 				memset(&rules->sufs[k].recipes, 0, sizeof(rules->sufs[k].recipes));
47 				addrecipes(&rules->sufs[k].recipes, &rule->recipes);
48+				rules->sufs[k].builtin = builtin;
49 				goto next_target;
50 			}
51 		}
52@@ -310,6 +313,7 @@ collectsufrule(struct SufRules *rules, const struct RuleNode *rule)
53 		rules->sufs[rules->nsufs].from = from;
54 		rules->sufs[rules->nsufs].to = to;
55 		addrecipes(&rules->sufs[rules->nsufs].recipes, &rule->recipes);
56+		rules->sufs[rules->nsufs].builtin = builtin;
57 		rules->nsufs++;
58 	next_target:
59 		;
60@@ -321,7 +325,8 @@ int
61 instsufrule(const struct SufRules *rules,
62             const struct Graph *graph,
63             struct Target *t,
64-            struct EvalCtx *ctx)
65+            struct EvalCtx *ctx,
66+            int allow_builtin)
67 {
68 	size_t i, k;
69 
70@@ -333,6 +338,8 @@ instsufrule(const struct SufRules *rules,
71 				char *stem, *src;
72 				int exists;
73 
74+				if (rules->sufs[r].builtin && !allow_builtin)
75+					continue;
76 				if (!sufeq(rules->sufs[r].to, rules->active.v[i]))
77 					continue;
78 				if (!sufeq(rules->sufs[r].from, rules->active.v[j]))
79@@ -389,6 +396,8 @@ instsufrule(const struct SufRules *rules,
80 			char *src;
81 			int exists;
82 
83+			if (rules->singlesuf[j].builtin && !allow_builtin)
84+				continue;
85 			if (!sufeq(rules->singlesuf[j].from, rules->active.v[i]))
86 				continue;
87 			src = cat3(t->name, "", rules->singlesuf[j].from);
+8, -0
1@@ -0,0 +1,8 @@
2+.POSIX:
3+
4+CPPFLAGS =
5+LDFLAGS =
6+LOADLIBES =
7+LDLIBS =
8+
9+wawa: wawa.o protocol.o
+3, -0
1@@ -0,0 +1,3 @@
2+c99 -O1   -c -o wawa.o wawa.c
3+c99 -O1   -c -o protocol.o protocol.c
4+c99   wawa.o protocol.o   -o wawa
+31, -0
 1@@ -0,0 +1,31 @@
 2+{
 3+  "case": "t002",
 4+  "category": "suffixrules",
 5+  "compare_output": true,
 6+  "description": ".POSIX changes some GNU defaults without disabling GNU implicit pattern rules",
 7+  "details": "In GNU mode, .POSIX selects c99 and -O1 while an explicitly listed object prereq makes the built-in .o pattern link rule viable",
 8+  "env": {},
 9+  "expected_exit": 0,
10+  "options": "-n wawa",
11+  "options_mode": "shell",
12+  "output_mode": "exact",
13+  "setup": [
14+    {
15+      "content": "int main(void) { return 0; }\n",
16+      "kind": "file",
17+      "mode": "0644",
18+      "mtime": 1778707209,
19+      "path": "wawa.c"
20+    },
21+    {
22+      "content": "int protocol;\n",
23+      "kind": "file",
24+      "mode": "0644",
25+      "mtime": 1778707209,
26+      "path": "protocol.c"
27+    }
28+  ],
29+  "stdin": "",
30+  "suite": "shin",
31+  "timeout_seconds": 60
32+}
+7, -0
1@@ -0,0 +1,7 @@
2+COMPILE.c = custom-compile
3+OUTPUT_OPTION = custom-output
4+LINK.o = custom-link
5+LOADLIBES = custom-loadlibs
6+LDLIBS = custom-ldlibs
7+
8+program: program.o
+2, -0
1@@ -0,0 +1,2 @@
2+custom-compile custom-output program.c
3+custom-link program.o custom-loadlibs custom-ldlibs -o program
+24, -0
 1@@ -0,0 +1,24 @@
 2+{
 3+  "case": "t003",
 4+  "category": "suffixrules",
 5+  "compare_output": true,
 6+  "description": "GNU built-in rules use their standard command variables",
 7+  "details": "The C compile and object link pattern rules allow overrides of COMPILE.c, OUTPUT_OPTION, LINK.o, LOADLIBES, and LDLIBS.",
 8+  "env": {},
 9+  "expected_exit": 0,
10+  "options": "-n program",
11+  "options_mode": "shell",
12+  "output_mode": "exact",
13+  "setup": [
14+    {
15+      "content": "int main(void) { return 0; }\n",
16+      "kind": "file",
17+      "mode": "0644",
18+      "mtime": 1778707209,
19+      "path": "program.c"
20+    }
21+  ],
22+  "stdin": "",
23+  "suite": "shin",
24+  "timeout_seconds": 60
25+}
+9, -0
 1@@ -0,0 +1,9 @@
 2+OBJ_DIR = ./obj
 3+
 4+all: $(OBJ_DIR)/x.o
 5+
 6+$(OBJ_DIR):
 7+	mkdir -p $@
 8+
 9+$(OBJ_DIR)/%.o: %.c | $(OBJ_DIR)
10+	cp $< $@
+2, -0
1@@ -0,0 +1,2 @@
2+mkdir -p ./obj
3+cp x.c ./obj/x.o
+24, -0
 1@@ -0,0 +1,24 @@
 2+{
 3+  "case": "t004",
 4+  "category": "suffixrules",
 5+  "compare_output": true,
 6+  "description": "dot prefixed dir targets are not suffix rules",
 7+  "details": "some target like ./obj contains a slash and must remain an explicit dir target when used as an order only prereq, although generic parsing may see it as a suffix rule (from dropbear)",
 8+  "env": {},
 9+  "expected_exit": 0,
10+  "options": "-n all",
11+  "options_mode": "shell",
12+  "output_mode": "exact",
13+  "setup": [
14+    {
15+      "content": "int x;\n",
16+      "kind": "file",
17+      "mode": "0644",
18+      "mtime": 1778707209,
19+      "path": "x.c"
20+    }
21+  ],
22+  "stdin": "",
23+  "suite": "shin",
24+  "timeout_seconds": 60
25+}