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		/* "export " + name + '=' + value + "; " + nul. */
107		need = len + 7 + nname + 1 + nquoted + 2 + 1;
108		if (need > cap) {
109			while (cap < need)
110				cap *= 2;
111			out = xrealloc(out, cap);
112		}
113		memcpy(out + len, "export ", 7);
114		len += 7;
115		memcpy(out + len, t->env.v[i].name, nname);
116		len += nname;
117		out[len++] = '=';
118		memcpy(out + len, quoted, nquoted);
119		len += nquoted;
120		out[len++] = ';';
121		out[len++] = ' ';
122		out[len] = 0;
123		free(quoted);
124	}
125	return out;
126}
127
128static char *
129normalizebody(const char *s)
130{
131	size_t i, n, j, cap;
132	char *out;
133
134	n = strlen(s);
135	cap = n * 2 + 1;
136	out = xmalloc(cap);
137	j = 0;
138	for (i = 0; i < n; i++) {
139		if (s[i] == '\\' && i + 1 < n && s[i + 1] == '\n') {
140			i++;
141		} else if (s[i] == '\n') {
142			while (j > 0 && out[j - 1] == ' ')
143				j--;
144			if (j > 0)
145				out[j++] = ';';
146			out[j++] = ' ';
147		} else {
148			out[j++] = s[i];
149		}
150	}
151	while (j > 0 && (out[j - 1] == ' ' || out[j - 1] == ';'))
152		j--;
153	out[j] = 0;
154	return out;
155}
156
157static char *
158joinrecipes(const struct Target *t)
159{
160	size_t i;
161	struct StrList bodies;
162	char *s;
163
164	memset(&bodies, 0, sizeof(bodies));
165	for (i = 0; i < t->recipes.n; i++) {
166		char *normalized, *body;
167
168		normalized = normalizebody(t->recipes.v[i].body);
169		if (t->recipes.n > 1) {
170			char *quoted = shellquote(normalized);
171			body = cat3("sh -c ", quoted, "");
172			free(quoted);
173			free(normalized);
174		} else {
175			body = normalized;
176		}
177		bodies.v = xrealloc(bodies.v, (bodies.n + 1) * sizeof(bodies.v[0]));
178		bodies.v[bodies.n++] = body;
179	}
180	s = joinstrs(&bodies, " && ");
181	for (i = 0; i < bodies.n; i++)
182		free(bodies.v[i]);
183	free(bodies.v);
184	return s;
185}
186
187static char *
188rulecmd(const struct Target *t)
189{
190	char *cmd, *env, *full, *escaped;
191
192	cmd = joinrecipes(t);
193	env = exportenv(t);
194	full = cat3(env, cmd, "");
195	escaped = escapeninja(full, 0);
196	free(full);
197	free(env);
198	free(cmd);
199	return escaped;
200}
201
202static int
203findrule(struct Rule *rules, size_t n, const char *cmd)
204{
205	size_t i;
206
207	for (i = 0; i < n; i++) {
208		if (strcmp(rules[i].cmd, cmd) == 0)
209			return rules[i].id;
210	}
211	return 0;
212}
213
214static const struct Target *
215wanteddefault(const struct Graph *graph, const char *prefix)
216{
217	const struct Target *t;
218	size_t i;
219
220	t = defaulttarget(graph, prefix);
221	if (t && t->wanted)
222		return t;
223	for (i = 0; i < graph->n; i++) {
224		if (!targetownedby(&graph->v[i], prefix))
225			continue;
226		if (graph->v[i].wanted)
227			return &graph->v[i];
228	}
229	return 0;
230}
231
232static int
233genninjafile(const struct Graph *graph, const char *path, const char *prefix, int root)
234{
235	FILE *fp;
236	size_t i, j;
237	int ruleid;
238	struct Rule *rules;
239	size_t nrules;
240	char *escaped;
241
242	for (i = 0; i < graph->nsubs; i++) {
243		if (!usesub(graph, i))
244			continue;
245		char *childpath;
246
247		childpath = joinpath(graph->subs[i].cwd, "build.ninja");
248		if (genninjafile(&graph->subs[i].graph, childpath, graph->subs[i].prefix, 0) < 0) {
249			free(childpath);
250			return -1;
251		}
252		free(childpath);
253	}
254
255	fp = fopen(path, "w");
256	if (!fp)
257		return -1;
258	fprintf(fp, "# this file was generated from a makefile by shinobi\n");
259	if (root)
260		fprintf(fp, "build __shin_always_build__: phony\n\n");
261	ruleid = 0;
262	rules = 0;
263	nrules = 0;
264	for (i = 0; i < graph->nsubs; i++) {
265		if (!usesub(graph, i))
266			continue;
267		fprintf(fp, "subninja %s/build.ninja\n", graph->subs[i].prefix);
268	}
269	for (i = 0; i < graph->nsubs; i++) {
270		if (usesub(graph, i)) {
271			fputc('\n', fp);
272			break;
273		}
274	}
275	for (i = 0; i < graph->n; i++) {
276		if (!targetownedby(&graph->v[i], prefix))
277			continue;
278		if (graph->v[i].recipes.n > 0) {
279			char *cmd;
280			int id;
281
282			cmd = rulecmd(&graph->v[i]);
283			id = findrule(rules, nrules, cmd);
284			if (!id) {
285				id = ++ruleid;
286				emitrule(fp, cmd, id);
287				rules = xrealloc(rules, (nrules + 1) * sizeof(rules[0]));
288				rules[nrules].cmd = cmd;
289				rules[nrules].id = id;
290				nrules++;
291			} else {
292				free(cmd);
293			}
294			escaped = escapeninja(graph->v[i].name, 1);
295			fputs("build ", fp);
296			fputs(escaped, fp);
297			fprintf(fp, ": r%d", id);
298			free(escaped);
299			if (graph->v[i].phony)
300				fprintf(fp, " __shin_always_build__");
301			for (j = 0; j < graph->v[i].impprereqs.n; j++) {
302				escaped = escapeninja(graph->v[i].impprereqs.v[j], 1);
303				fprintf(fp, " %s", escaped);
304				free(escaped);
305			}
306			for (j = 0; j < graph->v[i].prereqs.n; j++) {
307				escaped = escapeninja(graph->v[i].prereqs.v[j], 1);
308				fprintf(fp, " %s", escaped);
309				free(escaped);
310			}
311			if (graph->v[i].order_only.n) {
312				fprintf(fp, " ||");
313				for (j = 0; j < graph->v[i].order_only.n; j++) {
314					escaped = escapeninja(graph->v[i].order_only.v[j], 1);
315					fprintf(fp, " %s", escaped);
316					free(escaped);
317				}
318			}
319			fprintf(fp, "\n");
320			if (prefix && prefix[0])
321				fprintf(fp, "  description = [%s] build %s\n\n", prefix, graph->v[i].name);
322			else
323				fprintf(fp, "  description = build %s\n\n", graph->v[i].name);
324		} else if (graph->v[i].defined || graph->v[i].prereqs.n > 0 ||
325		           graph->v[i].impprereqs.n > 0 || graph->v[i].order_only.n > 0) {
326			escaped = escapeninja(graph->v[i].name, 1);
327			fputs("build ", fp);
328			fputs(escaped, fp);
329			fputs(": phony", fp);
330			free(escaped);
331			if (graph->v[i].phony)
332				fprintf(fp, " __shin_always_build__");
333			for (j = 0; j < graph->v[i].impprereqs.n; j++) {
334				escaped = escapeninja(graph->v[i].impprereqs.v[j], 1);
335				fprintf(fp, " %s", escaped);
336				free(escaped);
337			}
338			for (j = 0; j < graph->v[i].prereqs.n; j++) {
339				escaped = escapeninja(graph->v[i].prereqs.v[j], 1);
340				fprintf(fp, " %s", escaped);
341				free(escaped);
342			}
343			if (graph->v[i].order_only.n) {
344				fprintf(fp, " ||");
345				for (j = 0; j < graph->v[i].order_only.n; j++) {
346					escaped = escapeninja(graph->v[i].order_only.v[j], 1);
347					fprintf(fp, " %s", escaped);
348					free(escaped);
349				}
350			}
351			fprintf(fp, "\n\n");
352		}
353	}
354	if (root && wanteddefault(graph, prefix)) {
355		escaped = escapeninja(wanteddefault(graph, prefix)->name, 1);
356		fprintf(fp, "default %s\n", escaped);
357		free(escaped);
358	}
359
360	fclose(fp);
361	for (i = 0; i < nrules; i++)
362		free(rules[i].cmd);
363	free(rules);
364	return 0;
365}
366
367int
368genninja(const struct Graph *graph, const char *path)
369{
370	return genninjafile(graph, path, 0, 1);
371}