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}