commit 805b3a3

xplshn  ·  2026-05-17 18:52:06 +0000 UTC
parent 49f886f
Gating mechanism

Signed-off-by: xplshn <anto@xplshn.com.ar>
12 files changed,  +181, -73
+4, -1
 1@@ -44,9 +44,12 @@ HELPERSRCS = tests/runner/testhelp/main.go
 2 
 3 CC = cc
 4 CFLAGS = -O2 -Wall -Wextra -pedantic
 5-CPPFLAGS = -Isrc -Ibackends
 6+CPPFLAGS = -Isrc -Ibackends -DSHIN_WITH_GNU=$(SHIN_WITH_GNU)
 7 LDFLAGS = -static
 8 
 9+# set to 0 to exclude gnu mode from the runtime -M flag
10+SHIN_WITH_GNU = 1
11+
12 .PHONY: all fmt test runner helper install clean
13 
14 all: $(BIN)
+23, -2
 1@@ -13,7 +13,7 @@
 2 static void
 3 usage(FILE *fp, const char *argv0)
 4 {
 5-	fprintf(fp, "usage: %s [-age] [-G ninja|dot|compdb] [-C dir] [-f file] [target ...]\n", argv0);
 6+	fprintf(fp, "usage: %s [-age] [-G ninja|dot|compdb] [-M posix2024|posix2008|gnu] [-C dir] [-f file] [target ...]\n", argv0);
 7 }
 8 
 9 static int
10@@ -35,6 +35,8 @@ assignopname(enum AssignOp op)
11 		return "=";
12 	case ASSIGN_PLUS_EQ:
13 		return "+=";
14+	case ASSIGN_DCOLON_EQ:
15+		return "::=";
16 	case ASSIGN_COLON_EQ:
17 		return ":=";
18 	case ASSIGN_COLON3_EQ:
19@@ -226,6 +228,7 @@ main(int argc, char **argv)
20 	size_t nassigns;
21 	size_t ngoals;
22 	enum Generator gen;
23+	enum ShinMode mode;
24 	struct Ast ast;
25 	struct RuleSet rs;
26 	struct SubGraph sg;
27@@ -240,6 +243,7 @@ main(int argc, char **argv)
28 	dump_graph = 0;
29 	env_override = 0;
30 	gen = GEN_NINJA;
31+	mode = MODE_GNU;
32 	assigns = 0;
33 	nassigns = 0;
34 	goals = 0;
35@@ -274,6 +278,22 @@ main(int argc, char **argv)
36 				free(goals);
37 				return 2;
38 			}
39+		} else if (strcmp(argv[i], "-M") == 0) {
40+			if (i + 1 >= argc) {
41+				fprintf(stderr, "specify a mode\n\n");
42+				usage(stderr, argv[0]);
43+				free(assigns);
44+				free(goals);
45+				return 2;
46+			}
47+			++i;
48+			if (shinmode_parse(argv[i], &mode) < 0) {
49+				fprintf(stderr, "unknown mode: %s\n\n", argv[i]);
50+				usage(stderr, argv[0]);
51+				free(assigns);
52+				free(goals);
53+				return 2;
54+			}
55 		} else if (strcmp(argv[i], "-h") == 0 || strcmp(argv[i], "--help") == 0) {
56 			usage(stdout, argv[0]);
57 			free(assigns);
58@@ -384,7 +404,7 @@ main(int argc, char **argv)
59 				}
60 			}
61 		}
62-		if (eval(path, &ast, 0, env_override, &rs) < 0) {
63+		if (eval(path, &ast, 0, env_override, mode, &rs) < 0) {
64 			free(assigns);
65 			free(goals);
66 			freeast(&ast);
67@@ -402,6 +422,7 @@ main(int argc, char **argv)
68 		sg.makefile = xstrdup(path);
69 	if (env_override)
70 		addstr(&sg.flags, "-e");
71+	sg.mode = mode;
72 	free(assigns);
73 	free(goals);
74 	assigns = 0;
+32, -28
 1@@ -443,6 +443,8 @@ enum FuncMode {
 2 	FNCTX,
 3 };
 4 
 5+/* posix2008 and posix2024 both have no builtin functions, gnu has all of them */
 6+
 7 struct func {
 8 	const char *name;
 9 	enum FuncMode mode;
10@@ -498,36 +500,36 @@ static const char *const allfuncs[] = {
11 };
12 
13 static const struct func funcs[] = {
14-    {"wildcard", FNEXP1, {.f1 = fnwildcard}},
15-    {"shell", FNEXP1, {.f1 = fnshell}},
16-    {"sort", FNEXP1, {.f1 = fnsort}},
17-    {"info", FNCTX, {.ctx = fninfo}},
18-    {"origin", FNCTX, {.ctx = fnorigin}},
19-    {"notdir", FNEXP1, {.f1 = fnnotdir}},
20-    {"dir", FNEXP1, {.f1 = fndir}},
21-    {"basename", FNEXP1, {.f1 = fnbasename}},
22+    {"wildcard",   FNEXP1, {.f1 = fnwildcard}},
23+    {"shell",      FNEXP1, {.f1 = fnshell}},
24+    {"sort",       FNEXP1, {.f1 = fnsort}},
25+    {"info",       FNCTX,  {.ctx = fninfo}},
26+    {"origin",     FNCTX,  {.ctx = fnorigin}},
27+    {"notdir",     FNEXP1, {.f1 = fnnotdir}},
28+    {"dir",        FNEXP1, {.f1 = fndir}},
29+    {"basename",   FNEXP1, {.f1 = fnbasename}},
30     {"filter-out", FNEXP2, {.f2 = fnfilterout}},
31-    {"filter", FNEXP2, {.f2 = fnfilter}},
32+    {"filter",     FNEXP2, {.f2 = fnfilter}},
33     {"findstring", FNEXP2, {.f2 = fnfindstring}},
34-    {"addprefix", FNEXP2, {.f2 = fnaddprefix}},
35-    {"addsuffix", FNEXP2, {.f2 = fnaddsuffix}},
36-    {"join", FNEXP2, {.f2 = fnjoin}},
37-    {"strip", FNEXP1, {.f1 = fnstrip}},
38-    {"subst", FNEXP3, {.f3 = fnsubst}},
39-    {"patsubst", FNEXP3, {.f3 = fnpatsubst}},
40-    {"if", FNEXP3, {.f3 = fnif}},
41-    {"call", FNCTX, {.ctx = fncall}},
42-    {"foreach", FNCTX, {.ctx = fnforeach}},
43-    {"eval", FNCTX, {.ctx = fneval}},
44-    {"value", FNCTX, {.ctx = fnvalue}},
45-    {"words", FNEXP1, {.f1 = fnwords}},
46-    {"word", FNEXP2, {.f2 = fnword}},
47-    {"wordlist", FNEXP3, {.f3 = fnwordlist}},
48-    {"firstword", FNEXP1, {.f1 = fnfirstword}},
49-    {"lastword", FNEXP1, {.f1 = fnlastword}},
50-    {"realpath", FNEXP1, {.f1 = fnrealpath}},
51-    {"abspath", FNEXP1, {.f1 = fnabspath}},
52-    {0, FNEXP1, {.f1 = 0}},
53+    {"addprefix",  FNEXP2, {.f2 = fnaddprefix}},
54+    {"addsuffix",  FNEXP2, {.f2 = fnaddsuffix}},
55+    {"join",       FNEXP2, {.f2 = fnjoin}},
56+    {"strip",      FNEXP1, {.f1 = fnstrip}},
57+    {"subst",      FNEXP3, {.f3 = fnsubst}},
58+    {"patsubst",   FNEXP3, {.f3 = fnpatsubst}},
59+    {"if",         FNEXP3, {.f3 = fnif}},
60+    {"call",       FNCTX,  {.ctx = fncall}},
61+    {"foreach",    FNCTX,  {.ctx = fnforeach}},
62+    {"eval",       FNCTX,  {.ctx = fneval}},
63+    {"value",      FNCTX,  {.ctx = fnvalue}},
64+    {"words",      FNEXP1, {.f1 = fnwords}},
65+    {"word",       FNEXP2, {.f2 = fnword}},
66+    {"wordlist",   FNEXP3, {.f3 = fnwordlist}},
67+    {"firstword",  FNEXP1, {.f1 = fnfirstword}},
68+    {"lastword",   FNEXP1, {.f1 = fnlastword}},
69+    {"realpath",   FNEXP1, {.f1 = fnrealpath}},
70+    {"abspath",    FNEXP1, {.f1 = fnabspath}},
71+    {0,            FNEXP1, {.f1 = 0}},
72 };
73 
74 static char *
75@@ -537,6 +539,8 @@ funcref(struct EvalCtx *ctx, const char *s, size_t n)
76 	const struct func *f;
77 	char *val;
78 
79+	if (ctx->mode != MODE_GNU)
80+		return 0;
81 	for (i = 0; funcs[i].name; i++) {
82 		f = &funcs[i];
83 		namelen = strlen(f->name);
+19, -17
 1@@ -175,19 +175,21 @@ evalassign(struct EvalCtx *ctx, const struct AssignNode *in)
 2 	case ASSIGN_EQ:
 3 		envsetvar(env, lhs, xstrdup(in->rhs), 0, o, exported);
 4 		break;
 5+	case ASSIGN_DCOLON_EQ:
 6+		envsetvar(env, lhs, expandstr(ctx, in->rhs), 1, o, exported);
 7+		break;
 8 	case ASSIGN_COLON_EQ:
 9+		/* := is a gnu extension. posix uses ::= for simple expansion.
10+		 * bsd := is different again, equivalent to posix :::= */
11+		if (ctx->mode != MODE_GNU) {
12+			dielikemake(ctx->cur_path, ctx->cur_line, "':=' is not valid in posix mode, use '::='", 0);
13+			ctx->errors++;
14+			break;
15+		}
16 		envsetvar(env, lhs, expandstr(ctx, in->rhs), 1, o, exported);
17 		break;
18 	case ASSIGN_COLON3_EQ:
19-		/* :::= expands immediately and then it stores a recursive value with dollars escaped
20-		 * so when you expand later you get literal text. 
21-		 *
22-		 * TODO
23-		 * in gnu make, := and ::= are equivalent, and :::= is different.
24-		 * in posix, := is rejected, and ::= and :::= are different.
25-		 * in bsd make, := and :::= are equivalent. i don't know if ::= is handled.
26-		 * when we add feature gating, we need to honor these behaviours. right now,
27-		 * we handle it like gnu make.*/
28+		/* expand now, escape the result so re-expansion on use gives back the same value */
29 		rhs = expandstr(ctx, in->rhs);
30 		joined = escapedollars(rhs);
31 		free(rhs);
32@@ -329,7 +331,8 @@ addrulesetassign(struct AssignNode **vec, size_t *n, const struct Node *src, str
33 	dst->origin = src->data.assign.origin;
34 	dst->exported = assignexported(ctx, &src->data.assign);
35 	dst->tspec = src->data.assign.tspec;
36-	if (src->data.assign.op == ASSIGN_COLON_EQ ||
37+	if (src->data.assign.op == ASSIGN_DCOLON_EQ ||
38+	    src->data.assign.op == ASSIGN_COLON_EQ ||
39 	    src->data.assign.op == ASSIGN_COLON3_EQ) {
40 		char *rhs;
41 
42@@ -419,12 +422,10 @@ evalinclude(struct EvalCtx *ctx, const struct IncludeNode *inc)
43 		word = paths.v[i];
44 		nmatch = 0;
45 		grc = 0;
46-		if (hasglobmeta(word))
47+		/* glob in include paths is a gnu extension */
48+		if (ctx->mode == MODE_GNU && hasglobmeta(word)) {
49 			grc = glob(word, 0, 0, &g);
50-		if (hasglobmeta(word) && grc == 0 && g.gl_pathc > 0) {
51-			nmatch = g.gl_pathc;
52-		} else if (hasglobmeta(word)) {
53-			nmatch = 0;
54+			nmatch = (grc == 0 && g.gl_pathc > 0) ? g.gl_pathc : 0;
55 		} else {
56 			nmatch = 1;
57 			single = xstrdup(word);
58@@ -615,7 +616,7 @@ evalsnippet(struct EvalCtx *ctx, const char *path, const char *src)
59 }
60 
61 int
62-eval(const char *path, const struct Ast *ast, const struct Ast *pre, int envoverride, struct RuleSet *out)
63+eval(const char *path, const struct Ast *ast, const struct Ast *pre, int envoverride, enum ShinMode mode, struct RuleSet *out)
64 {
65 	struct Env env;
66 	struct EvalCtx ctx;
67@@ -625,10 +626,11 @@ eval(const char *path, const struct Ast *ast, const struct Ast *pre, int envover
68 	out->envoverride = envoverride;
69 	memset(&env, 0, sizeof(env));
70 	memset(&ctx, 0, sizeof(ctx));
71-	seedenv(&env, 0, out->envoverride);
72+	seedenv(&env, 0, out->envoverride, mode);
73 	ctx.env = &env;
74 	ctx.out = out;
75 	ctx.cur_path = path;
76+	ctx.mode = mode;
77 	if (pre) {
78 		rc = evalnodes((const struct NodeList *)pre, out, &ctx);
79 		if (rc < 0) {
+15, -7
 1@@ -30,6 +30,7 @@ struct GraphState {
 2 	struct TAssign *tas;
 3 	size_t ntas;
 4 	int saw_suffixes;
 5+	enum ShinMode mode;
 6 };
 7 
 8 static void
 9@@ -250,7 +251,7 @@ instdefaultrule(const struct GraphState *gs, struct Target *t, struct EvalCtx *c
10  * then expand placeholder targets until all prereqs
11  * we can find are in the graph. */
12 int
13-buildgraph(const struct RuleSet *ruleset, const struct StrList *goals, struct Graph *graph)
14+buildgraph(const struct RuleSet *ruleset, const struct StrList *goals, struct Graph *graph, enum ShinMode mode)
15 {
16 	size_t i, j;
17 	struct GraphState gs;
18@@ -262,11 +263,13 @@ buildgraph(const struct RuleSet *ruleset, const struct StrList *goals, struct Gr
19 	gs.graph = graph;
20 	gs.phony = &ruleset->phony;
21 	gs.defaultrule = &ruleset->defaultrule;
22-	seedenv(&gs.env, ruleset->posix, ruleset->envoverride);
23+	seedenv(&gs.env, ruleset->posix, ruleset->envoverride, mode);
24 	gs.rules = ruleset->rules;
25 	gs.nrules = ruleset->nrules;
26+	gs.mode = mode;
27 	ctx.env = &gs.env;
28-	imprules(&gs.sufs, ruleset->posix);
29+	ctx.mode = mode;
30+	imprules(&gs.sufs, ruleset->posix, mode);
31 
32 	for (i = 0; i < ruleset->nvars; i++)
33 		evalassign(&ctx, &ruleset->vars[i]);
34@@ -296,7 +299,9 @@ buildgraph(const struct RuleSet *ruleset, const struct StrList *goals, struct Gr
35 			continue;
36 		if (collectsufrule(&gs.sufs, &gs.rules[i]))
37 			continue;
38-		collectpat(&gs.patterns, &gs.rules[i]);
39+		/* pattern rules (%.o: %.c) are a gnu extension */
40+		if (gs.mode == MODE_GNU)
41+			collectpat(&gs.patterns, &gs.rules[i]);
42 		for (k = 0; k < gs.rules[i].targets.n; k++) {
43 			if (!ispat(gs.rules[i].targets.v[k]))
44 				addrule(&gs, gs.rules[i].targets.v[k], &gs.rules[i]);
45@@ -358,8 +363,10 @@ buildgraph(const struct RuleSet *ruleset, const struct StrList *goals, struct Gr
46 
47 				memset(&targetctx, 0, sizeof(targetctx));
48 				targetctx.env = &env;
49+				targetctx.mode = gs.mode;
50 				targetenv(&gs, &targetctx, &t->env, t->name);
51-				instpatrule(&gs.patterns, graph, t, &targetctx);
52+				if (gs.mode == MODE_GNU)
53+					instpatrule(&gs.patterns, graph, t, &targetctx);
54 				matched = t->recipes.n > 0;
55 				if (!matched) {
56 					instsufrule(&gs.sufs, graph, t, &targetctx);
57@@ -414,7 +421,7 @@ buildgraph(const struct RuleSet *ruleset, const struct StrList *goals, struct Gr
58 }
59 
60 int
61-expandgraph(struct Graph *graph)
62+expandgraph(struct Graph *graph, enum ShinMode mode)
63 {
64 	size_t i, k, j;
65 
66@@ -432,6 +439,7 @@ expandgraph(struct Graph *graph)
67 		memset(&side_effects, 0, sizeof(side_effects));
68 		memset(&new_recipes, 0, sizeof(new_recipes));
69 		ctx.env = &t->env;
70+		ctx.mode = mode;
71 		ctx.auto_target = t->name;
72 		{
73 			struct StrList allprereqs;
74@@ -507,7 +515,7 @@ expandgraph(struct Graph *graph)
75 	}
76 
77 	for (i = 0; i < graph->nsubs; i++) {
78-		if (expandgraph(&graph->subs[i].graph) < 0)
79+		if (expandgraph(&graph->subs[i].graph, mode) < 0)
80 			return -1;
81 	}
82 	return 0;
+4, -3
 1@@ -519,6 +519,7 @@ expandsubgraphs(struct SubGraph *sg, struct SubGraphStack *stack)
 2 			memset(&child, 0, sizeof(child));
 3 			child.cwd = r->sm.dir ? joinpath(sg->cwd, r->sm.dir) : xstrdup(sg->cwd);
 4 			child.prefix = joinprefix(sg->prefix, r->sm.dir);
 5+			child.mode = sg->mode;
 6 			addstr(&child.parents, tname);
 7 			if (r->sm.makefile)
 8 				child.makefile = xstrdup(r->sm.makefile);
 9@@ -660,13 +661,13 @@ buildsubgraph0(struct SubGraph *sg, struct SubGraphStack *stack)
10 	rc = parse(path, src, &ast);
11 	if (rc < 0)
12 		goto out;
13-	rc = eval(path, &ast, sg->assigns.n ? &pre : 0, envoverride, &rs);
14+	rc = eval(path, &ast, sg->assigns.n ? &pre : 0, envoverride, sg->mode, &rs);
15 	if (rc < 0)
16 		goto out;
17-	rc = buildgraph(&rs, &sg->goals, &sg->graph);
18+	rc = buildgraph(&rs, &sg->goals, &sg->graph, sg->mode);
19 	if (rc < 0)
20 		goto out;
21-	rc = expandgraph(&sg->graph);
22+	rc = expandgraph(&sg->graph, sg->mode);
23 	if (rc < 0)
24 		goto out;
25 	scopegraph(&sg->graph, sg->prefix);
+5, -1
 1@@ -24,6 +24,7 @@ struct EvalCtx {
 2 	struct StrList exports;
 3 	struct StrList unexports;
 4 	int export_all;
 5+	enum ShinMode mode;
 6 };
 7 
 8 struct SpecialTargets {
 9@@ -82,7 +83,7 @@ int handlespecialrule(struct SpecialTargets *targets, const struct RuleNode *rul
10 
11 struct Var *findvar(struct Env *env, const char *name);
12 char *expandstr(struct EvalCtx *ctx, const char *s);
13-void seedenv(struct Env *env, int posix, int envoverride);
14+void seedenv(struct Env *env, int posix, int envoverride, enum ShinMode mode);
15 void freeenv(struct Env *env);
16 void copyenv(struct Env *dst, const struct Env *src);
17 void evalassign(struct EvalCtx *ctx, const struct AssignNode *in);
18@@ -119,4 +120,7 @@ char *fnforeach(struct EvalCtx *ctx, const char *args);
19 char *fneval(struct EvalCtx *ctx, const char *args);
20 int evalsnippet(struct EvalCtx *ctx, const char *path, const char *src);
21 
22+const char *shinmode_name(enum ShinMode mode);
23+int shinmode_parse(const char *s, enum ShinMode *out);
24+
25 #endif
+1, -1
1@@ -239,7 +239,7 @@ findassign(const char *s, size_t n, size_t start)
2 		if (i + 2 < n && s[i] == ':' && s[i + 1] == ':' && s[i + 2] == '=') {
3 			out.pos = i;
4 			out.len = 3;
5-			out.op = ASSIGN_COLON_EQ;
6+			out.op = ASSIGN_DCOLON_EQ;
7 			out.ok = 1;
8 			return out;
9 		}
+27, -8
 1@@ -215,12 +215,10 @@ expandauto(const char *s, const struct Target *t, const char *stem)
 2 	return out;
 3 }
 4 
 5+/* rules shared by all dialects except the .c.o rule, which differs */
 6 #define IMPRULES_COMMON \
 7-	".SUFFIXES: .o .c .f .y .l .sh\n" \
 8 	".c:\n" \
 9 	"\t$(CC) $(CFLAGS) $(LDFLAGS) -o $@ $<\n" \
10-	".f:\n" \
11-	"\t$(FC) $(FFLAGS) $(LDFLAGS) -o $@ $<\n" \
12 	".sh:\n" \
13 	"\tcp $< $@\n" \
14 	"\tchmod a+x $@\n" \
15@@ -241,15 +239,28 @@ expandauto(const char *s, const struct Target *t, const char *stem)
16 	"\t$(LEX) $(LFLAGS) $<\n" \
17 	"\tmv lex.yy.c $@\n"
18 
19-#define IMPRULES_POSIX \
20+/* posix 2008 includes fortran; posix 2024 dropped it */
21+#define IMPRULES_POSIX_2008 \
22+	".SUFFIXES: .o .c .f .y .l .sh\n" \
23 	IMPRULES_COMMON \
24+	".f:\n" \
25+	"\t$(FC) $(FFLAGS) $(LDFLAGS) -o $@ $<\n" \
26 	".c.o:\n" \
27 	"\t$(CC) $(CFLAGS) -c $<\n" \
28 	".f.o:\n" \
29 	"\t$(FC) $(FFLAGS) -c $<\n"
30 
31+#define IMPRULES_POSIX_2024 \
32+	".SUFFIXES: .o .c .y .l .sh\n" \
33+	IMPRULES_COMMON \
34+	".c.o:\n" \
35+	"\t$(CC) $(CFLAGS) -c $<\n"
36+
37 #define IMPRULES_GNU \
38+	".SUFFIXES: .o .c .f .y .l .sh\n" \
39 	IMPRULES_COMMON \
40+	".f:\n" \
41+	"\t$(FC) $(FFLAGS) $(LDFLAGS) -o $@ $<\n" \
42 	".c.o:\n" \
43 	"\t$(CC) $(CPPFLAGS) $(CFLAGS) -c -o $@ $<\n" \
44 	".f.o:\n" \
45@@ -281,7 +292,7 @@ loadimprules(struct SufRules *rules, const char *src)
46 }
47 
48 void
49-seedenv(struct Env *env, int posix, int envoverride)
50+seedenv(struct Env *env, int posix, int envoverride, enum ShinMode mode)
51 {
52 	char *cwd;
53 	size_t i;
54@@ -314,7 +325,7 @@ seedenv(struct Env *env, int posix, int envoverride)
55 	 * unsure about behaviour under directory change condition or 
56 	 * in submakes. should work for most simple cases. */
57 	envsetvar(env, "CURDIR", cwd, 1, ORIGIN_FILE, 0);
58-	if (posix) {
59+	if (posix || mode != MODE_GNU) {
60 		envsetvar(env, "CC", xstrdup("c99"), 1, ORIGIN_DEFAULT, 0);
61 		envsetvar(env, "CFLAGS", xstrdup("-O1"), 1, ORIGIN_DEFAULT, 0);
62 		envsetvar(env, "FFLAGS", xstrdup("-O1"), 1, ORIGIN_DEFAULT, 0);
63@@ -334,9 +345,17 @@ seedenv(struct Env *env, int posix, int envoverride)
64 }
65 
66 void
67-imprules(struct SufRules *rules, int posix)
68+imprules(struct SufRules *rules, int posix, enum ShinMode mode)
69 {
70-	if (loadimprules(rules, posix ? IMPRULES_POSIX : IMPRULES_GNU) < 0) {
71+	const char *src;
72+
73+	if (mode == MODE_POSIX_2024)
74+		src = IMPRULES_POSIX_2024;
75+	else if (mode == MODE_POSIX_2008 || posix)
76+		src = IMPRULES_POSIX_2008;
77+	else
78+		src = IMPRULES_GNU;
79+	if (loadimprules(rules, src) < 0) {
80 		fprintf(stderr, "failed to load built-in implicit rules\n");
81 		exit(1);
82 	}
+2, -2
 1@@ -35,8 +35,8 @@ struct SufRules {
 2 
 3 int issinglesuf(const char *s, char **from);
 4 int issuf(const char *s, char **from, char **to);
 5-void seedenv(struct Env *env, int posix, int envoverride);
 6-void imprules(struct SufRules *rules, int posix);
 7+void seedenv(struct Env *env, int posix, int envoverride, enum ShinMode mode);
 8+void imprules(struct SufRules *rules, int posix, enum ShinMode mode);
 9 int collectsufrule(struct SufRules *rules, const struct RuleNode *rule);
10 int instsufrule(const struct SufRules *rules,
11                 const struct Graph *graph,
+12, -3
 1@@ -3,6 +3,13 @@
 2 
 3 #include <stddef.h>
 4 
 5+/* -M flag: controls the makefile dialect accepted at eval time */
 6+enum ShinMode {
 7+	MODE_GNU = 0,
 8+	MODE_POSIX_2024,
 9+	MODE_POSIX_2008,
10+};
11+
12 /*
13  * types
14  * PreLine: one preprocessed line with source location
15@@ -32,6 +39,7 @@ enum NodeKind {
16 enum AssignOp {
17 	ASSIGN_EQ,
18 	ASSIGN_PLUS_EQ,
19+	ASSIGN_DCOLON_EQ,
20 	ASSIGN_COLON_EQ,
21 	ASSIGN_COLON3_EQ,
22 	ASSIGN_QMARK_EQ,
23@@ -234,17 +242,18 @@ struct SubGraph {
24 	struct StrList flags;
25 	struct StrList goals;
26 	struct Graph graph;
27+	enum ShinMode mode;
28 };
29 
30 int preproc(const char *path, struct Pre *pre);
31 void freepre(struct Pre *pre);
32 int buildast(const char *path, const struct Pre *pre, struct Ast *ast);
33-int eval(const char *path, const struct Ast *ast, const struct Ast *pre, int envoverride, struct RuleSet *out);
34+int eval(const char *path, const struct Ast *ast, const struct Ast *pre, int envoverride, enum ShinMode mode, struct RuleSet *out);
35 int parse(const char *path, const char *src, struct Ast *ast);
36 void freeast(struct Ast *ast);
37 void freeruleset(struct RuleSet *ruleset);
38-int buildgraph(const struct RuleSet *ruleset, const struct StrList *goals, struct Graph *graph);
39-int expandgraph(struct Graph *graph);
40+int buildgraph(const struct RuleSet *ruleset, const struct StrList *goals, struct Graph *graph, enum ShinMode mode);
41+int expandgraph(struct Graph *graph, enum ShinMode mode);
42 void freegraph(struct Graph *graph);
43 int buildsubgraph(struct SubGraph *sg);
44 void freesubgraph(struct SubGraph *sg);
+37, -0
 1@@ -8,6 +8,43 @@
 2 
 3 /* shared utility functions */
 4 
 5+const char *
 6+shinmode_name(enum ShinMode mode)
 7+{
 8+	switch (mode) {
 9+	case MODE_GNU:
10+		return "gnu";
11+	case MODE_POSIX_2024:
12+		return "posix2024";
13+	case MODE_POSIX_2008:
14+		return "posix2008";
15+	}
16+	return "gnu";
17+}
18+
19+int
20+shinmode_parse(const char *s, enum ShinMode *out)
21+{
22+	if (strcmp(s, "gnu") == 0) {
23+#if SHIN_WITH_GNU
24+		*out = MODE_GNU;
25+		return 0;
26+#else
27+		/* gnu support was not compiled in */
28+		return -1;
29+#endif
30+	}
31+	if (strcmp(s, "posix2024") == 0 || strcmp(s, "posix") == 0) {
32+		*out = MODE_POSIX_2024;
33+		return 0;
34+	}
35+	if (strcmp(s, "posix2008") == 0) {
36+		*out = MODE_POSIX_2008;
37+		return 0;
38+	}
39+	return -1;
40+}
41+
42 static const char *progname = "shin";
43 
44 void