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;