commit f1aa24f

shrub  ·  2026-04-16 17:30:29 +0000 UTC
parent dd4e574
add backend for compile_commands.json and -G flag to choose backend
4 files changed,  +228, -9
+2, -2
 1@@ -3,8 +3,8 @@ DESTDIR =
 2 BINDIR = $(DESTDIR)$(PREFIX)/bin
 3 
 4 BIN = shin
 5-SRCS = cli/main.c src/parse.c src/eval/eval.c src/eval/dollar.c src/graph.c src/posix.c src/gnu/pattern.c backends/ninja.c backends/graphviz.c src/util.c src/gnu/functions.c
 6-FMTSRCS = $(SRCS) src/shinobi.h src/internal.h src/posix.h src/gnu/pattern.h backends/ninja.h backends/graphviz.h
 7+SRCS = cli/main.c src/parse.c src/eval/eval.c src/eval/dollar.c src/graph.c src/posix.c src/gnu/pattern.c backends/ninja.c backends/graphviz.c backends/compcmd.c src/util.c src/gnu/functions.c
 8+FMTSRCS = $(SRCS) src/shinobi.h src/internal.h src/posix.h src/gnu/pattern.h backends/ninja.h backends/graphviz.h backends/compcmd.h
 9 OBJS = $(SRCS:.c=.o)
10 
11 CC = clang
+179, -0
  1@@ -0,0 +1,179 @@
  2+#include "compcmd.h"
  3+#include "internal.h"
  4+
  5+#include <stdio.h>
  6+#include <stdlib.h>
  7+#include <string.h>
  8+#include <unistd.h>
  9+
 10+/* compile_commands.json backend */
 11+
 12+static void
 13+emitjson(FILE *fp, const char *s)
 14+{
 15+	size_t i;
 16+
 17+	for (i = 0; s[i]; i++) {
 18+		switch (s[i]) {
 19+		case '"':
 20+		case '\\':
 21+			fputc('\\', fp);
 22+			fputc(s[i], fp);
 23+			break;
 24+		case '\n':
 25+			fputs("\\n", fp);
 26+			break;
 27+		case '\r':
 28+			fputs("\\r", fp);
 29+			break;
 30+		case '\t':
 31+			fputs("\\t", fp);
 32+			break;
 33+		default:
 34+			fputc(s[i], fp);
 35+			break;
 36+		}
 37+	}
 38+}
 39+
 40+static int
 41+hassuffix(const char *s, const char *suffix)
 42+{
 43+	size_t ns, nfx;
 44+
 45+	ns = strlen(s);
 46+	nfx = strlen(suffix);
 47+	return ns >= nfx && strcmp(s + ns - nfx, suffix) == 0;
 48+}
 49+
 50+static int
 51+issource(const char *path)
 52+{
 53+	static const char *const exts[] = {
 54+		".c", ".cc", ".cpp", ".cxx", ".c++", ".m", ".mm", 0
 55+	};
 56+	size_t i;
 57+
 58+	for (i = 0; exts[i]; i++) {
 59+		if (hassuffix(path, exts[i]))
 60+			return 1;
 61+	}
 62+	return 0;
 63+}
 64+
 65+static int
 66+iscompile(const struct Target *t)
 67+{
 68+	if (t->recipes.n != 1 || t->prereqs.n != 1)
 69+		return 0;
 70+	if (!issource(t->prereqs.v[0]))
 71+		return 0;
 72+	if (strstr(t->recipes.v[0].body, " -c ") || strstr(t->recipes.v[0].body, "\t-c ") ||
 73+	    strstr(t->recipes.v[0].body, " -c\t") || hassuffix(t->recipes.v[0].body, " -c"))
 74+		return 1;
 75+	return strstr(t->recipes.v[0].body, "$<") != 0;
 76+}
 77+
 78+static char *
 79+translateauto(const char *s, const struct Target *t)
 80+{
 81+	size_t i, n, cap, len;
 82+	char *out;
 83+
 84+	n = strlen(s);
 85+	cap = n + 1;
 86+	len = 0;
 87+	out = xmalloc(cap);
 88+	for (i = 0; i < n; i++) {
 89+		const char *val;
 90+		size_t vlen;
 91+
 92+		if (s[i] != '$' || i + 1 >= n) {
 93+			if (len + 2 > cap) {
 94+				cap *= 2;
 95+				out = xrealloc(out, cap);
 96+			}
 97+			out[len++] = s[i];
 98+			continue;
 99+		}
100+
101+		val = 0;
102+		if (s[i + 1] == '$') {
103+			val = "$";
104+		} else if (s[i + 1] == '@') {
105+			val = t->name;
106+		} else if (s[i + 1] == '<') {
107+			val = t->prereqs.n ? t->prereqs.v[0] : "";
108+		} else if (s[i + 1] == '^' || s[i + 1] == '+' || s[i + 1] == '?') {
109+			val = t->prereqs.n ? t->prereqs.v[0] : "";
110+		}
111+
112+		if (!val) {
113+			if (len + 2 > cap) {
114+				cap *= 2;
115+				out = xrealloc(out, cap);
116+			}
117+			out[len++] = s[i];
118+			continue;
119+		}
120+
121+		vlen = strlen(val);
122+		if (len + vlen + 1 > cap) {
123+			cap = len + vlen + (n - i) + 1;
124+			out = xrealloc(out, cap);
125+		}
126+		memcpy(out + len, val, vlen);
127+		len += vlen;
128+		i++;
129+	}
130+	out[len] = 0;
131+	return out;
132+}
133+
134+int
135+gencompcmd(const struct Graph *graph, const char *path)
136+{
137+	FILE *fp;
138+	size_t i;
139+	int first;
140+	char cwd[4096];
141+
142+	fp = fopen(path, "w");
143+	if (!fp)
144+		return -1;
145+	if (!getcwd(cwd, sizeof(cwd))) {
146+		fclose(fp);
147+		return -1;
148+	}
149+
150+	fprintf(fp, "[\n");
151+	first = 1;
152+	for (i = 0; i < graph->n; i++) {
153+		char *cmd;
154+
155+		if (!iscompile(&graph->v[i]))
156+			continue;
157+		cmd = translateauto(graph->v[i].recipes.v[0].body, &graph->v[i]);
158+		if (!first)
159+			fprintf(fp, ",\n");
160+		first = 0;
161+		fprintf(fp, "  {\n");
162+		fprintf(fp, "    \"directory\": \"");
163+		emitjson(fp, cwd);
164+		fprintf(fp, "\",\n");
165+		fprintf(fp, "    \"command\": \"");
166+		emitjson(fp, cmd);
167+		fprintf(fp, "\",\n");
168+		fprintf(fp, "    \"file\": \"");
169+		emitjson(fp, graph->v[i].prereqs.v[0]);
170+		fprintf(fp, "\",\n");
171+		fprintf(fp, "    \"output\": \"");
172+		emitjson(fp, graph->v[i].name);
173+		fprintf(fp, "\"\n");
174+		fprintf(fp, "  }");
175+		free(cmd);
176+	}
177+	fprintf(fp, "\n]\n");
178+	fclose(fp);
179+	return 0;
180+}
+8, -0
1@@ -0,0 +1,8 @@
2+#ifndef COMPCMD_H
3+#define COMPCMD_H
4+
5+#include "shinobi.h"
6+
7+int gencompcmd(const struct Graph *graph, const char *path);
8+
9+#endif
+39, -7
  1@@ -1,4 +1,5 @@
  2 #include "shinobi.h"
  3+#include "compcmd.h"
  4 #include "graphviz.h"
  5 #include "ninja.h"
  6 
  7@@ -11,7 +12,7 @@
  8 static void
  9 usage(FILE *fp, const char *argv0)
 10 {
 11-	fprintf(fp, "usage: %s [-agd] [-C dir] [-f file]\n", argv0);
 12+	fprintf(fp, "usage: %s [-ag] [-G ninja|dot|compcmd] [-C dir] [-f file]\n", argv0);
 13 }
 14 
 15 static char *
 16@@ -206,31 +207,52 @@ dumpgraph(const struct Graph *graph)
 17 int
 18 main(int argc, char **argv)
 19 {
 20+	enum Generator {
 21+		GEN_NINJA,
 22+		GEN_DOT,
 23+		GEN_COMPCMD,
 24+	};
 25 	const char *path;
 26 	char *src;
 27 	int dump_ast;
 28-	int dump_dot;
 29 	int dump_graph;
 30 	int i;
 31 	char **assigns;
 32 	size_t nassigns;
 33+	enum Generator gen;
 34 	struct Ast ast;
 35 	struct RuleSet rs;
 36 	struct Graph graph;
 37 
 38 	path = "Makefile";
 39 	dump_ast = 0;
 40-	dump_dot = 0;
 41 	dump_graph = 0;
 42+	gen = GEN_NINJA;
 43 	assigns = 0;
 44 	nassigns = 0;
 45 	for (i = 1; i < argc; i++) {
 46 		if (strcmp(argv[i], "-a") == 0) {
 47 			dump_ast = 1;
 48-		} else if (strcmp(argv[i], "-d") == 0) {
 49-			dump_dot = 1;
 50 		} else if (strcmp(argv[i], "-g") == 0) {
 51 			dump_graph = 1;
 52+		} else if (strcmp(argv[i], "-G") == 0) {
 53+			if (i + 1 >= argc) {
 54+				fprintf(stderr, "specify a generator\n\n");
 55+				usage(stderr, argv[0]);
 56+				return 1;
 57+			}
 58+			++i;
 59+			if (strcmp(argv[i], "ninja") == 0) {
 60+				gen = GEN_NINJA;
 61+			} else if (strcmp(argv[i], "dot") == 0) {
 62+				gen = GEN_DOT;
 63+			} else if (strcmp(argv[i], "compcmd") == 0) {
 64+				gen = GEN_COMPCMD;
 65+			} else {
 66+				fprintf(stderr, "unknown generator: %s\n\n", argv[i]);
 67+				usage(stderr, argv[0]);
 68+				return 1;
 69+			}
 70 		} else if (strcmp(argv[i], "-h") == 0 || strcmp(argv[i], "--help") == 0) {
 71 			usage(stdout, argv[0]);
 72 			return 0;
 73@@ -322,7 +344,7 @@ main(int argc, char **argv)
 74 	}
 75 	if (dump_graph)
 76 		dumpgraph(&graph);
 77-	if (dump_dot) {
 78+	if (gen == GEN_DOT) {
 79 		if (gengraphviz(&graph, "build.dot") < 0) {
 80 			fprintf(stderr, "graphviz generation error\n");
 81 			freegraph(&graph);
 82@@ -332,7 +354,17 @@ main(int argc, char **argv)
 83 			return 1;
 84 		}
 85 	}
 86-	if (genninja(&graph, "build.ninja") < 0) {
 87+	if (gen == GEN_COMPCMD) {
 88+		if (gencompcmd(&graph, "compile_commands.json") < 0) {
 89+			fprintf(stderr, "compile_commands generation error\n");
 90+			freegraph(&graph);
 91+			freeruleset(&rs);
 92+			freeast(&ast);
 93+			free(src);
 94+			return 1;
 95+		}
 96+	}
 97+	if (gen == GEN_NINJA && genninja(&graph, "build.ninja") < 0) {
 98 		fprintf(stderr, "ninja generation error\n");
 99 		freegraph(&graph);
100 		freeruleset(&rs);