commit 32cdfd6
shrub
·
2026-05-25 13:40:46 +0000 UTC
parent a076350
pattern rules, static pattern rules, and some export things
11 files changed,
+277,
-26
+14,
-0
1@@ -196,6 +196,18 @@ rulecmd(const struct Target *t)
2 return escaped;
3 }
4
5+static int
6+issubgraphparent(const struct Graph *graph, const char *name)
7+{
8+ size_t i;
9+
10+ for (i = 0; i < graph->nsubs; i++) {
11+ if (hasword(&graph->subs[i].parents, name))
12+ return 1;
13+ }
14+ return 0;
15+}
16+
17 static int
18 findrule(struct Rule *rules, size_t n, const char *cmd)
19 {
20@@ -244,6 +256,8 @@ genninjafile(const struct Graph *graph, const char *path, const char *prefix, in
21 for (i = 0; i < graph->n; i++) {
22 if (!targetownedby(&graph->v[i], prefix))
23 continue;
24+ if (issubgraphparent(graph, graph->v[i].name) && graph->v[i].recipes.n == 0)
25+ continue;
26 if (graph->v[i].recipes.n > 0) {
27 char *cmd;
28 int id;
+9,
-1
1@@ -128,7 +128,9 @@ static void
2 dumpassign(const struct AssignNode *assign, int depth)
3 {
4 printindent(depth);
5- printf("assign %s %s %s\n", assign->lhs, assignopname(assign->op), assign->rhs);
6+ printf("assign%s %s %s %s\n",
7+ assign->exported ? " [exported]" : "",
8+ assign->lhs, assignopname(assign->op), assign->rhs);
9 if (assign->tspec)
10 dumpstrlist("targets", &assign->targets, depth + 1);
11 }
12@@ -139,6 +141,10 @@ dumprule(const struct RuleNode *rule, int depth)
13 printindent(depth);
14 printf("rule\n");
15 dumpstrlist("targets", &rule->targets, depth + 1);
16+ if (rule->target_pattern) {
17+ printindent(depth + 1);
18+ printf("target-pattern: %s\n", rule->target_pattern);
19+ }
20 dumpstrlist("prereqs", &rule->prereqs, depth + 1);
21 dumpstrlist("order-only", &rule->order_only, depth + 1);
22 dumprecipes(&rule->recipes, depth + 1);
23@@ -151,6 +157,8 @@ dumpruleset(const struct RuleSet *ruleset)
24
25 printf("ruleset (%zu vars, %zu target vars, %zu rules)\n",
26 ruleset->nvars, ruleset->ntvars, ruleset->nrules);
27+ printindent(1);
28+ printf("export_all=%d posix=%d\n", ruleset->export_all, ruleset->posix);
29 for (i = 0; i < ruleset->nvars; i++)
30 dumpassign(&ruleset->vars[i], 1);
31 for (i = 0; i < ruleset->ntvars; i++)
+84,
-0
1@@ -1,4 +1,5 @@
2 #include "posix.h"
3+#include "gnu/pattern.h"
4
5 #include <unistd.h>
6 #include <stdio.h>
7@@ -72,6 +73,60 @@ seeddefaults(struct Env *env, const struct DefaultVar *vars)
8 ".f.o:\n" \
9 "\t$(FC) $(FFLAGS) -c -o $@ $<\n"
10
11+#define IMPPATRULES_GNU \
12+ "%: %.o\n" \
13+ "\t$(CC) $(CFLAGS) $(CPPFLAGS) $(LDFLAGS) $^ $(LOADLIBES) $(LDLIBS) -o $@\n" \
14+ "%: %.c\n" \
15+ "\t$(CC) $(CFLAGS) $(CPPFLAGS) $(LDFLAGS) $^ $(LOADLIBES) $(LDLIBS) -o $@\n" \
16+ "%.o: %.c\n" \
17+ "\t$(CC) $(CPPFLAGS) $(CFLAGS) -c -o $@ $<\n" \
18+ "%: %.cc\n" \
19+ "\t$(CXX) $(CXXFLAGS) $(CPPFLAGS) $(LDFLAGS) $^ $(LOADLIBES) $(LDLIBS) -o $@\n" \
20+ "%.o: %.cc\n" \
21+ "\t$(CXX) $(CPPFLAGS) $(CXXFLAGS) -c -o $@ $<\n" \
22+ "%: %.C\n" \
23+ "\t$(CXX) $(CXXFLAGS) $(CPPFLAGS) $(LDFLAGS) $^ $(LOADLIBES) $(LDLIBS) -o $@\n" \
24+ "%.o: %.C\n" \
25+ "\t$(CXX) $(CPPFLAGS) $(CXXFLAGS) -c -o $@ $<\n" \
26+ "%: %.cpp\n" \
27+ "\t$(CXX) $(CXXFLAGS) $(CPPFLAGS) $(LDFLAGS) $^ $(LOADLIBES) $(LDLIBS) -o $@\n" \
28+ "%.o: %.cpp\n" \
29+ "\t$(CXX) $(CPPFLAGS) $(CXXFLAGS) -c -o $@ $<\n" \
30+ "%: %.p\n" \
31+ "\t$(FC) $(FFLAGS) $(LDFLAGS) $^ $(LOADLIBES) $(LDLIBS) -o $@\n" \
32+ "%.o: %.p\n" \
33+ "\t$(FC) $(FFLAGS) -c -o $@ $<\n" \
34+ "%: %.f\n" \
35+ "\t$(FC) $(FFLAGS) $(LDFLAGS) $^ $(LOADLIBES) $(LDLIBS) -o $@\n" \
36+ "%.o: %.f\n" \
37+ "\t$(FC) $(FFLAGS) -c -o $@ $<\n" \
38+ "%: %.F\n" \
39+ "\t$(FC) $(FFLAGS) $(CPPFLAGS) $(LDFLAGS) $^ $(LOADLIBES) $(LDLIBS) -o $@\n" \
40+ "%.o: %.F\n" \
41+ "\t$(FC) $(CPPFLAGS) $(FFLAGS) -c -o $@ $<\n" \
42+ "%.f: %.F\n" \
43+ "\t$(FC) $(CPPFLAGS) $(FFLAGS) -o $@ -c $<\n" \
44+ "%: %.m\n" \
45+ "\t$(OBJC) $(OBJCFLAGS) $(CPPFLAGS) $(LDFLAGS) $^ $(LOADLIBES) $(LDLIBS) -o $@\n" \
46+ "%.o: %.m\n" \
47+ "\t$(OBJC) $(CPPFLAGS) $(OBJCFLAGS) -c -o $@ $<\n" \
48+ "%: %.r\n" \
49+ "\t$(FC) $(FFLAGS) $(RFLAGS) $(LDFLAGS) $^ $(LOADLIBES) $(LDLIBS) -o $@\n" \
50+ "%.o: %.r\n" \
51+ "\t$(FC) $(FFLAGS) $(RFLAGS) -c -o $@ $<\n" \
52+ "%.f: %.r\n" \
53+ "\t$(FC) $(FFLAGS) $(RFLAGS) -F $< > $@\n" \
54+ "%: %.s\n" \
55+ "\t$(AS) $(ASFLAGS) $(LDFLAGS) $^ $(LOADLIBES) $(LDLIBS) -o $@\n" \
56+ "%.o: %.s\n" \
57+ "\t$(AS) $(ASFLAGS) -o $@ $<\n" \
58+ "%: %.S\n" \
59+ "\t$(CC) $(ASFLAGS) $(CPPFLAGS) $(LDFLAGS) $^ $(LOADLIBES) $(LDLIBS) -o $@\n" \
60+ "%.o: %.S\n" \
61+ "\t$(CC) $(ASFLAGS) $(CPPFLAGS) -c -o $@ $<\n" \
62+ "%.s: %.S\n" \
63+ "\t$(CC) $(ASFLAGS) $(CPPFLAGS) -E $< > $@\n"
64+
65 static int
66 loadimprules(struct SufRules *rules, const char *src)
67 {
68@@ -97,6 +152,27 @@ loadimprules(struct SufRules *rules, const char *src)
69 return 0;
70 }
71
72+static int
73+loadimppatrules(struct PatRules *rules, const char *src)
74+{
75+ struct Ast ast;
76+ size_t i;
77+ int rc;
78+
79+ rc = parse("<built-in-pattern-rules>", src, &ast, MODE_GNU);
80+ if (rc < 0)
81+ return -1;
82+ for (i = 0; i < ast.n; i++) {
83+ struct Node *node = &ast.v[i];
84+
85+ if (node->kind != NODE_RULE)
86+ continue;
87+ collectpat(rules, &node->data.rule);
88+ }
89+ freeast(&ast);
90+ return 0;
91+}
92+
93 void
94 seedenv(struct Env *env, int isposix, int envoverride, enum ShinMode mode)
95 {
96@@ -204,3 +280,11 @@ imprules(struct SufRules *rules, int posix, enum ShinMode mode)
97 exit(1);
98 }
99 }
100+
101+int
102+imppatrules(struct PatRules *rules, enum ShinMode mode)
103+{
104+ if (mode != MODE_GNU)
105+ return 0;
106+ return loadimppatrules(rules, IMPPATRULES_GNU);
107+}
+10,
-1
1@@ -264,9 +264,15 @@ evalexport(struct EvalCtx *ctx, const struct ExportNode *exp)
2 size_t i, j;
3
4 if (exp->all) {
5+ size_t j;
6+
7 ctx->export_all = exp->exported;
8 for (i = 0; i < ctx->env->n; i++)
9 ctx->env->v[i].exported = exp->exported;
10+ for (j = 0; j < ctx->out->nvars; j++)
11+ ctx->out->vars[j].exported = exp->exported;
12+ for (j = 0; j < ctx->out->ntvars; j++)
13+ ctx->out->tvars[j].exported = exp->exported;
14 return;
15 }
16 memset(&names, 0, sizeof(names));
17@@ -410,6 +416,8 @@ addrulesetrule(struct RuleSet *out, const struct RuleNode *src, struct EvalCtx *
18 memset(dst, 0, sizeof(*dst));
19 dst->dcolon = src->dcolon;
20 copywords(&dst->targets, &src->targets, ctx);
21+ if (src->target_pattern)
22+ dst->target_pattern = expandstr(ctx, src->target_pattern);
23 copywords(&dst->prereqs, &src->prereqs, ctx);
24 copywords(&dst->order_only, &src->order_only, ctx);
25 addrecipes(&dst->recipes, &src->recipes);
26@@ -672,7 +680,7 @@ evalnodes(const struct NodeList *in, struct RuleSet *out, struct EvalCtx *ctx)
27 if (out)
28 addwords(&out->phony, &targets.phony);
29 if (out)
30- out->export_all = targets.export_all;
31+ out->export_all = ctx->export_all || targets.export_all;
32 if (out)
33 out->posix = targets.posix;
34 freestrs(&targets.phony);
35@@ -748,6 +756,7 @@ freerules(struct RuleNode *v, size_t n)
36
37 for (i = 0; i < n; i++) {
38 freestrs(&v[i].targets);
39+ free(v[i].target_pattern);
40 freestrs(&v[i].prereqs);
41 freestrs(&v[i].order_only);
42 freerecipes(&v[i].recipes);
+12,
-12
1@@ -6,8 +6,8 @@
2
3 /*helpers for handling gnu % pattern rules*/
4
5-static char *
6-matchpat(const char *pat, const char *name)
7+char *
8+patmatchstem(const char *pat, const char *name)
9 {
10 const char *p;
11 size_t pre, suf, n, plen;
12@@ -29,8 +29,8 @@ matchpat(const char *pat, const char *name)
13 return xstrndup(name + pre, n - pre - suf);
14 }
15
16-static char *
17-applystem(const char *s, const char *stem)
18+char *
19+patapplystem(const char *s, const char *stem)
20 {
21 const char *p;
22 size_t pre, suf, stemlen, slen;
23@@ -51,8 +51,8 @@ applystem(const char *s, const char *stem)
24 return out;
25 }
26
27-static char *
28-expandstem(const char *s, const char *stem)
29+char *
30+patexpandstem(const char *s, const char *stem)
31 {
32 size_t i, n, cap, len;
33 char *out;
34@@ -98,7 +98,7 @@ patmatches(const char *pat, const char *name)
35 {
36 char *stem;
37
38- stem = matchpat(pat, name);
39+ stem = patmatchstem(pat, name);
40 if (!stem)
41 return 0;
42 free(stem);
43@@ -147,7 +147,7 @@ patruleviable(const struct PatRule *rule, const struct Graph *graph, const char
44 char *s;
45 int ok;
46
47- s = applystem(rule->prereqs.v[i], stem);
48+ s = patapplystem(rule->prereqs.v[i], stem);
49 ok = pattargetexists(graph, s);
50 free(s);
51 if (!ok)
52@@ -164,7 +164,7 @@ instpatrule(const struct PatRules *rules, const struct Graph *graph, struct Targ
53 for (i = 0; i < rules->n; i++) {
54 char *stem;
55
56- stem = matchpat(rules->v[i].target, t->name);
57+ stem = patmatchstem(rules->v[i].target, t->name);
58 if (!stem)
59 continue;
60 if (!patruleviable(&rules->v[i], graph, stem)) {
61@@ -179,13 +179,13 @@ instpatrule(const struct PatRules *rules, const struct Graph *graph, struct Targ
62 memmove(t->impprereqs.v + np, t->impprereqs.v,
63 t->impprereqs.n * sizeof(t->impprereqs.v[0]));
64 for (j = 0; j < np; j++)
65- t->impprereqs.v[j] = applystem(rules->v[i].prereqs.v[j], stem);
66+ t->impprereqs.v[j] = patapplystem(rules->v[i].prereqs.v[j], stem);
67 t->impprereqs.n += np;
68 }
69 for (j = 0; j < rules->v[i].order_only.n; j++) {
70 char *s;
71
72- s = applystem(rules->v[i].order_only.v[j], stem);
73+ s = patapplystem(rules->v[i].order_only.v[j], stem);
74 t->order_only.v = xrealloc(t->order_only.v,
75 (t->order_only.n + 1) * sizeof(t->order_only.v[0]));
76 t->order_only.v[t->order_only.n++] = s;
77@@ -193,7 +193,7 @@ instpatrule(const struct PatRules *rules, const struct Graph *graph, struct Targ
78 for (j = 0; j < rules->v[i].recipes.n; j++) {
79 char *exp;
80
81- exp = expandstem(rules->v[i].recipes.v[j].body, stem);
82+ exp = patexpandstem(rules->v[i].recipes.v[j].body, stem);
83 if (!exp[0]) {
84 free(exp);
85 continue;
+3,
-0
1@@ -17,6 +17,9 @@ struct PatRules {
2
3 int ispat(const char *s);
4 int patmatches(const char *pat, const char *name);
5+char *patmatchstem(const char *pat, const char *name);
6+char *patapplystem(const char *s, const char *stem);
7+char *patexpandstem(const char *s, const char *stem);
8 void collectpat(struct PatRules *rules, const struct RuleNode *rule);
9 int instpatrule(const struct PatRules *rules, const struct Graph *graph, struct Target *t, struct EvalCtx *ctx);
10 void freepatrule(struct PatRules *rules);
+57,
-5
1@@ -150,6 +150,26 @@ addglobword(struct StrList *out, const char *s)
2 globfree(&g);
3 }
4
5+static void
6+addstaticpatrecipe(struct Target *t, const struct Recipe *src, const char *stem)
7+{
8+ char *exp;
9+
10+ exp = patexpandstem(src->body, stem);
11+ if (!exp[0]) {
12+ free(exp);
13+ return;
14+ }
15+ t->recipes.v = xrealloc(t->recipes.v, (t->recipes.n + 1) * sizeof(t->recipes.v[0]));
16+ t->recipes.v[t->recipes.n].body = exp;
17+ t->recipes.v[t->recipes.n].silent = src->silent;
18+ t->recipes.v[t->recipes.n].ignore = src->ignore;
19+ t->recipes.v[t->recipes.n].recursive = src->recursive;
20+ t->recipes.v[t->recipes.n].submake = src->submake;
21+ copysubmake(&t->recipes.v[t->recipes.n].sm, &src->sm);
22+ t->recipes.n++;
23+}
24+
25 /* add an explicit target rule to the graph, and add all
26 * non-pattern prereqs also as placeholder nodes. later
27 * we discorver how to build them if they need to be built */
28@@ -162,11 +182,19 @@ addrule(struct GraphState *gs, const char *name, const struct RuleNode *rule)
29 struct EvalCtx ctx;
30 struct StrList prereqs;
31 struct StrList order_only;
32+ char *stem;
33
34 memset(&ctx, 0, sizeof(ctx));
35 memset(&prereqs, 0, sizeof(prereqs));
36 memset(&order_only, 0, sizeof(order_only));
37 ctx.env = &env;
38+ stem = 0;
39+
40+ if (rule->target_pattern) {
41+ stem = patmatchstem(rule->target_pattern, name);
42+ if (!stem)
43+ return;
44+ }
45
46 t = findtarget(gs->graph, name);
47 if (!t) {
48@@ -183,14 +211,29 @@ addrule(struct GraphState *gs, const char *name, const struct RuleNode *rule)
49 t->dcolon = rule->dcolon;
50 if (hasword(gs->phony, name))
51 t->phony = 1;
52- for (i = 0; i < rule->prereqs.n; i++)
53- addglobword(&prereqs, rule->prereqs.v[i]);
54- for (i = 0; i < rule->order_only.n; i++)
55- addglobword(&order_only, rule->order_only.v[i]);
56+ for (i = 0; i < rule->prereqs.n; i++) {
57+ char *word;
58+
59+ word = stem ? patapplystem(rule->prereqs.v[i], stem) : xstrdup(rule->prereqs.v[i]);
60+ addglobword(&prereqs, word);
61+ free(word);
62+ }
63+ for (i = 0; i < rule->order_only.n; i++) {
64+ char *word;
65+
66+ word = stem ? patapplystem(rule->order_only.v[i], stem) : xstrdup(rule->order_only.v[i]);
67+ addglobword(&order_only, word);
68+ free(word);
69+ }
70 addwords(&t->prereqs, &prereqs);
71 addwords(&t->order_only, &order_only);
72 targetenv(gs, &ctx, t->env.n ? &t->env : 0, name);
73- addrecipes(&t->recipes, &rule->recipes);
74+ if (stem) {
75+ for (i = 0; i < rule->recipes.n; i++)
76+ addstaticpatrecipe(t, &rule->recipes.v[i], stem);
77+ } else {
78+ addrecipes(&t->recipes, &rule->recipes);
79+ }
80 freeenv(&t->env);
81 copyenv(&t->env, &env);
82
83@@ -220,6 +263,7 @@ addrule(struct GraphState *gs, const char *name, const struct RuleNode *rule)
84 freestrs(&prereqs);
85 freestrs(&order_only);
86 freeenv(&env);
87+ free(stem);
88 }
89 static void
90 addtassign(struct GraphState *gs, const struct AssignNode *assign)
91@@ -268,6 +312,7 @@ buildgraph(const struct RuleSet *ruleset, const struct StrList *goals, struct Gr
92 gs.nrules = ruleset->nrules;
93 gs.mode = mode;
94 ctx.env = &gs.env;
95+ ctx.export_all = ruleset->export_all;
96 ctx.mode = mode;
97 imprules(&gs.sufs, ruleset->posix, mode);
98
99@@ -307,6 +352,13 @@ buildgraph(const struct RuleSet *ruleset, const struct StrList *goals, struct Gr
100 addrule(&gs, gs.rules[i].targets.v[k], &gs.rules[i]);
101 }
102 }
103+ if (imppatrules(&gs.patterns, gs.mode) < 0) {
104+ free(gs.tas);
105+ freepatrule(&gs.patterns);
106+ freesufrules(&gs.sufs);
107+ freeenv(&gs.env);
108+ return -1;
109+ }
110
111 if (goals) {
112 /* explicitly requested goals need to exist in the generated graph before ninja runs.
+62,
-3
1@@ -54,6 +54,19 @@ samelist(const struct StrList *a, const struct StrList *b)
2 return 1;
3 }
4
5+static void
6+addenvassign(struct StrList *list, const char *name, const char *val)
7+{
8+ char *s;
9+
10+ s = xmalloc(strlen(name) + 1 + strlen(val) + 1);
11+ strcpy(s, name);
12+ strcat(s, "=");
13+ strcat(s, val);
14+ list->v = xrealloc(list->v, (list->n + 1) * sizeof(list->v[0]));
15+ list->v[list->n++] = s;
16+}
17+
18 static char *
19 joinprefix(const char *parent, const char *child)
20 {
21@@ -205,6 +218,8 @@ subgraphkey(const struct SubGraph *sg)
22 char *key;
23
24 n = strlen(sg->cwd) + 1 + strlen(sg->makefile ? sg->makefile : "") + 1;
25+ for (i = 0; i < sg->envassigns.n; i++)
26+ n += strlen(sg->envassigns.v[i]) + 1;
27 for (i = 0; i < sg->assigns.n; i++)
28 n += strlen(sg->assigns.v[i]) + 1;
29 key = xmalloc(n + 1);
30@@ -218,6 +233,12 @@ subgraphkey(const struct SubGraph *sg)
31 memcpy(key + pos, sg->makefile, len);
32 pos += len;
33 }
34+ for (i = 0; i < sg->envassigns.n; i++) {
35+ key[pos++] = '|';
36+ len = strlen(sg->envassigns.v[i]);
37+ memcpy(key + pos, sg->envassigns.v[i], len);
38+ pos += len;
39+ }
40 for (i = 0; i < sg->assigns.n; i++) {
41 key[pos++] = '|';
42 len = strlen(sg->assigns.v[i]);
43@@ -394,6 +415,8 @@ sameinvocation(const struct SubGraph *a, const struct SubGraph *b)
44 return 0;
45 if (a->makefile && strcmp(a->makefile, b->makefile) != 0)
46 return 0;
47+ if (!samelist(&a->envassigns, &b->envassigns))
48+ return 0;
49 if (!samelist(&a->assigns, &b->assigns))
50 return 0;
51 if (!samelist(&a->flags, &b->flags))
52@@ -459,6 +482,17 @@ mergechildgraph(struct SubGraph *parent,
53 if (!hasword(&t->prereqs, goals->v[i]))
54 addstr(&t->prereqs, goals->v[i]);
55 }
56+ if (child->prefix && child->prefix[0] && isunderprefix(child->prefix, tname)) {
57+ size_t n;
58+ const char *local;
59+
60+ n = strlen(child->prefix);
61+ local = tname + n;
62+ if (*local == '/')
63+ local++;
64+ if (*local && !hasword(&t->prereqs, local))
65+ addstr(&t->prereqs, local);
66+ }
67 if (!sameprefix(parent->prefix, child->prefix)) {
68 if (known) {
69 mergesubmeta(known, child);
70@@ -523,6 +557,17 @@ expandsubgraphs(struct SubGraph *sg, struct SubGraphStack *stack)
71 addstr(&child.parents, tname);
72 if (r->sm.makefile)
73 child.makefile = xstrdup(r->sm.makefile);
74+ {
75+ size_t ei;
76+
77+ for (ei = 0; ei < sg->graph.v[i].env.n; ei++) {
78+ const struct Var *v = &sg->graph.v[i].env.v[ei];
79+
80+ if (!v->exported)
81+ continue;
82+ addenvassign(&child.envassigns, v->name, v->val);
83+ }
84+ }
85 addwords(&child.assigns, &r->sm.assigns);
86 addwords(&child.flags, &r->sm.flags);
87 addwords(&child.goals, &r->sm.goals);
88@@ -605,6 +650,7 @@ buildsubgraph0(struct SubGraph *sg, struct SubGraphStack *stack)
89 int rc;
90 size_t i;
91 int envoverride;
92+ size_t nenvassigns;
93
94 memset(&ast, 0, sizeof(ast));
95 memset(&pre, 0, sizeof(pre));
96@@ -645,17 +691,29 @@ buildsubgraph0(struct SubGraph *sg, struct SubGraphStack *stack)
97 break;
98 }
99 }
100- if (sg->assigns.n) {
101+ nenvassigns = sg->envassigns.n;
102+ if (sg->envassigns.n || sg->assigns.n) {
103 char *assignsrc;
104+ struct StrList allassigns;
105
106- assignsrc = appendassigns(xstrdup(""), &sg->assigns);
107+ memset(&allassigns, 0, sizeof(allassigns));
108+ addwords(&allassigns, &sg->envassigns);
109+ addwords(&allassigns, &sg->assigns);
110+ assignsrc = appendassigns(xstrdup(""), &allassigns);
111+ freestrs(&allassigns);
112 rc = parse("<command line>", assignsrc, &pre, sg->mode);
113 free(assignsrc);
114 if (rc < 0)
115 goto out;
116 for (i = 0; i < pre.n; i++) {
117- if (pre.v[i].kind == NODE_ASSIGN)
118+ if (pre.v[i].kind != NODE_ASSIGN)
119+ continue;
120+ if (nenvassigns > 0) {
121+ pre.v[i].data.assign.origin = envoverride ? ORIGIN_ENV_OVERRIDE : ORIGIN_ENV;
122+ nenvassigns--;
123+ } else {
124 pre.v[i].data.assign.origin = ORIGIN_COMMAND;
125+ }
126 }
127 }
128 rc = parse(path, src, &ast, sg->mode);
129@@ -719,6 +777,7 @@ freesubgraph(struct SubGraph *sg)
130 free(sg->makefile);
131 sg->makefile = 0;
132 freestrs(&sg->parents);
133+ freestrs(&sg->envassigns);
134 freestrs(&sg->assigns);
135 freestrs(&sg->flags);
136 freestrs(&sg->goals);
+21,
-4
1@@ -846,12 +846,13 @@ parseassign(const struct PreLine *line, const char *s, size_t n, size_t base, st
2 }
3
4 static struct Node
5-parserule(const struct PreLine *line, const char *s, size_t n, size_t colon, int dcolon)
6+parserule(const struct PreLine *line, const char *s, size_t n, size_t colon, int dcolon, enum ShinMode mode)
7 {
8 struct Node state;
9 const char *rhs;
10 size_t off;
11 size_t rhsn, split, semi;
12+ ptrdiff_t patcolon;
13 char *recipe;
14
15 memset(&state, 0, sizeof(state));
16@@ -860,8 +861,6 @@ parserule(const struct PreLine *line, const char *s, size_t n, size_t colon, int
17 state.loc.line1 = line->line1;
18 state.data.rule.dcolon = dcolon;
19
20- astsplitwords(&state.data.rule.targets, s, colon);
21-
22 off = dcolon ? 2 : 1;
23 rhs = s + colon + off;
24 rhsn = n - colon - off;
25@@ -876,6 +875,15 @@ parserule(const struct PreLine *line, const char *s, size_t n, size_t colon, int
26 }
27 rhsn = semi;
28 }
29+ astsplitwords(&state.data.rule.targets, s, colon);
30+ patcolon = -1;
31+ if (mode == MODE_GNU && !dcolon)
32+ patcolon = findtop(rhs, rhsn, ':');
33+ if (patcolon >= 0) {
34+ state.data.rule.target_pattern = atrimdup(rhs, (size_t)patcolon);
35+ rhs += (size_t)patcolon + 1;
36+ rhsn -= (size_t)patcolon + 1;
37+ }
38 split = (size_t)findtop(rhs, rhsn, '|');
39 if (split != (size_t)-1) {
40 astsplitwords(&state.data.rule.prereqs, rhs, split);
41@@ -968,6 +976,9 @@ branchrule(struct NodeList *out, const struct Node *src, const struct PreLine *l
42 state.loc.line1 = line->line1;
43 state.data.rule.dcolon = src->data.rule.dcolon;
44 addwords_ast(&state.data.rule.targets, &src->data.rule.targets);
45+ if (src->data.rule.target_pattern)
46+ state.data.rule.target_pattern = astdup(src->data.rule.target_pattern,
47+ strlen(src->data.rule.target_pattern));
48 addnode(out, state);
49 return &out->v[out->n - 1];
50 }
51@@ -1087,6 +1098,12 @@ parseline(const struct PreLine *line, const struct SpecialTargets *targets, enum
52 }
53 n = strlen(trim);
54
55+ if ((is_export || is_unexport) && n == 0) {
56+ state = parseexport(line, trim, is_export && !is_unexport);
57+ free(trim);
58+ return state;
59+ }
60+
61 if (!n) {
62 memset(&state, 0, sizeof(state));
63 state.kind = NODE_BLANK;
64@@ -1191,7 +1208,7 @@ parseline(const struct PreLine *line, const struct SpecialTargets *targets, enum
65 return state;
66 }
67 if (colon >= 0) {
68- state = parserule(line, trim, n, (size_t)colon, dcolon);
69+ state = parserule(line, trim, n, (size_t)colon, dcolon, mode);
70 free(trim);
71 return state;
72 }
+3,
-0
1@@ -33,10 +33,13 @@ struct SufRules {
2 struct SuffixList active;
3 };
4
5+struct PatRules;
6+
7 int issinglesuf(const char *s, char **from);
8 int issuf(const char *s, char **from, char **to);
9 void seedenv(struct Env *env, int posix, int envoverride, enum ShinMode mode);
10 void imprules(struct SufRules *rules, int posix, enum ShinMode mode);
11+int imppatrules(struct PatRules *rules, enum ShinMode mode);
12 int collectsufrule(struct SufRules *rules, const struct RuleNode *rule);
13 int instsufrule(const struct SufRules *rules,
14 const struct Graph *graph,
+2,
-0
1@@ -126,6 +126,7 @@ struct AssignNode {
2
3 struct RuleNode {
4 struct StrList targets;
5+ char *target_pattern;
6 struct StrList prereqs;
7 struct StrList order_only;
8 struct RecipeList recipes;
9@@ -240,6 +241,7 @@ struct SubGraph {
10 char *prefix;
11 char *makefile;
12 struct StrList parents;
13+ struct StrList envassigns;
14 struct StrList assigns;
15 struct StrList flags;
16 struct StrList goals;