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}