commit 47989f3
shrub
·
2026-07-03 14:01:55 +0000 UTC
parent 3025f65
handle multiple conflicting submake branches by picking the wanted branch in cases where there is a conflict
9 files changed,
+236,
-18
+71,
-7
1@@ -127,11 +127,12 @@ exportenv(const struct Target *t)
2 static char *
3 normalizebody(const char *s)
4 {
5- size_t i, n, j;
6+ size_t i, n, j, cap;
7 char *out;
8
9 n = strlen(s);
10- out = xmalloc(n + 1);
11+ cap = n * 2 + 1;
12+ out = xmalloc(cap);
13 j = 0;
14 for (i = 0; i < n; i++) {
15 if (s[i] == '\\' && i + 1 < n && s[i + 1] == '\n') {
16@@ -209,6 +210,60 @@ findrule(struct Rule *rules, size_t n, const char *cmd)
17 return 0;
18 }
19
20+static const struct Target *
21+wanteddefault(const struct Graph *graph, const char *prefix)
22+{
23+ const struct Target *t;
24+ size_t i;
25+
26+ t = defaulttarget(graph, prefix);
27+ if (t && t->wanted)
28+ return t;
29+ for (i = 0; i < graph->n; i++) {
30+ if (!targetownedby(&graph->v[i], prefix))
31+ continue;
32+ if (graph->v[i].wanted)
33+ return &graph->v[i];
34+ }
35+ return 0;
36+}
37+
38+static int
39+emitgraphsub(const struct Graph *graph, size_t idx)
40+{
41+ size_t i;
42+ int saw_conflict, saw_wanted;
43+
44+ saw_conflict = 0;
45+ saw_wanted = 0;
46+ for (i = 0; i < graph->nsubs; i++) {
47+ if (i == idx)
48+ continue;
49+ if (strcmp(graph->subs[i].prefix, graph->subs[idx].prefix) != 0)
50+ continue;
51+ saw_conflict = 1;
52+ if (graph->subs[i].wanted)
53+ saw_wanted = 1;
54+ }
55+ if (!saw_conflict)
56+ return 1;
57+ if (graph->subs[idx].wanted) {
58+ for (i = 0; i < idx; i++) {
59+ if (strcmp(graph->subs[i].prefix, graph->subs[idx].prefix) == 0 &&
60+ graph->subs[i].wanted)
61+ return 0;
62+ }
63+ return 1;
64+ }
65+ if (saw_wanted)
66+ return 0;
67+ for (i = 0; i < idx; i++) {
68+ if (strcmp(graph->subs[i].prefix, graph->subs[idx].prefix) == 0)
69+ return 0;
70+ }
71+ return 1;
72+}
73+
74 static int
75 genninjafile(const struct Graph *graph, const char *path, const char *prefix, int root)
76 {
77@@ -219,6 +274,8 @@ genninjafile(const struct Graph *graph, const char *path, const char *prefix, in
78 size_t nrules;
79
80 for (i = 0; i < graph->nsubs; i++) {
81+ if (!emitgraphsub(graph, i))
82+ continue;
83 char *childpath;
84
85 childpath = joinpath(graph->subs[i].cwd, "build.ninja");
86@@ -238,10 +295,17 @@ genninjafile(const struct Graph *graph, const char *path, const char *prefix, in
87 ruleid = 0;
88 rules = 0;
89 nrules = 0;
90- for (i = 0; i < graph->nsubs; i++)
91+ for (i = 0; i < graph->nsubs; i++) {
92+ if (!emitgraphsub(graph, i))
93+ continue;
94 fprintf(fp, "subninja %s/build.ninja\n", graph->subs[i].prefix);
95- if (graph->nsubs)
96- fputc('\n', fp);
97+ }
98+ for (i = 0; i < graph->nsubs; i++) {
99+ if (emitgraphsub(graph, i)) {
100+ fputc('\n', fp);
101+ break;
102+ }
103+ }
104 for (i = 0; i < graph->n; i++) {
105 if (!targetownedby(&graph->v[i], prefix))
106 continue;
107@@ -295,8 +359,8 @@ genninjafile(const struct Graph *graph, const char *path, const char *prefix, in
108 fprintf(fp, "\n\n");
109 }
110 }
111- if (root && defaulttarget(graph, prefix))
112- fprintf(fp, "default %s\n", defaulttarget(graph, prefix)->name);
113+ if (root && wanteddefault(graph, prefix))
114+ fprintf(fp, "default %s\n", wanteddefault(graph, prefix)->name);
115
116 fclose(fp);
117 for (i = 0; i < nrules; i++)
+1,
-0
1@@ -431,6 +431,7 @@ main(int argc, char **argv)
2 if (env_override)
3 addstr(&sg.flags, "-e");
4 sg.mode = mode;
5+ sg.wanted = 1;
6 free(assigns);
7 free(goals);
8 assigns = 0;
+18,
-2
1@@ -184,20 +184,29 @@ issubstref(const char *s, size_t n, size_t *colon, size_t *eq)
2 static ptrdiff_t
3 findargcomma(const char *s, size_t n)
4 {
5- size_t i, depth;
6+ size_t i, depth, rawdepth;
7
8 depth = 0;
9+ rawdepth = 0;
10 for (i = 0; i < n; i++) {
11 if (s[i] == '$' && i + 1 < n && (s[i + 1] == '(' || s[i + 1] == '{')) {
12 depth++;
13 i++;
14 continue;
15 }
16+ if (s[i] == '(') {
17+ rawdepth++;
18+ continue;
19+ }
20+ if (s[i] == ')' && rawdepth > 0) {
21+ rawdepth--;
22+ continue;
23+ }
24 if ((s[i] == ')' || s[i] == '}') && depth > 0) {
25 depth--;
26 continue;
27 }
28- if (s[i] == ',' && depth == 0)
29+ if (s[i] == ',' && depth == 0 && rawdepth == 0)
30 return (ptrdiff_t)i;
31 }
32 return -1;
33@@ -207,15 +216,22 @@ static size_t
34 findclose(const char *s, size_t i, size_t n, char close)
35 {
36 size_t j, inner;
37+ char open;
38
39 j = i + 2;
40 inner = 1;
41+ open = close == ')' ? '(' : '{';
42 while (j < n && inner) {
43 if (s[j] == '$' && j + 1 < n && (s[j + 1] == '(' || s[j + 1] == '{')) {
44 inner++;
45 j += 2;
46 continue;
47 }
48+ if (s[j] == open) {
49+ inner++;
50+ j++;
51+ continue;
52+ }
53 if (s[j] == close)
54 inner--;
55 j++;
+29,
-2
1@@ -10,6 +10,7 @@
2 /* implementations of gnu make builtin functions*/
3
4 static char *trimspacesdup(const char *s);
5+static char *shellquote(const char *s);
6
7 static int
8 matchword(const char *patterns, const char *word, size_t nword)
9@@ -71,6 +72,29 @@ matchword(const char *patterns, const char *word, size_t nword)
10 return 0;
11 }
12
13+static char *
14+shellquote(const char *s)
15+{
16+ size_t i, len, cap;
17+ char *out;
18+
19+ cap = strlen(s) * 4 + 3;
20+ out = xmalloc(cap);
21+ len = 0;
22+ out[len++] = '\'';
23+ for (i = 0; s[i]; i++) {
24+ if (s[i] == '\'') {
25+ memcpy(out + len, "'\\''", 4);
26+ len += 4;
27+ } else {
28+ out[len++] = s[i];
29+ }
30+ }
31+ out[len++] = '\'';
32+ out[len] = 0;
33+ return out;
34+}
35+
36 char *
37 fnwildcard(const char *patterns)
38 {
39@@ -532,12 +556,15 @@ fninfo(struct EvalCtx *ctx, const char *args)
40
41 text = expandstr(ctx, args);
42 if (ctx->avoid_io && ctx->side_effects) {
43- char *cmd;
44+ char *cmd, *quoted;
45
46 /* this is kinda a hack, but it gets more tests to pass:
47 * we tag delayed $(info) output so the test wrapper can distinguish
48 synthetic info side effects from real normal echo-having recipes. */
49- cmd = cat3("echo ", text, " # __shin_info__");
50+ quoted = shellquote(text);
51+ cmd = xmalloc(strlen("printf '%s\\n' # __shin_info__") + strlen(quoted) + 1);
52+ sprintf(cmd, "printf '%%s\\n' %s # __shin_info__", quoted);
53+ free(quoted);
54 addstr(ctx->side_effects, cmd);
55 free(cmd);
56 } else {
+94,
-7
1@@ -170,6 +170,24 @@ markreachable(const struct Graph *graph, const char *name, unsigned char *seen)
2 markreachable(graph, t->order_only.v[i], seen);
3 }
4
5+static int
6+goalreaches(const struct Graph *graph, const char *goal, const char *name)
7+{
8+ const struct Target *t;
9+ unsigned char *seen;
10+ int ok;
11+
12+ t = findctarget(graph, name);
13+ if (!t)
14+ return 0;
15+ seen = xmalloc(graph->n ? graph->n : 1);
16+ memset(seen, 0, graph->n ? graph->n : 1);
17+ markreachable(graph, goal, seen);
18+ ok = seen[(size_t)(t - graph->v)] != 0;
19+ free(seen);
20+ return ok;
21+}
22+
23 static int
24 allsubmake(const struct Target *t)
25 {
26@@ -351,6 +369,7 @@ mergetarget(struct Graph *graph, const struct Target *src)
27 if (!dst->defined && src->defined)
28 dst->dcolon = src->dcolon;
29 dst->defined |= src->defined;
30+ dst->wanted |= src->wanted;
31 if (src->defined)
32 dst->dcolon = src->dcolon;
33 addwords(&dst->prereqs, &src->prereqs);
34@@ -394,18 +413,77 @@ subgraphorder(struct Graph *graph, const char *owner, const struct StrList *prev
35 }
36 }
37
38+static int
39+sameinvocation(const struct SubGraph *a, const struct SubGraph *b);
40+
41 static struct SubGraph *
42-findgraphsub(struct Graph *graph, const char *prefix)
43+findsubgraph(struct Graph *graph, const struct SubGraph *want)
44 {
45 size_t i;
46
47 for (i = 0; i < graph->nsubs; i++) {
48- if (sameprefix(graph->subs[i].prefix, prefix))
49+ if (!sameprefix(graph->subs[i].prefix, want->prefix))
50+ continue;
51+ if (sameinvocation(&graph->subs[i], want))
52 return &graph->subs[i];
53 }
54 return 0;
55 }
56
57+static int
58+haswantedclash(const struct Graph *graph, const struct SubGraph *want)
59+{
60+ size_t i;
61+
62+ for (i = 0; i < graph->nsubs; i++) {
63+ const struct SubGraph *sg;
64+
65+ sg = &graph->subs[i];
66+ if (!sameprefix(sg->prefix, want->prefix))
67+ continue;
68+ if (sameinvocation(sg, want))
69+ continue;
70+ if (sg->wanted && want->wanted)
71+ return 1;
72+ }
73+ return 0;
74+}
75+
76+static void
77+markwanted(struct SubGraph *sg)
78+{
79+ struct StrList roots;
80+ unsigned char *reachable;
81+ size_t i, n;
82+
83+ n = sg->graph.n;
84+ if (!sg->wanted) {
85+ for (i = 0; i < n; i++)
86+ sg->graph.v[i].wanted = 0;
87+ return;
88+ }
89+ if (resolvegoals(sg, &roots) < 0)
90+ return;
91+ reachable = xmalloc(n ? n : 1);
92+ memset(reachable, 0, n ? n : 1);
93+ for (i = 0; i < roots.n; i++)
94+ markreachable(&sg->graph, roots.v[i], reachable);
95+ for (i = 0; i < n; i++)
96+ sg->graph.v[i].wanted = reachable[i] != 0;
97+ free(reachable);
98+ freestrs(&roots);
99+}
100+
101+static void
102+markwantedtree(struct SubGraph *sg)
103+{
104+ size_t i;
105+
106+ markwanted(sg);
107+ for (i = 0; i < sg->graph.nsubs; i++)
108+ markwantedtree(&sg->graph.subs[i]);
109+}
110+
111 static int
112 sameinvocation(const struct SubGraph *a, const struct SubGraph *b)
113 {
114@@ -452,13 +530,13 @@ mergechildgraph(struct SubGraph *parent,
115 struct Target *t;
116 size_t i;
117
118- known = sameprefix(parent->prefix, child->prefix) ? 0 : findgraphsub(&parent->graph, child->prefix);
119- if (known && !sameinvocation(known, child)) {
120+ known = sameprefix(parent->prefix, child->prefix) ? 0 : findsubgraph(&parent->graph, child);
121+ if (!sameprefix(parent->prefix, child->prefix) && haswantedclash(&parent->graph, child)) {
122 fprintf(stderr, "multiple incompatible subgraphs map to %s/build.ninja\n",
123 child->prefix ? child->prefix : ".");
124 return -1;
125 }
126- if (!known) {
127+ if (!known && child->wanted) {
128 reachable = xmalloc(child->graph.n ? child->graph.n : 1);
129 memset(reachable, 0, child->graph.n ? child->graph.n : 1);
130 for (i = 0; i < goals->n; i++)
131@@ -479,6 +557,11 @@ mergechildgraph(struct SubGraph *parent,
132 return -1;
133 }
134 for (i = 0; i < goals->n; i++) {
135+ /* the child graph might have a default goal like "lib/all" to build
136+ * the parent. if we add that goal back as a prerequisite of
137+ * the parent target would create a synthetic cycle; dont do that. */
138+ if (goalreaches(&child->graph, goals->v[i], tname))
139+ continue;
140 if (!hasword(&t->prereqs, goals->v[i]))
141 addstr(&t->prereqs, goals->v[i]);
142 }
143@@ -512,6 +595,7 @@ expandsubgraphs(struct SubGraph *sg, struct SubGraphStack *stack)
144 unsigned char *reachable;
145 size_t i, n;
146
147+ markwanted(sg);
148 if (resolvegoals(sg, &roots) < 0)
149 return -1;
150 for (i = 0; i < sg->graph.n; i++) {
151@@ -554,6 +638,7 @@ expandsubgraphs(struct SubGraph *sg, struct SubGraphStack *stack)
152 child.cwd = r->sm.dir ? joinpath(sg->cwd, r->sm.dir) : xstrdup(sg->cwd);
153 child.prefix = joinprefix(sg->prefix, r->sm.dir);
154 child.mode = sg->mode;
155+ child.wanted = sg->graph.v[i].wanted;
156 addstr(&child.parents, tname);
157 if (r->sm.makefile)
158 child.makefile = xstrdup(r->sm.makefile);
159@@ -581,7 +666,7 @@ expandsubgraphs(struct SubGraph *sg, struct SubGraphStack *stack)
160 if (rc > 0) {
161 struct SubGraph *known;
162
163- known = sameprefix(sg->prefix, child.prefix) ? 0 : findgraphsub(&sg->graph, child.prefix);
164+ known = sameprefix(sg->prefix, child.prefix) ? 0 : findsubgraph(&sg->graph, &child);
165 if (known && sameinvocation(known, &child) && child.goals.n > 0) {
166 size_t gi;
167 struct Target *pt;
168@@ -722,7 +807,7 @@ buildsubgraph0(struct SubGraph *sg, struct SubGraphStack *stack)
169 rc = eval(path, &ast, sg->assigns.n ? &pre : 0, envoverride, sg->mode, &rs);
170 if (rc < 0)
171 goto out;
172- rc = buildgraph(&rs, &sg->goals, &sg->graph, sg->mode);
173+ rc = buildgraph(&rs, sg->wanted ? &sg->goals : 0, &sg->graph, sg->mode);
174 if (rc < 0)
175 goto out;
176 rc = expandgraph(&sg->graph, sg->mode);
177@@ -730,6 +815,8 @@ buildsubgraph0(struct SubGraph *sg, struct SubGraphStack *stack)
178 goto out;
179 scopegraph(&sg->graph, sg->prefix);
180 rc = expandsubgraphs(sg, stack);
181+ if (rc >= 0)
182+ markwantedtree(sg);
183 out:
184 freeruleset(&rs);
185 freeast(&pre);
+2,
-0
1@@ -225,6 +225,7 @@ struct Target {
2 int dcolon;
3 int defined;
4 int phony;
5+ int wanted;
6 };
7
8 struct SubGraph;
9@@ -247,6 +248,7 @@ struct SubGraph {
10 struct StrList goals;
11 struct Graph graph;
12 enum ShinMode mode;
13+ int wanted;
14 };
15
16 int preproc(const char *path, struct Pre *pre, enum ShinMode mode);
+4,
-0
1@@ -0,0 +1,4 @@
2+X = $(if ,ok,echo "(skip)")
3+
4+all:
5+ @echo $(X)
+1,
-0
1@@ -0,0 +1 @@
2+echo (skip)
+16,
-0
1@@ -0,0 +1,16 @@
2+{
3+ "case": "t004",
4+ "category": "variables",
5+ "compare_output": true,
6+ "description": "function arguments preserve balanced literal parentheses",
7+ "details": "",
8+ "env": {},
9+ "expected_exit": 0,
10+ "options": "",
11+ "options_mode": "argv",
12+ "output_mode": "exact",
13+ "setup": [],
14+ "stdin": "",
15+ "suite": "shin",
16+ "timeout_seconds": 60
17+}