main shrub/shinobi / backends / compdb.c
  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}