1#include "compdb.h"
2#include "internal.h"
3
4#include <stdio.h>
5#include <stdlib.h>
6#include <string.h>
7#include <unistd.h>
8
9/* compile_commands.json backend */
10
11static void
12emitjson(FILE *fp, const char *s)
13{
14 size_t i;
15
16 for (i = 0; s[i]; i++) {
17 switch (s[i]) {
18 case '"':
19 case '\\':
20 fputc('\\', fp);
21 fputc(s[i], fp);
22 break;
23 case '\n':
24 fputs("\\n", fp);
25 break;
26 case '\r':
27 fputs("\\r", fp);
28 break;
29 case '\t':
30 fputs("\\t", fp);
31 break;
32 default:
33 fputc(s[i], fp);
34 break;
35 }
36 }
37}
38
39static int
40hassuffix(const char *s, const char *suffix)
41{
42 size_t ns, nfx;
43
44 ns = strlen(s);
45 nfx = strlen(suffix);
46 return ns >= nfx && strcmp(s + ns - nfx, suffix) == 0;
47}
48
49static int
50issource(const char *path)
51{
52 static const char *const exts[] = {
53 ".c", ".cc", ".C", ".cpp", ".cxx", ".c++", ".m", ".mm", 0};
54 size_t i;
55
56 for (i = 0; exts[i]; i++) {
57 if (hassuffix(path, exts[i]))
58 return 1;
59 }
60 return 0;
61}
62
63static int
64argboundary(char c)
65{
66 return c == 0 || c == ' ' || c == '\t' || c == '\n' || c == ';' || c == '&' || c == '|' ||
67 c == '(' || c == ')' || c == '\'' || c == '"';
68}
69
70static int
71hasarg(const char *command, const char *arg)
72{
73 size_t n;
74 const char *p;
75
76 n = strlen(arg);
77 for (p = command; (p = strstr(p, arg)) != 0; p++) {
78 if ((p == command || argboundary(p[-1])) && argboundary(p[n]))
79 return 1;
80 }
81 return 0;
82}
83
84static int
85findcompile(const struct Target *t, const char **source, const char **command)
86{
87 size_t i, j, nowner, nprereqs;
88
89 for (i = 0; i < t->recipes.n; i++) {
90 const char *body, *fallback;
91
92 body = t->recipes.v[i].body;
93 if (!hasarg(body, "-c"))
94 continue;
95 fallback = 0;
96 nowner = t->owner ? strlen(t->owner) : 0;
97 nprereqs = t->impprereqs.n + t->prereqs.n;
98 for (j = 0; j < nprereqs; j++) {
99 const char *p, *local;
100
101 p = j < t->impprereqs.n ? t->impprereqs.v[j] :
102 t->prereqs.v[j - t->impprereqs.n];
103 if (!issource(p))
104 continue;
105 if (!fallback)
106 fallback = p;
107 local = nowner && strncmp(p, t->owner, nowner) == 0 && p[nowner] == '/'
108 ? p + nowner + 1 : p;
109 if (hasarg(body, local)) {
110 fallback = p;
111 break;
112 }
113 }
114 if (fallback) {
115 *source = fallback;
116 *command = body;
117 return 1;
118 }
119 }
120 return 0;
121}
122
123static char *
124fullpath(const char *cwd, const char *path)
125{
126 char *joined, *normalized;
127
128 joined = joinpath(cwd, path);
129 normalized = normpath(joined);
130 free(joined);
131 return normalized;
132}
133
134static void
135emitgraph(FILE *fp, const struct Graph *graph, const char *prefix, const char *cwd, int *first)
136{
137 size_t i;
138
139 for (i = 0; i < graph->n; i++) {
140 const struct Target *t;
141 const char *command, *source;
142 char *directory, *file, *output;
143 size_t nowner;
144
145 t = &graph->v[i];
146 if (!targetownedby(t, prefix))
147 continue;
148 if (!findcompile(t, &source, &command))
149 continue;
150 directory = fullpath(cwd, t->owner ? t->owner : "");
151 file = fullpath(cwd, source);
152 output = fullpath(cwd, t->name);
153 nowner = t->owner ? strlen(t->owner) : 0;
154 if (nowner && strncmp(command, "cd ", 3) == 0 &&
155 strncmp(command + 3, t->owner, nowner) == 0 &&
156 strncmp(command + 3 + nowner, " && ", 4) == 0)
157 command += 3 + nowner + 4;
158 if (!*first)
159 fprintf(fp, ",\n");
160 *first = 0;
161 fprintf(fp, " {\n");
162 fprintf(fp, " \"directory\": \"");
163 emitjson(fp, directory);
164 fprintf(fp, "\",\n");
165 fprintf(fp, " \"command\": \"");
166 emitjson(fp, command);
167 fprintf(fp, "\",\n");
168 fprintf(fp, " \"file\": \"");
169 emitjson(fp, file);
170 fprintf(fp, "\",\n");
171 fprintf(fp, " \"output\": \"");
172 emitjson(fp, output);
173 fprintf(fp, "\"\n");
174 fprintf(fp, " }");
175 free(directory);
176 free(file);
177 free(output);
178 }
179 for (i = 0; i < graph->nsubs; i++) {
180 if (usesub(graph, i))
181 emitgraph(fp, &graph->subs[i].graph, graph->subs[i].prefix, cwd, first);
182 }
183}
184
185int
186gencompdb(const struct Graph *graph, const char *path)
187{
188 FILE *fp;
189 int first, failed;
190 char *cwd;
191
192 fp = fopen(path, "w");
193 if (!fp)
194 return -1;
195 cwd = getcwddup();
196 if (!cwd) {
197 fclose(fp);
198 return -1;
199 }
200
201 fprintf(fp, "[\n");
202 first = 1;
203 emitgraph(fp, graph, 0, cwd, &first);
204 fprintf(fp, "\n]\n");
205 failed = ferror(fp);
206 free(cwd);
207 if (fclose(fp) != 0)
208 failed = 1;
209 return failed ? -1 : 0;
210}