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 };