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}