commit 75ca578

shrub  ·  2026-05-05 22:08:36 +0000 UTC
parent d2e2d0c
tighten recursive make parsing and emission, make ninja backend a little prettier
4 files changed,  +229, -72
+10, -31
 1@@ -88,43 +88,18 @@ joinrecipes(const struct Target *t)
 2 	size_t i;
 3 	struct StrList bodies;
 4 	char *s;
 5-	char *prefix;
 6-	size_t nprefix;
 7-	int strip_prefix;
 8 
 9 	memset(&bodies, 0, sizeof(bodies));
10-	prefix = 0;
11-	nprefix = 0;
12-	strip_prefix = 0;
13-	if (t->owner && t->owner[0]) {
14-		prefix = cat3("cd ", t->owner, " && ");
15-		nprefix = strlen(prefix);
16-		strip_prefix = 1;
17-		for (i = 0; i < t->recipes.n; i++) {
18-			if (strncmp(t->recipes.v[i].body, prefix, nprefix) != 0) {
19-				strip_prefix = 0;
20-				break;
21-			}
22-		}
23-	}
24 	for (i = 0; i < t->recipes.n; i++) {
25-		const char *body;
26+		char *body;
27 
28-		body = t->recipes.v[i].body;
29-		if (strip_prefix)
30-			body += nprefix;
31+		body = cat3("( ", t->recipes.v[i].body, " )");
32 		bodies.v = xrealloc(bodies.v, (bodies.n + 1) * sizeof(bodies.v[0]));
33-		bodies.v[bodies.n++] = (char *)body;
34+		bodies.v[bodies.n++] = body;
35 	}
36 	s = joinstrs(&bodies, " && ");
37-	if (strip_prefix) {
38-		char *tmp;
39-
40-		tmp = cat3(prefix, s, "");
41-		free(s);
42-		s = tmp;
43-	}
44-	free(prefix);
45+	for (i = 0; i < bodies.n; i++)
46+		free(bodies.v[i]);
47 	free(bodies.v);
48 	return s;
49 }
50@@ -316,7 +291,11 @@ genninjafile(const struct Graph *graph, const char *path, const char *prefix, in
51 				for (j = 0; j < graph->v[i].order_only.n; j++)
52 					fprintf(fp, " %s", graph->v[i].order_only.v[j]);
53 			}
54-			fprintf(fp, "\n\n");
55+			fprintf(fp, "\n");
56+			if (prefix && prefix[0])
57+				fprintf(fp, "  description = [%s] build %s\n\n", prefix, graph->v[i].name);
58+			else
59+				fprintf(fp, "  description = build %s\n\n", graph->v[i].name);
60 		} else if (graph->v[i].defined || graph->v[i].prereqs.n > 0 || graph->v[i].order_only.n > 0) {
61 			fprintf(fp, "build %s: phony", graph->v[i].name);
62 			if (graph->v[i].phony)
+108, -7
  1@@ -27,6 +27,19 @@ sameprefix(const char *a, const char *b)
  2 	return strcmp(a, b) == 0;
  3 }
  4 
  5+static int
  6+isunderprefix(const char *prefix, const char *name)
  7+{
  8+	size_t n;
  9+
 10+	if (isempty(prefix))
 11+		return 1;
 12+	n = strlen(prefix);
 13+	if (strncmp(name, prefix, n) != 0)
 14+		return 0;
 15+	return name[n] == 0 || name[n] == '/';
 16+}
 17+
 18 static int
 19 samelist(const struct StrList *a, const struct StrList *b)
 20 {
 21@@ -119,6 +132,39 @@ resolvegoals(const struct SubGraph *sg, struct StrList *out)
 22 	return -1;
 23 }
 24 
 25+static void
 26+markreachable(const struct Graph *graph, const char *name, unsigned char *seen)
 27+{
 28+	const struct Target *t;
 29+	size_t i, idx;
 30+
 31+	t = findctarget(graph, name);
 32+	if (!t)
 33+		return;
 34+	idx = (size_t)(t - graph->v);
 35+	if (seen[idx])
 36+		return;
 37+	seen[idx] = 1;
 38+	for (i = 0; i < t->prereqs.n; i++)
 39+		markreachable(graph, t->prereqs.v[i], seen);
 40+	for (i = 0; i < t->order_only.n; i++)
 41+		markreachable(graph, t->order_only.v[i], seen);
 42+}
 43+
 44+static int
 45+allsubmake(const struct Target *t)
 46+{
 47+	size_t i;
 48+
 49+	if (!t || t->recipes.n == 0)
 50+		return 0;
 51+	for (i = 0; i < t->recipes.n; i++) {
 52+		if (!t->recipes.v[i].submake)
 53+			return 0;
 54+	}
 55+	return 1;
 56+}
 57+
 58 static void
 59 pushstack(struct SubGraphStack *stack, const char *key)
 60 {
 61@@ -236,7 +282,8 @@ scopegraph(struct Graph *graph, const char *prefix)
 62 		graph->v[i].name = intern(name);
 63 		free(name);
 64 		free(graph->v[i].owner);
 65-		if (graph->v[i].defined || graph->v[i].recipes.n > 0)
 66+		if ((graph->v[i].defined || graph->v[i].recipes.n > 0) &&
 67+		    isunderprefix(prefix, graph->v[i].name))
 68 			graph->v[i].owner = xstrdup(prefix);
 69 		else
 70 			graph->v[i].owner = 0;
 71@@ -370,6 +417,7 @@ mergechildgraph(struct SubGraph *parent,
 72                 const struct StrList *goals)
 73 {
 74 	struct SubGraph *known;
 75+	unsigned char *reachable;
 76 	struct Target *t;
 77 	size_t i;
 78 
 79@@ -380,10 +428,19 @@ mergechildgraph(struct SubGraph *parent,
 80 		return -1;
 81 	}
 82 	if (!known) {
 83+		reachable = xmalloc(child->graph.n ? child->graph.n : 1);
 84+		memset(reachable, 0, child->graph.n ? child->graph.n : 1);
 85+		for (i = 0; i < goals->n; i++)
 86+			markreachable(&child->graph, goals->v[i], reachable);
 87 		for (i = 0; i < child->graph.n; i++) {
 88-			if (mergetarget(&parent->graph, &child->graph.v[i]) < 0)
 89+			if (!reachable[i])
 90+				continue;
 91+			if (mergetarget(&parent->graph, &child->graph.v[i]) < 0) {
 92+				free(reachable);
 93 				return -1;
 94+			}
 95 		}
 96+		free(reachable);
 97 	}
 98 	t = findtarget(&parent->graph, tname);
 99 	if (!t) {
100@@ -409,13 +466,35 @@ static int buildsubgraph0(struct SubGraph *sg, struct SubGraphStack *stack);
101 static int
102 expandsubgraphs(struct SubGraph *sg, struct SubGraphStack *stack)
103 {
104-	size_t i;
105+	struct StrList roots;
106+	unsigned char *reachable;
107+	size_t i, n;
108 
109+	if (resolvegoals(sg, &roots) < 0)
110+		return -1;
111 	for (i = 0; i < sg->graph.n; i++) {
112+		if (!targetownedby(&sg->graph.v[i], sg->prefix))
113+			continue;
114+		if (!sg->graph.v[i].phony && !allsubmake(&sg->graph.v[i]))
115+			continue;
116+		if (!hasword(&roots, sg->graph.v[i].name))
117+			addstr(&roots, sg->graph.v[i].name);
118+	}
119+	n = sg->graph.n;
120+	reachable = xmalloc(n ? n : 1);
121+	memset(reachable, 0, n ? n : 1);
122+	for (i = 0; i < roots.n; i++)
123+		markreachable(&sg->graph, roots.v[i], reachable);
124+	freestrs(&roots);
125+
126+	for (i = 0; i < n; i++) {
127 		size_t k;
128 		const char *tname;
129 		struct StrList prevgoals;
130 
131+		if (!reachable[i])
132+			continue;
133+
134 		memset(&prevgoals, 0, sizeof(prevgoals));
135 		tname = sg->graph.v[i].name;
136 		for (k = 0; k < sg->graph.v[i].recipes.n;) {
137@@ -440,14 +519,32 @@ expandsubgraphs(struct SubGraph *sg, struct SubGraphStack *stack)
138 			addwords(&child.goals, &r->sm.goals);
139 			if (strcmp(child.cwd, sg->cwd) == 0 &&
140 			    (!child.makefile || strcmp(child.makefile, sg->makefile) == 0)) {
141-				fprintf(stderr, "cannot expand recursive submake graph: %s\n",
142-				        r->body);
143 				freesubgraph(&child);
144-				freestrs(&prevgoals);
145-				return -1;
146+				k++;
147+				continue;
148 			}
149 			rc = buildsubgraph0(&child, stack);
150 			if (rc > 0) {
151+				struct SubGraph *known;
152+
153+				known = sameprefix(sg->prefix, child.prefix) ? 0 : findgraphsub(&sg->graph, child.prefix);
154+				if (known && sameinvocation(known, &child) && child.goals.n > 0) {
155+					size_t gi;
156+					struct Target *pt;
157+
158+					removerecipe(&sg->graph.v[i].recipes, k);
159+					pt = findtarget(&sg->graph, tname);
160+					for (gi = 0; pt && gi < child.goals.n; gi++) {
161+						char *gname;
162+
163+						gname = rebaseword(child.prefix, child.goals.v[gi]);
164+						if (!hasword(&pt->prereqs, gname))
165+							addstr(&pt->prereqs, gname);
166+						free(gname);
167+					}
168+					freesubgraph(&child);
169+					continue;
170+				}
171 				freesubgraph(&child);
172 				k++;
173 				continue;
174@@ -455,6 +552,7 @@ expandsubgraphs(struct SubGraph *sg, struct SubGraphStack *stack)
175 			if (rc < 0) {
176 				freesubgraph(&child);
177 				freestrs(&prevgoals);
178+				free(reachable);
179 				return -1;
180 			}
181 			/* remove the make invocation, we replace it with a graph*/
182@@ -462,6 +560,7 @@ expandsubgraphs(struct SubGraph *sg, struct SubGraphStack *stack)
183 			if (resolvegoals(&child, &goals) < 0) {
184 				freesubgraph(&child);
185 				freestrs(&prevgoals);
186+				free(reachable);
187 				return -1;
188 			}
189 			subgraphorder(&child.graph, child.prefix, &prevgoals);
190@@ -469,6 +568,7 @@ expandsubgraphs(struct SubGraph *sg, struct SubGraphStack *stack)
191 				freesubgraph(&child);
192 				freestrs(&goals);
193 				freestrs(&prevgoals);
194+				free(reachable);
195 				return -1;
196 			}
197 			freestrs(&prevgoals);
198@@ -479,6 +579,7 @@ expandsubgraphs(struct SubGraph *sg, struct SubGraphStack *stack)
199 		}
200 		freestrs(&prevgoals);
201 	}
202+	free(reachable);
203 	return 0;
204 }
205 
+95, -33
  1@@ -6,33 +6,81 @@
  2 
  3 /* recursive make handling */
  4 
  5+static void
  6+tokview(const char *s, const char **out, size_t *n)
  7+{
  8+	const char *p;
  9+	size_t len;
 10+
 11+	p = s;
 12+	while (*p == '(')
 13+		p++;
 14+	len = strlen(p);
 15+	while (len > 0 && (p[len - 1] == ')' || p[len - 1] == ';'))
 16+		len--;
 17+	*out = p;
 18+	*n = len;
 19+}
 20+
 21+static int
 22+tokeq(const char *s, const char *lit)
 23+{
 24+	const char *p;
 25+	size_t n, ln;
 26+
 27+	tokview(s, &p, &n);
 28+	ln = strlen(lit);
 29+	return n == ln && strncmp(p, lit, n) == 0;
 30+}
 31+
 32+static char *
 33+tokdup(const char *s)
 34+{
 35+	const char *p;
 36+	size_t n;
 37+
 38+	tokview(s, &p, &n);
 39+	return xstrndup(p, n);
 40+}
 41+
 42 static int
 43 ismakevarref(const char *s)
 44 {
 45+	const char *p;
 46 	char close;
 47 	size_t n;
 48 
 49-	if (!s || s[0] != '$')
 50+	if (!s)
 51 		return 0;
 52-	if (s[1] != '(' && s[1] != '{')
 53+	tokview(s, &p, &n);
 54+	if (n == 0 || p[0] != '$')
 55 		return 0;
 56-	n = strlen(s);
 57 	if (n != 7)
 58 		return 0;
 59-	close = s[1] == '(' ? ')' : '}';
 60-	return strncmp(s + 2, "MAKE", 4) == 0 && s[6] == close && s[7] == 0;
 61+	if (p[1] != '(' && p[1] != '{')
 62+		return 0;
 63+	close = p[1] == '(' ? ')' : '}';
 64+	return strncmp(p + 2, "MAKE", 4) == 0 && p[6] == close;
 65 }
 66 
 67 static int
 68 ismaketoken(const char *s)
 69 {
 70+	const char *p;
 71 	const char *base;
 72+	size_t n;
 73+	int ok;
 74+	char *tmp;
 75 
 76 	if (ismakevarref(s))
 77 		return 1;
 78-	base = strrchr(s, '/');
 79-	base = base ? base + 1 : s;
 80-	return strcmp(base, "make") == 0 || strcmp(base, "gmake") == 0;
 81+	tokview(s, &p, &n);
 82+	tmp = xstrndup(p, n);
 83+	base = strrchr(tmp, '/');
 84+	base = base ? base + 1 : tmp;
 85+	ok = strcmp(base, "make") == 0 || strcmp(base, "gmake") == 0;
 86+	free(tmp);
 87+	return ok;
 88 }
 89 
 90 static int
 91@@ -170,7 +218,7 @@ parsesubmake(struct SubMake *dst, const char *cmd)
 92 	makeprog = 0;
 93 	tokenizecmd(&toks, cmd);
 94 	start = 0;
 95-	if (toks.n >= 3 && strcmp(toks.v[0], "cd") == 0 && strcmp(toks.v[2], "&&") == 0) {
 96+	if (toks.n >= 3 && tokeq(toks.v[0], "cd") && tokeq(toks.v[2], "&&")) {
 97 		cddir = toks.v[1];
 98 		start = 3;
 99 	}
100@@ -179,65 +227,79 @@ parsesubmake(struct SubMake *dst, const char *cmd)
101 		return 0;
102 	}
103 	makeprog = toks.v[start];
104-	dst->makeprog = xstrdup(makeprog);
105+	dst->makeprog = tokdup(makeprog);
106 	if (cddir)
107-		dst->dir = xstrdup(cddir);
108+		dst->dir = tokdup(cddir);
109 	for (i = start + 1; i < toks.n; i++) {
110 		const char *tok;
111+		char *ntok;
112 
113 		tok = toks.v[i];
114-		if (strcmp(tok, "&&") == 0)
115+		ntok = tokdup(tok);
116+		if (strcmp(ntok, "&&") == 0) {
117+			free(ntok);
118 			break;
119-		if ((strcmp(tok, "-C") == 0 || strcmp(tok, "--directory") == 0) && i + 1 < toks.n) {
120-			addstr(&dst->flags, tok);
121+ 		}
122+		if ((strcmp(ntok, "-C") == 0 || strcmp(ntok, "--directory") == 0) && i + 1 < toks.n) {
123+			addstr(&dst->flags, ntok);
124 			free(dst->dir);
125-			dst->dir = xstrdup(toks.v[++i]);
126+			dst->dir = tokdup(toks.v[++i]);
127+			free(ntok);
128 			continue;
129 		}
130-		if (strncmp(tok, "-C", 2) == 0 && tok[2]) {
131+		if (strncmp(ntok, "-C", 2) == 0 && ntok[2]) {
132 			addstr(&dst->flags, "-C");
133 			free(dst->dir);
134-			dst->dir = xstrdup(tok + 2);
135+			dst->dir = xstrdup(ntok + 2);
136+			free(ntok);
137 			continue;
138 		}
139-		if (strncmp(tok, "--directory=", 12) == 0) {
140+		if (strncmp(ntok, "--directory=", 12) == 0) {
141 			addstr(&dst->flags, "--directory");
142 			free(dst->dir);
143-			dst->dir = xstrdup(tok + 12);
144+			dst->dir = xstrdup(ntok + 12);
145+			free(ntok);
146 			continue;
147 		}
148-		if ((strcmp(tok, "-f") == 0 || strcmp(tok, "--file") == 0) && i + 1 < toks.n) {
149-			addstr(&dst->flags, tok);
150+		if ((strcmp(ntok, "-f") == 0 || strcmp(ntok, "--file") == 0) && i + 1 < toks.n) {
151+			addstr(&dst->flags, ntok);
152 			free(dst->makefile);
153-			dst->makefile = xstrdup(toks.v[++i]);
154+			dst->makefile = tokdup(toks.v[++i]);
155+			free(ntok);
156 			continue;
157 		}
158-		if (strncmp(tok, "-f", 2) == 0 && tok[2]) {
159+		if (strncmp(ntok, "-f", 2) == 0 && ntok[2]) {
160 			addstr(&dst->flags, "-f");
161 			free(dst->makefile);
162-			dst->makefile = xstrdup(tok + 2);
163+			dst->makefile = xstrdup(ntok + 2);
164+			free(ntok);
165 			continue;
166 		}
167-		if (strncmp(tok, "--file=", 7) == 0) {
168+		if (strncmp(ntok, "--file=", 7) == 0) {
169 			addstr(&dst->flags, "--file");
170 			free(dst->makefile);
171-			dst->makefile = xstrdup(tok + 7);
172+			dst->makefile = xstrdup(ntok + 7);
173+			free(ntok);
174 			continue;
175 		}
176-		if (flagsarg(tok) && i + 1 < toks.n) {
177-			addstr(&dst->flags, tok);
178+		if (flagsarg(ntok) && i + 1 < toks.n) {
179+			addstr(&dst->flags, ntok);
180 			i++;
181+			free(ntok);
182 			continue;
183 		}
184-		if (tok[0] == '-') {
185-			addstr(&dst->flags, tok);
186+		if (ntok[0] == '-') {
187+			addstr(&dst->flags, ntok);
188+			free(ntok);
189 			continue;
190 		}
191-		if (isassignment(tok)) {
192-			addstr(&dst->assigns, tok);
193+		if (isassignment(ntok)) {
194+			addstr(&dst->assigns, ntok);
195+			free(ntok);
196 			continue;
197 		}
198-		addstr(&dst->goals, tok);
199+		addstr(&dst->goals, ntok);
200+		free(ntok);
201 	}
202 	freetoks(&toks);
203 	return 1;
+16, -1
 1@@ -462,14 +462,29 @@ const struct Target *
 2 defaulttarget(const struct Graph *graph, const char *owner)
 3 {
 4 	const struct Target *all;
 5+	const char *base;
 6+	char *ownedall;
 7 	size_t i;
 8 
 9-	all = findctarget(graph, "all");
10+	ownedall = 0;
11+	if (owner && owner[0]) {
12+		ownedall = cat3(owner, "/", "all");
13+		all = findctarget(graph, ownedall);
14+		free(ownedall);
15+	} else {
16+		all = findctarget(graph, "all");
17+	}
18 	if (targetownedby(all, owner))
19 		return all;
20 	for (i = 0; i < graph->n; i++) {
21 		if (!targetownedby(&graph->v[i], owner))
22 			continue;
23+		base = strrchr(graph->v[i].name, '/');
24+		base = base ? base + 1 : graph->v[i].name;
25+		if (base[0] == '.')
26+			continue;
27+		if (strcmp(base, "_PHONY") == 0)
28+			continue;
29 		if (graph->v[i].name == intern("clean") || graph->v[i].name == intern("fmt"))
30 			continue;
31 		if (graph->v[i].recipes.n > 0 || graph->v[i].prereqs.n > 0 || graph->v[i].order_only.n > 0)