1#include "internal.h"
2
3#include <stdio.h>
4#include <stdlib.h>
5#include <string.h>
6#include <unistd.h>
7
8struct SubGraphStack {
9 char **v;
10 size_t n;
11 struct Arena arena;
12};
13
14static int
15isempty(const char *s)
16{
17 return !s || !s[0];
18}
19
20static int
21sameprefix(const char *a, const char *b)
22{
23 if (isempty(a))
24 return isempty(b);
25 if (isempty(b))
26 return 0;
27 return strcmp(a, b) == 0;
28}
29
30static int
31isunderprefix(const char *prefix, const char *name)
32{
33 size_t n;
34
35 if (isempty(prefix))
36 return 1;
37 n = strlen(prefix);
38 if (strncmp(name, prefix, n) != 0)
39 return 0;
40 return name[n] == 0 || name[n] == '/';
41}
42
43static int
44samelist(const struct StrList *a, const struct StrList *b)
45{
46 size_t i;
47
48 if (a->n != b->n)
49 return 0;
50 for (i = 0; i < a->n; i++) {
51 if (strcmp(a->v[i], b->v[i]) != 0)
52 return 0;
53 }
54 return 1;
55}
56
57static void
58addenvassign(struct StrList *list, const char *name, const char *val)
59{
60 char *s;
61
62 s = xmalloc(strlen(name) + 1 + strlen(val) + 1);
63 strcpy(s, name);
64 strcat(s, "=");
65 strcat(s, val);
66 list->v = xrealloc(list->v, (list->n + 1) * sizeof(list->v[0]));
67 list->v[list->n++] = s;
68}
69
70static char *
71joinprefix(const char *parent, const char *child)
72{
73 char *raw, *norm;
74
75 if (isempty(child))
76 return parent ? xstrdup(parent) : 0;
77 if (child[0] == '/')
78 return normpath(child);
79 if (isempty(parent))
80 return normpath(child);
81 raw = cat3(parent, "/", child);
82 norm = normpath(raw);
83 free(raw);
84 return norm;
85}
86
87static char *
88rebaseword(const char *prefix, const char *name)
89{
90 char *raw, *norm;
91
92 if (!name[0] || name[0] == '/')
93 return normpath(name);
94 if (isempty(prefix))
95 return normpath(name);
96 raw = cat3(prefix, "/", name);
97 norm = normpath(raw);
98 free(raw);
99 return norm;
100}
101
102static char *
103prefixrecipe(const char *prefix, const char *body)
104{
105 size_t nprefix, nbody;
106 char *out;
107
108 if (isempty(prefix) || strcmp(prefix, ".") == 0)
109 return xstrdup(body);
110 nprefix = strlen(prefix);
111 nbody = strlen(body);
112 out = xmalloc(5 + nprefix + 4 + nbody + 1);
113 memcpy(out, "cd ", 3);
114 memcpy(out + 3, prefix, nprefix);
115 memcpy(out + 3 + nprefix, " && ", 4);
116 memcpy(out + 7 + nprefix, body, nbody);
117 out[7 + nprefix + nbody] = 0;
118 return out;
119}
120
121static int
122resolvegoals(const struct SubGraph *sg, struct StrList *out)
123{
124 const struct Target *t;
125
126 memset(out, 0, sizeof(*out));
127 if (sg->goals.n) {
128 size_t i;
129
130 for (i = 0; i < sg->goals.n; i++) {
131 char *name;
132
133 name = rebaseword(sg->prefix, sg->goals.v[i]);
134 addstr(out, name);
135 free(name);
136 }
137 return 0;
138 }
139 t = defaulttarget(&sg->graph, sg->prefix);
140 if (t) {
141 addstr(out, t->name);
142 return 0;
143 }
144 fprintf(stderr, "submake has no default target\n");
145 return -1;
146}
147
148/* when expanding a submake, start from a target name and walk the graph backwards
149 * only for that target. if we expand the entire subgraph from the beginning,
150 * we get a lot of cases where the subgraph will recurse and try to create a new
151 * subgraph of itself */
152static void
153markreachable(const struct Graph *graph, const char *name, unsigned char *seen)
154{
155 const struct Target *t;
156 size_t i, idx;
157
158 t = findctarget(graph, name);
159 if (!t)
160 return;
161 idx = (size_t)(t - graph->v);
162 if (seen[idx])
163 return;
164 seen[idx] = 1;
165 for (i = 0; i < t->prereqs.n; i++)
166 markreachable(graph, t->prereqs.v[i], seen);
167 for (i = 0; i < t->impprereqs.n; i++)
168 markreachable(graph, t->impprereqs.v[i], seen);
169 for (i = 0; i < t->order_only.n; i++)
170 markreachable(graph, t->order_only.v[i], seen);
171}
172
173static int
174allsubmake(const struct Target *t)
175{
176 size_t i;
177
178 if (!t || t->recipes.n == 0)
179 return 0;
180 for (i = 0; i < t->recipes.n; i++) {
181 if (!t->recipes.v[i].submake)
182 return 0;
183 }
184 return 1;
185}
186
187static void
188pushstack(struct SubGraphStack *stack, const char *key)
189{
190 stack->v = xrealloc(stack->v, (stack->n + 1) * sizeof(stack->v[0]));
191 stack->v[stack->n++] = arena_strdup(&stack->arena, key);
192}
193
194static void
195popstack(struct SubGraphStack *stack)
196{
197 if (!stack->n)
198 return;
199 stack->n--;
200}
201
202static int
203instack(const struct SubGraphStack *stack, const char *key)
204{
205 size_t i;
206
207 for (i = 0; i < stack->n; i++) {
208 if (strcmp(stack->v[i], key) == 0)
209 return 1;
210 }
211 return 0;
212}
213
214static char *
215subgraphkey(const struct SubGraph *sg)
216{
217 size_t i, n, pos, len;
218 char *key;
219
220 n = strlen(sg->cwd) + 1 + strlen(sg->makefile ? sg->makefile : "") + 1;
221 for (i = 0; i < sg->envassigns.n; i++)
222 n += strlen(sg->envassigns.v[i]) + 1;
223 for (i = 0; i < sg->assigns.n; i++)
224 n += strlen(sg->assigns.v[i]) + 1;
225 key = xmalloc(n + 1);
226 pos = 0;
227 len = strlen(sg->cwd);
228 memcpy(key + pos, sg->cwd, len);
229 pos += len;
230 key[pos++] = '|';
231 if (sg->makefile) {
232 len = strlen(sg->makefile);
233 memcpy(key + pos, sg->makefile, len);
234 pos += len;
235 }
236 for (i = 0; i < sg->envassigns.n; i++) {
237 key[pos++] = '|';
238 len = strlen(sg->envassigns.v[i]);
239 memcpy(key + pos, sg->envassigns.v[i], len);
240 pos += len;
241 }
242 for (i = 0; i < sg->assigns.n; i++) {
243 key[pos++] = '|';
244 len = strlen(sg->assigns.v[i]);
245 memcpy(key + pos, sg->assigns.v[i], len);
246 pos += len;
247 }
248 key[pos] = 0;
249 return key;
250}
251
252static void
253removerecipe(struct RecipeList *list, size_t idx)
254{
255 size_t i;
256
257 free(list->v[idx].body);
258 freesubmake(&list->v[idx].sm);
259 for (i = idx + 1; i < list->n; i++)
260 list->v[i - 1] = list->v[i];
261 list->n--;
262 if (!list->n) {
263 free(list->v);
264 list->v = 0;
265 return;
266 }
267 list->v = xrealloc(list->v, list->n * sizeof(list->v[0]));
268}
269
270static void
271scopelist(struct StrList *list, const char *prefix)
272{
273 size_t i;
274
275 for (i = 0; i < list->n; i++) {
276 char *name;
277
278 name = rebaseword(prefix, list->v[i]);
279 free(list->v[i]);
280 list->v[i] = name;
281 }
282}
283
284static void
285scoperecipes(struct RecipeList *list, const char *prefix)
286{
287 size_t i;
288
289 for (i = 0; i < list->n; i++) {
290 char *body;
291
292 body = prefixrecipe(prefix, list->v[i].body);
293 free(list->v[i].body);
294 list->v[i].body = body;
295 }
296}
297
298static void
299scopegraph(struct Graph *graph, const char *prefix)
300{
301 size_t i;
302
303 if (isempty(prefix))
304 return;
305 for (i = 0; i < graph->n; i++) {
306 char *name;
307
308 name = rebaseword(prefix, graph->v[i].name);
309 graph->v[i].name = intern(name);
310 free(name);
311 free(graph->v[i].owner);
312 if ((graph->v[i].defined || graph->v[i].recipes.n > 0) &&
313 isunderprefix(prefix, graph->v[i].name))
314 graph->v[i].owner = xstrdup(prefix);
315 else
316 graph->v[i].owner = 0;
317 scopelist(&graph->v[i].prereqs, prefix);
318 scopelist(&graph->v[i].impprereqs, prefix);
319 scopelist(&graph->v[i].order_only, prefix);
320 scoperecipes(&graph->v[i].recipes, prefix);
321 }
322}
323
324static int
325mergetarget(struct Graph *graph, const struct Target *src)
326{
327 struct Target *dst;
328
329 dst = findtarget(graph, src->name);
330 if (!dst) {
331 graph->v = xrealloc(graph->v, (graph->n + 1) * sizeof(graph->v[0]));
332 dst = &graph->v[graph->n++];
333 memset(dst, 0, sizeof(*dst));
334 dst->name = src->name;
335 if (src->owner)
336 dst->owner = xstrdup(src->owner);
337 } else if (!dst->owner && src->owner) {
338 dst->owner = xstrdup(src->owner);
339 } else if (dst->owner && src->owner && !sameprefix(dst->owner, src->owner)) {
340 /*
341 * shared targets like ../lib/libzstd.a may be referenced from
342 * multiple sibling subgraphs; if that happens, treat the
343 * target as shared instead of attributing it to one owner and erroring.
344 */
345 free(dst->owner);
346 dst->owner = 0;
347 } else if (dst->defined && src->defined && dst->dcolon != src->dcolon) {
348 fprintf(stderr, "target file `%s' has both : and :: entries, i can't handle that!\n", dst->name);
349 return -1;
350 }
351 if (!dst->defined && src->defined)
352 dst->dcolon = src->dcolon;
353 dst->defined |= src->defined;
354 if (src->defined)
355 dst->dcolon = src->dcolon;
356 addwords(&dst->prereqs, &src->prereqs);
357 addwords(&dst->impprereqs, &src->impprereqs);
358 addwords(&dst->order_only, &src->order_only);
359 addrecipes(&dst->recipes, &src->recipes);
360 return 0;
361}
362
363static int
364addgraphsub(struct Graph *graph, struct SubGraph *child)
365{
366 graph->subs = xrealloc(graph->subs, (graph->nsubs + 1) * sizeof(graph->subs[0]));
367 graph->subs[graph->nsubs++] = *child;
368 memset(child, 0, sizeof(*child));
369 return 0;
370}
371
372/* in make, recursive make calls run sequentially*/
373static void
374subgraphorder(struct Graph *graph, const char *owner, const struct StrList *prev)
375{
376 size_t i, j, n;
377
378 if (!prev->n)
379 return;
380 n = owner ? strlen(owner) : 0;
381 for (i = 0; i < graph->n; i++) {
382 struct Target *t = &graph->v[i];
383
384 if (!t->owner)
385 continue;
386 if (strncmp(t->owner, owner, n) != 0)
387 continue;
388 if (t->owner[n] != 0 && t->owner[n] != '/')
389 continue;
390 for (j = 0; j < prev->n; j++) {
391 if (!hasword(&t->order_only, prev->v[j]))
392 addstr(&t->order_only, prev->v[j]);
393 }
394 }
395}
396
397static struct SubGraph *
398findgraphsub(struct Graph *graph, const char *prefix)
399{
400 size_t i;
401
402 for (i = 0; i < graph->nsubs; i++) {
403 if (sameprefix(graph->subs[i].prefix, prefix))
404 return &graph->subs[i];
405 }
406 return 0;
407}
408
409static int
410sameinvocation(const struct SubGraph *a, const struct SubGraph *b)
411{
412 if (strcmp(a->cwd, b->cwd) != 0)
413 return 0;
414 if (!!a->makefile != !!b->makefile)
415 return 0;
416 if (a->makefile && strcmp(a->makefile, b->makefile) != 0)
417 return 0;
418 if (!samelist(&a->envassigns, &b->envassigns))
419 return 0;
420 if (!samelist(&a->assigns, &b->assigns))
421 return 0;
422 if (!samelist(&a->flags, &b->flags))
423 return 0;
424 return 1;
425}
426
427static void
428mergesubmeta(struct SubGraph *dst, struct SubGraph *src)
429{
430 size_t i;
431
432 for (i = 0; i < src->parents.n; i++) {
433 if (!hasword(&dst->parents, src->parents.v[i]))
434 addstr(&dst->parents, src->parents.v[i]);
435 }
436 for (i = 0; i < src->goals.n; i++) {
437 if (!hasword(&dst->goals, src->goals.v[i]))
438 addstr(&dst->goals, src->goals.v[i]);
439 }
440 freesubgraph(src);
441 memset(src, 0, sizeof(*src));
442}
443
444static int
445mergechildgraph(struct SubGraph *parent,
446 const char *tname,
447 struct SubGraph *child,
448 const struct StrList *goals)
449{
450 struct SubGraph *known;
451 unsigned char *reachable;
452 struct Target *t;
453 size_t i;
454
455 known = sameprefix(parent->prefix, child->prefix) ? 0 : findgraphsub(&parent->graph, child->prefix);
456 if (known && !sameinvocation(known, child)) {
457 fprintf(stderr, "multiple incompatible subgraphs map to %s/build.ninja\n",
458 child->prefix ? child->prefix : ".");
459 return -1;
460 }
461 if (!known) {
462 reachable = xmalloc(child->graph.n ? child->graph.n : 1);
463 memset(reachable, 0, child->graph.n ? child->graph.n : 1);
464 for (i = 0; i < goals->n; i++)
465 markreachable(&child->graph, goals->v[i], reachable);
466 for (i = 0; i < child->graph.n; i++) {
467 if (!reachable[i])
468 continue;
469 if (mergetarget(&parent->graph, &child->graph.v[i]) < 0) {
470 free(reachable);
471 return -1;
472 }
473 }
474 free(reachable);
475 }
476 t = findtarget(&parent->graph, tname);
477 if (!t) {
478 fprintf(stderr, "lost parent target `%s' while expanding submake\n", tname);
479 return -1;
480 }
481 for (i = 0; i < goals->n; i++) {
482 if (!hasword(&t->prereqs, goals->v[i]))
483 addstr(&t->prereqs, goals->v[i]);
484 }
485 if (child->prefix && child->prefix[0] && isunderprefix(child->prefix, tname)) {
486 size_t n;
487 const char *local;
488
489 n = strlen(child->prefix);
490 local = tname + n;
491 if (*local == '/')
492 local++;
493 if (*local && !hasword(&t->prereqs, local))
494 addstr(&t->prereqs, local);
495 }
496 if (!sameprefix(parent->prefix, child->prefix)) {
497 if (known) {
498 mergesubmeta(known, child);
499 } else if (addgraphsub(&parent->graph, child) < 0) {
500 return -1;
501 }
502 }
503 return 0;
504}
505
506static int buildsubgraph0(struct SubGraph *sg, struct SubGraphStack *stack);
507
508static int
509expandsubgraphs(struct SubGraph *sg, struct SubGraphStack *stack)
510{
511 struct StrList roots;
512 unsigned char *reachable;
513 size_t i, n;
514
515 if (resolvegoals(sg, &roots) < 0)
516 return -1;
517 for (i = 0; i < sg->graph.n; i++) {
518 if (!targetownedby(&sg->graph.v[i], sg->prefix))
519 continue;
520 if (!sg->graph.v[i].phony && !allsubmake(&sg->graph.v[i]))
521 continue;
522 if (!hasword(&roots, sg->graph.v[i].name))
523 addstr(&roots, sg->graph.v[i].name);
524 }
525 n = sg->graph.n;
526 reachable = xmalloc(n ? n : 1);
527 memset(reachable, 0, n ? n : 1);
528 for (i = 0; i < roots.n; i++)
529 markreachable(&sg->graph, roots.v[i], reachable);
530 freestrs(&roots);
531
532 for (i = 0; i < n; i++) {
533 size_t k;
534 const char *tname;
535 struct StrList prevgoals;
536
537 if (!reachable[i])
538 continue;
539
540 memset(&prevgoals, 0, sizeof(prevgoals));
541 tname = sg->graph.v[i].name;
542 for (k = 0; k < sg->graph.v[i].recipes.n;) {
543 struct Recipe *r;
544 struct SubGraph child;
545 struct StrList goals;
546 int rc;
547
548 r = &sg->graph.v[i].recipes.v[k];
549 if (!r->submake) {
550 k++;
551 continue;
552 }
553 memset(&child, 0, sizeof(child));
554 child.cwd = r->sm.dir ? joinpath(sg->cwd, r->sm.dir) : xstrdup(sg->cwd);
555 child.prefix = joinprefix(sg->prefix, r->sm.dir);
556 child.mode = sg->mode;
557 addstr(&child.parents, tname);
558 if (r->sm.makefile)
559 child.makefile = xstrdup(r->sm.makefile);
560 {
561 size_t ei;
562
563 for (ei = 0; ei < sg->graph.v[i].env.n; ei++) {
564 const struct Var *v = &sg->graph.v[i].env.v[ei];
565
566 if (!v->exported)
567 continue;
568 addenvassign(&child.envassigns, v->name, v->val);
569 }
570 }
571 addwords(&child.assigns, &r->sm.assigns);
572 addwords(&child.flags, &r->sm.flags);
573 addwords(&child.goals, &r->sm.goals);
574 if (strcmp(child.cwd, sg->cwd) == 0 &&
575 (!child.makefile || strcmp(child.makefile, sg->makefile) == 0)) {
576 freesubgraph(&child);
577 k++;
578 continue;
579 }
580 rc = buildsubgraph0(&child, stack);
581 if (rc > 0) {
582 struct SubGraph *known;
583
584 known = sameprefix(sg->prefix, child.prefix) ? 0 : findgraphsub(&sg->graph, child.prefix);
585 if (known && sameinvocation(known, &child) && child.goals.n > 0) {
586 size_t gi;
587 struct Target *pt;
588
589 removerecipe(&sg->graph.v[i].recipes, k);
590 pt = findtarget(&sg->graph, tname);
591 for (gi = 0; pt && gi < child.goals.n; gi++) {
592 char *gname;
593
594 gname = rebaseword(child.prefix, child.goals.v[gi]);
595 if (!hasword(&pt->prereqs, gname))
596 addstr(&pt->prereqs, gname);
597 free(gname);
598 }
599 freesubgraph(&child);
600 continue;
601 }
602 freesubgraph(&child);
603 k++;
604 continue;
605 }
606 if (rc < 0) {
607 freesubgraph(&child);
608 freestrs(&prevgoals);
609 free(reachable);
610 return -1;
611 }
612 /* remove the make invocation, we replace it with a graph*/
613 removerecipe(&sg->graph.v[i].recipes, k);
614 if (resolvegoals(&child, &goals) < 0) {
615 freesubgraph(&child);
616 freestrs(&prevgoals);
617 free(reachable);
618 return -1;
619 }
620 subgraphorder(&child.graph, child.prefix, &prevgoals);
621 if (mergechildgraph(sg, tname, &child, &goals) < 0) {
622 freesubgraph(&child);
623 freestrs(&goals);
624 freestrs(&prevgoals);
625 free(reachable);
626 return -1;
627 }
628 freestrs(&prevgoals);
629 prevgoals = goals;
630 if (!sameprefix(sg->prefix, child.prefix))
631 continue;
632 freesubgraph(&child);
633 }
634 freestrs(&prevgoals);
635 }
636 free(reachable);
637 return 0;
638}
639
640static int
641buildsubgraph0(struct SubGraph *sg, struct SubGraphStack *stack)
642{
643 struct Ast ast;
644 struct Ast pre;
645 struct RuleSet rs;
646 char *src;
647 char *path;
648 char *oldcwd;
649 char *key;
650 int rc;
651 size_t i;
652 int envoverride;
653 size_t nenvassigns;
654
655 memset(&ast, 0, sizeof(ast));
656 memset(&pre, 0, sizeof(pre));
657 memset(&rs, 0, sizeof(rs));
658 freegraph(&sg->graph);
659 memset(&sg->graph, 0, sizeof(sg->graph));
660 src = 0;
661 path = 0;
662 oldcwd = getcwddup();
663 if (chdir(sg->cwd) != 0) {
664 free(oldcwd);
665 return 1;
666 }
667 free(sg->cwd);
668 sg->cwd = getcwddup();
669 if (loadmakefile(sg->makefile, &path, &src) < 0) {
670 chdir(oldcwd);
671 free(oldcwd);
672 return 1;
673 }
674 free(sg->makefile);
675 sg->makefile = xstrdup(path);
676 key = subgraphkey(sg);
677 if (instack(stack, key)) {
678 free(key);
679 free(path);
680 free(src);
681 chdir(oldcwd);
682 free(oldcwd);
683 return 1;
684 }
685 pushstack(stack, key);
686 free(key);
687 envoverride = 0;
688 for (i = 0; i < sg->flags.n; i++) {
689 if (strcmp(sg->flags.v[i], "-e") == 0) {
690 envoverride = 1;
691 break;
692 }
693 }
694 nenvassigns = sg->envassigns.n;
695 if (sg->envassigns.n || sg->assigns.n) {
696 char *assignsrc;
697 struct StrList allassigns;
698
699 memset(&allassigns, 0, sizeof(allassigns));
700 addwords(&allassigns, &sg->envassigns);
701 addwords(&allassigns, &sg->assigns);
702 assignsrc = appendassigns(xstrdup(""), &allassigns);
703 freestrs(&allassigns);
704 rc = parse("<command line>", assignsrc, &pre, sg->mode);
705 free(assignsrc);
706 if (rc < 0)
707 goto out;
708 for (i = 0; i < pre.n; i++) {
709 if (pre.v[i].kind != NODE_ASSIGN)
710 continue;
711 if (nenvassigns > 0) {
712 pre.v[i].data.assign.origin = envoverride ? ORIGIN_ENV_OVERRIDE : ORIGIN_ENV;
713 nenvassigns--;
714 } else {
715 pre.v[i].data.assign.origin = ORIGIN_COMMAND;
716 }
717 }
718 }
719 rc = parse(path, src, &ast, sg->mode);
720 if (rc < 0)
721 goto out;
722 rc = eval(path, &ast, sg->assigns.n ? &pre : 0, envoverride, sg->mode, &rs);
723 if (rc < 0)
724 goto out;
725 rc = buildgraph(&rs, &sg->goals, &sg->graph, sg->mode);
726 if (rc < 0)
727 goto out;
728 rc = expandgraph(&sg->graph, sg->mode);
729 if (rc < 0)
730 goto out;
731 scopegraph(&sg->graph, sg->prefix);
732 rc = expandsubgraphs(sg, stack);
733out:
734 freeruleset(&rs);
735 freeast(&pre);
736 freeast(&ast);
737 free(src);
738 free(path);
739 popstack(stack);
740 chdir(oldcwd);
741 free(oldcwd);
742 return rc;
743}
744
745int
746buildsubgraph(struct SubGraph *sg)
747{
748 struct SubGraphStack stack;
749 int rc;
750
751 memset(&stack, 0, sizeof(stack));
752 arena_init(&stack.arena, 0);
753 if (!sg->cwd)
754 sg->cwd = getcwddup();
755 rc = buildsubgraph0(sg, &stack);
756 if (rc < 0) {
757 while (stack.n)
758 popstack(&stack);
759 arena_free(&stack.arena);
760 free(stack.v);
761 return rc;
762 }
763 arena_free(&stack.arena);
764 free(stack.v);
765 return 0;
766}
767
768void
769freesubgraph(struct SubGraph *sg)
770{
771 if (!sg)
772 return;
773 free(sg->cwd);
774 sg->cwd = 0;
775 free(sg->prefix);
776 sg->prefix = 0;
777 free(sg->makefile);
778 sg->makefile = 0;
779 freestrs(&sg->parents);
780 freestrs(&sg->envassigns);
781 freestrs(&sg->assigns);
782 freestrs(&sg->flags);
783 freestrs(&sg->goals);
784 freegraph(&sg->graph);
785}