main shinobi / cli / main.c
  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}