main shrub/shinobi / backends / ninja.c
  1#include "ninja.h"
  2#include "internal.h"
  3
  4#include <stdio.h>
  5#include <stdlib.h>
  6#include <string.h>
  7
  8/* check so we dont export special vars */
  9static int
 10isvarname(const char *s)
 11{
 12	size_t i;
 13
 14	if (!s || !s[0])
 15		return 0;
 16	for (i = 0; s[i]; i++) {
 17		if (!(('a' <= s[i] && s[i] <= 'z') ||
 18		      ('A' <= s[i] && s[i] <= 'Z') ||
 19		      ('0' <= s[i] && s[i] <= '9') ||
 20		      s[i] == '_'))
 21			return 0;
 22	}
 23	return 1;
 24}
 25
 26struct Rule {
 27	char *cmd;
 28	int id;
 29};
 30
 31static void
 32emitrule(FILE *fp, const char *cmd, int id)
 33{
 34	fprintf(fp, "rule r%d\n", id);
 35	fprintf(fp, "  command = %s\n", cmd);
 36	fprintf(fp, "  generator = 1\n\n");
 37}
 38
 39static char *
 40shellquote(const char *s)
 41{
 42	size_t i, len, cap;
 43	char *out;
 44
 45	cap = strlen(s) * 4 + 3;
 46	out = xmalloc(cap);
 47	len = 0;
 48	out[len++] = '\'';
 49	for (i = 0; s[i]; i++) {
 50		if (s[i] == '\'') {
 51			memcpy(out + len, "'\\''", 4);
 52			len += 4;
 53		} else {
 54			out[len++] = s[i];
 55		}
 56	}
 57	out[len++] = '\'';
 58	out[len] = 0;
 59	return out;
 60}
 61
 62static char *
 63escapeninja(const char *s, int path)
 64{
 65	size_t i, n, nescc, j;
 66	char *out;
 67
 68	n = strlen(s);
 69	nescc = 0;
 70	for (i = 0; i < n; i++) {
 71		if (s[i] == '$' || (path && (s[i] == ' ' || s[i] == ':')))
 72			nescc++;
 73	}
 74	out = xmalloc(n + nescc + 1);
 75	j = 0;
 76	for (i = 0; i < n; i++) {
 77		if (s[i] == '$' || (path && (s[i] == ' ' || s[i] == ':')))
 78			out[j++] = '$';
 79		out[j++] = s[i];
 80	}
 81	out[j] = 0;
 82	return out;
 83}
 84
 85static char *
 86exportenv(const struct Target *t)
 87{
 88	size_t i, len, cap;
 89	char *out;
 90
 91	cap = 16;
 92	len = 0;
 93	out = xmalloc(cap);
 94	out[0] = 0;
 95	for (i = 0; i < t->env.n; i++) {
 96		char *quoted;
 97		size_t need, nname, nquoted;
 98
 99		if (!t->env.v[i].exported)
100			continue;
101		if (!isvarname(t->env.v[i].name))
102			continue;
103		quoted = shellquote(t->env.v[i].val);
104		nname = strlen(t->env.v[i].name);
105		nquoted = strlen(quoted);
106		need = len + nname + nquoted + 10;
107		if (need > cap) {
108			while (cap < need)
109				cap *= 2;
110			out = xrealloc(out, cap);
111		}
112		memcpy(out + len, "export ", 7);
113		len += 7;
114		memcpy(out + len, t->env.v[i].name, nname);
115		len += nname;
116		out[len++] = '=';
117		memcpy(out + len, quoted, nquoted);
118		len += nquoted;
119		out[len++] = ';';
120		out[len++] = ' ';
121		out[len] = 0;
122		free(quoted);
123	}
124	return out;
125}
126
127static char *
128normalizebody(const char *s)
129{
130	size_t i, n, j, cap;
131	char *out;
132
133	n = strlen(s);
134	cap = n * 2 + 1;
135	out = xmalloc(cap);
136	j = 0;
137	for (i = 0; i < n; i++) {
138		if (s[i] == '\\' && i + 1 < n && s[i + 1] == '\n') {
139			i++;
140		} else if (s[i] == '\n') {
141			while (j > 0 && out[j - 1] == ' ')
142				j--;
143			if (j > 0)
144				out[j++] = ';';
145			out[j++] = ' ';
146		} else {
147			out[j++] = s[i];
148		}
149	}
150	while (j > 0 && (out[j - 1] == ' ' || out[j - 1] == ';'))
151		j--;
152	out[j] = 0;
153	return out;
154}
155
156static char *
157joinrecipes(const struct Target *t)
158{
159	size_t i;
160	struct StrList bodies;
161	char *s;
162
163	memset(&bodies, 0, sizeof(bodies));
164	for (i = 0; i < t->recipes.n; i++) {
165		char *normalized, *body;
166
167		normalized = normalizebody(t->recipes.v[i].body);
168		if (t->recipes.n > 1) {
169			char *quoted = shellquote(normalized);
170			body = cat3("sh -c ", quoted, "");
171			free(quoted);
172			free(normalized);
173		} else {
174			body = normalized;
175		}
176		bodies.v = xrealloc(bodies.v, (bodies.n + 1) * sizeof(bodies.v[0]));
177		bodies.v[bodies.n++] = body;
178	}
179	s = joinstrs(&bodies, " && ");
180	for (i = 0; i < bodies.n; i++)
181		free(bodies.v[i]);
182	free(bodies.v);
183	return s;
184}
185
186static char *
187rulecmd(const struct Target *t)
188{
189	char *cmd, *env, *full, *escaped;
190
191	cmd = joinrecipes(t);
192	env = exportenv(t);
193	full = cat3(env, cmd, "");
194	escaped = escapeninja(full, 0);
195	free(full);
196	free(env);
197	free(cmd);
198	return escaped;
199}
200
201static int
202findrule(struct Rule *rules, size_t n, const char *cmd)
203{
204	size_t i;
205
206	for (i = 0; i < n; i++) {
207		if (strcmp(rules[i].cmd, cmd) == 0)
208			return rules[i].id;
209	}
210	return 0;
211}
212
213static const struct Target *
214wanteddefault(const struct Graph *graph, const char *prefix)
215{
216	const struct Target *t;
217	size_t i;
218
219	t = defaulttarget(graph, prefix);
220	if (t && t->wanted)
221		return t;
222	for (i = 0; i < graph->n; i++) {
223		if (!targetownedby(&graph->v[i], prefix))
224			continue;
225		if (graph->v[i].wanted)
226			return &graph->v[i];
227	}
228	return 0;
229}
230
231static int
232emitgraphsub(const struct Graph *graph, size_t idx)
233{
234	size_t i;
235	int saw_conflict, saw_wanted;
236
237	saw_conflict = 0;
238	saw_wanted = 0;
239	for (i = 0; i < graph->nsubs; i++) {
240		if (i == idx)
241			continue;
242		if (strcmp(graph->subs[i].prefix, graph->subs[idx].prefix) != 0)
243			continue;
244		saw_conflict = 1;
245		if (graph->subs[i].wanted)
246			saw_wanted = 1;
247	}
248	if (!saw_conflict)
249		return 1;
250	if (graph->subs[idx].wanted) {
251		for (i = 0; i < idx; i++) {
252			if (strcmp(graph->subs[i].prefix, graph->subs[idx].prefix) == 0 &&
253			    graph->subs[i].wanted)
254				return 0;
255		}
256		return 1;
257	}
258	if (saw_wanted)
259		return 0;
260	for (i = 0; i < idx; i++) {
261		if (strcmp(graph->subs[i].prefix, graph->subs[idx].prefix) == 0)
262			return 0;
263	}
264	return 1;
265}
266
267static int
268genninjafile(const struct Graph *graph, const char *path, const char *prefix, int root)
269{
270	FILE *fp;
271	size_t i, j;
272	int ruleid;
273	struct Rule *rules;
274	size_t nrules;
275	char *escaped;
276
277	for (i = 0; i < graph->nsubs; i++) {
278		if (!emitgraphsub(graph, i))
279			continue;
280		char *childpath;
281
282		childpath = joinpath(graph->subs[i].cwd, "build.ninja");
283		if (genninjafile(&graph->subs[i].graph, childpath, graph->subs[i].prefix, 0) < 0) {
284			free(childpath);
285			return -1;
286		}
287		free(childpath);
288	}
289
290	fp = fopen(path, "w");
291	if (!fp)
292		return -1;
293	fprintf(fp, "# this file was generated from a makefile by shinobi\n");
294	if (root)
295		fprintf(fp, "build __shin_always_build__: phony\n\n");
296	ruleid = 0;
297	rules = 0;
298	nrules = 0;
299	for (i = 0; i < graph->nsubs; i++) {
300		if (!emitgraphsub(graph, i))
301			continue;
302		fprintf(fp, "subninja %s/build.ninja\n", graph->subs[i].prefix);
303	}
304	for (i = 0; i < graph->nsubs; i++) {
305		if (emitgraphsub(graph, i)) {
306			fputc('\n', fp);
307			break;
308		}
309	}
310	for (i = 0; i < graph->n; i++) {
311		if (!targetownedby(&graph->v[i], prefix))
312			continue;
313		if (graph->v[i].recipes.n > 0) {
314			char *cmd;
315			int id;
316
317			cmd = rulecmd(&graph->v[i]);
318			id = findrule(rules, nrules, cmd);
319			if (!id) {
320				id = ++ruleid;
321				emitrule(fp, cmd, id);
322				rules = xrealloc(rules, (nrules + 1) * sizeof(rules[0]));
323				rules[nrules].cmd = cmd;
324				rules[nrules].id = id;
325				nrules++;
326			} else {
327				free(cmd);
328			}
329			escaped = escapeninja(graph->v[i].name, 1);
330			fputs("build ", fp);
331			fputs(escaped, fp);
332			fprintf(fp, ": r%d", id);
333			free(escaped);
334			if (graph->v[i].phony)
335				fprintf(fp, " __shin_always_build__");
336			for (j = 0; j < graph->v[i].impprereqs.n; j++) {
337				escaped = escapeninja(graph->v[i].impprereqs.v[j], 1);
338				fprintf(fp, " %s", escaped);
339				free(escaped);
340			}
341			for (j = 0; j < graph->v[i].prereqs.n; j++) {
342				escaped = escapeninja(graph->v[i].prereqs.v[j], 1);
343				fprintf(fp, " %s", escaped);
344				free(escaped);
345			}
346			if (graph->v[i].order_only.n) {
347				fprintf(fp, " ||");
348				for (j = 0; j < graph->v[i].order_only.n; j++) {
349					escaped = escapeninja(graph->v[i].order_only.v[j], 1);
350					fprintf(fp, " %s", escaped);
351					free(escaped);
352				}
353			}
354			fprintf(fp, "\n");
355			if (prefix && prefix[0])
356				fprintf(fp, "  description = [%s] build %s\n\n", prefix, graph->v[i].name);
357			else
358				fprintf(fp, "  description = build %s\n\n", graph->v[i].name);
359		} else if (graph->v[i].defined || graph->v[i].prereqs.n > 0 ||
360		           graph->v[i].impprereqs.n > 0 || graph->v[i].order_only.n > 0) {
361			escaped = escapeninja(graph->v[i].name, 1);
362			fputs("build ", fp);
363			fputs(escaped, fp);
364			fputs(": phony", fp);
365			free(escaped);
366			if (graph->v[i].phony)
367				fprintf(fp, " __shin_always_build__");
368			for (j = 0; j < graph->v[i].impprereqs.n; j++) {
369				escaped = escapeninja(graph->v[i].impprereqs.v[j], 1);
370				fprintf(fp, " %s", escaped);
371				free(escaped);
372			}
373			for (j = 0; j < graph->v[i].prereqs.n; j++) {
374				escaped = escapeninja(graph->v[i].prereqs.v[j], 1);
375				fprintf(fp, " %s", escaped);
376				free(escaped);
377			}
378			if (graph->v[i].order_only.n) {
379				fprintf(fp, " ||");
380				for (j = 0; j < graph->v[i].order_only.n; j++) {
381					escaped = escapeninja(graph->v[i].order_only.v[j], 1);
382					fprintf(fp, " %s", escaped);
383					free(escaped);
384				}
385			}
386			fprintf(fp, "\n\n");
387		}
388	}
389	if (root && wanteddefault(graph, prefix)) {
390		escaped = escapeninja(wanteddefault(graph, prefix)->name, 1);
391		fprintf(fp, "default %s\n", escaped);
392		free(escaped);
393	}
394
395	fclose(fp);
396	for (i = 0; i < nrules; i++)
397		free(rules[i].cmd);
398	free(rules);
399	return 0;
400}
401
402int
403genninja(const struct Graph *graph, const char *path)
404{
405	return genninjafile(graph, path, 0, 1);
406}