commit 9782164

shrub  ·  2026-05-14 12:12:24 +0000 UTC
parent 77fb0bd
handle :::= properly. stop translating automatic variables in the backend
8 files changed,  +86, -139
+1, -0
1@@ -1,5 +1,6 @@
2 build.dot
3 *.o
4+tests/.runner
5 tests/runner/runner
6 tests/runner/testhelper
7 tests/work
+1, -65
 1@@ -76,66 +76,6 @@ iscompile(const struct Target *t)
 2 	return strstr(t->recipes.v[0].body, "$<") != 0;
 3 }
 4 
 5-static char *
 6-translateauto(const char *s, const struct Target *t)
 7-{
 8-	size_t i, n, cap, len;
 9-	char *out;
10-
11-	n = strlen(s);
12-	cap = n + 1;
13-	len = 0;
14-	out = xmalloc(cap);
15-	for (i = 0; i < n; i++) {
16-		const char *val;
17-		size_t vlen;
18-
19-		if (s[i] != '$' || i + 1 >= n) {
20-			if (len + 2 > cap) {
21-				cap *= 2;
22-				out = xrealloc(out, cap);
23-			}
24-			out[len++] = s[i];
25-			continue;
26-		}
27-
28-		val = 0;
29-		if (s[i + 1] == '$') {
30-			val = "$";
31-		} else if (s[i + 1] == '@') {
32-			val = t->name;
33-		} else if (s[i + 1] == '<') {
34-			val = firstprereq(t);
35-			if (!val)
36-				val = "";
37-		} else if (s[i + 1] == '^' || s[i + 1] == '+' || s[i + 1] == '?') {
38-			val = firstprereq(t);
39-			if (!val)
40-				val = "";
41-		}
42-
43-		if (!val) {
44-			if (len + 2 > cap) {
45-				cap *= 2;
46-				out = xrealloc(out, cap);
47-			}
48-			out[len++] = s[i];
49-			continue;
50-		}
51-
52-		vlen = strlen(val);
53-		if (len + vlen + 1 > cap) {
54-			cap = len + vlen + (n - i) + 1;
55-			out = xrealloc(out, cap);
56-		}
57-		memcpy(out + len, val, vlen);
58-		len += vlen;
59-		i++;
60-	}
61-	out[len] = 0;
62-	return out;
63-}
64-
65 int
66 gencompcmd(const struct Graph *graph, const char *path)
67 {
68@@ -155,11 +95,8 @@ gencompcmd(const struct Graph *graph, const char *path)
69 	fprintf(fp, "[\n");
70 	first = 1;
71 	for (i = 0; i < graph->n; i++) {
72-		char *cmd;
73-
74 		if (!iscompile(&graph->v[i]))
75 			continue;
76-		cmd = translateauto(graph->v[i].recipes.v[0].body, &graph->v[i]);
77 		if (!first)
78 			fprintf(fp, ",\n");
79 		first = 0;
80@@ -168,7 +105,7 @@ gencompcmd(const struct Graph *graph, const char *path)
81 		emitjson(fp, cwd);
82 		fprintf(fp, "\",\n");
83 		fprintf(fp, "    \"command\": \"");
84-		emitjson(fp, cmd);
85+		emitjson(fp, graph->v[i].recipes.v[0].body);
86 		fprintf(fp, "\",\n");
87 		fprintf(fp, "    \"file\": \"");
88 		emitjson(fp, firstprereq(&graph->v[i]));
89@@ -177,7 +114,6 @@ gencompcmd(const struct Graph *graph, const char *path)
90 		emitjson(fp, graph->v[i].name);
91 		fprintf(fp, "\"\n");
92 		fprintf(fp, "  }");
93-		free(cmd);
94 	}
95 	fprintf(fp, "\n]\n");
96 	fclose(fp);
+26, -70
  1@@ -58,6 +58,29 @@ shellquote(const char *s)
  2 	return out;
  3 }
  4 
  5+static char *
  6+escapeninja(const char *s)
  7+{
  8+	size_t i, n, ndollar, j;
  9+	char *out;
 10+
 11+	n = strlen(s);
 12+	ndollar = 0;
 13+	for (i = 0; i < n; i++) {
 14+		if (s[i] == '$')
 15+			ndollar++;
 16+	}
 17+	out = xmalloc(n + ndollar + 1);
 18+	j = 0;
 19+	for (i = 0; i < n; i++) {
 20+		if (s[i] == '$')
 21+			out[j++] = '$';
 22+		out[j++] = s[i];
 23+	}
 24+	out[j] = 0;
 25+	return out;
 26+}
 27+
 28 static char *
 29 exportenv(const struct Target *t)
 30 {
 31@@ -166,86 +189,19 @@ joinlocalprereqs(const struct Target *t)
 32 	return s;
 33 }
 34 
 35-static char *
 36-translateauto(const char *s, const struct Target *t)
 37-{
 38-	size_t i, n, cap, len;
 39-	char *out;
 40-
 41-	n = strlen(s);
 42-	cap = n + 1;
 43-	len = 0;
 44-	out = xmalloc(cap);
 45-	for (i = 0; i < n; i++) {
 46-		const char *val;
 47-		int mustfree;
 48-		size_t vlen;
 49-
 50-		if (s[i] != '$' || i + 1 >= n) {
 51-			if (len + 2 > cap) {
 52-				cap *= 2;
 53-				out = xrealloc(out, cap);
 54-			}
 55-			out[len++] = s[i];
 56-			continue;
 57-		}
 58-
 59-		val = 0;
 60-		mustfree = 0;
 61-		if (s[i + 1] == '$') {
 62-			val = "$$";
 63-		} else if (s[i + 1] == '@') {
 64-			val = localpath(t, t->name);
 65-			mustfree = 1;
 66-		} else if (s[i + 1] == '<') {
 67-			const char *prereq;
 68-
 69-			prereq = firstprereq(t);
 70-			val = prereq ? localpath(t, prereq) : xstrdup("");
 71-			mustfree = 1;
 72-		} else if (s[i + 1] == '^' || s[i + 1] == '+' || s[i + 1] == '?') {
 73-			val = joinlocalprereqs(t);
 74-			mustfree = 1;
 75-		}
 76-
 77-		if (!val) {
 78-			if (len + 3 > cap) {
 79-				cap = len + (n - i) + 3;
 80-				out = xrealloc(out, cap);
 81-			}
 82-			out[len++] = '$';
 83-			out[len++] = '$';
 84-			continue;
 85-		}
 86-
 87-		vlen = strlen(val);
 88-		if (len + vlen + 1 > cap) {
 89-			cap = len + vlen + (n - i) + 1;
 90-			out = xrealloc(out, cap);
 91-		}
 92-		memcpy(out + len, val, vlen);
 93-		len += vlen;
 94-		if (mustfree)
 95-			free((char *)val);
 96-		i++;
 97-	}
 98-	out[len] = 0;
 99-	return out;
100-}
101-
102 static char *
103 rulecmd(const struct Target *t)
104 {
105-	char *cmd, *env, *full, *xlat;
106+	char *cmd, *env, *full, *escaped;
107 
108 	cmd = joinrecipes(t);
109 	env = exportenv(t);
110 	full = cat3(env, cmd, "");
111-	xlat = translateauto(full, t);
112+	escaped = escapeninja(full);
113 	free(full);
114 	free(env);
115 	free(cmd);
116-	return xlat;
117+	return escaped;
118 }
119 
120 static int
+2, -0
1@@ -37,6 +37,8 @@ assignopname(enum AssignOp op)
2 		return "+=";
3 	case ASSIGN_COLON_EQ:
4 		return ":=";
5+	case ASSIGN_COLON3_EQ:
6+		return ":::=";
7 	case ASSIGN_QMARK_EQ:
8 		return "?=";
9 	case ASSIGN_BANG_EQ:
+5, -1
 1@@ -351,7 +351,11 @@ expandvarref(struct EvalCtx *ctx, const char *s, size_t n)
 2 
 3 	v = findvar(ctx->env, name);
 4 	free(name);
 5-	val = v ? expandstr(ctx, v->val) : xstrdup("");
 6+	if (!v)
 7+		return xstrdup("");
 8+	if (v->simple)
 9+		return xstrdup(v->val);
10+	val = expandstr(ctx, v->val);
11 	return val;
12 }
13 
+49, -2
 1@@ -103,6 +103,30 @@ runshellassign(const char *cmd)
 2 	return out;
 3 }
 4 
 5+static char *
 6+escapedollars(const char *s)
 7+{
 8+	size_t i, n, ndollar;
 9+	char *out;
10+	size_t j;
11+
12+	n = strlen(s);
13+	ndollar = 0;
14+	for (i = 0; i < n; i++) {
15+		if (s[i] == '$')
16+			ndollar++;
17+	}
18+	out = xmalloc(n + ndollar + 1);
19+	j = 0;
20+	for (i = 0; i < n; i++) {
21+		if (s[i] == '$')
22+			out[j++] = '$';
23+		out[j++] = s[i];
24+	}
25+	out[j] = 0;
26+	return out;
27+}
28+
29 static void
30 removeexportname(struct StrList *list, const char *name)
31 {
32@@ -152,6 +176,21 @@ evalassign(struct EvalCtx *ctx, const struct AssignNode *in)
33 	case ASSIGN_COLON_EQ:
34 		envsetvar(env, lhs, expandstr(ctx, in->rhs), 1, o, exported);
35 		break;
36+	case ASSIGN_COLON3_EQ:
37+		/* :::= expands immediately and then it stores a recursive value with dollars escaped
38+		 * so when you expand later you get literal text. 
39+		 *
40+		 * TODO
41+		 * in gnu make, := and ::= are equivalent, and :::= is different.
42+		 * in posix, := is rejected, and ::= and :::= are different.
43+		 * in bsd make, := and :::= are equivalent. i don't know if ::= is handled.
44+		 * when we add feature gating, we need to honor these behaviours. right now,
45+		 * we handle it like gnu make.*/
46+		rhs = expandstr(ctx, in->rhs);
47+		joined = escapedollars(rhs);
48+		free(rhs);
49+		envsetvar(env, lhs, joined, 0, o, exported);
50+		break;
51 	case ASSIGN_QMARK_EQ:
52 		if (!findvar(env, lhs))
53 			envsetvar(env, lhs, xstrdup(in->rhs), 0, o, exported);
54@@ -288,10 +327,18 @@ addrulesetassign(struct AssignNode **vec, size_t *n, const struct Node *src, str
55 	dst->origin = src->data.assign.origin;
56 	dst->exported = assignexported(ctx, &src->data.assign);
57 	dst->tspec = src->data.assign.tspec;
58-	if (src->data.assign.op == ASSIGN_COLON_EQ || src->data.assign.op == ASSIGN_BANG_EQ)
59+	if (src->data.assign.op == ASSIGN_COLON_EQ ||
60+	    src->data.assign.op == ASSIGN_COLON3_EQ) {
61+		char *rhs;
62+
63+		rhs = expandstr(ctx, src->data.assign.rhs);
64+		dst->rhs = escapedollars(rhs);
65+		free(rhs);
66+	} else if (src->data.assign.op == ASSIGN_BANG_EQ) {
67 		dst->rhs = expandstr(ctx, src->data.assign.rhs);
68-	else
69+	} else {
70 		dst->rhs = xstrdup(src->data.assign.rhs);
71+	}
72 	copywords(&dst->targets, &src->data.assign.targets, ctx);
73 }
74 
+1, -1
1@@ -238,7 +238,7 @@ findassign(const char *s, size_t n, size_t start)
2 		if (i + 3 < n && s[i] == ':' && s[i + 1] == ':' && s[i + 2] == ':' && s[i + 3] == '=') {
3 			out.pos = i;
4 			out.len = 4;
5-			out.op = ASSIGN_COLON_EQ;
6+			out.op = ASSIGN_COLON3_EQ;
7 			out.ok = 1;
8 			return out;
9 		}
+1, -0
1@@ -33,6 +33,7 @@ enum AssignOp {
2 	ASSIGN_EQ,
3 	ASSIGN_PLUS_EQ,
4 	ASSIGN_COLON_EQ,
5+	ASSIGN_COLON3_EQ,
6 	ASSIGN_QMARK_EQ,
7 	ASSIGN_BANG_EQ,
8 };