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
M
Makefile
+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);