main 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)
 64{
 65	size_t i, n, ndollar, j;
 66	char *out;
 67
 68	n = strlen(s);
 69	ndollar = 0;
 70	for (i = 0; i < n; i++) {
 71		if (s[i] == '$')
 72			ndollar++;
 73	}
 74	out = xmalloc(n + ndollar + 1);
 75	j = 0;
 76	for (i = 0; i < n; i++) {
 77		if (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;
131	char *out;
132
133	n = strlen(s);
134	out = xmalloc(n + 1);
135	j = 0;
136	for (i = 0; i < n; i++) {
137		if (s[i] == '\\' && i + 1 < n && s[i + 1] == '\n') {
138			i++;
139		} else if (s[i] == '\n') {
140			while (j > 0 && out[j - 1] == ' ')
141				j--;
142			if (j > 0)
143				out[j++] = ';';
144			out[j++] = ' ';
145		} else {
146			out[j++] = s[i];
147		}
148	}
149	while (j > 0 && (out[j - 1] == ' ' || out[j - 1] == ';'))
150		j--;
151	out[j] = 0;
152	return out;
153}
154
155static char *
156joinrecipes(const struct Target *t)
157{
158	size_t i;
159	struct StrList bodies;
160	char *s;
161
162	memset(&bodies, 0, sizeof(bodies));
163	for (i = 0; i < t->recipes.n; i++) {
164		char *normalized, *body;
165
166		normalized = normalizebody(t->recipes.v[i].body);
167		if (t->recipes.n > 1) {
168			char *quoted = shellquote(normalized);
169			body = cat3("sh -c ", quoted, "");
170			free(quoted);
171			free(normalized);
172		} else {
173			body = normalized;
174		}
175		bodies.v = xrealloc(bodies.v, (bodies.n + 1) * sizeof(bodies.v[0]));
176		bodies.v[bodies.n++] = body;
177	}
178	s = joinstrs(&bodies, " && ");
179	for (i = 0; i < bodies.n; i++)
180		free(bodies.v[i]);
181	free(bodies.v);
182	return s;
183}
184
185static char *
186rulecmd(const struct Target *t)
187{
188	char *cmd, *env, *full, *escaped;
189
190	cmd = joinrecipes(t);
191	env = exportenv(t);
192	full = cat3(env, cmd, "");
193	escaped = escapeninja(full);
194	free(full);
195	free(env);
196	free(cmd);
197	return escaped;
198}
199
200static int
201findrule(struct Rule *rules, size_t n, const char *cmd)
202{
203	size_t i;
204
205	for (i = 0; i < n; i++) {
206		if (strcmp(rules[i].cmd, cmd) == 0)
207			return rules[i].id;
208	}
209	return 0;
210}
211
212static int
213genninjafile(const struct Graph *graph, const char *path, const char *prefix, int root)
214{
215	FILE *fp;
216	size_t i, j;
217	int ruleid;
218	struct Rule *rules;
219	size_t nrules;
220
221	for (i = 0; i < graph->nsubs; i++) {
222		char *childpath;
223
224		childpath = joinpath(graph->subs[i].cwd, "build.ninja");
225		if (genninjafile(&graph->subs[i].graph, childpath, graph->subs[i].prefix, 0) < 0) {
226			free(childpath);
227			return -1;
228		}
229		free(childpath);
230	}
231
232	fp = fopen(path, "w");
233	if (!fp)
234		return -1;
235	fprintf(fp, "# this file was generated from a makefile by shinobi\n");
236	if (root)
237		fprintf(fp, "build __shin_always_build__: phony\n\n");
238	ruleid = 0;
239	rules = 0;
240	nrules = 0;
241	for (i = 0; i < graph->nsubs; i++)
242		fprintf(fp, "subninja %s/build.ninja\n", graph->subs[i].prefix);
243	if (graph->nsubs)
244		fputc('\n', fp);
245	for (i = 0; i < graph->n; i++) {
246		if (!targetownedby(&graph->v[i], prefix))
247			continue;
248		if (graph->v[i].recipes.n > 0) {
249			char *cmd;
250			int id;
251
252			cmd = rulecmd(&graph->v[i]);
253			id = findrule(rules, nrules, cmd);
254			if (!id) {
255				id = ++ruleid;
256				emitrule(fp, cmd, id);
257				rules = xrealloc(rules, (nrules + 1) * sizeof(rules[0]));
258				rules[nrules].cmd = cmd;
259				rules[nrules].id = id;
260				nrules++;
261			} else {
262				free(cmd);
263			}
264			fprintf(fp, "build %s: r%d", graph->v[i].name, id);
265			if (graph->v[i].phony)
266				fprintf(fp, " __shin_always_build__");
267			for (j = 0; j < graph->v[i].impprereqs.n; j++)
268				fprintf(fp, " %s", graph->v[i].impprereqs.v[j]);
269			for (j = 0; j < graph->v[i].prereqs.n; j++)
270				fprintf(fp, " %s", graph->v[i].prereqs.v[j]);
271			if (graph->v[i].order_only.n) {
272				fprintf(fp, " ||");
273				for (j = 0; j < graph->v[i].order_only.n; j++)
274					fprintf(fp, " %s", graph->v[i].order_only.v[j]);
275			}
276			fprintf(fp, "\n");
277			if (prefix && prefix[0])
278				fprintf(fp, "  description = [%s] build %s\n\n", prefix, graph->v[i].name);
279			else
280				fprintf(fp, "  description = build %s\n\n", graph->v[i].name);
281		} else if (graph->v[i].defined || graph->v[i].prereqs.n > 0 ||
282		           graph->v[i].impprereqs.n > 0 || graph->v[i].order_only.n > 0) {
283			fprintf(fp, "build %s: phony", graph->v[i].name);
284			if (graph->v[i].phony)
285				fprintf(fp, " __shin_always_build__");
286			for (j = 0; j < graph->v[i].impprereqs.n; j++)
287				fprintf(fp, " %s", graph->v[i].impprereqs.v[j]);
288			for (j = 0; j < graph->v[i].prereqs.n; j++)
289				fprintf(fp, " %s", graph->v[i].prereqs.v[j]);
290			if (graph->v[i].order_only.n) {
291				fprintf(fp, " ||");
292				for (j = 0; j < graph->v[i].order_only.n; j++)
293					fprintf(fp, " %s", graph->v[i].order_only.v[j]);
294			}
295			fprintf(fp, "\n\n");
296		}
297	}
298	if (root && defaulttarget(graph, prefix))
299		fprintf(fp, "default %s\n", defaulttarget(graph, prefix)->name);
300
301	fclose(fp);
302	for (i = 0; i < nrules; i++)
303		free(rules[i].cmd);
304	free(rules);
305	return 0;
306}
307
308int
309genninja(const struct Graph *graph, const char *path)
310{
311	return genninjafile(graph, path, 0, 1);
312}