main shrub/shinobi / src / graph / graph.c
  1#include "shinobi.h"
  2#include "internal.h"
  3#include "posix.h"
  4#include "gnu/pattern.h"
  5
  6#include <glob.h>
  7#include <stdio.h>
  8#include <stdlib.h>
  9#include <string.h>
 10
 11/* graph builder */
 12
 13struct TAssign {
 14	struct StrList targets;
 15	char *lhs;
 16	char *rhs;
 17	enum AssignOp op;
 18	enum Origin origin;
 19};
 20
 21struct GraphState {
 22	struct Graph *graph;
 23	struct Env env;
 24	const struct StrList *phony;
 25	const struct RecipeList *defaultrule;
 26	const struct RuleNode *rules;
 27	size_t nrules;
 28	struct PatRules patterns;
 29	struct SufRules sufs;
 30	struct TAssign *tas;
 31	size_t ntas;
 32	int saw_suffixes;
 33	enum ShinMode mode;
 34};
 35
 36static void
 37seedsufs(struct SufRules *rules, const struct RuleNode *rule)
 38{
 39	char *from, *to;
 40
 41	if (rule->targets.n != 1 || rule->prereqs.n != 0 || rule->order_only.n != 0)
 42		return;
 43	if (issinglesuf(rule->targets.v[0], &from)) {
 44		struct StrList list;
 45
 46		memset(&list, 0, sizeof(list));
 47		list.v = &from;
 48		list.n = 1;
 49		addsufs(&rules->active, &list);
 50		free(from);
 51		return;
 52	}
 53	if (!issuf(rule->targets.v[0], &from, &to))
 54		return;
 55	{
 56		char *tmp[2];
 57		struct StrList list;
 58
 59		tmp[0] = from;
 60		tmp[1] = to;
 61		memset(&list, 0, sizeof(list));
 62		list.v = tmp;
 63		list.n = 2;
 64		addsufs(&rules->active, &list);
 65	}
 66	free(from);
 67	free(to);
 68}
 69
 70static int
 71targetmatches(const char *pat, const char *name)
 72{
 73	if (!ispat(pat))
 74		return strcmp(pat, name) == 0;
 75	return patmatches(pat, name);
 76}
 77
 78/* we apply target assignments from the graphstate (gs.tas) to those specific
 79 * targets, for some target like
 80 *
 81 * binary : LDFLAGS += -static
 82 */
 83static void
 84applytassigns(struct GraphState *gs, struct EvalCtx *ctx, const char *name)
 85{
 86	size_t i, j;
 87
 88	for (i = 0; i < gs->ntas; i++) {
 89		struct AssignNode in;
 90
 91		for (j = 0; j < gs->tas[i].targets.n; j++) {
 92			if (!targetmatches(gs->tas[i].targets.v[j], name))
 93				continue;
 94			memset(&in, 0, sizeof(in));
 95			in.lhs = gs->tas[i].lhs;
 96			in.rhs = gs->tas[i].rhs;
 97			in.op = gs->tas[i].op;
 98			in.origin = gs->tas[i].origin;
 99			evalassign(ctx, &in);
100		}
101	}
102}
103
104static void
105targetenv(struct GraphState *gs, struct EvalCtx *ctx, const struct Env *base, const char *name)
106{
107	if (base && base->n)
108		copyenv(ctx->env, base);
109	else
110		copyenv(ctx->env, &gs->env);
111	applytassigns(gs, ctx, name);
112}
113
114static int
115hasglobmeta(const char *s)
116{
117	size_t i;
118
119	for (i = 0; s[i]; i++) {
120		if (s[i] == '\\' && s[i + 1]) {
121			i++;
122			continue;
123		}
124		if (s[i] == '*' || s[i] == '?' || s[i] == '[')
125			return 1;
126	}
127	return 0;
128}
129
130static void
131addglobword(struct StrList *out, const char *s)
132{
133	glob_t g;
134	size_t i;
135	int rc;
136
137	if (!hasglobmeta(s)) {
138		addstr(out, s);
139		return;
140	}
141
142	memset(&g, 0, sizeof(g));
143	rc = glob(s, 0, 0, &g);
144	if (rc == 0 && g.gl_pathc > 0) {
145		for (i = 0; i < g.gl_pathc; i++)
146			addstr(out, g.gl_pathv[i]);
147	} else {
148		addstr(out, s);
149	}
150	globfree(&g);
151}
152
153static void
154addstaticpatrecipe(struct Target *t, const struct Recipe *src, const char *stem)
155{
156	char *exp;
157
158	exp = patexpandstem(src->body, stem);
159	if (!exp[0]) {
160		free(exp);
161		return;
162	}
163	t->recipes.v = xrealloc(t->recipes.v, (t->recipes.n + 1) * sizeof(t->recipes.v[0]));
164	t->recipes.v[t->recipes.n].body = exp;
165	t->recipes.v[t->recipes.n].silent = src->silent;
166	t->recipes.v[t->recipes.n].ignore = src->ignore;
167	t->recipes.v[t->recipes.n].recursive = src->recursive;
168	t->recipes.v[t->recipes.n].submake = src->submake;
169	copysubmake(&t->recipes.v[t->recipes.n].sm, &src->sm);
170	t->recipes.n++;
171}
172
173/* add an explicit target rule to the graph, and add all
174 * non-pattern prereqs also as placeholder nodes. later
175 * we discorver how to build them if they need to be built */
176static void
177addrule(struct GraphState *gs, const char *name, const struct RuleNode *rule)
178{
179	size_t i;
180	struct Target *t;
181	struct Env env;
182	struct EvalCtx ctx;
183	struct StrList prereqs;
184	struct StrList order_only;
185	char *stem;
186
187	memset(&ctx, 0, sizeof(ctx));
188	memset(&prereqs, 0, sizeof(prereqs));
189	memset(&order_only, 0, sizeof(order_only));
190	ctx.env = &env;
191	stem = 0;
192
193	if (rule->target_pattern) {
194		stem = patmatchstem(rule->target_pattern, name);
195		if (!stem)
196			return;
197	}
198
199	t = gettarget(gs->graph, name, 0);
200	if (!t->defined) {
201		t->dcolon = rule->dcolon;
202	} else if (t->defined && t->dcolon != rule->dcolon) {
203		fprintf(stderr, "target file `%s' has both : and :: entries, i can't handle that!\n", name);
204		exit(1);
205	}
206	t->defined = 1;
207	t->dcolon = rule->dcolon;
208	if (hasword(gs->phony, name))
209		t->phony = 1;
210	for (i = 0; i < rule->prereqs.n; i++) {
211		char *word;
212
213		word = stem ? patapplystem(rule->prereqs.v[i], stem) : xstrdup(rule->prereqs.v[i]);
214		addglobword(&prereqs, word);
215		free(word);
216	}
217	for (i = 0; i < rule->order_only.n; i++) {
218		char *word;
219
220		word = stem ? patapplystem(rule->order_only.v[i], stem) : xstrdup(rule->order_only.v[i]);
221		addglobword(&order_only, word);
222		free(word);
223	}
224	addwords(&t->prereqs, &prereqs);
225	addwords(&t->order_only, &order_only);
226	targetenv(gs, &ctx, t->env.n ? &t->env : 0, name);
227	if (stem) {
228		for (i = 0; i < rule->recipes.n; i++)
229			addstaticpatrecipe(t, &rule->recipes.v[i], stem);
230	} else {
231		addrecipes(&t->recipes, &rule->recipes);
232	}
233	freeenv(&t->env);
234	copyenv(&t->env, &env);
235
236	for (i = 0; i < prereqs.n; i++) {
237		struct Env penv;
238		struct EvalCtx pctx;
239
240		if (strchr(prereqs.v[i], '%'))
241			continue;
242		t = gettarget(gs->graph, prereqs.v[i], 0);
243		if (hasword(gs->phony, t->name))
244			t->phony = 1;
245		memset(&penv, 0, sizeof(penv));
246		memset(&pctx, 0, sizeof(pctx));
247		pctx.env = &penv;
248		targetenv(gs, &pctx, &env, t->name);
249		freeenv(&t->env);
250		copyenv(&t->env, pctx.env);
251		freeenv(&penv);
252	}
253	freestrs(&prereqs);
254	freestrs(&order_only);
255	freeenv(&env);
256	free(stem);
257}
258static void
259addtassign(struct GraphState *gs, const struct AssignNode *assign)
260{
261	struct TAssign *ta;
262
263	gs->tas = xrealloc(gs->tas, (gs->ntas + 1) * sizeof(gs->tas[0]));
264	ta = &gs->tas[gs->ntas++];
265	memset(ta, 0, sizeof(*ta));
266	addwords(&ta->targets, &assign->targets);
267	ta->lhs = xstrdup(assign->lhs);
268	ta->rhs = xstrdup(assign->rhs);
269	ta->op = assign->op;
270	ta->origin = assign->origin;
271}
272
273static void
274instdefaultrule(const struct GraphState *gs, struct Target *t, struct EvalCtx *ctx)
275{
276	if (!gs->defaultrule || gs->defaultrule->n == 0 || t->defined || t->phony || t->recipes.n > 0)
277		return;
278	addrecipes(&t->recipes, gs->defaultrule);
279	freeenv(&t->env);
280	copyenv(&t->env, ctx->env);
281}
282
283/*generate the graph from the evaluated rule set
284 * get the env and target specific assignments,
285 * then expand placeholder targets until all prereqs
286 * we can find are in the graph. */
287int
288buildgraph(const struct RuleSet *ruleset, const struct StrList *goals, struct Graph *graph, enum ShinMode mode)
289{
290	size_t i, j;
291	struct GraphState gs;
292	struct EvalCtx ctx;
293
294	memset(graph, 0, sizeof(*graph));
295	memset(&gs, 0, sizeof(gs));
296	memset(&ctx, 0, sizeof(ctx));
297	gs.graph = graph;
298	gs.phony = &ruleset->phony;
299	gs.defaultrule = &ruleset->defaultrule;
300	seedenv(&gs.env, ruleset->posix, ruleset->envoverride, mode);
301	gs.rules = ruleset->rules;
302	gs.nrules = ruleset->nrules;
303	gs.mode = mode;
304	ctx.env = &gs.env;
305	ctx.export_all = ruleset->export_all;
306	ctx.mode = mode;
307	imprules(&gs.sufs, mode);
308
309	for (i = 0; i < ruleset->nvars; i++)
310		evalassign(&ctx, &ruleset->vars[i]);
311
312	for (i = 0; i < ruleset->ntvars; i++)
313		addtassign(&gs, &ruleset->tvars[i]);
314
315	for (i = 0; i < gs.nrules; i++) {
316		if (!issufrule(&gs.rules[i]))
317			continue;
318		gs.saw_suffixes = 1;
319		if (gs.rules[i].prereqs.n == 0)
320			clearsufs(&gs.sufs.active);
321		else
322			addsufs(&gs.sufs.active, &gs.rules[i].prereqs);
323	}
324
325	if (!gs.saw_suffixes) {
326		for (i = 0; i < gs.nrules; i++)
327			seedsufs(&gs.sufs, &gs.rules[i]);
328	}
329
330	for (i = 0; i < gs.nrules; i++) {
331		size_t k;
332
333		if (issufrule(&gs.rules[i]))
334			continue;
335		if (collectsufrule(&gs.sufs, &gs.rules[i], 0))
336			continue;
337		/* pattern rules (%.o: %.c) are a gnu extension */
338		if (gs.mode == MODE_GNU)
339			collectpat(&gs.patterns, &gs.rules[i], 0);
340		for (k = 0; k < gs.rules[i].targets.n; k++) {
341			if (!ispat(gs.rules[i].targets.v[k]))
342				addrule(&gs, gs.rules[i].targets.v[k], &gs.rules[i]);
343		}
344	}
345	if (imppatrules(&gs.patterns, gs.mode) < 0) {
346		free(gs.tas);
347		freepatrule(&gs.patterns);
348		freesufrules(&gs.sufs);
349		freeenv(&gs.env);
350		return -1;
351	}
352
353	if (goals) {
354		/* explicitly requested goals need to exist in the generated graph before ninja runs.
355		 * if a requested goal is missing but a .DEFAULT rule exists, create a
356		 * placeholder target here so the default recipe will be attached later. */
357		for (i = 0; i < goals->n; i++) {
358			struct Target *t;
359
360			t = findtarget(graph, goals->v[i]);
361			if (t)
362				continue;
363			if (ruleset->defaultrule.n == 0) {
364				/* if there is no default rule but a target that doesn't exist
365				 * is requested, die */
366				char *detail;
367
368				detail = cat3("'", goals->v[i], "'");
369				dielikemake(0, 0, "No rule to make target", detail);
370				free(detail);
371				freepatrule(&gs.patterns);
372				freesufrules(&gs.sufs);
373				freeenv(&gs.env);
374				free(gs.tas);
375				return -1;
376			}
377			t = gettarget(graph, goals->v[i], 0);
378			if (hasword(gs.phony, t->name))
379				t->phony = 1;
380		}
381	}
382
383	for (;;) {
384		int changed;
385
386		changed = 0;
387		for (i = 0; i < graph->n; i++) {
388			struct Target *t = &graph->v[i];
389			size_t nprereqs;
390			size_t old_graph_n, old_recipes_n, old_impprereqs_n;
391
392			old_graph_n = graph->n;
393			old_recipes_n = t->recipes.n;
394			old_impprereqs_n = t->impprereqs.n;
395			/* for targets with no recipe, try a pattern rule or
396			 * suffix rule. existing prereqs dont block this,
397			 * the rule may still supply the recipe */
398			if (t->recipes.n == 0) {
399				int matched;
400				struct Env env;
401				struct EvalCtx targetctx;
402
403				memset(&targetctx, 0, sizeof(targetctx));
404				targetctx.env = &env;
405				targetctx.mode = gs.mode;
406				targetenv(&gs, &targetctx, &t->env, t->name);
407				/* rule precedence in GNU mode:
408				 * 1) user-defined pattern rules
409				 * 2) user-defined suffix rules
410				 * 3) built-in pattern rules
411				 * 4) built-in suffix rules
412				 *
413				 * this makes sure we keep local suffix compile recipes ahead of GNU builtins,
414				 * while allowing builtins such as %: %.o to beat the built-in .c
415				 * link rule when we are interpreting a GNU makefile which matches what gmake does. */
416				if (gs.mode == MODE_GNU)
417					instpatrule(&gs.patterns, graph, t, &targetctx, 0);
418				matched = t->recipes.n > 0;
419				if (!matched) {
420					instsufrule(&gs.sufs, graph, t, &targetctx, 0);
421					if (gs.mode == MODE_GNU && t->recipes.n == 0) {
422						instpatrule(&gs.patterns, graph, t, &targetctx, 1);
423					}
424					if (t->recipes.n == 0)
425						instsufrule(&gs.sufs, graph, t, &targetctx, 1);
426					if (t->recipes.n == 0)
427						instdefaultrule(&gs, t, &targetctx);
428				}
429				freeenv(&env);
430				if (targetctx.errors)
431					ctx.errors += targetctx.errors;
432			}
433			if (graph->n != old_graph_n || t->recipes.n != old_recipes_n ||
434			    t->impprereqs.n != old_impprereqs_n)
435				changed = 1;
436			nprereqs = totalprereqs(t);
437			for (j = 0; j < nprereqs; j++) {
438				const char *prereq;
439
440				if (j < t->impprereqs.n)
441					prereq = t->impprereqs.v[j];
442				else
443					prereq = t->prereqs.v[j - t->impprereqs.n];
444
445				if (!findtarget(graph, prereq)) {
446					struct Target *nt;
447
448					nt = gettarget(graph, prereq, 0);
449					if (hasword(gs.phony, nt->name))
450						nt->phony = 1;
451					changed = 1;
452					t = &graph->v[i];
453				}
454			}
455		}
456		if (!changed)
457			break;
458	}
459
460	for (i = 0; i < gs.ntas; i++) {
461		freestrs(&gs.tas[i].targets);
462		free(gs.tas[i].lhs);
463		free(gs.tas[i].rhs);
464	}
465	free(gs.tas);
466	freepatrule(&gs.patterns);
467	freesufrules(&gs.sufs);
468	freeenv(&gs.env);
469
470	return ctx.errors ? -1 : 0;
471}
472
473int
474expandgraph(struct Graph *graph, enum ShinMode mode)
475{
476	size_t i, k, j;
477
478	for (i = 0; i < graph->n; i++) {
479		struct Target *t = &graph->v[i];
480		struct EvalCtx ctx;
481		struct StrList side_effects;
482		struct RecipeList new_recipes;
483
484		if (!t->env.n) {
485			freeenv(&t->env);
486			continue;
487		}
488		memset(&ctx, 0, sizeof(ctx));
489		memset(&side_effects, 0, sizeof(side_effects));
490		memset(&new_recipes, 0, sizeof(new_recipes));
491		ctx.env = &t->env;
492		ctx.mode = mode;
493		ctx.auto_target = t->name;
494		{
495			struct StrList allprereqs;
496
497			memset(&allprereqs, 0, sizeof(allprereqs));
498			addwords(&allprereqs, &t->impprereqs);
499			addwords(&allprereqs, &t->prereqs);
500			ctx.auto_prereqs = &allprereqs;
501			ctx.avoid_io = 1;
502			ctx.side_effects = &side_effects;
503
504			for (k = 0; k < t->recipes.n; k++) {
505				struct Recipe *r = &t->recipes.v[k];
506				char *exp;
507
508			freestrs(&side_effects);
509			exp = expandstr(&ctx, r->body);
510			/* strip recipe prefixes that may have been
511			 * introduced by variable expansion, eg something like Q_CC = @echo ... */
512			{
513				const char *s = exp;
514				while (*s == '@' || *s == '-' || *s == '+') {
515					if (*s == '@')
516						r->silent = 1;
517					else if (*s == '-')
518						r->ignore = 1;
519					else if (*s == '+')
520						r->recursive = 1;
521					s++;
522					while (*s == ' ' || *s == '\t')
523						s++;
524				}
525				if (s != exp) {
526					char *trimmed = xstrdup(s);
527					free(exp);
528					exp = trimmed;
529				}
530			}
531			for (j = 0; j < side_effects.n; j++) {
532				struct Recipe *nr;
533
534				new_recipes.v = xrealloc(new_recipes.v,
535				                         (new_recipes.n + 1) * sizeof(new_recipes.v[0]));
536				nr = &new_recipes.v[new_recipes.n++];
537				memset(nr, 0, sizeof(*nr));
538				nr->body = xstrdup(side_effects.v[j]);
539				nr->silent = r->silent;
540				nr->ignore = r->ignore;
541			}
542			if (exp[0]) {
543				struct Recipe *nr;
544
545				new_recipes.v = xrealloc(new_recipes.v,
546				                         (new_recipes.n + 1) * sizeof(new_recipes.v[0]));
547				nr = &new_recipes.v[new_recipes.n++];
548				*nr = *r;
549				nr->body = exp;
550					freesubmake(&nr->sm);
551					nr->submake = parsesubmake(&nr->sm, nr->body);
552					free(r->body);
553					r->body = 0;
554					memset(&r->sm, 0, sizeof(r->sm));
555				} else {
556					free(exp);
557				}
558			}
559			freestrs(&allprereqs);
560		}
561
562		freestrs(&side_effects);
563		freerecipes(&t->recipes);
564		t->recipes = new_recipes;
565	}
566
567	for (i = 0; i < graph->nsubs; i++) {
568		if (expandgraph(&graph->subs[i].graph, mode) < 0)
569			return -1;
570	}
571	return 0;
572}
573
574void
575freegraph(struct Graph *graph)
576{
577	size_t i;
578
579	if (!graph)
580		return;
581	for (i = 0; i < graph->n; i++) {
582		free(graph->v[i].owner);
583		freestrs(&graph->v[i].prereqs);
584		freestrs(&graph->v[i].impprereqs);
585		freestrs(&graph->v[i].order_only);
586		freerecipes(&graph->v[i].recipes);
587		freeenv(&graph->v[i].env);
588	}
589	for (i = 0; i < graph->nsubs; i++)
590		freesubgraph(&graph->subs[i]);
591	free(graph->v);
592	free(graph->targetindex);
593	free(graph->subs);
594	graph->v = 0;
595	graph->n = 0;
596	graph->targetindex = 0;
597	graph->ntargetindex = 0;
598	graph->cap_targetindex = 0;
599	graph->subs = 0;
600	graph->nsubs = 0;
601}