1#include "shinobi.h"
2#include "internal.h"
3#include "compdb.h"
4#include "graphviz.h"
5#include "ninja.h"
6
7#include <stdio.h>
8#include <stdlib.h>
9#include <string.h>
10
11#include <unistd.h>
12
13static void
14usage(FILE *fp, const char *argv0)
15{
16 fprintf(fp, "usage: %s [-age] [-G ninja|dot|compdb] [-M posix2024|posix2008|gnu] [-C dir] [-f file] [target ...]\n", argv0);
17}
18
19static int
20isvarassign(const char *s)
21{
22 const char *eq;
23
24 eq = strchr(s, '=');
25 if (!eq || eq == s)
26 return 0;
27 return 1;
28}
29
30static const char *
31assignopname(enum AssignOp op)
32{
33 switch (op) {
34 case ASSIGN_EQ:
35 return "=";
36 case ASSIGN_PLUS_EQ:
37 return "+=";
38 case ASSIGN_DCOLON_EQ:
39 return "::=";
40 case ASSIGN_COLON_EQ:
41 return ":=";
42 case ASSIGN_COLON3_EQ:
43 return ":::=";
44 case ASSIGN_QMARK_EQ:
45 return "?=";
46 case ASSIGN_BANG_EQ:
47 return "!=";
48 }
49 return "?";
50}
51
52static void
53printindent(int depth)
54{
55 int i;
56
57 for (i = 0; i < depth; i++)
58 fputs(" ", stdout);
59}
60
61static void
62dumpstrlist(const char *label, const struct StrList *list, int depth)
63{
64 size_t i;
65
66 printindent(depth);
67 printf("%s (%zu):", label, list->n);
68 for (i = 0; i < list->n; i++)
69 printf(" %s", list->v[i]);
70 putchar('\n');
71}
72
73static void
74dumpinlinewords(const char *label, const struct StrList *list)
75{
76 size_t i;
77
78 if (!list->n)
79 return;
80 printf(" %s=", label);
81 for (i = 0; i < list->n; i++) {
82 if (i)
83 putchar(',');
84 fputs(list->v[i], stdout);
85 }
86}
87
88static void
89dumprecipesubmake(const struct Recipe *r)
90{
91 if (!r->submake)
92 return;
93 printf(" [submake");
94 if (r->sm.makeprog)
95 printf(" make=%s", r->sm.makeprog);
96 if (r->sm.dir)
97 printf(" dir=%s", r->sm.dir);
98 if (r->sm.makefile)
99 printf(" makefile=%s", r->sm.makefile);
100 dumpinlinewords("assigns", &r->sm.assigns);
101 dumpinlinewords("flags", &r->sm.flags);
102 dumpinlinewords("goals", &r->sm.goals);
103 putchar(']');
104}
105
106static void
107dumprecipes(const struct RecipeList *list, int depth)
108{
109 size_t i;
110
111 printindent(depth);
112 printf("recipes (%zu):\n", list->n);
113 for (i = 0; i < list->n; i++) {
114 printindent(depth + 1);
115 if (list->v[i].silent)
116 putchar('@');
117 if (list->v[i].ignore)
118 putchar('-');
119 if (list->v[i].recursive)
120 putchar('+');
121 printf("%s", list->v[i].body);
122 dumprecipesubmake(&list->v[i]);
123 putchar('\n');
124 }
125}
126
127static void
128dumpassign(const struct AssignNode *assign, int depth)
129{
130 printindent(depth);
131 printf("assign%s %s %s %s\n",
132 assign->exported ? " [exported]" : "",
133 assign->lhs, assignopname(assign->op), assign->rhs);
134 if (assign->tspec)
135 dumpstrlist("targets", &assign->targets, depth + 1);
136}
137
138static void
139dumprule(const struct RuleNode *rule, int depth)
140{
141 printindent(depth);
142 printf("rule\n");
143 dumpstrlist("targets", &rule->targets, depth + 1);
144 if (rule->target_pattern) {
145 printindent(depth + 1);
146 printf("target-pattern: %s\n", rule->target_pattern);
147 }
148 dumpstrlist("prereqs", &rule->prereqs, depth + 1);
149 dumpstrlist("order-only", &rule->order_only, depth + 1);
150 dumprecipes(&rule->recipes, depth + 1);
151}
152
153static void
154dumpruleset(const struct RuleSet *ruleset)
155{
156 size_t i;
157
158 printf("ruleset (%zu vars, %zu target vars, %zu rules)\n",
159 ruleset->nvars, ruleset->ntvars, ruleset->nrules);
160 printindent(1);
161 printf("export_all=%d posix=%d\n", ruleset->export_all, ruleset->posix);
162 for (i = 0; i < ruleset->nvars; i++)
163 dumpassign(&ruleset->vars[i], 1);
164 for (i = 0; i < ruleset->ntvars; i++)
165 dumpassign(&ruleset->tvars[i], 1);
166 for (i = 0; i < ruleset->nrules; i++)
167 dumprule(&ruleset->rules[i], 1);
168}
169
170static void
171dumpgraph(const struct Graph *graph)
172{
173 size_t i;
174
175 printf("graph (%zu targets)\n", graph->n);
176 for (i = 0; i < graph->n; i++) {
177 const struct Target *t = &graph->v[i];
178
179 printindent(1);
180 printf("target %s", t->name);
181 if (t->owner)
182 printf(" [owner=%s]", t->owner);
183 putchar('\n');
184 dumpstrlist("prereqs", &t->prereqs, 2);
185 dumpstrlist("implicit-prereqs", &t->impprereqs, 2);
186 dumpstrlist("order-only", &t->order_only, 2);
187 dumprecipes(&t->recipes, 2);
188 }
189 if (graph->nsubs) {
190 printindent(1);
191 printf("subgraphs (%zu):\n", graph->nsubs);
192 for (i = 0; i < graph->nsubs; i++) {
193 size_t j;
194
195 printindent(2);
196 printf("%s", graph->subs[i].prefix ? graph->subs[i].prefix : ".");
197 if (graph->subs[i].parents.n) {
198 printf(" parents=");
199 for (j = 0; j < graph->subs[i].parents.n; j++) {
200 if (j)
201 putchar(',');
202 fputs(graph->subs[i].parents.v[j], stdout);
203 }
204 }
205 if (graph->subs[i].goals.n) {
206 printf(" goals=");
207 for (j = 0; j < graph->subs[i].goals.n; j++) {
208 if (j)
209 putchar(',');
210 fputs(graph->subs[i].goals.v[j], stdout);
211 }
212 }
213 putchar('\n');
214 }
215 }
216}
217
218int
219main(int argc, char **argv)
220{
221 enum Generator {
222 GEN_NINJA,
223 GEN_DOT,
224 GEN_COMPDB,
225 };
226 const char *path;
227 const char *pname;
228 char *pathbuf;
229 char *src;
230 int dump_ast;
231 int dump_graph;
232 int env_override;
233 char **goals;
234 int i;
235 char **assigns;
236 size_t nassigns;
237 size_t ngoals;
238 enum Generator gen;
239 enum ShinMode mode;
240 struct Ast ast;
241 struct RuleSet rs;
242 struct SubGraph sg;
243
244 /* needed for test runner outputs to have accurate 'prog:' prefix */
245 pname = getenv("SHIN_PROGNAME");
246 if (pname)
247 set_progname(pname);
248 path = 0;
249 pathbuf = 0;
250 dump_ast = 0;
251 dump_graph = 0;
252 env_override = 0;
253 gen = GEN_NINJA;
254 mode = MODE_GNU;
255 assigns = 0;
256 nassigns = 0;
257 goals = 0;
258 ngoals = 0;
259 memset(&sg, 0, sizeof(sg));
260 memset(&ast, 0, sizeof(ast));
261 memset(&rs, 0, sizeof(rs));
262 src = 0;
263 for (i = 1; i < argc; i++) {
264 if (strcmp(argv[i], "-a") == 0) {
265 dump_ast = 1;
266 } else if (strcmp(argv[i], "-g") == 0) {
267 dump_graph = 1;
268 } else if (strcmp(argv[i], "-G") == 0) {
269 if (i + 1 >= argc) {
270 fprintf(stderr, "specify a generator\n\n");
271 usage(stderr, argv[0]);
272 free(assigns);
273 return 2;
274 }
275 ++i;
276 if (strcmp(argv[i], "ninja") == 0) {
277 gen = GEN_NINJA;
278 } else if (strcmp(argv[i], "dot") == 0) {
279 gen = GEN_DOT;
280 } else if (strcmp(argv[i], "compdb") == 0) {
281 gen = GEN_COMPDB;
282 } else {
283 fprintf(stderr, "unknown generator: %s\n\n", argv[i]);
284 usage(stderr, argv[0]);
285 free(assigns);
286 free(goals);
287 return 2;
288 }
289 } else if (strcmp(argv[i], "-M") == 0) {
290 if (i + 1 >= argc) {
291 fprintf(stderr, "specify a mode\n\n");
292 usage(stderr, argv[0]);
293 free(assigns);
294 free(goals);
295 return 2;
296 }
297 ++i;
298 if (shinmode_parse(argv[i], &mode) < 0) {
299 fprintf(stderr, "unknown mode: %s\n\n", argv[i]);
300 usage(stderr, argv[0]);
301 free(assigns);
302 free(goals);
303 return 2;
304 }
305 } else if (strcmp(argv[i], "-h") == 0 || strcmp(argv[i], "--help") == 0) {
306 usage(stdout, argv[0]);
307 free(assigns);
308 free(goals);
309 return 0;
310 } else if (strcmp(argv[i], "-C") == 0) {
311 if (i + 1 >= argc) {
312 fprintf(stderr, "specify a directory\n\n");
313 usage(stderr, argv[0]);
314 free(assigns);
315 free(goals);
316 return 2;
317 }
318 ++i;
319
320 if (chdir(argv[i]) != 0) {
321 fprintf(stderr, "failed to chdir to %s", argv[i]);
322 free(assigns);
323 free(goals);
324 return 2;
325 }
326 } else if (strcmp(argv[i], "-f") == 0) {
327 if (i + 1 >= argc) {
328 fprintf(stderr, "specify a file\n\n");
329 usage(stderr, argv[0]);
330 free(assigns);
331 free(goals);
332 return 2;
333 }
334 ++i;
335 path = argv[i];
336 } else if (strcmp(argv[i], "-e") == 0) {
337 env_override = 1;
338 } else if (isvarassign(argv[i])) {
339 char **tmp;
340
341 tmp = realloc(assigns, (nassigns + 1) * sizeof(assigns[0]));
342 if (!tmp) {
343 fprintf(stderr, "out of memory\n");
344 free(assigns);
345 free(goals);
346 return 2;
347 }
348 assigns = tmp;
349 assigns[nassigns++] = argv[i];
350 } else if (argv[i][0] == '-') {
351 usage(stderr, argv[0]);
352 free(assigns);
353 free(goals);
354 return 2;
355 } else {
356 char **tmp;
357
358 tmp = realloc(goals, (ngoals + 1) * sizeof(goals[0]));
359 if (!tmp) {
360 fprintf(stderr, "out of memory\n");
361 free(assigns);
362 free(goals);
363 return 2;
364 }
365 goals = tmp;
366 goals[ngoals++] = argv[i];
367 }
368 }
369 for (i = 0; i < (int)nassigns; i++)
370 addstr(&sg.assigns, assigns[i]);
371 for (i = 0; i < (int)ngoals; i++)
372 addstr(&sg.goals, goals[i]);
373 if (dump_ast) {
374 char *mkpath;
375
376 mkpath = 0;
377 if (loadmakefile(path, &mkpath, &src) < 0) {
378 if (path)
379 fprintf(stderr, "could not read %s\n", path);
380 else
381 fprintf(stderr, "could not find a makefile\n");
382 free(assigns);
383 free(goals);
384 freesubgraph(&sg);
385 return 2;
386 }
387 path = mkpath;
388 pathbuf = mkpath;
389 src = appendassigns(src, &sg.assigns);
390 {
391 int rc;
392
393 rc = parse(path, src, &ast, mode);
394 if (rc < 0) {
395 free(assigns);
396 free(goals);
397 free(pathbuf);
398 free(src);
399 freesubgraph(&sg);
400 return rc == -2 ? 2 : 1;
401 }
402 }
403 {
404 size_t marked = 0;
405 size_t k = ast.n;
406
407 while (k > 0 && marked < nassigns) {
408 k--;
409 if (ast.v[k].kind == NODE_ASSIGN) {
410 ast.v[k].data.assign.origin = ORIGIN_COMMAND;
411 marked++;
412 }
413 }
414 }
415 if (eval(path, &ast, 0, env_override, mode, &rs) < 0) {
416 free(assigns);
417 free(goals);
418 freeast(&ast);
419 free(pathbuf);
420 free(src);
421 freesubgraph(&sg);
422 return 2;
423 }
424 dumpruleset(&rs);
425 freeruleset(&rs);
426 freeast(&ast);
427 free(src);
428 }
429 if (path)
430 sg.makefile = xstrdup(path);
431 if (env_override)
432 addstr(&sg.flags, "-e");
433 sg.mode = mode;
434 free(assigns);
435 free(goals);
436 assigns = 0;
437 goals = 0;
438 {
439 int rc;
440
441 rc = buildsubgraph(&sg);
442 if (rc < 0) {
443 free(pathbuf);
444 freesubgraph(&sg);
445 return rc == -2 ? 2 : 1;
446 }
447 }
448 if (dump_graph)
449 dumpgraph(&sg.graph);
450 if (gen == GEN_DOT) {
451 if (gengraphviz(&sg.graph, "build.dot") < 0) {
452 fprintf(stderr, "graphviz generation error\n");
453 free(pathbuf);
454 freesubgraph(&sg);
455 return 2;
456 }
457 }
458 if (gen == GEN_COMPDB) {
459 if (gencompdb(&sg.graph, "compile_commands.json") < 0) {
460 fprintf(stderr, "compile_commands generation error\n");
461 free(pathbuf);
462 freesubgraph(&sg);
463 return 2;
464 }
465 }
466 if (gen == GEN_NINJA && genninja(&sg.graph, "build.ninja") < 0) {
467 fprintf(stderr, "ninja generation error\n");
468 free(pathbuf);
469 freesubgraph(&sg);
470 return 2;
471 }
472 free(pathbuf);
473 freesubgraph(&sg);
474 return 0;
475}