commit b508dcb

shrub  ·  2026-05-18 17:42:51 +0000 UTC
parent 805b3a3
make posix modes more compliant
4 files changed,  +122, -8
+108, -2
  1@@ -176,19 +176,34 @@ evalassign(struct EvalCtx *ctx, const struct AssignNode *in)
  2 		envsetvar(env, lhs, xstrdup(in->rhs), 0, o, exported);
  3 		break;
  4 	case ASSIGN_DCOLON_EQ:
  5+		if (ctx->mode == MODE_POSIX_2008) {
  6+			dielikemake(ctx->cur_path, ctx->cur_line, "'::=' is not valid in POSIX 2008", 0);
  7+			ctx->errors++;
  8+			break;
  9+		}
 10 		envsetvar(env, lhs, expandstr(ctx, in->rhs), 1, o, exported);
 11 		break;
 12 	case ASSIGN_COLON_EQ:
 13 		/* := is a gnu extension. posix uses ::= for simple expansion.
 14 		 * bsd := is different again, equivalent to posix :::= */
 15-		if (ctx->mode != MODE_GNU) {
 16-			dielikemake(ctx->cur_path, ctx->cur_line, "':=' is not valid in posix mode, use '::='", 0);
 17+		if (ctx->mode == MODE_POSIX_2024) {
 18+			dielikemake(ctx->cur_path, ctx->cur_line, "':=' is not valid in POSIX 2024, use '::='", 0);
 19+			ctx->errors++;
 20+			break;
 21+		}
 22+		if (ctx->mode == MODE_POSIX_2008) {
 23+			dielikemake(ctx->cur_path, ctx->cur_line, "':=' is not valid in POSIX 2008", 0);
 24 			ctx->errors++;
 25 			break;
 26 		}
 27 		envsetvar(env, lhs, expandstr(ctx, in->rhs), 1, o, exported);
 28 		break;
 29 	case ASSIGN_COLON3_EQ:
 30+		if (ctx->mode == MODE_POSIX_2008) {
 31+			dielikemake(ctx->cur_path, ctx->cur_line, "':::=' is not valid in POSIX 2008", 0);
 32+			ctx->errors++;
 33+			break;
 34+		}
 35 		/* expand now, escape the result so re-expansion on use gives back the same value */
 36 		rhs = expandstr(ctx, in->rhs);
 37 		joined = escapedollars(rhs);
 38@@ -196,10 +211,20 @@ evalassign(struct EvalCtx *ctx, const struct AssignNode *in)
 39 		envsetvar(env, lhs, joined, 0, o, exported);
 40 		break;
 41 	case ASSIGN_QMARK_EQ:
 42+		if (ctx->mode == MODE_POSIX_2008) {
 43+			dielikemake(ctx->cur_path, ctx->cur_line, "'?=' is not valid in POSIX 2008", 0);
 44+			ctx->errors++;
 45+			break;
 46+		}
 47 		if (!findvar(env, lhs))
 48 			envsetvar(env, lhs, xstrdup(in->rhs), 0, o, exported);
 49 		break;
 50 	case ASSIGN_PLUS_EQ:
 51+		if (ctx->mode == MODE_POSIX_2008) {
 52+			dielikemake(ctx->cur_path, ctx->cur_line, "'+=' is not valid in POSIX 2008", 0);
 53+			ctx->errors++;
 54+			break;
 55+		}
 56 		v = findvar(env, lhs);
 57 		if (!v) {
 58 			envsetvar(env, lhs, xstrdup(in->rhs), 0, o, in->exported);
 59@@ -217,6 +242,11 @@ evalassign(struct EvalCtx *ctx, const struct AssignNode *in)
 60 			v->exported = 1;
 61 		break;
 62 	case ASSIGN_BANG_EQ:
 63+		if (ctx->mode == MODE_POSIX_2008) {
 64+			dielikemake(ctx->cur_path, ctx->cur_line, "'!=' is not valid in POSIX 2008", 0);
 65+			ctx->errors++;
 66+			break;
 67+		}
 68 		rhs = expandstr(ctx, in->rhs);
 69 		joined = runshellassign(rhs);
 70 		free(rhs);
 71@@ -410,6 +440,31 @@ evalinclude(struct EvalCtx *ctx, const struct IncludeNode *inc)
 72 	memset(&paths, 0, sizeof(paths));
 73 	splitwords(&paths, exp, strlen(exp));
 74 	free(exp);
 75+	/* posix 2008 only allows plain includes with one file, posix 2024 allows both 
 76+	 * include and -include with multiple files, but no sinclude. */
 77+	if (ctx->mode == MODE_POSIX_2008) {
 78+		if (inc->optional) {
 79+			dielikemake(ctx->cur_path, ctx->cur_line,
 80+			            "optional includes are not valid in POSIX 2008", 0);
 81+			ctx->errors++;
 82+			freestrs(&paths);
 83+			return 0;
 84+		}
 85+		if (paths.n != 1) {
 86+			dielikemake(ctx->cur_path, ctx->cur_line,
 87+			            "include in POSIX 2008 must specify exactly one file", 0);
 88+			ctx->errors++;
 89+			freestrs(&paths);
 90+			return 0;
 91+		}
 92+	}
 93+	if (ctx->mode == MODE_POSIX_2024 && inc->sinclude) {
 94+		dielikemake(ctx->cur_path, ctx->cur_line,
 95+		            "'sinclude' is not valid in POSIX 2024; use '-include'", 0);
 96+		ctx->errors++;
 97+		freestrs(&paths);
 98+		return 0;
 99+	}
100 	for (i = 0; i < paths.n; i++) {
101 		size_t j, nmatch;
102 		char *single;
103@@ -497,6 +552,23 @@ evalinclude(struct EvalCtx *ctx, const struct IncludeNode *inc)
104 	return 0;
105 }
106 
107+static int
108+isgnutarget(const char *s)
109+{
110+	/*gnu only special targets that we actually handle*/
111+	static const char *const gnutargets[] = {
112+		".EXPORT_ALL_VARIABLES",
113+		0
114+	};
115+	size_t i;
116+
117+	for (i = 0; gnutargets[i]; i++) {
118+		if (strcmp(s, gnutargets[i]) == 0)
119+			return 1;
120+	}
121+	return 0;
122+}
123+
124 static int
125 evalnodes(const struct NodeList *in, struct RuleSet *out, struct EvalCtx *ctx)
126 {
127@@ -527,6 +599,11 @@ evalnodes(const struct NodeList *in, struct RuleSet *out, struct EvalCtx *ctx)
128 				return -1;
129 			break;
130 		case NODE_COND:
131+			if (ctx->mode != MODE_GNU) {
132+				dielikemake(ctx->cur_path, ctx->cur_line, "conditionals are only valid in GNU", 0);
133+				ctx->errors++;
134+				break;
135+			}
136 			if (testcond(ctx, &src->data.cond)) {
137 				if (evalnodes(&src->data.cond.thenpart, out, ctx) < 0)
138 					return -1;
139@@ -536,6 +613,16 @@ evalnodes(const struct NodeList *in, struct RuleSet *out, struct EvalCtx *ctx)
140 			}
141 			continue;
142 		case NODE_ASSIGN:
143+			if (ctx->mode != MODE_GNU && src->data.assign.define_block) {
144+				dielikemake(ctx->cur_path, ctx->cur_line, "'define'/'endef' are only valid in GNU", 0);
145+				ctx->errors++;
146+				break;
147+			}
148+			if (ctx->mode != MODE_GNU && src->data.assign.exported != 0) {
149+				dielikemake(ctx->cur_path, ctx->cur_line, "'export'/'unexport' are only valid in GNU", 0);
150+				ctx->errors++;
151+				break;
152+			}
153 			updatespecialassign(&targets, src->data.assign.lhs, src->data.assign.rhs);
154 			if (src->data.assign.tspec) {
155 				addrulesetassign(&out->tvars, &out->ntvars, src, ctx);
156@@ -545,6 +632,11 @@ evalnodes(const struct NodeList *in, struct RuleSet *out, struct EvalCtx *ctx)
157 			}
158 			break;
159 		case NODE_EXPORT:
160+			if (ctx->mode != MODE_GNU) {
161+				dielikemake(ctx->cur_path, ctx->cur_line, "'export'/'unexport' are only valid in GNU", 0);
162+				ctx->errors++;
163+				break;
164+			}
165 			evalexport(ctx, &src->data.export);
166 			break;
167 		case NODE_RULE: {
168@@ -559,6 +651,20 @@ evalnodes(const struct NodeList *in, struct RuleSet *out, struct EvalCtx *ctx)
169 			copywords(&exptargets, &src->data.rule.targets, ctx);
170 			tmprule = src->data.rule;
171 			tmprule.targets = exptargets;
172+			if (ctx->mode != MODE_GNU && tmprule.dcolon) {
173+				dielikemake(ctx->cur_path, ctx->cur_line,
174+				            "double-colon rules are only valid in GNU", 0);
175+				ctx->errors++;
176+				freestrs(&exptargets);
177+				break;
178+			}
179+			/* ignore gnu only targets if not in gnu mode */
180+			if (ctx->mode != MODE_GNU &&
181+			    tmprule.targets.n == 1 &&
182+			    isgnutarget(tmprule.targets.v[0])) {
183+				freestrs(&exptargets);
184+				break;
185+			}
186 			if (tmprule.targets.n == 1 &&
187 			    strcmp(tmprule.targets.v[0], ".EXPORT_ALL_VARIABLES") == 0) {
188 				ctx->export_all = 1;
+7, -5
 1@@ -564,12 +564,12 @@ preprocfile0(const char *path, const char *src_override, struct Pre *pre, struct
 2 
 3 			stripcomment(line.text);
 4 			trim = trimdup(line.text, strlen(line.text));
 5-			if (haskw(trim, "include") || haskw(trim, "-include") || haskw(trim, "sinclude")) {
 6-				int rc;
 7-				size_t kwlen;
 8+				if (haskw(trim, "include") || haskw(trim, "-include")) {
 9+					int rc;
10+					size_t kwlen;
11 
12-				opt = haskw(trim, "-include") || haskw(trim, "sinclude");
13-				kwlen = haskw(trim, "-include") || haskw(trim, "sinclude") ? 8 : 7;
14+					opt = haskw(trim, "-include");
15+					kwlen = haskw(trim, "-include") ? 8 : 7;
16 				incarg = trimdup(trim + kwlen, strlen(trim + kwlen));
17 				if (isplainpath(incarg)) {
18 					rc = preprocinclude(dir, incarg, pre, inc, targets);
19@@ -674,6 +674,7 @@ parseinclude(const struct PreLine *line, const char *s)
20 		off = strlen("-include");
21 	} else if (haskw(s, "sinclude")) {
22 		state.data.include.optional = 1;
23+		state.data.include.sinclude = 1;
24 		off = strlen("sinclude");
25 	} else {
26 		off = strlen("include");
27@@ -943,6 +944,7 @@ parsedefine(const struct Pre *pre, size_t *i, struct Node *out)
28 	out->data.assign.lhs = name;
29 	out->data.assign.op = ASSIGN_EQ;
30 	out->data.assign.origin = ORIGIN_FILE;
31+	out->data.assign.define_block = 1;
32 
33 	bodycap = 64;
34 	bodylen = 0;
+5, -1
 1@@ -326,7 +326,11 @@ seedenv(struct Env *env, int posix, int envoverride, enum ShinMode mode)
 2 	 * in submakes. should work for most simple cases. */
 3 	envsetvar(env, "CURDIR", cwd, 1, ORIGIN_FILE, 0);
 4 	if (posix || mode != MODE_GNU) {
 5-		envsetvar(env, "CC", xstrdup("c99"), 1, ORIGIN_DEFAULT, 0);
 6+		const char *cc = "c99";
 7+
 8+		if (mode == MODE_POSIX_2024)
 9+			cc = "c17";
10+		envsetvar(env, "CC", xstrdup(cc), 1, ORIGIN_DEFAULT, 0);
11 		envsetvar(env, "CFLAGS", xstrdup("-O1"), 1, ORIGIN_DEFAULT, 0);
12 		envsetvar(env, "FFLAGS", xstrdup("-O1"), 1, ORIGIN_DEFAULT, 0);
13 	}
+2, -0
 1@@ -119,6 +119,7 @@ struct AssignNode {
 2 	enum AssignOp op;
 3 	enum Origin origin;
 4 	int exported;
 5+	int define_block;
 6 	int tspec;
 7 	struct StrList targets;
 8 };
 9@@ -133,6 +134,7 @@ struct RuleNode {
10 
11 struct IncludeNode {
12 	int optional;
13+	int sinclude;
14 	char *path;
15 };
16