commit 30eeafb

shrub  ·  2026-04-15 14:42:20 +0000 UTC
parent b28bd5e
handle implicit suffix rules and .SUFFIXES and key=val assigns on cli
7 files changed,  +420, -52
M TODO
+0, -3
 1@@ -17,9 +17,6 @@ all: $(BIN)
 2 $(BIN): $(OBJS)
 3 	$(CC) $(LDFLAGS) -o $(BIN) $(OBJS) $(LDLIBS)
 4 
 5-.c.o:
 6-	$(CC) $(CFLAGS) -c -o $@ $<
 7-
 8 fmt:
 9 	clang-format -i $(FMTSRCS)
10 
M TODO
+3, -0
1@@ -0,0 +1,3 @@
2+implement .POSIX
3+handle recursive ${MAKE} as subninja
4+
+62, -0
 1@@ -48,6 +48,51 @@ readfile(const char *path)
 2 	return buf;
 3 }
 4 
 5+static int
 6+isvarassign(const char *s)
 7+{
 8+	const char *eq;
 9+
10+	eq = strchr(s, '=');
11+	if (!eq || eq == s)
12+		return 0;
13+	return 1;
14+}
15+
16+static char *
17+appendassigns(char *src, char **assigns, size_t nassigns)
18+{
19+	size_t i, len, extra;
20+	char *out;
21+
22+	if (nassigns == 0)
23+		return src;
24+	len = strlen(src);
25+	extra = len > 0 && src[len - 1] != '\n' ? 1 : 0;
26+	for (i = 0; i < nassigns; i++)
27+		extra += strlen(assigns[i]) + 1;
28+	out = malloc(len + extra + 1);
29+	if (!out) {
30+		free(src);
31+		return 0;
32+	}
33+	memcpy(out, src, len);
34+	extra = len;
35+	if (extra > 0 && out[extra - 1] != '\n')
36+		out[extra++] = '\n';
37+	for (i = 0; i < nassigns; i++) {
38+		size_t n;
39+
40+		n = strlen(assigns[i]);
41+		memcpy(out + extra, assigns[i], n);
42+		extra += n;
43+		out[extra++] = '\n';
44+	}
45+	out[extra] = 0;
46+	free(src);
47+	return out;
48+}
49+
50 static const char *
51 assignopname(enum AssignOp op)
52 {
53@@ -218,6 +263,8 @@ main(int argc, char **argv)
54 	int dump_dot;
55 	int dump_graph;
56 	int i;
57+	char **assigns;
58+	size_t nassigns;
59 	struct Ast ast;
60 	struct Ast out;
61 	struct Graph graph;
62@@ -226,6 +273,8 @@ main(int argc, char **argv)
63 	dump_ast = 0;
64 	dump_dot = 0;
65 	dump_graph = 0;
66+	assigns = 0;
67+	nassigns = 0;
68 	for (i = 1; i < argc; i++) {
69 		if (strcmp(argv[i], "-a") == 0) {
70 			dump_ast = 1;
71@@ -256,6 +305,13 @@ main(int argc, char **argv)
72 		  }
73 		  ++i;
74 		  path = argv[i];
75+		} else if (isvarassign(argv[i])) {
76+		  assigns = realloc(assigns, (nassigns + 1) * sizeof(assigns[0]));
77+		  if (!assigns) {
78+		    fprintf(stderr, "out of memory\n");
79+		    return 1;
80+		  }
81+		  assigns[nassigns++] = argv[i];
82 		} else if (argv[i][0] == '-') {
83 			usage(stderr, argv[0]);
84 			return 1;
85@@ -288,6 +344,12 @@ main(int argc, char **argv)
86 		  }
87 	      }
88 	}
89+	src = appendassigns(src, assigns, nassigns);
90+	free(assigns);
91+	if (!src) {
92+	  fprintf(stderr, "out of memory\n");
93+	  return 1;
94+	}
95 	if (parse(path, src, &ast) < 0) {
96 		fprintf(stderr, "parse error in %s\n", path);
97 		free(src);
+75, -0
  1@@ -24,8 +24,69 @@ struct GraphState {
  2 	struct SufRules sufs;
  3 	struct TAssign *tas;
  4 	size_t ntas;
  5+	int saw_suffixes;
  6 };
  7 
  8+static int
  9+isonesuffix(const char *s, char **from)
 10+{
 11+	if (!s || s[0] != '.')
 12+		return 0;
 13+	if (!s[1] || strchr(s + 1, '.'))
 14+		return 0;
 15+	*from = xstrdup(s);
 16+	return 1;
 17+}
 18+
 19+static int
 20+istwosuffix(const char *s, char **from, char **to)
 21+{
 22+	const char *mid;
 23+
 24+	if (!s || s[0] != '.')
 25+		return 0;
 26+	mid = strchr(s + 1, '.');
 27+	if (!mid || mid == s + 1 || !mid[1] || strchr(mid + 1, '.'))
 28+		return 0;
 29+	*from = xstrndup(s, (size_t)(mid - s));
 30+	*to = xstrdup(mid);
 31+	return 1;
 32+}
 33+
 34+static void
 35+seedsufs(struct SufRules *rules, const struct RuleNode *rule)
 36+{
 37+	char *from, *to;
 38+
 39+	if (rule->targets.n != 1 || rule->prereqs.n != 0 || rule->order_only.n != 0)
 40+		return;
 41+	if (isonesuffix(rule->targets.v[0], &from)) {
 42+		struct StrList list;
 43+
 44+		memset(&list, 0, sizeof(list));
 45+		list.v = &from;
 46+		list.n = 1;
 47+		addsufs(&rules->active, &list);
 48+		free(from);
 49+		return;
 50+	}
 51+	if (!istwosuffix(rule->targets.v[0], &from, &to))
 52+		return;
 53+	{
 54+		char *tmp[2];
 55+		struct StrList list;
 56+
 57+		tmp[0] = from;
 58+		tmp[1] = to;
 59+		memset(&list, 0, sizeof(list));
 60+		list.v = tmp;
 61+		list.n = 2;
 62+		addsufs(&rules->active, &list);
 63+	}
 64+	free(from);
 65+	free(to);
 66+}
 67+
 68 static void
 69 collectrule(struct GraphState *gs, const struct RuleNode *rule)
 70 {
 71@@ -153,6 +214,7 @@ buildgraph(const struct Ast *ast, struct Graph *graph)
 72 	memset(&gs, 0, sizeof(gs));
 73 	gs.graph = graph;
 74 	seedenv(&gs.env);
 75+	imprules(&gs.sufs);
 76 
 77 	/* get the state, env vars in gs.env, target-specific var in gs.tas,
 78 	 * and rules in gs.rules */
 79@@ -163,10 +225,23 @@ buildgraph(const struct Ast *ast, struct Graph *graph)
 80 			else
 81 				evalassign(&gs.env, &ast->v[i].data.assign);
 82 		} else if (ast->v[i].kind == NODE_RULE) {
 83+			if (issufrule(&ast->v[i].data.rule)) {
 84+				gs.saw_suffixes = 1;
 85+				if (ast->v[i].data.rule.prereqs.n == 0)
 86+					clearsufs(&gs.sufs.active);
 87+				else
 88+					addsufs(&gs.sufs.active, &ast->v[i].data.rule.prereqs);
 89+				continue;
 90+			}
 91 			collectrule(&gs, &ast->v[i].data.rule);
 92 		}
 93 	}
 94 
 95+	if (!gs.saw_suffixes) {
 96+		for (i = 0; i < gs.nrules; i++)
 97+			seedsufs(&gs.sufs, gs.rules[i]);
 98+	}
 99+
100 	for (i = 0; i < gs.nrules; i++) {
101 		size_t k;
102 
+245, -44
  1@@ -4,6 +4,90 @@
  2 #include <stdlib.h>
  3 #include <string.h>
  4 
  5+static int
  6+sufeq(const char *a, const char *b)
  7+{
  8+	return strcmp(a, b) == 0;
  9+}
 10+
 11+static int
 12+sufidx(const struct SuffixList *list, const char *suf)
 13+{
 14+	size_t i;
 15+
 16+	for (i = 0; i < list->n; i++) {
 17+		if (sufeq(list->v[i], suf))
 18+			return (int)i;
 19+	}
 20+	return -1;
 21+}
 22+
 23+static int
 24+sufactive(const struct SuffixList *list, const char *suf)
 25+{
 26+	return sufidx(list, suf) >= 0;
 27+}
 28+
 29+static void
 30+addimprecipe(struct RecipeList *recipes, const char *line)
 31+{
 32+	recipes->v = xrealloc(recipes->v, (recipes->n + 1) * sizeof(recipes->v[0]));
 33+	recipes->v[recipes->n++] = xstrdup(line);
 34+}
 35+
 36+static void
 37+addimpsingle(struct SufRules *rules, const char *from, const char *const *recipev, size_t nrecipe)
 38+{
 39+	size_t i, k;
 40+	struct SingleSufRule *sr;
 41+
 42+	for (i = 0; i < rules->nsinglesuf; i++) {
 43+		if (strcmp(rules->singlesuf[i].from, from) == 0) {
 44+			freerecipes(&rules->singlesuf[i].recipes);
 45+			memset(&rules->singlesuf[i].recipes, 0, sizeof(rules->singlesuf[i].recipes));
 46+			for (k = 0; k < nrecipe; k++)
 47+				addimprecipe(&rules->singlesuf[i].recipes, recipev[k]);
 48+			return;
 49+		}
 50+	}
 51+	rules->singlesuf = xrealloc(rules->singlesuf,
 52+	                            (rules->nsinglesuf + 1) * sizeof(rules->singlesuf[0]));
 53+	sr = &rules->singlesuf[rules->nsinglesuf++];
 54+	memset(sr, 0, sizeof(*sr));
 55+	sr->from = xstrdup(from);
 56+	for (k = 0; k < nrecipe; k++)
 57+		addimprecipe(&sr->recipes, recipev[k]);
 58+}
 59+
 60+static void
 61+addimpdouble(struct SufRules *rules,
 62+             const char *from,
 63+             const char *to,
 64+             const char *const *recipev,
 65+             size_t nrecipe)
 66+{
 67+	size_t i, k;
 68+	struct SufRule *sr;
 69+
 70+	for (i = 0; i < rules->nsufs; i++) {
 71+		if (strcmp(rules->sufs[i].from, from) == 0 &&
 72+		    strcmp(rules->sufs[i].to, to) == 0) {
 73+			freerecipes(&rules->sufs[i].recipes);
 74+			memset(&rules->sufs[i].recipes, 0, sizeof(rules->sufs[i].recipes));
 75+			for (k = 0; k < nrecipe; k++)
 76+				addimprecipe(&rules->sufs[i].recipes, recipev[k]);
 77+			return;
 78+		}
 79+	}
 80+	rules->sufs = xrealloc(rules->sufs, (rules->nsufs + 1) * sizeof(rules->sufs[0]));
 81+	sr = &rules->sufs[rules->nsufs++];
 82+	memset(sr, 0, sizeof(*sr));
 83+	sr->from = xstrdup(from);
 84+	sr->to = xstrdup(to);
 85+	for (k = 0; k < nrecipe; k++)
 86+		addimprecipe(&sr->recipes, recipev[k]);
 87+}
 88+
 89 static int
 90 pathorargetexists(const struct Graph *graph, const char *name)
 91 {
 92@@ -153,6 +237,8 @@ seedenv(struct Env *env)
 93 	envsetvar(env, "CFLAGS", xstrdup(""), 1);
 94 	envsetvar(env, "CXX", xstrdup("c++"), 1);
 95 	envsetvar(env, "CPP", xstrdup("$(CC) -E"), 0);
 96+	envsetvar(env, "FC", xstrdup("fort77"), 1);
 97+	envsetvar(env, "FFLAGS", xstrdup(""), 1);
 98 	envsetvar(env, "AR", xstrdup("ar"), 1);
 99 	envsetvar(env, "ARFLAGS", xstrdup("-rv"), 1);
100 	envsetvar(env, "AS", xstrdup("as"), 1);
101@@ -169,6 +255,99 @@ seedenv(struct Env *env)
102 	envsetvar(env, "SHELL", xstrdup("/bin/sh"), 1);
103 }
104 
105+void
106+imprules(struct SufRules *rules)
107+{
108+	static const char *const c_bin[] = {
109+	    "$(CC) $(CFLAGS) $(LDFLAGS) -o $@ $<",
110+	};
111+	static const char *const f_bin[] = {
112+	    "$(FC) $(FFLAGS) $(LDFLAGS) -o $@ $<",
113+	};
114+	static const char *const sh_bin[] = {
115+	    "cp $< $@",
116+	    "chmod a+x $@",
117+	};
118+	/* TODO -o here is NOT POSIX. im keeping it here for now for gnu compatibility,
119+	 * because it's conveinient. when .POSIX is passed, we should drop the -o. */
120+	static const char *const c_o[] = {
121+	    "$(CC) $(CFLAGS) -c -o $@ $<",
122+	};
123+	static const char *const f_o[] = {
124+	    "$(FC) $(FFLAGS) -c -o $@ $<",
125+	};
126+	static const char *const y_o[] = {
127+	    "$(YACC) $(YFLAGS) $<",
128+	    "$(CC) $(CFLAGS) -c y.tab.c",
129+	    "rm -f y.tab.c",
130+	    "mv y.tab.o $@",
131+	};
132+	static const char *const l_o[] = {
133+	    "$(LEX) $(LFLAGS) $<",
134+	    "$(CC) $(CFLAGS) -c lex.yy.c",
135+	    "rm -f lex.yy.c",
136+	    "mv lex.yy.o $@",
137+	};
138+	static const char *const y_c[] = {
139+	    "$(YACC) $(YFLAGS) $<",
140+	    "mv y.tab.c $@",
141+	};
142+	static const char *const l_c[] = {
143+	    "$(LEX) $(LFLAGS) $<",
144+	    "mv lex.yy.c $@",
145+	};
146+	static const char *const suffixes[] = {
147+	    ".o", ".c", ".f", ".y", ".l", ".sh",
148+	};
149+	struct StrList list;
150+
151+	memset(&list, 0, sizeof(list));
152+	list.v = (char **)suffixes;
153+	list.n = sizeof(suffixes) / sizeof(suffixes[0]);
154+	addsufs(&rules->active, &list);
155+
156+	addimpsingle(rules, ".c", c_bin, sizeof(c_bin) / sizeof(c_bin[0]));
157+	addimpsingle(rules, ".f", f_bin, sizeof(f_bin) / sizeof(f_bin[0]));
158+	addimpsingle(rules, ".sh", sh_bin, sizeof(sh_bin) / sizeof(sh_bin[0]));
159+	addimpdouble(rules, ".c", ".o", c_o, sizeof(c_o) / sizeof(c_o[0]));
160+	addimpdouble(rules, ".f", ".o", f_o, sizeof(f_o) / sizeof(f_o[0]));
161+	addimpdouble(rules, ".y", ".o", y_o, sizeof(y_o) / sizeof(y_o[0]));
162+	addimpdouble(rules, ".l", ".o", l_o, sizeof(l_o) / sizeof(l_o[0]));
163+	addimpdouble(rules, ".y", ".c", y_c, sizeof(y_c) / sizeof(y_c[0]));
164+	addimpdouble(rules, ".l", ".c", l_c, sizeof(l_c) / sizeof(l_c[0]));
165+}
166+
167+int
168+issufrule(const struct RuleNode *rule)
169+{
170+	return rule->targets.n == 1 && strcmp(rule->targets.v[0], ".SUFFIXES") == 0;
171+}
172+
173+void
174+addsufs(struct SuffixList *list, const struct StrList *sufs)
175+{
176+	size_t i;
177+
178+	for (i = 0; i < sufs->n; i++) {
179+		if (sufactive(list, sufs->v[i]))
180+			continue;
181+		list->v = xrealloc(list->v, (list->n + 1) * sizeof(list->v[0]));
182+		list->v[list->n++] = xstrdup(sufs->v[i]);
183+	}
184+}
185+
186+void
187+clearsufs(struct SuffixList *list)
188+{
189+	size_t i;
190+
191+	for (i = 0; i < list->n; i++)
192+		free(list->v[i]);
193+	free(list->v);
194+	list->v = 0;
195+	list->n = 0;
196+}
197+
198 int
199 collectsufrule(struct SufRules *rules, const struct RuleNode *rule)
200 {
201@@ -231,61 +410,82 @@ instsufrule(const struct SufRules *rules,
202 {
203 	size_t i, k;
204 
205-	for (i = 0; i < rules->nsufs; i++) {
206-		char *stem, *src;
207+	for (i = 0; i < rules->active.n; i++) {
208+		size_t j, r;
209 
210-		if (!matchsuf(rules->sufs[i].to, t->name, &stem))
211-			continue;
212-		src = cat3(stem, "", rules->sufs[i].from);
213-		t->prereqs.v = xrealloc(t->prereqs.v, (t->prereqs.n + 1) * sizeof(t->prereqs.v[0]));
214-		memmove(t->prereqs.v + 1, t->prereqs.v, t->prereqs.n * sizeof(t->prereqs.v[0]));
215-		t->prereqs.v[0] = src;
216-		t->prereqs.n++;
217-		for (k = 0; k < rules->sufs[i].recipes.n; k++) {
218-			char *vars, *exp;
219-
220-			vars = expandstr(env, rules->sufs[i].recipes.v[k]);
221-			exp = expandauto(vars, t, stem);
222-			free(vars);
223-			if (!exp[0]) {
224-				free(exp);
225-				continue;
226+		for (j = 0; j < rules->active.n; j++) {
227+			for (r = 0; r < rules->nsufs; r++) {
228+				char *stem, *src;
229+
230+				if (!sufeq(rules->sufs[r].to, rules->active.v[i]))
231+					continue;
232+				if (!sufeq(rules->sufs[r].from, rules->active.v[j]))
233+					continue;
234+				if (!matchsuf(rules->sufs[r].to, t->name, &stem))
235+					continue;
236+				src = cat3(stem, "", rules->sufs[r].from);
237+				if (!pathorargetexists(graph, src)) {
238+					free(stem);
239+					free(src);
240+					continue;
241+				}
242+				t->prereqs.v = xrealloc(t->prereqs.v, (t->prereqs.n + 1) * sizeof(t->prereqs.v[0]));
243+				memmove(t->prereqs.v + 1, t->prereqs.v, t->prereqs.n * sizeof(t->prereqs.v[0]));
244+				t->prereqs.v[0] = src;
245+				t->prereqs.n++;
246+				for (k = 0; k < rules->sufs[r].recipes.n; k++) {
247+					char *vars, *exp;
248+
249+					vars = expandstr(env, rules->sufs[r].recipes.v[k]);
250+					exp = expandauto(vars, t, stem);
251+					free(vars);
252+					if (!exp[0]) {
253+						free(exp);
254+						continue;
255+					}
256+					t->recipes.v = xrealloc(t->recipes.v, (t->recipes.n + 1) * sizeof(t->recipes.v[0]));
257+					t->recipes.v[t->recipes.n++] = exp;
258+				}
259+				free(stem);
260+				return 1;
261 			}
262-			t->recipes.v = xrealloc(t->recipes.v, (t->recipes.n + 1) * sizeof(t->recipes.v[0]));
263-			t->recipes.v[t->recipes.n++] = exp;
264 		}
265-		free(stem);
266-		return 1;
267 	}
268 
269 	if (strchr(t->name, '.'))
270 		return 0;
271-	for (i = 0; i < rules->nsinglesuf; i++) {
272-		char *src;
273+	for (i = 0; i < rules->active.n; i++) {
274+		size_t j;
275 
276-		src = cat3(t->name, "", rules->singlesuf[i].from);
277-		if (!pathorargetexists(graph, src)) {
278-			free(src);
279-			continue;
280-		}
281-		t->prereqs.v = xrealloc(t->prereqs.v, (t->prereqs.n + 1) * sizeof(t->prereqs.v[0]));
282-		memmove(t->prereqs.v + 1, t->prereqs.v, t->prereqs.n * sizeof(t->prereqs.v[0]));
283-		t->prereqs.v[0] = src;
284-		t->prereqs.n++;
285-		for (k = 0; k < rules->singlesuf[i].recipes.n; k++) {
286-			char *vars, *exp;
287-
288-			vars = expandstr(env, rules->singlesuf[i].recipes.v[k]);
289-			exp = expandauto(vars, t, t->name);
290-			free(vars);
291-			if (!exp[0]) {
292-				free(exp);
293+		for (j = 0; j < rules->nsinglesuf; j++) {
294+			char *src;
295+
296+			if (!sufeq(rules->singlesuf[j].from, rules->active.v[i]))
297+				continue;
298+			src = cat3(t->name, "", rules->singlesuf[j].from);
299+			if (!pathorargetexists(graph, src)) {
300+				free(src);
301 				continue;
302 			}
303-			t->recipes.v = xrealloc(t->recipes.v, (t->recipes.n + 1) * sizeof(t->recipes.v[0]));
304-			t->recipes.v[t->recipes.n++] = exp;
305+			t->prereqs.v = xrealloc(t->prereqs.v, (t->prereqs.n + 1) * sizeof(t->prereqs.v[0]));
306+			memmove(t->prereqs.v + 1, t->prereqs.v, t->prereqs.n * sizeof(t->prereqs.v[0]));
307+			t->prereqs.v[0] = src;
308+			t->prereqs.n++;
309+			for (k = 0; k < rules->singlesuf[j].recipes.n; k++) {
310+				char *vars, *exp;
311+
312+				vars = expandstr(env, rules->singlesuf[j].recipes.v[k]);
313+				exp = expandauto(vars, t, t->name);
314+				free(vars);
315+				if (!exp[0]) {
316+					free(exp);
317+					continue;
318+				}
319+				t->recipes.v = xrealloc(t->recipes.v, (t->recipes.n + 1) * sizeof(t->recipes.v[0]));
320+				t->recipes.v[t->recipes.n++] = exp;
321+			}
322+			return 1;
323 		}
324-		return 1;
325 	}
326 	return 0;
327 }
328@@ -304,6 +504,7 @@ freesufrules(struct SufRules *rules)
329 		free(rules->sufs[i].to);
330 		freerecipes(&rules->sufs[i].recipes);
331 	}
332+	clearsufs(&rules->active);
333 	free(rules->singlesuf);
334 	free(rules->sufs);
335 	rules->singlesuf = 0;
+13, -3
 1@@ -12,6 +12,11 @@ struct SingleSufRule {
 2 	struct RecipeList recipes;
 3 };
 4 
 5+struct SuffixList {
 6+	char **v;
 7+	size_t n;
 8+};
 9+
10 /* normal suffix like .c.o */
11 struct SufRule {
12 	char *from;
13@@ -25,14 +30,19 @@ struct SufRules {
14 	size_t nsinglesuf;
15 	struct SufRule *sufs;
16 	size_t nsufs;
17+	struct SuffixList active;
18 };
19 
20 void seedenv(struct Env *env);
21+void imprules(struct SufRules *rules);
22 int collectsufrule(struct SufRules *rules, const struct RuleNode *rule);
23 int instsufrule(const struct SufRules *rules,
24-                const struct Graph *graph,
25-                struct Target *t,
26-                struct Env *env);
27+            const struct Graph *graph,
28+            struct Target *t,
29+            struct Env *env);
30+int issufrule(const struct RuleNode *rule);
31+void addsufs(struct SuffixList *list, const struct StrList *sufs);
32+void clearsufs(struct SuffixList *list);
33 void freesufrules(struct SufRules *rules);
34 
35 #endif
+22, -2
 1@@ -12,6 +12,7 @@ workdir=
 2 dryrun=0
 3 jobs=
 4 targets=
 5+vars=
 6 
 7 lie() {
 8 	printf '%s\n' 'GNU Make 4.4'
 9@@ -110,8 +111,16 @@ while [ "$#" -gt 0 ]; do
10 		shift
11 		;;
12 	*)
13-		targets=${targets}${targets:+'
14+		case $1 in
15+		*=*)
16+			vars=${vars}${vars:+'
17 '}$1
18+			;;
19+		*)
20+			targets=${targets}${targets:+'
21+'}$1
22+			;;
23+		esac
24 		shift
25 		;;
26 	esac
27@@ -125,7 +134,18 @@ if probemk; then
28 	exit 0
29 fi
30 
31-if ! "$shin_bin" "$makefile"; then
32+set -- "$shin_bin" -f "$makefile"
33+if [ -n "$vars" ]; then
34+	oldifs=$IFS
35+	IFS='
36+'
37+	for var in $vars; do
38+		set -- "$@" "$var"
39+	done
40+	IFS=$oldifs
41+fi
42+
43+if ! "$@"; then
44 	exit $?
45 fi
46