commit 884b91f
shrub
·
2026-05-13 07:41:31 +0000 UTC
parent 4aa3f55
fix suffix rule chaining and diffrentiate between implcitly inferred and explicit prereqs
12 files changed,
+311,
-108
M
TODO
+4,
-2
1@@ -13,14 +13,12 @@ targets:
2 a lot of things like .PRECIOUS etc dont map to ninja, those are non-goals
3
4 functions:
5- abspath
6 flavor
7 file
8 error
9 warning
10 intcmp
11 let
12- realpath
13
14 directives
15 undefine
16@@ -31,3 +29,7 @@ directives
17 special vars
18 .VARIABLES
19
20+other
21+ feature gate flags like -M posix2024, -M posix2008, -M gnu
22+ eventually handle bsd make and kati-specific features once we have feature gating. never evaluate two feature sets at once
23+
+12,
-5
1@@ -63,9 +63,12 @@ issource(const char *path)
2 static int
3 iscompile(const struct Target *t)
4 {
5- if (t->recipes.n != 1 || t->prereqs.n != 1)
6+ const char *src;
7+
8+ if (t->recipes.n != 1 || totalprereqs(t) != 1)
9 return 0;
10- if (!issource(t->prereqs.v[0]))
11+ src = firstprereq(t);
12+ if (!src || !issource(src))
13 return 0;
14 if (strstr(t->recipes.v[0].body, " -c ") || strstr(t->recipes.v[0].body, "\t-c ") ||
15 strstr(t->recipes.v[0].body, " -c\t") || hassuffix(t->recipes.v[0].body, " -c"))
16@@ -102,9 +105,13 @@ translateauto(const char *s, const struct Target *t)
17 } else if (s[i + 1] == '@') {
18 val = t->name;
19 } else if (s[i + 1] == '<') {
20- val = t->prereqs.n ? t->prereqs.v[0] : "";
21+ val = firstprereq(t);
22+ if (!val)
23+ val = "";
24 } else if (s[i + 1] == '^' || s[i + 1] == '+' || s[i + 1] == '?') {
25- val = t->prereqs.n ? t->prereqs.v[0] : "";
26+ val = firstprereq(t);
27+ if (!val)
28+ val = "";
29 }
30
31 if (!val) {
32@@ -164,7 +171,7 @@ gencompcmd(const struct Graph *graph, const char *path)
33 emitjson(fp, cmd);
34 fprintf(fp, "\",\n");
35 fprintf(fp, " \"file\": \"");
36- emitjson(fp, graph->v[i].prereqs.v[0]);
37+ emitjson(fp, firstprereq(&graph->v[i]));
38 fprintf(fp, "\",\n");
39 fprintf(fp, " \"output\": \"");
40 emitjson(fp, graph->v[i].name);
+7,
-1
1@@ -35,7 +35,7 @@ emitnode(FILE *fp, size_t i, const struct Target *t)
2 fprintf(fp, "\"");
3 if (t->recipes.n > 0) {
4 fprintf(fp, ", shape=box");
5- } else if (t->prereqs.n > 0 || t->order_only.n > 0) {
6+ } else if (t->prereqs.n > 0 || t->impprereqs.n > 0 || t->order_only.n > 0) {
7 fprintf(fp, ", shape=box, style=dashed");
8 } else {
9 fprintf(fp, ", shape=box");
10@@ -88,6 +88,12 @@ emitedges(FILE *fp, const struct Graph *graph, size_t i, const struct Target *t)
11 continue;
12 fprintf(fp, " n%zu -> n%zu;\n", (size_t)(dep - graph->v), i);
13 }
14+ for (j = 0; j < t->impprereqs.n; j++) {
15+ dep = findctarget(graph, t->impprereqs.v[j]);
16+ if (!dep)
17+ continue;
18+ fprintf(fp, " n%zu -> n%zu;\n", (size_t)(dep - graph->v), i);
19+ }
20 for (j = 0; j < t->order_only.n; j++) {
21 dep = findctarget(graph, t->order_only.v[j]);
22 if (!dep)
+17,
-2
1@@ -147,6 +147,13 @@ joinlocalprereqs(const struct Target *t)
2 char *s;
3
4 memset(&list, 0, sizeof(list));
5+ for (i = 0; i < t->impprereqs.n; i++) {
6+ char *name;
7+
8+ name = localpath(t, t->impprereqs.v[i]);
9+ addstr(&list, name);
10+ free(name);
11+ }
12 for (i = 0; i < t->prereqs.n; i++) {
13 char *name;
14
15@@ -191,7 +198,10 @@ translateauto(const char *s, const struct Target *t)
16 val = localpath(t, t->name);
17 mustfree = 1;
18 } else if (s[i + 1] == '<') {
19- val = t->prereqs.n ? localpath(t, t->prereqs.v[0]) : xstrdup("");
20+ const char *prereq;
21+
22+ prereq = firstprereq(t);
23+ val = prereq ? localpath(t, prereq) : xstrdup("");
24 mustfree = 1;
25 } else if (s[i + 1] == '^' || s[i + 1] == '+' || s[i + 1] == '?') {
26 val = joinlocalprereqs(t);
27@@ -305,6 +315,8 @@ genninjafile(const struct Graph *graph, const char *path, const char *prefix, in
28 fprintf(fp, "build %s: r%d", graph->v[i].name, id);
29 if (graph->v[i].phony)
30 fprintf(fp, " __shin_always_build__");
31+ for (j = 0; j < graph->v[i].impprereqs.n; j++)
32+ fprintf(fp, " %s", graph->v[i].impprereqs.v[j]);
33 for (j = 0; j < graph->v[i].prereqs.n; j++)
34 fprintf(fp, " %s", graph->v[i].prereqs.v[j]);
35 if (graph->v[i].order_only.n) {
36@@ -317,10 +329,13 @@ genninjafile(const struct Graph *graph, const char *path, const char *prefix, in
37 fprintf(fp, " description = [%s] build %s\n\n", prefix, graph->v[i].name);
38 else
39 fprintf(fp, " description = build %s\n\n", graph->v[i].name);
40- } else if (graph->v[i].defined || graph->v[i].prereqs.n > 0 || graph->v[i].order_only.n > 0) {
41+ } else if (graph->v[i].defined || graph->v[i].prereqs.n > 0 ||
42+ graph->v[i].impprereqs.n > 0 || graph->v[i].order_only.n > 0) {
43 fprintf(fp, "build %s: phony", graph->v[i].name);
44 if (graph->v[i].phony)
45 fprintf(fp, " __shin_always_build__");
46+ for (j = 0; j < graph->v[i].impprereqs.n; j++)
47+ fprintf(fp, " %s", graph->v[i].impprereqs.v[j]);
48 for (j = 0; j < graph->v[i].prereqs.n; j++)
49 fprintf(fp, " %s", graph->v[i].prereqs.v[j]);
50 if (graph->v[i].order_only.n) {
+1,
-0
1@@ -170,6 +170,7 @@ dumpgraph(const struct Graph *graph)
2 printf(" [owner=%s]", t->owner);
3 putchar('\n');
4 dumpstrlist("prereqs", &t->prereqs, 2);
5+ dumpstrlist("implicit-prereqs", &t->impprereqs, 2);
6 dumpstrlist("order-only", &t->order_only, 2);
7 dumprecipes(&t->recipes, 2);
8 }
+7,
-8
1@@ -171,17 +171,16 @@ instpatrule(const struct PatRules *rules, const struct Graph *graph, struct Targ
2 free(stem);
3 continue;
4 }
5- /* prepend so $< is the pattern source, not an extra dep
6- * added by an explicit rule for the same target */
7+ /* prepend so $< is the inferred pattern source, before explicit prereqs */
8 if (rules->v[i].prereqs.n > 0) {
9 size_t np = rules->v[i].prereqs.n;
10- t->prereqs.v = xrealloc(t->prereqs.v,
11- (t->prereqs.n + np) * sizeof(t->prereqs.v[0]));
12- memmove(t->prereqs.v + np, t->prereqs.v,
13- t->prereqs.n * sizeof(t->prereqs.v[0]));
14+ t->impprereqs.v = xrealloc(t->impprereqs.v,
15+ (t->impprereqs.n + np) * sizeof(t->impprereqs.v[0]));
16+ memmove(t->impprereqs.v + np, t->impprereqs.v,
17+ t->impprereqs.n * sizeof(t->impprereqs.v[0]));
18 for (j = 0; j < np; j++)
19- t->prereqs.v[j] = applystem(rules->v[i].prereqs.v[j], stem);
20- t->prereqs.n += np;
21+ t->impprereqs.v[j] = applystem(rules->v[i].prereqs.v[j], stem);
22+ t->impprereqs.n += np;
23 }
24 for (j = 0; j < rules->v[i].order_only.n; j++) {
25 char *s;
+69,
-44
1@@ -241,7 +241,7 @@ addtassign(struct GraphState *gs, const struct AssignNode *assign)
2 int
3 buildgraph(const struct RuleSet *ruleset, struct Graph *graph)
4 {
5- size_t i, j, start;
6+ size_t i, j;
7 struct GraphState gs;
8 struct EvalCtx ctx;
9
10@@ -291,12 +291,18 @@ buildgraph(const struct RuleSet *ruleset, struct Graph *graph)
11 }
12 }
13
14- start = 0;
15- while (start < graph->n) {
16- size_t current_n = graph->n;
17- for (i = start; i < current_n; i++) {
18+ for (;;) {
19+ int changed;
20+
21+ changed = 0;
22+ for (i = 0; i < graph->n; i++) {
23 struct Target *t = &graph->v[i];
24 size_t nprereqs;
25+ size_t old_graph_n, old_recipes_n, old_impprereqs_n;
26+
27+ old_graph_n = graph->n;
28+ old_recipes_n = t->recipes.n;
29+ old_impprereqs_n = t->impprereqs.n;
30 /* for targets with no recipe, try a pattern rule or
31 * suffix rule. existing prereqs dont block this,
32 * the rule may still supply the recipe */
33@@ -317,9 +323,17 @@ buildgraph(const struct RuleSet *ruleset, struct Graph *graph)
34 if (targetctx.errors)
35 ctx.errors += targetctx.errors;
36 }
37- nprereqs = t->prereqs.n;
38+ if (graph->n != old_graph_n || t->recipes.n != old_recipes_n ||
39+ t->impprereqs.n != old_impprereqs_n)
40+ changed = 1;
41+ nprereqs = totalprereqs(t);
42 for (j = 0; j < nprereqs; j++) {
43- const char *prereq = t->prereqs.v[j];
44+ const char *prereq;
45+
46+ if (j < t->impprereqs.n)
47+ prereq = t->impprereqs.v[j];
48+ else
49+ prereq = t->prereqs.v[j - t->impprereqs.n];
50
51 if (!findtarget(graph, prereq)) {
52 struct Target *nt;
53@@ -330,11 +344,13 @@ buildgraph(const struct RuleSet *ruleset, struct Graph *graph)
54 nt->name = intern(prereq);
55 if (hasword(gs.phony, nt->name))
56 nt->phony = 1;
57+ changed = 1;
58 t = &graph->v[i];
59 }
60 }
61 }
62- start = current_n;
63+ if (!changed)
64+ break;
65 }
66
67 for (i = 0; i < gs.ntas; i++) {
68@@ -370,43 +386,51 @@ expandgraph(struct Graph *graph)
69 memset(&new_recipes, 0, sizeof(new_recipes));
70 ctx.env = &t->env;
71 ctx.auto_target = t->name;
72- ctx.auto_prereqs = &t->prereqs;
73- ctx.avoid_io = 1;
74- ctx.side_effects = &side_effects;
75-
76- for (k = 0; k < t->recipes.n; k++) {
77- struct Recipe *r = &t->recipes.v[k];
78- char *exp;
79-
80- freestrs(&side_effects);
81- exp = expandstr(&ctx, r->body);
82- for (j = 0; j < side_effects.n; j++) {
83- struct Recipe *nr;
84-
85- new_recipes.v = xrealloc(new_recipes.v,
86- (new_recipes.n + 1) * sizeof(new_recipes.v[0]));
87- nr = &new_recipes.v[new_recipes.n++];
88- memset(nr, 0, sizeof(*nr));
89- nr->body = xstrdup(side_effects.v[j]);
90- nr->silent = r->silent;
91- nr->ignore = r->ignore;
92- }
93- if (exp[0]) {
94- struct Recipe *nr;
95-
96- new_recipes.v = xrealloc(new_recipes.v,
97- (new_recipes.n + 1) * sizeof(new_recipes.v[0]));
98- nr = &new_recipes.v[new_recipes.n++];
99- *nr = *r;
100- nr->body = exp;
101- freesubmake(&nr->sm);
102- nr->submake = parsesubmake(&nr->sm, nr->body);
103- free(r->body);
104- r->body = 0;
105- memset(&r->sm, 0, sizeof(r->sm));
106- } else {
107- free(exp);
108+ {
109+ struct StrList allprereqs;
110+
111+ memset(&allprereqs, 0, sizeof(allprereqs));
112+ addwords(&allprereqs, &t->impprereqs);
113+ addwords(&allprereqs, &t->prereqs);
114+ ctx.auto_prereqs = &allprereqs;
115+ ctx.avoid_io = 1;
116+ ctx.side_effects = &side_effects;
117+
118+ for (k = 0; k < t->recipes.n; k++) {
119+ struct Recipe *r = &t->recipes.v[k];
120+ char *exp;
121+
122+ freestrs(&side_effects);
123+ exp = expandstr(&ctx, r->body);
124+ for (j = 0; j < side_effects.n; j++) {
125+ struct Recipe *nr;
126+
127+ new_recipes.v = xrealloc(new_recipes.v,
128+ (new_recipes.n + 1) * sizeof(new_recipes.v[0]));
129+ nr = &new_recipes.v[new_recipes.n++];
130+ memset(nr, 0, sizeof(*nr));
131+ nr->body = xstrdup(side_effects.v[j]);
132+ nr->silent = r->silent;
133+ nr->ignore = r->ignore;
134+ }
135+ if (exp[0]) {
136+ struct Recipe *nr;
137+
138+ new_recipes.v = xrealloc(new_recipes.v,
139+ (new_recipes.n + 1) * sizeof(new_recipes.v[0]));
140+ nr = &new_recipes.v[new_recipes.n++];
141+ *nr = *r;
142+ nr->body = exp;
143+ freesubmake(&nr->sm);
144+ nr->submake = parsesubmake(&nr->sm, nr->body);
145+ free(r->body);
146+ r->body = 0;
147+ memset(&r->sm, 0, sizeof(r->sm));
148+ } else {
149+ free(exp);
150+ }
151 }
152+ freestrs(&allprereqs);
153 }
154
155 freestrs(&side_effects);
156@@ -431,6 +455,7 @@ freegraph(struct Graph *graph)
157 for (i = 0; i < graph->n; i++) {
158 free(graph->v[i].owner);
159 freestrs(&graph->v[i].prereqs);
160+ freestrs(&graph->v[i].impprereqs);
161 freestrs(&graph->v[i].order_only);
162 freerecipes(&graph->v[i].recipes);
163 freeenv(&graph->v[i].env);
+4,
-0
1@@ -151,6 +151,8 @@ markreachable(const struct Graph *graph, const char *name, unsigned char *seen)
2 seen[idx] = 1;
3 for (i = 0; i < t->prereqs.n; i++)
4 markreachable(graph, t->prereqs.v[i], seen);
5+ for (i = 0; i < t->impprereqs.n; i++)
6+ markreachable(graph, t->impprereqs.v[i], seen);
7 for (i = 0; i < t->order_only.n; i++)
8 markreachable(graph, t->order_only.v[i], seen);
9 }
10@@ -292,6 +294,7 @@ scopegraph(struct Graph *graph, const char *prefix)
11 else
12 graph->v[i].owner = 0;
13 scopelist(&graph->v[i].prereqs, prefix);
14+ scopelist(&graph->v[i].impprereqs, prefix);
15 scopelist(&graph->v[i].order_only, prefix);
16 scoperecipes(&graph->v[i].recipes, prefix);
17 }
18@@ -330,6 +333,7 @@ mergetarget(struct Graph *graph, const struct Target *src)
19 if (src->defined)
20 dst->dcolon = src->dcolon;
21 addwords(&dst->prereqs, &src->prereqs);
22+ addwords(&dst->impprereqs, &src->impprereqs);
23 addwords(&dst->order_only, &src->order_only);
24 addrecipes(&dst->recipes, &src->recipes);
25 return 0;
+4,
-0
1@@ -59,6 +59,7 @@ void addnode(struct NodeList *list, struct Node node);
2 void splitwords(struct StrList *out, const char *s, size_t n);
3 char *cat3(const char *a, const char *b, const char *c);
4 void addwords(struct StrList *dest, const struct StrList *src);
5+void adduniqwords(struct StrList *dest, const struct StrList *src);
6 void addrecipe(struct RecipeList *dest, const char *raw);
7 void addrecipes(struct RecipeList *dest, const struct RecipeList *src);
8 void freestrs(struct StrList *list);
9@@ -70,6 +71,9 @@ void envsetvar(struct Env *env, const char *name, char *val, int simple, enum Or
10 int exported);
11 struct Target *findtarget(struct Graph *graph, const char *name);
12 const struct Target *findctarget(const struct Graph *graph, const char *name);
13+const char *firstprereq(const struct Target *t);
14+char *joinallprereqs(const struct Target *t, const char *sep);
15+size_t totalprereqs(const struct Target *t);
16 void initspecialtargets(struct SpecialTargets *targets);
17 void updatespecialassign(struct SpecialTargets *targets, const char *lhs, const char *rhs);
18 int handlespecialrule(struct SpecialTargets *targets, const struct RuleNode *rule);
+141,
-45
1@@ -7,6 +7,8 @@
2
3 extern char **environ;
4
5+static int matchsuf(const char *suf, const char *name, char **stem);
6+
7 static int
8 sufeq(const char *a, const char *b)
9 {
10@@ -34,11 +36,71 @@ sufactive(const struct SuffixList *list, const char *suf)
11 static int
12 pathorargetexists(const struct Graph *graph, const char *name)
13 {
14- if (findctarget(graph, name))
15+ const struct Target *t;
16+
17+ t = findctarget(graph, name);
18+ if (t && (t->defined || t->recipes.n > 0 || totalprereqs(t) > 0))
19 return 1;
20 return access(name, F_OK) == 0;
21 }
22
23+/* do multi layer suf rule inference: check whether a missing suffix rule
24+ * source can be created by another suffix rule or suffix rule chain, so
25+ * we can infer things like .ssa -> .s -> .o without inventing fkae prereqs
26+ * such as all.scd. (examples from hare compiler makefile which makes
27+ * quite heavy use of this) */
28+static int
29+sufbuildable(const struct SufRules *rules, const struct Graph *graph, const char *name, size_t depth)
30+{
31+ size_t i;
32+
33+ if (pathorargetexists(graph, name))
34+ return 1;
35+ if (depth > rules->active.n + rules->nsufs + rules->nsinglesuf)
36+ return 0;
37+
38+ for (i = 0; i < rules->nsufs; i++) {
39+ size_t j;
40+ char *stem;
41+
42+ if (sufidx(&rules->active, rules->sufs[i].to) < 0 ||
43+ sufidx(&rules->active, rules->sufs[i].from) < 0)
44+ continue;
45+ if (!matchsuf(rules->sufs[i].to, name, &stem))
46+ continue;
47+ j = strlen(stem);
48+ free(stem);
49+ {
50+ char *src;
51+ int ok;
52+
53+ src = xmalloc(j + strlen(rules->sufs[i].from) + 1);
54+ memcpy(src, name, j);
55+ strcpy(src + j, rules->sufs[i].from);
56+ ok = sufbuildable(rules, graph, src, depth + 1);
57+ free(src);
58+ if (ok)
59+ return 1;
60+ }
61+ }
62+
63+ if (strchr(name, '.'))
64+ return 0;
65+ for (i = 0; i < rules->nsinglesuf; i++) {
66+ char *src;
67+ int ok;
68+
69+ if (sufidx(&rules->active, rules->singlesuf[i].from) < 0)
70+ continue;
71+ src = cat3(name, "", rules->singlesuf[i].from);
72+ ok = sufbuildable(rules, graph, src, depth + 1);
73+ free(src);
74+ if (ok)
75+ return 1;
76+ }
77+ return 0;
78+}
79+
80 int
81 issuf(const char *s, char **from, char **to)
82 {
83@@ -123,9 +185,11 @@ expandauto(const char *s, const struct Target *t, const char *stem)
84 if (s[i + 1] == '@') {
85 val = t->name;
86 } else if (s[i + 1] == '<') {
87- val = t->prereqs.n > 0 ? t->prereqs.v[0] : "";
88+ val = firstprereq(t);
89+ if (!val)
90+ val = "";
91 } else if (s[i + 1] == '^' || s[i + 1] == '+' || s[i + 1] == '?') {
92- val = joinstrs(&t->prereqs, " ");
93+ val = joinallprereqs(t, " ");
94 mustfree = 1;
95 } else if (s[i + 1] == '*') {
96 val = stem ? stem : "";
97@@ -315,36 +379,50 @@ clearsufs(struct SuffixList *list)
98 int
99 collectsufrule(struct SufRules *rules, const struct RuleNode *rule)
100 {
101- char *from, *to;
102+ size_t i;
103
104- if (rule->targets.n != 1 || rule->prereqs.n != 0 || rule->order_only.n != 0)
105+ if (rule->targets.n == 0 || rule->prereqs.n != 0 || rule->order_only.n != 0)
106 return 0;
107- if (issinglesuf(rule->targets.v[0], &from)) {
108- size_t k;
109- struct SingleSufRule *sr;
110+ for (i = 0; i < rule->targets.n; i++) {
111+ char *from, *to;
112
113- for (k = 0; k < rules->nsinglesuf; k++) {
114- if (strcmp(rules->singlesuf[k].from, from) == 0) {
115- free(from);
116- freerecipes(&rules->singlesuf[k].recipes);
117- memset(&rules->singlesuf[k].recipes, 0, sizeof(rules->singlesuf[k].recipes));
118- addrecipes(&rules->singlesuf[k].recipes, &rule->recipes);
119- return 1;
120- }
121+ if (issinglesuf(rule->targets.v[i], &from)) {
122+ free(from);
123+ continue;
124+ }
125+ if (issuf(rule->targets.v[i], &from, &to)) {
126+ free(from);
127+ free(to);
128+ continue;
129 }
130- rules->singlesuf = xrealloc(rules->singlesuf,
131- (rules->nsinglesuf + 1) * sizeof(rules->singlesuf[0]));
132- sr = &rules->singlesuf[rules->nsinglesuf++];
133- memset(sr, 0, sizeof(*sr));
134- sr->from = from;
135- addrecipes(&sr->recipes, &rule->recipes);
136- return 1;
137- }
138- if (!issuf(rule->targets.v[0], &from, &to))
139 return 0;
140- {
141+ }
142+ for (i = 0; i < rule->targets.n; i++) {
143+ char *from, *to;
144 size_t k;
145
146+ if (issinglesuf(rule->targets.v[i], &from)) {
147+ struct SingleSufRule *sr;
148+
149+ for (k = 0; k < rules->nsinglesuf; k++) {
150+ if (strcmp(rules->singlesuf[k].from, from) == 0) {
151+ free(from);
152+ freerecipes(&rules->singlesuf[k].recipes);
153+ memset(&rules->singlesuf[k].recipes, 0, sizeof(rules->singlesuf[k].recipes));
154+ addrecipes(&rules->singlesuf[k].recipes, &rule->recipes);
155+ goto next_target;
156+ }
157+ }
158+ rules->singlesuf = xrealloc(rules->singlesuf,
159+ (rules->nsinglesuf + 1) * sizeof(rules->singlesuf[0]));
160+ sr = &rules->singlesuf[rules->nsinglesuf++];
161+ memset(sr, 0, sizeof(*sr));
162+ sr->from = from;
163+ addrecipes(&sr->recipes, &rule->recipes);
164+ continue;
165+ }
166+ issuf(rule->targets.v[i], &from, &to);
167+
168 for (k = 0; k < rules->nsufs; k++) {
169 if (strcmp(rules->sufs[k].from, from) == 0 &&
170 strcmp(rules->sufs[k].to, to) == 0) {
171@@ -353,16 +431,18 @@ collectsufrule(struct SufRules *rules, const struct RuleNode *rule)
172 freerecipes(&rules->sufs[k].recipes);
173 memset(&rules->sufs[k].recipes, 0, sizeof(rules->sufs[k].recipes));
174 addrecipes(&rules->sufs[k].recipes, &rule->recipes);
175- return 1;
176+ goto next_target;
177 }
178 }
179+ rules->sufs = xrealloc(rules->sufs, (rules->nsufs + 1) * sizeof(rules->sufs[0]));
180+ memset(&rules->sufs[rules->nsufs], 0, sizeof(rules->sufs[rules->nsufs]));
181+ rules->sufs[rules->nsufs].from = from;
182+ rules->sufs[rules->nsufs].to = to;
183+ addrecipes(&rules->sufs[rules->nsufs].recipes, &rule->recipes);
184+ rules->nsufs++;
185+ next_target:
186+ ;
187 }
188- rules->sufs = xrealloc(rules->sufs, (rules->nsufs + 1) * sizeof(rules->sufs[0]));
189- memset(&rules->sufs[rules->nsufs], 0, sizeof(rules->sufs[rules->nsufs]));
190- rules->sufs[rules->nsufs].from = from;
191- rules->sufs[rules->nsufs].to = to;
192- addrecipes(&rules->sufs[rules->nsufs].recipes, &rule->recipes);
193- rules->nsufs++;
194 return 1;
195 }
196
197@@ -380,6 +460,7 @@ instsufrule(const struct SufRules *rules,
198 for (j = 0; j < rules->active.n; j++) {
199 for (r = 0; r < rules->nsufs; r++) {
200 char *stem, *src;
201+ int exists;
202
203 if (!sufeq(rules->sufs[r].to, rules->active.v[i]))
204 continue;
205@@ -388,15 +469,23 @@ instsufrule(const struct SufRules *rules,
206 if (!matchsuf(rules->sufs[r].to, t->name, &stem))
207 continue;
208 src = cat3(stem, "", rules->sufs[r].from);
209- if (!pathorargetexists(graph, src)) {
210+ exists = pathorargetexists(graph, src);
211+ if (!exists && !sufbuildable(rules, graph, src, 0)) {
212 free(stem);
213 free(src);
214 continue;
215 }
216- t->prereqs.v = xrealloc(t->prereqs.v, (t->prereqs.n + 1) * sizeof(t->prereqs.v[0]));
217- memmove(t->prereqs.v + 1, t->prereqs.v, t->prereqs.n * sizeof(t->prereqs.v[0]));
218- t->prereqs.v[0] = src;
219- t->prereqs.n++;
220+ if (!hasword(&t->impprereqs, src)) {
221+ t->impprereqs.v = xrealloc(t->impprereqs.v,
222+ (t->impprereqs.n + 1) * sizeof(t->impprereqs.v[0]));
223+ memmove(t->impprereqs.v + 1, t->impprereqs.v,
224+ t->impprereqs.n * sizeof(t->impprereqs.v[0]));
225+ t->impprereqs.v[0] = src;
226+ t->impprereqs.n++;
227+ } else {
228+ free(src);
229+ }
230+ (void)exists;
231 for (k = 0; k < rules->sufs[r].recipes.n; k++) {
232 char *exp;
233
234@@ -422,25 +511,32 @@ instsufrule(const struct SufRules *rules,
235 }
236 }
237
238- if (strchr(t->name, '.'))
239- return 0;
240 for (i = 0; i < rules->active.n; i++) {
241 size_t j;
242
243 for (j = 0; j < rules->nsinglesuf; j++) {
244 char *src;
245+ int exists;
246
247 if (!sufeq(rules->singlesuf[j].from, rules->active.v[i]))
248 continue;
249 src = cat3(t->name, "", rules->singlesuf[j].from);
250- if (!pathorargetexists(graph, src)) {
251+ exists = pathorargetexists(graph, src);
252+ if (!exists && !sufbuildable(rules, graph, src, 0)) {
253 free(src);
254 continue;
255 }
256- t->prereqs.v = xrealloc(t->prereqs.v, (t->prereqs.n + 1) * sizeof(t->prereqs.v[0]));
257- memmove(t->prereqs.v + 1, t->prereqs.v, t->prereqs.n * sizeof(t->prereqs.v[0]));
258- t->prereqs.v[0] = src;
259- t->prereqs.n++;
260+ if (!hasword(&t->impprereqs, src)) {
261+ t->impprereqs.v = xrealloc(t->impprereqs.v,
262+ (t->impprereqs.n + 1) * sizeof(t->impprereqs.v[0]));
263+ memmove(t->impprereqs.v + 1, t->impprereqs.v,
264+ t->impprereqs.n * sizeof(t->impprereqs.v[0]));
265+ t->impprereqs.v[0] = src;
266+ t->impprereqs.n++;
267+ } else {
268+ free(src);
269+ }
270+ (void)exists;
271 for (k = 0; k < rules->singlesuf[j].recipes.n; k++) {
272 char *exp;
273
+1,
-0
1@@ -205,6 +205,7 @@ struct Target {
2 const char *name; /* interned */
3 char *owner;
4 struct StrList prereqs;
5+ struct StrList impprereqs;
6 struct StrList order_only;
7 struct RecipeList recipes;
8 struct Env env;
+44,
-1
1@@ -487,7 +487,8 @@ defaulttarget(const struct Graph *graph, const char *owner)
2 continue;
3 if (graph->v[i].name == intern("clean") || graph->v[i].name == intern("fmt"))
4 continue;
5- if (graph->v[i].recipes.n > 0 || graph->v[i].prereqs.n > 0 || graph->v[i].order_only.n > 0)
6+ if (graph->v[i].recipes.n > 0 || graph->v[i].prereqs.n > 0 ||
7+ graph->v[i].impprereqs.n > 0 || graph->v[i].order_only.n > 0)
8 return &graph->v[i];
9 }
10 return 0;
11@@ -533,6 +534,18 @@ addwords(struct StrList *dest, const struct StrList *src)
12 dest->v[dest->n++] = xstrdup(src->v[i]);
13 }
14
15+void
16+adduniqwords(struct StrList *dest, const struct StrList *src)
17+{
18+ size_t i;
19+
20+ for (i = 0; i < src->n; i++) {
21+ if (hasword(dest, src->v[i]))
22+ continue;
23+ addstr(dest, src->v[i]);
24+ }
25+}
26+
27 void
28 addrecipe(struct RecipeList *dest, const char *raw)
29 {
30@@ -613,6 +626,36 @@ findctarget(const struct Graph *graph, const char *name)
31 return findtarget0(graph, name);
32 }
33
34+const char *
35+firstprereq(const struct Target *t)
36+{
37+ if (t->impprereqs.n > 0)
38+ return t->impprereqs.v[0];
39+ if (t->prereqs.n > 0)
40+ return t->prereqs.v[0];
41+ return 0;
42+}
43+
44+char *
45+joinallprereqs(const struct Target *t, const char *sep)
46+{
47+ struct StrList list;
48+ char *s;
49+
50+ memset(&list, 0, sizeof(list));
51+ addwords(&list, &t->impprereqs);
52+ addwords(&list, &t->prereqs);
53+ s = joinstrs(&list, sep);
54+ freestrs(&list);
55+ return s;
56+}
57+
58+size_t
59+totalprereqs(const struct Target *t)
60+{
61+ return t->prereqs.n + t->impprereqs.n;
62+}
63+
64 void
65 freestrs(struct StrList *list)
66 {