commit 088da53

shrub  ·  2026-04-20 16:41:51 +0000 UTC
parent 2fadeb0
handle recursive make invocations
14 files changed,  +1148, -359
+2, -1
 1@@ -7,7 +7,8 @@ SRCS =  cli/main.c \
 2 	src/parse.c \
 3 	src/eval/eval.c \
 4 	src/eval/dollar.c \
 5-	src/graph.c \
 6+	src/graph/graph.c \
 7+	src/graph/subgraph.c \
 8 	src/posix.c \
 9 	src/gnu/pattern.c \
10 	backends/ninja.c \
+112, -9
  1@@ -2,9 +2,58 @@
  2 #include "internal.h"
  3 
  4 #include <stdio.h>
  5+#include <stdlib.h>
  6+#include <string.h>
  7+#include <time.h>
  8 
  9 /* graphviz backend */
 10 
 11+struct Style {
 12+	const char *stroke;
 13+	const char *fill;
 14+	const char *cluster;
 15+};
 16+
 17+static const struct Style styles[] = {
 18+	{ "#0F766E", "#CCFBF1", "#F0FDFA" },
 19+	{ "#2563EB", "#DBEAFE", "#EFF6FF" },
 20+	{ "#CA8A04", "#FEF3C7", "#FFFBEB" },
 21+	{ "#C2410C", "#FED7AA", "#FFF7ED" },
 22+	{ "#9333EA", "#E9D5FF", "#FAF5FF" },
 23+	{ "#BE185D", "#FBCFE8", "#FDF2F8" },
 24+	{ "#4F46E5", "#C7D2FE", "#EEF2FF" },
 25+	{ "#059669", "#D1FAE5", "#ECFDF5" },
 26+};
 27+
 28+static size_t styleorder[sizeof(styles) / sizeof(styles[0])];
 29+static size_t nextstyle;
 30+
 31+static void
 32+shufflestyles(void)
 33+{
 34+	size_t i;
 35+
 36+	for (i = 0; i < sizeof(styles) / sizeof(styles[0]); i++)
 37+		styleorder[i] = i;
 38+	for (i = sizeof(styles) / sizeof(styles[0]); i > 1; i--) {
 39+		size_t j, t;
 40+
 41+		j = (size_t)(rand() % (int)i);
 42+		t = styleorder[i - 1];
 43+		styleorder[i - 1] = styleorder[j];
 44+		styleorder[j] = t;
 45+	}
 46+	nextstyle = 0;
 47+}
 48+
 49+static const struct Style *
 50+pickstyle(void)
 51+{
 52+	if (nextstyle >= sizeof(styles) / sizeof(styles[0]))
 53+		shufflestyles();
 54+	return &styles[styleorder[nextstyle++]];
 55+}
 56+
 57 static void
 58 emitstr(FILE *fp, const char *s)
 59 {
 60@@ -17,20 +66,66 @@ emitstr(FILE *fp, const char *s)
 61 	}
 62 }
 63 
 64+static void
 65+emitownerlabel(FILE *fp, const char *owner)
 66+{
 67+	const char *label;
 68+
 69+	label = owner && owner[0] ? owner : "(root)";
 70+	emitstr(fp, label);
 71+}
 72+
 73 static void
 74 emitnode(FILE *fp, size_t i, const struct Target *t)
 75 {
 76 	fprintf(fp, "  n%zu [label=\"", i);
 77 	emitstr(fp, t->name);
 78-	fprintf(fp, "\"");
 79+	fprintf(fp, "\", penwidth=1.2, color=\"#000000\", fillcolor=\"#FFFFFF\"");
 80 	if (t->recipes.n > 0) {
 81-		fprintf(fp, ", shape=box");
 82+		fprintf(fp, ", shape=box, style=\"filled,rounded\"");
 83 	} else if (t->prereqs.n > 0 || t->order_only.n > 0) {
 84-		fprintf(fp, ", style=dashed");
 85+		fprintf(fp, ", shape=box, style=\"filled,dashed,rounded\"");
 86+	} else {
 87+		fprintf(fp, ", style=filled");
 88 	}
 89 	fprintf(fp, "];\n");
 90 }
 91 
 92+static void
 93+emitowners(FILE *fp, const struct Graph *graph, const char *owner)
 94+{
 95+	size_t i;
 96+
 97+	for (i = 0; i < graph->n; i++) {
 98+		if (!targetownedby(&graph->v[i], owner))
 99+			continue;
100+		emitnode(fp, i, &graph->v[i]);
101+	}
102+}
103+
104+static void
105+emitcluster(FILE *fp, const struct Graph *root, const struct SubGraph *sg, size_t *nextid)
106+{
107+	size_t i;
108+	const struct Style *st;
109+	size_t id;
110+
111+	id = (*nextid)++;
112+	st = pickstyle();
113+	fprintf(fp, "  subgraph cluster_%zu {\n", id);
114+	fprintf(fp, "    label=\"");
115+	emitownerlabel(fp, sg->prefix);
116+	fprintf(fp, "\";\n");
117+	fprintf(fp, "    labelloc=t;\n");
118+	fprintf(fp, "    margin=18;\n");
119+	fprintf(fp, "    style=\"rounded,filled\";\n");
120+	fprintf(fp, "    color=\"%s\";\n    fillcolor=\"%s\";\n", st->stroke, st->cluster);
121+	emitowners(fp, root, sg->prefix);
122+	for (i = 0; i < sg->graph.nsubs; i++)
123+		emitcluster(fp, root, &sg->graph.subs[i], nextid);
124+	fprintf(fp, "  }\n");
125+}
126+
127 static void
128 emitedges(FILE *fp, const struct Graph *graph, size_t i, const struct Target *t)
129 {
130@@ -41,13 +136,15 @@ emitedges(FILE *fp, const struct Graph *graph, size_t i, const struct Target *t)
131 		dep = findctarget(graph, t->prereqs.v[j]);
132 		if (!dep)
133 			continue;
134-		fprintf(fp, "  n%zu -> n%zu;\n", (size_t)(dep - graph->v), i);
135+		fprintf(fp, "  n%zu -> n%zu [color=\"#475569\", penwidth=1.2];\n",
136+		        (size_t)(dep - graph->v), i);
137 	}
138 	for (j = 0; j < t->order_only.n; j++) {
139 		dep = findctarget(graph, t->order_only.v[j]);
140 		if (!dep)
141 			continue;
142-		fprintf(fp, "  n%zu -> n%zu [style=dashed];\n", (size_t)(dep - graph->v), i);
143+		fprintf(fp, "  n%zu -> n%zu [style=dashed, color=\"#F59E0B\", penwidth=1.2];\n",
144+		        (size_t)(dep - graph->v), i);
145 	}
146 }
147 
148@@ -55,17 +152,23 @@ int
149 gengraphviz(const struct Graph *graph, const char *path)
150 {
151 	FILE *fp;
152-	size_t i;
153+	size_t i, clusterid;
154 
155 	fp = fopen(path, "w");
156 	if (!fp)
157 		return -1;
158+	srand((unsigned)time(0));
159+	shufflestyles();
160 
161 	fprintf(fp, "digraph shin {\n");
162 	fprintf(fp, "  rankdir=LR;\n");
163-	fprintf(fp, "  node [fontname=\"monospace\"];\n");
164-	for (i = 0; i < graph->n; i++)
165-		emitnode(fp, i, &graph->v[i]);
166+	fprintf(fp, "  graph [bgcolor=\"#FFFFFF\", pad=0.3, nodesep=0.4, ranksep=0.7, splines=true];\n");
167+	fprintf(fp, "  node [fontname=\"monospace\", fontsize=10];\n");
168+	fprintf(fp, "  edge [arrowsize=0.7];\n");
169+	emitowners(fp, graph, 0);
170+	clusterid = 0;
171+	for (i = 0; i < graph->nsubs; i++)
172+		emitcluster(fp, graph, &graph->subs[i], &clusterid);
173 	fprintf(fp, "\n");
174 	for (i = 0; i < graph->n; i++)
175 		emitedges(fp, graph, i, &graph->v[i]);
+33, -26
  1@@ -23,16 +23,16 @@ static char *
  2 joinrecipes(const struct Target *t)
  3 {
  4 	size_t i;
  5+	struct StrList bodies;
  6 	char *s;
  7 
  8-	s = xstrdup("");
  9+	memset(&bodies, 0, sizeof(bodies));
 10 	for (i = 0; i < t->recipes.n; i++) {
 11-		char *next;
 12-
 13-		next = s[0] ? cat3(s, " && ", t->recipes.v[i].body) : xstrdup(t->recipes.v[i].body);
 14-		free(s);
 15-		s = next;
 16+		bodies.v = xrealloc(bodies.v, (bodies.n + 1) * sizeof(bodies.v[0]));
 17+		bodies.v[bodies.n++] = t->recipes.v[i].body;
 18 	}
 19+	s = joinstrs(&bodies, " && ");
 20+	free(bodies.v);
 21 	return s;
 22 }
 23 
 24@@ -116,24 +116,8 @@ findrule(struct Rule *rules, size_t n, const char *cmd)
 25 	return 0;
 26 }
 27 
 28-static const char *
 29-defaulttarget(const struct Graph *graph)
 30-{
 31-	size_t i;
 32-
 33-	if (findctarget(graph, "all"))
 34-		return "all";
 35-	for (i = 0; i < graph->n; i++) {
 36-		if (strcmp(graph->v[i].name, "clean") == 0 || strcmp(graph->v[i].name, "fmt") == 0)
 37-			continue;
 38-		if (graph->v[i].recipes.n > 0 || graph->v[i].prereqs.n > 0 || graph->v[i].order_only.n > 0)
 39-			return graph->v[i].name;
 40-	}
 41-	return 0;
 42-}
 43-
 44-int
 45-genninja(const struct Graph *graph, const char *path)
 46+static int
 47+genninjafile(const struct Graph *graph, const char *path, const char *prefix, int root)
 48 {
 49 	FILE *fp;
 50 	size_t i, j;
 51@@ -141,6 +125,17 @@ genninja(const struct Graph *graph, const char *path)
 52 	struct Rule *rules;
 53 	size_t nrules;
 54 
 55+	for (i = 0; i < graph->nsubs; i++) {
 56+		char *childpath;
 57+
 58+		childpath = joinpath(graph->subs[i].cwd, "build.ninja");
 59+		if (genninjafile(&graph->subs[i].graph, childpath, graph->subs[i].prefix, 0) < 0) {
 60+			free(childpath);
 61+			return -1;
 62+		}
 63+		free(childpath);
 64+	}
 65+
 66 	fp = fopen(path, "w");
 67 	if (!fp)
 68 		return -1;
 69@@ -148,7 +143,13 @@ genninja(const struct Graph *graph, const char *path)
 70 	ruleid = 0;
 71 	rules = 0;
 72 	nrules = 0;
 73+	for (i = 0; i < graph->nsubs; i++)
 74+		fprintf(fp, "subninja %s/build.ninja\n", graph->subs[i].prefix);
 75+	if (graph->nsubs)
 76+		fputc('\n', fp);
 77 	for (i = 0; i < graph->n; i++) {
 78+		if (!targetownedby(&graph->v[i], prefix))
 79+			continue;
 80 		if (graph->v[i].recipes.n > 0) {
 81 			char *cmd;
 82 			int id;
 83@@ -186,8 +187,8 @@ genninja(const struct Graph *graph, const char *path)
 84 			fprintf(fp, "\n\n");
 85 		}
 86 	}
 87-	if (defaulttarget(graph))
 88-		fprintf(fp, "default %s\n", defaulttarget(graph));
 89+	if (root && defaulttarget(graph, prefix))
 90+		fprintf(fp, "default %s\n", defaulttarget(graph, prefix)->name);
 91 
 92 	fclose(fp);
 93 	for (i = 0; i < nrules; i++)
 94@@ -195,3 +196,9 @@ genninja(const struct Graph *graph, const char *path)
 95 	free(rules);
 96 	return 0;
 97 }
 98+
 99+int
100+genninja(const struct Graph *graph, const char *path)
101+{
102+	return genninjafile(graph, path, 0, 1);
103+}
+101, -144
  1@@ -1,4 +1,5 @@
  2 #include "shinobi.h"
  3+#include "internal.h"
  4 #include "compcmd.h"
  5 #include "graphviz.h"
  6 #include "ninja.h"
  7@@ -15,40 +16,6 @@ usage(FILE *fp, const char *argv0)
  8 	fprintf(fp, "usage: %s [-ag] [-G ninja|dot|compcmd] [-C dir] [-f file]\n", argv0);
  9 }
 10 
 11-static char *
 12-readfile(const char *path)
 13-{
 14-	FILE *fp;
 15-	long n;
 16-	char *buf;
 17-
 18-	fp = fopen(path, "rb");
 19-	if (!fp)
 20-		return 0;
 21-	if (fseek(fp, 0, SEEK_END) < 0) {
 22-		fclose(fp);
 23-		return 0;
 24-	}
 25-	n = ftell(fp);
 26-	if (n < 0) {
 27-		fclose(fp);
 28-		return 0;
 29-	}
 30-	if (fseek(fp, 0, SEEK_SET) < 0) {
 31-		fclose(fp);
 32-		return 0;
 33-	}
 34-	buf = malloc((size_t)n + 1);
 35-	if (fread(buf, 1, (size_t)n, fp) != (size_t)n) {
 36-		fclose(fp);
 37-		free(buf);
 38-		return 0;
 39-	}
 40-	buf[n] = 0;
 41-	fclose(fp);
 42-	return buf;
 43-}
 44-
 45 static int
 46 isvarassign(const char *s)
 47 {
 48@@ -60,40 +27,6 @@ isvarassign(const char *s)
 49 	return 1;
 50 }
 51 
 52-static char *
 53-appendassigns(char *src, char **assigns, size_t nassigns)
 54-{
 55-	size_t i, len, extra;
 56-	char *out;
 57-
 58-	if (nassigns == 0)
 59-		return src;
 60-	len = strlen(src);
 61-	extra = len > 0 && src[len - 1] != '\n' ? 1 : 0;
 62-	for (i = 0; i < nassigns; i++)
 63-		extra += strlen(assigns[i]) + 1;
 64-	out = malloc(len + extra + 1);
 65-	if (!out) {
 66-		free(src);
 67-		return 0;
 68-	}
 69-	memcpy(out, src, len);
 70-	extra = len;
 71-	if (extra > 0 && out[extra - 1] != '\n')
 72-		out[extra++] = '\n';
 73-	for (i = 0; i < nassigns; i++) {
 74-		size_t n;
 75-
 76-		n = strlen(assigns[i]);
 77-		memcpy(out + extra, assigns[i], n);
 78-		extra += n;
 79-		out[extra++] = '\n';
 80-	}
 81-	out[extra] = 0;
 82-	free(src);
 83-	return out;
 84-}
 85-
 86 static const char *
 87 assignopname(enum AssignOp op)
 88 {
 89@@ -232,11 +165,41 @@ dumpgraph(const struct Graph *graph)
 90 		const struct Target *t = &graph->v[i];
 91 
 92 		printindent(1);
 93-		printf("target %s\n", t->name);
 94+		printf("target %s", t->name);
 95+		if (t->owner)
 96+			printf(" [owner=%s]", t->owner);
 97+		putchar('\n');
 98 		dumpstrlist("prereqs", &t->prereqs, 2);
 99 		dumpstrlist("order-only", &t->order_only, 2);
100 		dumprecipes(&t->recipes, 2);
101 	}
102+	if (graph->nsubs) {
103+		printindent(1);
104+		printf("subgraphs (%zu):\n", graph->nsubs);
105+		for (i = 0; i < graph->nsubs; i++) {
106+			size_t j;
107+
108+			printindent(2);
109+			printf("%s", graph->subs[i].prefix ? graph->subs[i].prefix : ".");
110+			if (graph->subs[i].parents.n) {
111+				printf(" parents=");
112+				for (j = 0; j < graph->subs[i].parents.n; j++) {
113+					if (j)
114+						putchar(',');
115+					fputs(graph->subs[i].parents.v[j], stdout);
116+				}
117+			}
118+			if (graph->subs[i].goals.n) {
119+				printf(" goals=");
120+				for (j = 0; j < graph->subs[i].goals.n; j++) {
121+					if (j)
122+						putchar(',');
123+					fputs(graph->subs[i].goals.v[j], stdout);
124+				}
125+			}
126+			putchar('\n');
127+		}
128+	}
129 }
130 
131 int
132@@ -248,6 +211,7 @@ main(int argc, char **argv)
133 		GEN_COMPCMD,
134 	};
135 	const char *path;
136+	char *pathbuf;
137 	char *src;
138 	int dump_ast;
139 	int dump_graph;
140@@ -257,14 +221,19 @@ main(int argc, char **argv)
141 	enum Generator gen;
142 	struct Ast ast;
143 	struct RuleSet rs;
144-	struct Graph graph;
145+	struct SubGraph sg;
146 
147 	path = 0;
148+	pathbuf = 0;
149 	dump_ast = 0;
150 	dump_graph = 0;
151 	gen = GEN_NINJA;
152 	assigns = 0;
153 	nassigns = 0;
154+	memset(&sg, 0, sizeof(sg));
155+	memset(&ast, 0, sizeof(ast));
156+	memset(&rs, 0, sizeof(rs));
157+	src = 0;
158 	for (i = 1; i < argc; i++) {
159 		if (strcmp(argv[i], "-a") == 0) {
160 			dump_ast = 1;
161@@ -326,105 +295,93 @@ main(int argc, char **argv)
162 			return 1;
163 		}
164 	}
165-	if (path) {
166-		src = readfile(path);
167-		if (!src) {
168-			fprintf(stderr, "could not read %s\n", path);
169-			return 1;
170-		}
171-	} else {
172-		static const char *const defaults[] = {
173-		    "GNUmakefile",
174-		    "makefile",
175-		    "Makefile",
176-		    0,
177-		};
178-		size_t mk;
179+	for (i = 0; i < (int)nassigns; i++)
180+		addstr(&sg.assigns, assigns[i]);
181+	if (dump_ast) {
182+		char *mkpath;
183 
184-		src = 0;
185-		for (mk = 0; defaults[mk]; mk++) {
186-			path = defaults[mk];
187-			src = readfile(path);
188-			if (src)
189-				break;
190+		mkpath = 0;
191+		if (loadmakefile(path, &mkpath, &src) < 0) {
192+			if (path)
193+				fprintf(stderr, "could not read %s\n", path);
194+			else
195+				fprintf(stderr, "could not find a makefile\n");
196+			free(assigns);
197+			freesubgraph(&sg);
198+			return 1;
199 		}
200-		if (!src) {
201-			fprintf(stderr, "could not find a makefile\n");
202+		path = mkpath;
203+		pathbuf = mkpath;
204+		src = appendassigns(src, &sg.assigns);
205+		if (parse(path, src, &ast) < 0) {
206+			fprintf(stderr, "parse error in %s\n", path);
207+			free(assigns);
208+			free(pathbuf);
209+			free(src);
210+			freesubgraph(&sg);
211 			return 1;
212 		}
213-	}
214-	src = appendassigns(src, assigns, nassigns);
215-	if (!src) {
216-		free(assigns);
217-		fprintf(stderr, "out of memory\n");
218-		return 1;
219-	}
220-	if (parse(path, src, &ast) < 0) {
221-		fprintf(stderr, "parse error in %s\n", path);
222-		free(assigns);
223-		free(src);
224-		return 1;
225-	}
226-	{
227-		size_t marked = 0;
228-		size_t k = ast.n;
229-		while (k > 0 && marked < nassigns) {
230-			k--;
231-			if (ast.v[k].kind == NODE_ASSIGN) {
232-				ast.v[k].data.assign.origin = ORIGIN_COMMAND;
233-				marked++;
234+		{
235+			size_t marked = 0;
236+			size_t k = ast.n;
237+
238+			while (k > 0 && marked < nassigns) {
239+				k--;
240+				if (ast.v[k].kind == NODE_ASSIGN) {
241+					ast.v[k].data.assign.origin = ORIGIN_COMMAND;
242+					marked++;
243+				}
244 			}
245 		}
246-	}
247-	free(assigns);
248-	if (eval(&ast, &rs) < 0) {
249-		fprintf(stderr, "eval error in %s\n", path);
250-		freeast(&ast);
251-		free(src);
252-		return 1;
253-	}
254-	if (dump_ast)
255+		if (eval(&ast, &rs) < 0) {
256+			fprintf(stderr, "eval error in %s\n", path);
257+			free(assigns);
258+			freeast(&ast);
259+			free(pathbuf);
260+			free(src);
261+			freesubgraph(&sg);
262+			return 1;
263+		}
264 		dumpruleset(&rs);
265-	if (buildgraph(&rs, &graph) < 0) {
266-		fprintf(stderr, "graph error in %s\n", path);
267 		freeruleset(&rs);
268 		freeast(&ast);
269 		free(src);
270+	}
271+	if (path)
272+		sg.makefile = xstrdup(path);
273+	free(assigns);
274+	assigns = 0;
275+	if (buildsubgraph(&sg) < 0) {
276+		fprintf(stderr, "graph error in %s\n", path ? path : "(default)");
277+		free(pathbuf);
278+		freesubgraph(&sg);
279 		return 1;
280 	}
281 	if (dump_graph)
282-		dumpgraph(&graph);
283+		dumpgraph(&sg.graph);
284 	if (gen == GEN_DOT) {
285-		if (gengraphviz(&graph, "build.dot") < 0) {
286+		if (gengraphviz(&sg.graph, "build.dot") < 0) {
287 			fprintf(stderr, "graphviz generation error\n");
288-			freegraph(&graph);
289-			freeruleset(&rs);
290-			freeast(&ast);
291-			free(src);
292+			free(pathbuf);
293+			freesubgraph(&sg);
294 			return 1;
295 		}
296 	}
297 	if (gen == GEN_COMPCMD) {
298-		if (gencompcmd(&graph, "compile_commands.json") < 0) {
299+		if (gencompcmd(&sg.graph, "compile_commands.json") < 0) {
300 			fprintf(stderr, "compile_commands generation error\n");
301-			freegraph(&graph);
302-			freeruleset(&rs);
303-			freeast(&ast);
304-			free(src);
305+			free(pathbuf);
306+			freesubgraph(&sg);
307 			return 1;
308 		}
309 	}
310-	if (gen == GEN_NINJA && genninja(&graph, "build.ninja") < 0) {
311+	if (gen == GEN_NINJA && genninja(&sg.graph, "build.ninja") < 0) {
312 		fprintf(stderr, "ninja generation error\n");
313-		freegraph(&graph);
314-		freeruleset(&rs);
315-		freeast(&ast);
316-		free(src);
317+		free(pathbuf);
318+		freesubgraph(&sg);
319 		return 1;
320 	}
321-	freegraph(&graph);
322-	freeruleset(&rs);
323-	freeast(&ast);
324-	free(src);
325+	free(pathbuf);
326+	freesubgraph(&sg);
327 	return 0;
328 }
+1, -19
 1@@ -138,24 +138,6 @@ copywords(struct StrList *out, const struct StrList *in, struct EvalCtx *ctx)
 2 	}
 3 }
 4 
 5-static void
 6-copyrecipes(struct RecipeList *out, const struct RecipeList *in)
 7-{
 8-	size_t i;
 9-
10-	memset(out, 0, sizeof(*out));
11-	for (i = 0; i < in->n; i++) {
12-		out->v = xrealloc(out->v, (out->n + 1) * sizeof(out->v[0]));
13-		out->v[out->n].body = xstrdup(in->v[i].body);
14-		out->v[out->n].silent = in->v[i].silent;
15-		out->v[out->n].ignore = in->v[i].ignore;
16-		out->v[out->n].recursive = in->v[i].recursive;
17-		out->v[out->n].submake = in->v[i].submake;
18-		copysubmake(&out->v[out->n].sm, &in->v[i].sm);
19-		out->n++;
20-	}
21-}
22-
23 static void
24 addrulesetassign(struct AssignNode **vec, size_t *n, const struct Node *src, struct EvalCtx *ctx)
25 {
26@@ -187,7 +169,7 @@ addrulesetrule(struct RuleSet *out, const struct RuleNode *src, struct EvalCtx *
27 	copywords(&dst->targets, &src->targets, ctx);
28 	copywords(&dst->prereqs, &src->prereqs, ctx);
29 	copywords(&dst->order_only, &src->order_only, ctx);
30-	copyrecipes(&dst->recipes, &src->recipes);
31+	addrecipes(&dst->recipes, &src->recipes);
32 }
33 
34 static int
R src/graph.c => src/graph/graph.c
+8, -78
  1@@ -28,32 +28,6 @@ struct GraphState {
  2 	int saw_suffixes;
  3 };
  4 
  5-static int
  6-isonesuffix(const char *s, char **from)
  7-{
  8-	if (!s || s[0] != '.')
  9-		return 0;
 10-	if (!s[1] || strchr(s + 1, '.'))
 11-		return 0;
 12-	*from = xstrdup(s);
 13-	return 1;
 14-}
 15-
 16-static int
 17-istwosuffix(const char *s, char **from, char **to)
 18-{
 19-	const char *mid;
 20-
 21-	if (!s || s[0] != '.')
 22-		return 0;
 23-	mid = strchr(s + 1, '.');
 24-	if (!mid || mid == s + 1 || !mid[1] || strchr(mid + 1, '.'))
 25-		return 0;
 26-	*from = xstrndup(s, (size_t)(mid - s));
 27-	*to = xstrdup(mid);
 28-	return 1;
 29-}
 30-
 31 static void
 32 seedsufs(struct SufRules *rules, const struct RuleNode *rule)
 33 {
 34@@ -61,7 +35,7 @@ seedsufs(struct SufRules *rules, const struct RuleNode *rule)
 35 
 36 	if (rule->targets.n != 1 || rule->prereqs.n != 0 || rule->order_only.n != 0)
 37 		return;
 38-	if (isonesuffix(rule->targets.v[0], &from)) {
 39+	if (issinglesuf(rule->targets.v[0], &from)) {
 40 		struct StrList list;
 41 
 42 		memset(&list, 0, sizeof(list));
 43@@ -71,7 +45,7 @@ seedsufs(struct SufRules *rules, const struct RuleNode *rule)
 44 		free(from);
 45 		return;
 46 	}
 47-	if (!istwosuffix(rule->targets.v[0], &from, &to))
 48+	if (!issuf(rule->targets.v[0], &from, &to))
 49 		return;
 50 	{
 51 		char *tmp[2];
 52@@ -120,37 +94,6 @@ addexprecipes(struct RecipeList *dest, const struct RecipeList *src, struct Eval
 53 	}
 54 }
 55 
 56-static int
 57-hasword(const struct StrList *list, const char *word)
 58-{
 59-	size_t i;
 60-
 61-	for (i = 0; i < list->n; i++) {
 62-		if (strcmp(list->v[i], word) == 0)
 63-			return 1;
 64-	}
 65-	return 0;
 66-}
 67-
 68-static void
 69-marksubmake(struct Graph *graph, const char *tname)
 70-{
 71-	const char *name = "SubMake";
 72-	struct Target *t;
 73-
 74-	if (!findtarget(graph, name)) {
 75-		graph->v = xrealloc(graph->v, (graph->n + 1) * sizeof(graph->v[0]));
 76-		memset(&graph->v[graph->n], 0, sizeof(graph->v[graph->n]));
 77-		graph->v[graph->n].name = xstrdup(name);
 78-		graph->n++;
 79-	}
 80-	t = tname ? findtarget(graph, tname) : 0;
 81-	if (t && !hasword(&t->order_only, name)) {
 82-		t->order_only.v = xrealloc(t->order_only.v, (t->order_only.n + 1) * sizeof(t->order_only.v[0]));
 83-		t->order_only.v[t->order_only.n++] = xstrdup(name);
 84-	}
 85-}
 86-
 87 /* we apply target assignments from the graphstate (gs.tas) to those specific
 88  * targets, for some target like
 89  *
 90@@ -339,25 +282,6 @@ buildgraph(const struct RuleSet *ruleset, struct Graph *graph)
 91 		start = current_n;
 92 	}
 93 
 94-	for (i = 0; i < graph->n; i++) {
 95-		struct Target *t = &graph->v[i];
 96-		size_t k;
 97-
 98-		for (k = 0; k < t->recipes.n; k++) {
 99-			char *tname;
100-
101-			if (!t->recipes.v[k].submake)
102-				continue;
103-			tname = xstrdup(t->name);
104-			marksubmake(graph, tname);
105-			free(tname);
106-			fprintf(stderr, "recursive make detected! i dont know how to handle that yet!\n");
107-			ctx.errors++;
108-			i = graph->n;
109-			break;
110-		}
111-	}
112-
113 	for (i = 0; i < gs.ntas; i++) {
114 		freestrs(&gs.tas[i].targets);
115 		free(gs.tas[i].lhs);
116@@ -380,11 +304,17 @@ freegraph(struct Graph *graph)
117 		return;
118 	for (i = 0; i < graph->n; i++) {
119 		free(graph->v[i].name);
120+		free(graph->v[i].owner);
121 		freestrs(&graph->v[i].prereqs);
122 		freestrs(&graph->v[i].order_only);
123 		freerecipes(&graph->v[i].recipes);
124 	}
125+	for (i = 0; i < graph->nsubs; i++)
126+		freesubgraph(&graph->subs[i]);
127 	free(graph->v);
128+	free(graph->subs);
129 	graph->v = 0;
130 	graph->n = 0;
131+	graph->subs = 0;
132+	graph->nsubs = 0;
133 }
+584, -0
  1@@ -0,0 +1,584 @@
  2+#include "internal.h"
  3+
  4+#include <stdio.h>
  5+#include <stdlib.h>
  6+#include <string.h>
  7+#include <unistd.h>
  8+
  9+struct SubGraphStack {
 10+	char **v;
 11+	size_t n;
 12+};
 13+
 14+static int
 15+isempty(const char *s)
 16+{
 17+	return !s || !s[0];
 18+}
 19+
 20+static int
 21+sameprefix(const char *a, const char *b)
 22+{
 23+	if (isempty(a))
 24+		return isempty(b);
 25+	if (isempty(b))
 26+		return 0;
 27+	return strcmp(a, b) == 0;
 28+}
 29+
 30+static int
 31+samelist(const struct StrList *a, const struct StrList *b)
 32+{
 33+	size_t i;
 34+
 35+	if (a->n != b->n)
 36+		return 0;
 37+	for (i = 0; i < a->n; i++) {
 38+		if (strcmp(a->v[i], b->v[i]) != 0)
 39+			return 0;
 40+	}
 41+	return 1;
 42+}
 43+
 44+static char *
 45+joinprefix(const char *parent, const char *child)
 46+{
 47+	char *raw, *norm;
 48+
 49+	if (isempty(child))
 50+		return parent ? xstrdup(parent) : 0;
 51+	if (child[0] == '/')
 52+		return normpath(child);
 53+	if (isempty(parent))
 54+		return normpath(child);
 55+	raw = cat3(parent, "/", child);
 56+	norm = normpath(raw);
 57+	free(raw);
 58+	return norm;
 59+}
 60+
 61+static char *
 62+rebaseword(const char *prefix, const char *name)
 63+{
 64+	char *raw, *norm;
 65+
 66+	if (!name[0] || name[0] == '/')
 67+		return normpath(name);
 68+	if (isempty(prefix))
 69+		return normpath(name);
 70+	raw = cat3(prefix, "/", name);
 71+	norm = normpath(raw);
 72+	free(raw);
 73+	return norm;
 74+}
 75+
 76+static char *
 77+prefixrecipe(const char *prefix, const char *body)
 78+{
 79+	size_t nprefix, nbody;
 80+	char *out;
 81+
 82+	if (isempty(prefix) || strcmp(prefix, ".") == 0)
 83+		return xstrdup(body);
 84+	nprefix = strlen(prefix);
 85+	nbody = strlen(body);
 86+	out = xmalloc(5 + nprefix + 4 + nbody + 1);
 87+	memcpy(out, "cd ", 3);
 88+	memcpy(out + 3, prefix, nprefix);
 89+	memcpy(out + 3 + nprefix, " && ", 4);
 90+	memcpy(out + 7 + nprefix, body, nbody);
 91+	out[7 + nprefix + nbody] = 0;
 92+	return out;
 93+}
 94+
 95+static int
 96+resolvegoals(const struct SubGraph *sg, struct StrList *out)
 97+{
 98+	const struct Target *t;
 99+
100+	memset(out, 0, sizeof(*out));
101+	if (sg->goals.n) {
102+		size_t i;
103+
104+		for (i = 0; i < sg->goals.n; i++) {
105+			char *name;
106+
107+			name = rebaseword(sg->prefix, sg->goals.v[i]);
108+			addstr(out, name);
109+			free(name);
110+		}
111+		return 0;
112+	}
113+	t = defaulttarget(&sg->graph, sg->prefix);
114+	if (t) {
115+		addstr(out, t->name);
116+		return 0;
117+	}
118+	fprintf(stderr, "submake has no default target\n");
119+	return -1;
120+}
121+
122+static void
123+pushstack(struct SubGraphStack *stack, const char *key)
124+{
125+	stack->v = xrealloc(stack->v, (stack->n + 1) * sizeof(stack->v[0]));
126+	stack->v[stack->n++] = xstrdup(key);
127+}
128+
129+static void
130+popstack(struct SubGraphStack *stack)
131+{
132+	if (!stack->n)
133+		return;
134+	free(stack->v[stack->n - 1]);
135+	stack->n--;
136+}
137+
138+static int
139+instack(const struct SubGraphStack *stack, const char *key)
140+{
141+	size_t i;
142+
143+	for (i = 0; i < stack->n; i++) {
144+		if (strcmp(stack->v[i], key) == 0)
145+			return 1;
146+	}
147+	return 0;
148+}
149+
150+static char *
151+subgraphkey(const struct SubGraph *sg)
152+{
153+	size_t i, n;
154+	char *key;
155+
156+	n = strlen(sg->cwd) + 1 + strlen(sg->makefile ? sg->makefile : "") + 1;
157+	for (i = 0; i < sg->assigns.n; i++)
158+		n += strlen(sg->assigns.v[i]) + 1;
159+	key = xmalloc(n + 1);
160+	key[0] = 0;
161+	strcat(key, sg->cwd);
162+	strcat(key, "|");
163+	if (sg->makefile)
164+		strcat(key, sg->makefile);
165+	for (i = 0; i < sg->assigns.n; i++) {
166+		strcat(key, "|");
167+		strcat(key, sg->assigns.v[i]);
168+	}
169+	return key;
170+}
171+
172+static void
173+removerecipe(struct RecipeList *list, size_t idx)
174+{
175+	size_t i;
176+
177+	free(list->v[idx].body);
178+	freesubmake(&list->v[idx].sm);
179+	for (i = idx + 1; i < list->n; i++)
180+		list->v[i - 1] = list->v[i];
181+	list->n--;
182+	if (!list->n) {
183+		free(list->v);
184+		list->v = 0;
185+		return;
186+	}
187+	list->v = xrealloc(list->v, list->n * sizeof(list->v[0]));
188+}
189+
190+static void
191+scopelist(struct StrList *list, const char *prefix)
192+{
193+	size_t i;
194+
195+	for (i = 0; i < list->n; i++) {
196+		char *name;
197+
198+		name = rebaseword(prefix, list->v[i]);
199+		free(list->v[i]);
200+		list->v[i] = name;
201+	}
202+}
203+
204+static void
205+scoperecipes(struct RecipeList *list, const char *prefix)
206+{
207+	size_t i;
208+
209+	for (i = 0; i < list->n; i++) {
210+		char *body;
211+
212+		body = prefixrecipe(prefix, list->v[i].body);
213+		free(list->v[i].body);
214+		list->v[i].body = body;
215+	}
216+}
217+
218+static void
219+scopegraph(struct Graph *graph, const char *prefix)
220+{
221+	size_t i;
222+
223+	if (isempty(prefix))
224+		return;
225+	for (i = 0; i < graph->n; i++) {
226+		char *name;
227+
228+		name = rebaseword(prefix, graph->v[i].name);
229+		free(graph->v[i].name);
230+		graph->v[i].name = name;
231+		free(graph->v[i].owner);
232+		if (graph->v[i].defined || graph->v[i].recipes.n > 0 ||
233+		    graph->v[i].prereqs.n > 0 || graph->v[i].order_only.n > 0)
234+			graph->v[i].owner = xstrdup(prefix);
235+		else
236+			graph->v[i].owner = 0;
237+		scopelist(&graph->v[i].prereqs, prefix);
238+		scopelist(&graph->v[i].order_only, prefix);
239+		scoperecipes(&graph->v[i].recipes, prefix);
240+	}
241+}
242+
243+static int
244+mergetarget(struct Graph *graph, const struct Target *src)
245+{
246+	struct Target *dst;
247+
248+	dst = findtarget(graph, src->name);
249+	if (!dst) {
250+		graph->v = xrealloc(graph->v, (graph->n + 1) * sizeof(graph->v[0]));
251+		dst = &graph->v[graph->n++];
252+		memset(dst, 0, sizeof(*dst));
253+		dst->name = xstrdup(src->name);
254+		if (src->owner)
255+			dst->owner = xstrdup(src->owner);
256+	} else if (!dst->owner && src->owner) {
257+		dst->owner = xstrdup(src->owner);
258+	} else if (dst->owner && src->owner && !sameprefix(dst->owner, src->owner)) {
259+		fprintf(stderr, "target `%s' crosses subgraph ownership (%s vs %s)\n",
260+		        src->name,
261+		        dst->owner ? dst->owner : ".",
262+		        src->owner ? src->owner : ".");
263+		return -1;
264+	} else if (dst->defined && src->defined && dst->dcolon != src->dcolon) {
265+		fprintf(stderr, "target file `%s' has both : and :: entries, i can't handle that!\n", dst->name);
266+		return -1;
267+	}
268+	if (!dst->defined && src->defined)
269+		dst->dcolon = src->dcolon;
270+	dst->defined |= src->defined;
271+	if (src->defined)
272+		dst->dcolon = src->dcolon;
273+	addwords(&dst->prereqs, &src->prereqs);
274+	addwords(&dst->order_only, &src->order_only);
275+	addrecipes(&dst->recipes, &src->recipes);
276+	return 0;
277+}
278+
279+static int
280+addgraphsub(struct Graph *graph, struct SubGraph *child)
281+{
282+	graph->subs = xrealloc(graph->subs, (graph->nsubs + 1) * sizeof(graph->subs[0]));
283+	graph->subs[graph->nsubs++] = *child;
284+	memset(child, 0, sizeof(*child));
285+	return 0;
286+}
287+
288+/* in make, recursive make calls run sequentially*/
289+static void
290+subgraphorder(struct Graph *graph, const char *owner, const struct StrList *prev)
291+{
292+	size_t i, j, n;
293+
294+	if (!prev->n)
295+		return;
296+	n = owner ? strlen(owner) : 0;
297+	for (i = 0; i < graph->n; i++) {
298+		struct Target *t = &graph->v[i];
299+
300+		if (!t->owner)
301+			continue;
302+		if (strncmp(t->owner, owner, n) != 0)
303+			continue;
304+		if (t->owner[n] != 0 && t->owner[n] != '/')
305+			continue;
306+		for (j = 0; j < prev->n; j++) {
307+			if (!hasword(&t->order_only, prev->v[j]))
308+				addstr(&t->order_only, prev->v[j]);
309+		}
310+	}
311+}
312+
313+static struct SubGraph *
314+findgraphsub(struct Graph *graph, const char *prefix)
315+{
316+	size_t i;
317+
318+	for (i = 0; i < graph->nsubs; i++) {
319+		if (sameprefix(graph->subs[i].prefix, prefix))
320+			return &graph->subs[i];
321+	}
322+	return 0;
323+}
324+
325+static int
326+sameinvocation(const struct SubGraph *a, const struct SubGraph *b)
327+{
328+	if (strcmp(a->cwd, b->cwd) != 0)
329+		return 0;
330+	if (!!a->makefile != !!b->makefile)
331+		return 0;
332+	if (a->makefile && strcmp(a->makefile, b->makefile) != 0)
333+		return 0;
334+	if (!samelist(&a->assigns, &b->assigns))
335+		return 0;
336+	if (!samelist(&a->flags, &b->flags))
337+		return 0;
338+	return 1;
339+}
340+
341+static void
342+mergesubmeta(struct SubGraph *dst, struct SubGraph *src)
343+{
344+	size_t i;
345+
346+	for (i = 0; i < src->parents.n; i++) {
347+		if (!hasword(&dst->parents, src->parents.v[i]))
348+			addstr(&dst->parents, src->parents.v[i]);
349+	}
350+	for (i = 0; i < src->goals.n; i++) {
351+		if (!hasword(&dst->goals, src->goals.v[i]))
352+			addstr(&dst->goals, src->goals.v[i]);
353+	}
354+	freesubgraph(src);
355+	memset(src, 0, sizeof(*src));
356+}
357+
358+static int
359+mergechildgraph(struct SubGraph *parent,
360+                const char *tname,
361+                struct SubGraph *child,
362+                const struct StrList *goals)
363+{
364+	struct SubGraph *known;
365+	struct Target *t;
366+	size_t i;
367+
368+	known = sameprefix(parent->prefix, child->prefix) ? 0 : findgraphsub(&parent->graph, child->prefix);
369+	if (known && !sameinvocation(known, child)) {
370+		fprintf(stderr, "multiple incompatible subgraphs map to %s/build.ninja\n",
371+		        child->prefix ? child->prefix : ".");
372+		return -1;
373+	}
374+	if (!known) {
375+		for (i = 0; i < child->graph.n; i++) {
376+			if (mergetarget(&parent->graph, &child->graph.v[i]) < 0)
377+				return -1;
378+		}
379+	}
380+	t = findtarget(&parent->graph, tname);
381+	if (!t) {
382+		fprintf(stderr, "lost parent target `%s' while expanding submake\n", tname);
383+		return -1;
384+	}
385+	for (i = 0; i < goals->n; i++) {
386+		if (!hasword(&t->prereqs, goals->v[i]))
387+			addstr(&t->prereqs, goals->v[i]);
388+	}
389+	if (!sameprefix(parent->prefix, child->prefix)) {
390+		if (known) {
391+			mergesubmeta(known, child);
392+		} else if (addgraphsub(&parent->graph, child) < 0) {
393+			return -1;
394+		}
395+	}
396+	return 0;
397+}
398+
399+static int buildsubgraph0(struct SubGraph *sg, struct SubGraphStack *stack);
400+
401+static int
402+expandsubgraphs(struct SubGraph *sg, struct SubGraphStack *stack)
403+{
404+	size_t i;
405+
406+	for (i = 0; i < sg->graph.n; i++) {
407+		size_t k;
408+		char *tname;
409+		struct StrList prevgoals;
410+
411+		memset(&prevgoals, 0, sizeof(prevgoals));
412+		tname = xstrdup(sg->graph.v[i].name);
413+		for (k = 0; k < sg->graph.v[i].recipes.n;) {
414+			struct Recipe *r;
415+			struct SubGraph child;
416+			struct StrList goals;
417+
418+			r = &sg->graph.v[i].recipes.v[k];
419+			if (!r->submake) {
420+				k++;
421+				continue;
422+			}
423+			memset(&child, 0, sizeof(child));
424+			child.cwd = r->sm.dir ? joinpath(sg->cwd, r->sm.dir) : xstrdup(sg->cwd);
425+			child.prefix = joinprefix(sg->prefix, r->sm.dir);
426+			addstr(&child.parents, tname);
427+			if (r->sm.makefile)
428+				child.makefile = xstrdup(r->sm.makefile);
429+			addwords(&child.assigns, &r->sm.assigns);
430+			addwords(&child.flags, &r->sm.flags);
431+			addwords(&child.goals, &r->sm.goals);
432+			/* remove the make invocation, we replace it with a graph*/
433+			removerecipe(&sg->graph.v[i].recipes, k);
434+			if (buildsubgraph0(&child, stack) < 0) {
435+				freesubgraph(&child);
436+				freestrs(&prevgoals);
437+				free(tname);
438+				return -1;
439+			}
440+			if (resolvegoals(&child, &goals) < 0) {
441+				freesubgraph(&child);
442+				freestrs(&prevgoals);
443+				free(tname);
444+				return -1;
445+			}
446+			subgraphorder(&child.graph, child.prefix, &prevgoals);
447+			if (mergechildgraph(sg, tname, &child, &goals) < 0) {
448+				freesubgraph(&child);
449+				freestrs(&goals);
450+				freestrs(&prevgoals);
451+				free(tname);
452+				return -1;
453+			}
454+			freestrs(&prevgoals);
455+			prevgoals = goals;
456+			if (!sameprefix(sg->prefix, child.prefix))
457+				continue;
458+			freesubgraph(&child);
459+		}
460+		freestrs(&prevgoals);
461+		free(tname);
462+	}
463+	return 0;
464+}
465+
466+static int
467+buildsubgraph0(struct SubGraph *sg, struct SubGraphStack *stack)
468+{
469+	struct Ast ast;
470+	struct RuleSet rs;
471+	char *src;
472+	char *path;
473+	char *oldcwd;
474+	char *key;
475+	int rc;
476+	size_t marked;
477+	size_t k;
478+
479+	memset(&ast, 0, sizeof(ast));
480+	memset(&rs, 0, sizeof(rs));
481+	freegraph(&sg->graph);
482+	memset(&sg->graph, 0, sizeof(sg->graph));
483+	src = 0;
484+	path = 0;
485+	oldcwd = getcwddup();
486+	if (chdir(sg->cwd) != 0) {
487+		fprintf(stderr, "failed to chdir to %s\n", sg->cwd);
488+		free(oldcwd);
489+		return -1;
490+	}
491+	free(sg->cwd);
492+	sg->cwd = getcwddup();
493+	if (loadmakefile(sg->makefile, &path, &src) < 0) {
494+		fprintf(stderr, "could not read submakefile in %s\n", sg->cwd);
495+		chdir(oldcwd);
496+		free(oldcwd);
497+		return -1;
498+	}
499+	free(sg->makefile);
500+	sg->makefile = xstrdup(path);
501+	key = subgraphkey(sg);
502+	if (instack(stack, key)) {
503+		fprintf(stderr, "recursive subgraph cycle detected: %s\n", key);
504+		free(key);
505+		free(path);
506+		free(src);
507+		chdir(oldcwd);
508+		free(oldcwd);
509+		return -1;
510+	}
511+	pushstack(stack, key);
512+	free(key);
513+	src = appendassigns(src, &sg->assigns);
514+	rc = parse(path, src, &ast);
515+	if (rc < 0) {
516+		fprintf(stderr, "parse error in %s/%s\n", sg->cwd, path);
517+		goto out;
518+	}
519+	marked = 0;
520+	k = ast.n;
521+	while (k > 0 && marked < sg->assigns.n) {
522+		k--;
523+		if (ast.v[k].kind == NODE_ASSIGN) {
524+			ast.v[k].data.assign.origin = ORIGIN_COMMAND;
525+			marked++;
526+		}
527+	}
528+	rc = eval(&ast, &rs);
529+	if (rc < 0) {
530+		fprintf(stderr, "eval error in %s/%s\n", sg->cwd, path);
531+		goto out;
532+	}
533+	rc = buildgraph(&rs, &sg->graph);
534+	if (rc < 0) {
535+		fprintf(stderr, "graph error in %s/%s\n", sg->cwd, path);
536+		goto out;
537+	}
538+	scopegraph(&sg->graph, sg->prefix);
539+	rc = expandsubgraphs(sg, stack);
540+out:
541+	freeruleset(&rs);
542+	freeast(&ast);
543+	free(src);
544+	free(path);
545+	popstack(stack);
546+	chdir(oldcwd);
547+	free(oldcwd);
548+	return rc;
549+}
550+
551+int
552+buildsubgraph(struct SubGraph *sg)
553+{
554+	struct SubGraphStack stack;
555+
556+	memset(&stack, 0, sizeof(stack));
557+	if (!sg->cwd)
558+		sg->cwd = getcwddup();
559+	if (buildsubgraph0(sg, &stack) < 0) {
560+		while (stack.n)
561+			popstack(&stack);
562+		free(stack.v);
563+		return -1;
564+	}
565+	free(stack.v);
566+	return 0;
567+}
568+
569+void
570+freesubgraph(struct SubGraph *sg)
571+{
572+	if (!sg)
573+		return;
574+	free(sg->cwd);
575+	sg->cwd = 0;
576+	free(sg->prefix);
577+	sg->prefix = 0;
578+	free(sg->makefile);
579+	sg->makefile = 0;
580+	freestrs(&sg->parents);
581+	freestrs(&sg->assigns);
582+	freestrs(&sg->flags);
583+	freestrs(&sg->goals);
584+	freegraph(&sg->graph);
585+}
+11, -0
 1@@ -32,6 +32,17 @@ void *xmalloc(size_t n);
 2 void *xrealloc(void *p, size_t n);
 3 char *xstrndup(const char *s, size_t n);
 4 char *xstrdup(const char *s);
 5+char *readfile(const char *path);
 6+int loadmakefile(const char *path, char **path_out, char **src_out);
 7+char *appendassigns(char *src, const struct StrList *assigns);
 8+char *getcwddup(void);
 9+char *joinpath(const char *dir, const char *name);
10+char *normpath(const char *path);
11+char *joinstrs(const struct StrList *list, const char *sep);
12+void addstr(struct StrList *list, const char *s);
13+int hasword(const struct StrList *list, const char *word);
14+int targetownedby(const struct Target *t, const char *owner);
15+const struct Target *defaulttarget(const struct Graph *graph, const char *owner);
16 void addnode(struct NodeList *list, struct Node node);
17 void splitwords(struct StrList *out, const char *s, size_t n);
18 char *cat3(const char *a, const char *b, const char *c);
+8, -58
 1@@ -300,40 +300,6 @@ readline(const char **src, int *lineno, struct PreLine *line)
 2 	return 1;
 3 }
 4 
 5-static char *
 6-readfile(const char *path)
 7-{
 8-	FILE *fp;
 9-	long n;
10-	char *buf;
11-
12-	fp = fopen(path, "rb");
13-	if (!fp)
14-		return 0;
15-	if (fseek(fp, 0, SEEK_END) < 0) {
16-		fclose(fp);
17-		return 0;
18-	}
19-	n = ftell(fp);
20-	if (n < 0) {
21-		fclose(fp);
22-		return 0;
23-	}
24-	if (fseek(fp, 0, SEEK_SET) < 0) {
25-		fclose(fp);
26-		return 0;
27-	}
28-	buf = xmalloc((size_t)n + 1);
29-	if (fread(buf, 1, (size_t)n, fp) != (size_t)n) {
30-		fclose(fp);
31-		free(buf);
32-		return 0;
33-	}
34-	buf[n] = 0;
35-	fclose(fp);
36-	return buf;
37-}
38-
39 static void
40 popinc(struct Inc *inc)
41 {
42@@ -380,22 +346,6 @@ dirpart(const char *path)
43 	return xstrndup(path, (size_t)(slash - path));
44 }
45 
46-static char *
47-joinpath(const char *dir, const char *name)
48-{
49-	size_t ndir, nname;
50-	char *out;
51-
52-	ndir = strlen(dir);
53-	nname = strlen(name);
54-	out = xmalloc(ndir + 1 + nname + 1);
55-	memcpy(out, dir, ndir);
56-	out[ndir] = '/';
57-	memcpy(out + ndir + 1, name, nname);
58-	out[ndir + 1 + nname] = 0;
59-	return out;
60-}
61-
62 static int
63 preprocfile(const char *path, const char *src_override, struct Pre *pre, struct Inc *inc)
64 {
65@@ -433,14 +383,14 @@ preprocfile(const char *path, const char *src_override, struct Pre *pre, struct
66 				int rc;
67 				size_t kwlen;
68 
69-				opt = haskw(trim, "-include") || haskw(trim, "sinclude");
70-				kwlen = haskw(trim, "-include") || haskw(trim, "sinclude") ? 8 : 7;
71-				incarg = trimdup(trim + kwlen, strlen(trim + kwlen));
72-				if (isplainpath(incarg)) {
73-					full = incarg[0] == '/' ? xstrdup(incarg) : joinpath(dir, incarg);
74-					free(line.path);
75-					free(line.text);
76-					free(trim);
77+					opt = haskw(trim, "-include") || haskw(trim, "sinclude");
78+					kwlen = haskw(trim, "-include") || haskw(trim, "sinclude") ? 8 : 7;
79+					incarg = trimdup(trim + kwlen, strlen(trim + kwlen));
80+					if (isplainpath(incarg)) {
81+						full = joinpath(dir, incarg);
82+						free(line.path);
83+						free(line.text);
84+						free(trim);
85 					free(incarg);
86 					rc = preprocfile(full, 0, pre, inc);
87 					free(full);
+3, -17
 1@@ -99,7 +99,7 @@ pathorargetexists(const struct Graph *graph, const char *name)
 2 	return access(name, F_OK) == 0;
 3 }
 4 
 5-static int
 6+int
 7 issuf(const char *s, char **from, char **to)
 8 {
 9 	const char *mid;
10@@ -114,7 +114,7 @@ issuf(const char *s, char **from, char **to)
11 	return 1;
12 }
13 
14-static int
15+int
16 issinglesuf(const char *s, char **from)
17 {
18 	if (!s || s[0] != '.')
19@@ -138,20 +138,6 @@ matchsuf(const char *suf, const char *name, char **stem)
20 	return 1;
21 }
22 
23-static char *
24-joinprereqs(const struct Target *t)
25-{
26-	size_t i;
27-	char *joined, *next;
28-
29-	joined = xstrdup("");
30-	for (i = 0; i < t->prereqs.n; i++) {
31-		next = joined[0] ? cat3(joined, " ", t->prereqs.v[i]) : xstrdup(t->prereqs.v[i]);
32-		free(joined);
33-		joined = next;
34-	}
35-	return joined;
36-}
37 
38 /*
39  * expand automatic variables like so:
40@@ -200,7 +186,7 @@ expandauto(const char *s, const struct Target *t, const char *stem)
41 			} else if (s[i + 1] == '<') {
42 				val = t->prereqs.n > 0 ? t->prereqs.v[0] : "";
43 			} else if (s[i + 1] == '^' || s[i + 1] == '+' || s[i + 1] == '?') {
44-				val = joinprereqs(t);
45+				val = joinstrs(&t->prereqs, " ");
46 				mustfree = 1;
47 			} else if (s[i + 1] == '*') {
48 				val = stem ? stem : "";
+2, -0
1@@ -33,6 +33,8 @@ struct SufRules {
2 	struct SuffixList active;
3 };
4 
5+int issinglesuf(const char *s, char **from);
6+int issuf(const char *s, char **from, char **to);
7 void seedenv(struct Env *env);
8 void imprules(struct SufRules *rules);
9 int collectsufrule(struct SufRules *rules, const struct RuleNode *rule);
+18, -0
 1@@ -161,6 +161,7 @@ struct RuleSet {
 2 
 3 struct Target {
 4 	char *name;
 5+	char *owner;
 6 	struct StrList prereqs;
 7 	struct StrList order_only;
 8 	struct RecipeList recipes;
 9@@ -168,9 +169,24 @@ struct Target {
10 	int defined;
11 };
12 
13+struct SubGraph;
14+
15 struct Graph {
16 	struct Target *v;
17 	size_t n;
18+	struct SubGraph *subs;
19+	size_t nsubs;
20+};
21+
22+struct SubGraph {
23+	char *cwd;
24+	char *prefix;
25+	char *makefile;
26+	struct StrList parents;
27+	struct StrList assigns;
28+	struct StrList flags;
29+	struct StrList goals;
30+	struct Graph graph;
31 };
32 
33 int preproc(const char *path, struct Pre *pre);
34@@ -182,5 +198,7 @@ void freeast(struct Ast *ast);
35 void freeruleset(struct RuleSet *ruleset);
36 int buildgraph(const struct RuleSet *ruleset, struct Graph *graph);
37 void freegraph(struct Graph *graph);
38+int buildsubgraph(struct SubGraph *sg);
39+void freesubgraph(struct SubGraph *sg);
40 
41 #endif
+0, -7
 1@@ -65,13 +65,6 @@ flagsarg(const char *s)
 2 	return 0;
 3 }
 4 
 5-static void
 6-addstr(struct StrList *list, const char *s)
 7-{
 8-	list->v = xrealloc(list->v, (list->n + 1) * sizeof(list->v[0]));
 9-	list->v[list->n++] = xstrdup(s);
10-}
11-
12 static void
13 addtok(struct StrList *out, const char *s, size_t n)
14 {
+265, -0
  1@@ -4,6 +4,7 @@
  2 #include <stdlib.h>
  3 #include <string.h>
  4 #include <ctype.h>
  5+#include <unistd.h>
  6 
  7 /* shared utility functions */
  8 
  9@@ -50,6 +51,270 @@ xstrdup(const char *s)
 10 	return xstrndup(s, strlen(s));
 11 }
 12 
 13+char *
 14+readfile(const char *path)
 15+{
 16+	FILE *fp;
 17+	long n;
 18+	char *buf;
 19+
 20+	fp = fopen(path, "rb");
 21+	if (!fp)
 22+		return 0;
 23+	if (fseek(fp, 0, SEEK_END) < 0) {
 24+		fclose(fp);
 25+		return 0;
 26+	}
 27+	n = ftell(fp);
 28+	if (n < 0) {
 29+		fclose(fp);
 30+		return 0;
 31+	}
 32+	if (fseek(fp, 0, SEEK_SET) < 0) {
 33+		fclose(fp);
 34+		return 0;
 35+	}
 36+	buf = xmalloc((size_t)n + 1);
 37+	if (fread(buf, 1, (size_t)n, fp) != (size_t)n) {
 38+		fclose(fp);
 39+		free(buf);
 40+		return 0;
 41+	}
 42+	buf[n] = 0;
 43+	fclose(fp);
 44+	return buf;
 45+}
 46+
 47+int
 48+loadmakefile(const char *path, char **path_out, char **src_out)
 49+{
 50+	static const char *const defaults[] = {
 51+	    "GNUmakefile",
 52+	    "makefile",
 53+	    "Makefile",
 54+	    0,
 55+	};
 56+	size_t i;
 57+	char *src;
 58+	char *fullpath;
 59+
 60+	src = 0;
 61+	fullpath = 0;
 62+	if (path) {
 63+		fullpath = xstrdup(path);
 64+		src = readfile(fullpath);
 65+	} else {
 66+		for (i = 0; defaults[i]; i++) {
 67+			src = readfile(defaults[i]);
 68+			if (!src)
 69+				continue;
 70+			fullpath = xstrdup(defaults[i]);
 71+			break;
 72+		}
 73+	}
 74+	if (!src)
 75+		return -1;
 76+	*path_out = fullpath;
 77+	*src_out = src;
 78+	return 0;
 79+}
 80+
 81+char *
 82+appendassigns(char *src, const struct StrList *assigns)
 83+{
 84+	size_t i, len, extra;
 85+	char *out;
 86+
 87+	if (!assigns->n)
 88+		return src;
 89+	len = strlen(src);
 90+	extra = len > 0 && src[len - 1] != '\n' ? 1 : 0;
 91+	for (i = 0; i < assigns->n; i++)
 92+		extra += strlen(assigns->v[i]) + 1;
 93+	out = xmalloc(len + extra + 1);
 94+	memcpy(out, src, len);
 95+	extra = len;
 96+	if (extra > 0 && out[extra - 1] != '\n')
 97+		out[extra++] = '\n';
 98+	for (i = 0; i < assigns->n; i++) {
 99+		size_t n;
100+
101+		n = strlen(assigns->v[i]);
102+		memcpy(out + extra, assigns->v[i], n);
103+		extra += n;
104+		out[extra++] = '\n';
105+	}
106+	out[extra] = 0;
107+	free(src);
108+	return out;
109+}
110+
111+char *
112+getcwddup(void)
113+{
114+	size_t size;
115+	char *buf;
116+
117+	size = 128;
118+	for (;;) {
119+		buf = xmalloc(size);
120+		if (getcwd(buf, size))
121+			return buf;
122+		free(buf);
123+		size *= 2;
124+	}
125+}
126+
127+char *
128+joinpath(const char *dir, const char *name)
129+{
130+	size_t ndir, nname;
131+	char *out;
132+
133+	if (!name || !name[0])
134+		return xstrdup(dir);
135+	if (name[0] == '/')
136+		return xstrdup(name);
137+	ndir = strlen(dir);
138+	nname = strlen(name);
139+	out = xmalloc(ndir + 1 + nname + 1);
140+	memcpy(out, dir, ndir);
141+	out[ndir] = '/';
142+	memcpy(out + ndir + 1, name, nname);
143+	out[ndir + 1 + nname] = 0;
144+	return out;
145+}
146+
147+char *
148+normpath(const char *path)
149+{
150+	size_t i, n, parts_n, outlen;
151+	int absolute;
152+	char **parts;
153+	char *out;
154+
155+	absolute = path[0] == '/';
156+	n = strlen(path);
157+	parts = xmalloc((n + 1) * sizeof(parts[0]));
158+	parts_n = 0;
159+	for (i = 0; i < n;) {
160+		size_t start, len;
161+
162+		while (path[i] == '/')
163+			i++;
164+		start = i;
165+		while (path[i] && path[i] != '/')
166+			i++;
167+		len = i - start;
168+		if (!len)
169+			continue;
170+		if (len == 1 && path[start] == '.')
171+			continue;
172+		if (len == 2 && path[start] == '.' && path[start + 1] == '.') {
173+			if (parts_n > 0 && strcmp(parts[parts_n - 1], "..") != 0) {
174+				free(parts[--parts_n]);
175+				continue;
176+			}
177+			if (!absolute)
178+				parts[parts_n++] = xstrndup(path + start, len);
179+			continue;
180+		}
181+		parts[parts_n++] = xstrndup(path + start, len);
182+	}
183+	if (absolute && parts_n == 0) {
184+		free(parts);
185+		return xstrdup("/");
186+	}
187+	if (!absolute && parts_n == 0) {
188+		free(parts);
189+		return xstrdup(".");
190+	}
191+	outlen = absolute ? 1 : 0;
192+	for (i = 0; i < parts_n; i++)
193+		outlen += strlen(parts[i]) + 1;
194+	out = xmalloc(outlen + 1);
195+	n = 0;
196+	if (absolute)
197+		out[n++] = '/';
198+	for (i = 0; i < parts_n; i++) {
199+		size_t len;
200+
201+		if (n > 0 && out[n - 1] != '/')
202+			out[n++] = '/';
203+		len = strlen(parts[i]);
204+		memcpy(out + n, parts[i], len);
205+		n += len;
206+		free(parts[i]);
207+	}
208+	out[n] = 0;
209+	free(parts);
210+	return out;
211+}
212+
213+char *
214+joinstrs(const struct StrList *list, const char *sep)
215+{
216+	size_t i;
217+	char *s, *next;
218+
219+	s = xstrdup("");
220+	for (i = 0; i < list->n; i++) {
221+		next = s[0] ? cat3(s, sep, list->v[i]) : xstrdup(list->v[i]);
222+		free(s);
223+		s = next;
224+	}
225+	return s;
226+}
227+
228+void
229+addstr(struct StrList *list, const char *s)
230+{
231+	list->v = xrealloc(list->v, (list->n + 1) * sizeof(list->v[0]));
232+	list->v[list->n++] = xstrdup(s);
233+}
234+
235+int
236+hasword(const struct StrList *list, const char *word)
237+{
238+	size_t i;
239+
240+	for (i = 0; i < list->n; i++) {
241+		if (strcmp(list->v[i], word) == 0)
242+			return 1;
243+	}
244+	return 0;
245+}
246+
247+int
248+targetownedby(const struct Target *t, const char *owner)
249+{
250+	if (!t)
251+		return 0;
252+	if (!owner || !owner[0])
253+		return !t->owner || !t->owner[0];
254+	return t->owner && strcmp(t->owner, owner) == 0;
255+}
256+
257+const struct Target *
258+defaulttarget(const struct Graph *graph, const char *owner)
259+{
260+	const struct Target *all;
261+	size_t i;
262+
263+	all = findctarget(graph, "all");
264+	if (targetownedby(all, owner))
265+		return all;
266+	for (i = 0; i < graph->n; i++) {
267+		if (!targetownedby(&graph->v[i], owner))
268+			continue;
269+		if (strcmp(graph->v[i].name, "clean") == 0 || strcmp(graph->v[i].name, "fmt") == 0)
270+			continue;
271+		if (graph->v[i].recipes.n > 0 || graph->v[i].prereqs.n > 0 || graph->v[i].order_only.n > 0)
272+			return &graph->v[i];
273+	}
274+	return 0;
275+}
276+
277 char *
278 cat3(const char *a, const char *b, const char *c)
279 {