commit bd8ad09
shrub
·
2026-04-23 18:56:56 +0000 UTC
parent a999e66
error more like make to get more test passes
4 files changed,
+145,
-25
+24,
-12
1@@ -317,13 +317,19 @@ main(int argc, char **argv)
2 path = mkpath;
3 pathbuf = mkpath;
4 src = appendassigns(src, &sg.assigns);
5- if (parse(path, src, &ast) < 0) {
6- fprintf(stderr, "parse error in %s\n", path);
7- free(assigns);
8- free(pathbuf);
9- free(src);
10- freesubgraph(&sg);
11- return 1;
12+ {
13+ int rc;
14+
15+ rc = parse(path, src, &ast);
16+ if (rc < 0) {
17+ if (rc != -2)
18+ fprintf(stderr, "parse error in %s\n", path);
19+ free(assigns);
20+ free(pathbuf);
21+ free(src);
22+ freesubgraph(&sg);
23+ return rc == -2 ? 2 : 1;
24+ }
25 }
26 {
27 size_t marked = 0;
28@@ -358,11 +364,17 @@ main(int argc, char **argv)
29 addstr(&sg.flags, "-e");
30 free(assigns);
31 assigns = 0;
32- if (buildsubgraph(&sg) < 0) {
33- fprintf(stderr, "graph error in %s\n", path ? path : "(default)");
34- free(pathbuf);
35- freesubgraph(&sg);
36- return 1;
37+ {
38+ int rc;
39+
40+ rc = buildsubgraph(&sg);
41+ if (rc < 0) {
42+ if (rc != -2)
43+ fprintf(stderr, "graph error in %s\n", path ? path : "(default)");
44+ free(pathbuf);
45+ freesubgraph(&sg);
46+ return rc == -2 ? 2 : 1;
47+ }
48 }
49 if (expandgraph(&sg.graph) < 0) {
50 fprintf(stderr, "expand error in %s\n", path ? path : "(default)");
+6,
-3
1@@ -531,7 +531,8 @@ buildsubgraph0(struct SubGraph *sg, struct SubGraphStack *stack)
2 src = appendassigns(src, &sg->assigns);
3 rc = parse(path, src, &ast);
4 if (rc < 0) {
5- fprintf(stderr, "parse error in %s/%s\n", sg->cwd, path);
6+ if (rc != -2)
7+ fprintf(stderr, "parse error in %s/%s\n", sg->cwd, path);
8 goto out;
9 }
10 marked = 0;
11@@ -571,15 +572,17 @@ int
12 buildsubgraph(struct SubGraph *sg)
13 {
14 struct SubGraphStack stack;
15+ int rc;
16
17 memset(&stack, 0, sizeof(stack));
18 if (!sg->cwd)
19 sg->cwd = getcwddup();
20- if (buildsubgraph0(sg, &stack) < 0) {
21+ rc = buildsubgraph0(sg, &stack);
22+ if (rc < 0) {
23 while (stack.n)
24 popstack(&stack);
25 free(stack.v);
26- return -1;
27+ return rc;
28 }
29 free(stack.v);
30 return 0;
+99,
-8
1@@ -30,6 +30,7 @@ struct Inc {
2 };
3
4 static int parseerrs;
5+static int deadlikemake;
6
7 static int preprocfile(const char *path, const char *src_override, struct Pre *pre, struct Inc *inc);
8 static int preprocfile0(const char *path, const char *src_override, struct Pre *pre, struct Inc *inc,
9@@ -47,6 +48,22 @@ parseerr(const struct PreLine *line, const char *msg, const char *detail)
10 parseerrs++;
11 }
12
13+static void
14+parsewarn(const struct PreLine *line, const char *msg)
15+{
16+ fprintf(stderr, "%s:%d: warning: %s\n",
17+ line->path, line->line0, msg);
18+}
19+
20+static void
21+dielikemake(const struct PreLine *line, const char *msg)
22+{
23+ fprintf(stderr, "%s:%d: *** %s. Stop.\n",
24+ line->path, line->line0, msg);
25+ deadlikemake = 1;
26+ parseerrs++;
27+}
28+
29 static const char *const unsupported_kws[] = {
30 "unexport",
31 "undefine", "vpath", "private", "load", 0};
32@@ -74,6 +91,26 @@ haskw(const char *s, const char *kw)
33 return s[n] == 0 || isspace((unsigned char)s[n]);
34 }
35
36+static int
37+iscondkw(const char *s)
38+{
39+ return haskw(s, "ifeq") || haskw(s, "ifneq") ||
40+ haskw(s, "ifdef") || haskw(s, "ifndef") ||
41+ haskw(s, "else") || haskw(s, "endif");
42+}
43+
44+static int
45+hasws(const char *s)
46+{
47+ size_t i;
48+
49+ for (i = 0; s[i]; i++) {
50+ if (isspace((unsigned char)s[i]))
51+ return 1;
52+ }
53+ return 0;
54+}
55+
56 static int
57 hascont(const char *s, size_t n)
58 {
59@@ -730,11 +767,15 @@ parseexpr(const struct PreLine *line, const char *s)
60
61 /* unclassified line, treat as unsupported syntax */
62 static struct Node
63-parseraw(const struct PreLine *line, const char *s)
64+parseraw(const struct PreLine *line, const char *s, char recipeprefix)
65 {
66 struct Node state;
67
68- parseerr(line, "unrecognized line", s);
69+ (void)s;
70+ if (recipeprefix == '\t' && strncmp(line->text, " ", 8) == 0)
71+ dielikemake(line, "missing separator (did you mean TAB instead of 8 spaces?)");
72+ else
73+ dielikemake(line, "missing separator");
74 memset(&state, 0, sizeof(state));
75 state.kind = NODE_BLANK;
76 state.loc.line0 = line->line0;
77@@ -851,7 +892,7 @@ parsedefine(const struct Pre *pre, size_t *i, struct Node *out)
78 }
79
80 static struct Node
81-parseline(const struct PreLine *line)
82+parseline(const struct PreLine *line, const struct SpecialTargets *targets)
83 {
84 struct Node state;
85 struct AssignScan as;
86@@ -899,6 +940,11 @@ parseline(const struct PreLine *line)
87 state.data.raw.text = trim;
88 return state;
89 }
90+ if (strncmp(trim, "ifeq(", 5) == 0 || strncmp(trim, "ifneq(", 6) == 0) {
91+ dielikemake(line, "missing separator (ifeq/ifneq must be followed by whitespace)");
92+ free(trim);
93+ return blanknode(line);
94+ }
95 if (haskw(trim, "ifeq") || haskw(trim, "ifneq") ||
96 haskw(trim, "ifdef") || haskw(trim, "ifndef") ||
97 haskw(trim, "else") || haskw(trim, "endif")) {
98@@ -953,6 +999,16 @@ parseline(const struct PreLine *line)
99 }
100 }
101 if (as.ok && (colon < 0 || as.pos <= (size_t)colon)) {
102+ char *lhs;
103+
104+ lhs = trimdup(trim, as.pos);
105+ if (hasws(lhs)) {
106+ free(lhs);
107+ dielikemake(line, "missing separator");
108+ free(trim);
109+ return blanknode(line);
110+ }
111+ free(lhs);
112 state = parseassign(line, trim, n, 0, as, 0, 0);
113 if (is_override)
114 state.data.assign.origin = ORIGIN_OVERRIDE;
115@@ -977,7 +1033,7 @@ parseline(const struct PreLine *line)
116 free(trim);
117 return state;
118 }
119- state = parseraw(line, trim);
120+ state = parseraw(line, trim, targets->recipeprefix);
121 free(trim);
122 return state;
123 }
124@@ -995,6 +1051,19 @@ parseblock(const struct Pre *pre, size_t *i, struct NodeList *out, struct Node *
125 char *trim;
126
127 line = &pre->v[*i];
128+ if (line->isrecipe) {
129+ char *rt;
130+ int condtab;
131+
132+ rt = trimdup(line->text + 1, strlen(line->text + 1));
133+ condtab = iscondkw(rt);
134+ free(rt);
135+ if (condtab) {
136+ parsewarn(line, "conditional directive lines cannot start with TAB");
137+ memmove(line->text, line->text + 1, strlen(line->text + 1) + 1);
138+ line->isrecipe = 0;
139+ }
140+ }
141 if (line->isrecipe) {
142 if (last_rule) {
143 if (!ruleinlist(out, last_rule))
144@@ -1063,19 +1132,28 @@ parseblock(const struct Pre *pre, size_t *i, struct NodeList *out, struct Node *
145 (*i)++;
146 if (parseblock(pre, i, &state.data.cond.elsepart, &last_rule, targets) < 0)
147 return -1;
148- if (*i >= pre->n)
149+ if (*i >= pre->n) {
150+ dielikemake(&pre->v[pre->n - 1], "missing 'endif'");
151 return -1;
152+ }
153 endline = &pre->v[*i];
154 endtrim = trimdup(endline->text, strlen(endline->text));
155+ if (haskw(endtrim, "else")) {
156+ free(endtrim);
157+ dielikemake(endline, "only one 'else' per conditional");
158+ return -1;
159+ }
160 }
161 if (!haskw(endtrim, "endif")) {
162 free(endtrim);
163+ dielikemake(endline, "missing 'endif'");
164 return -1;
165 }
166 state.loc.line1 = endline->line1;
167 free(endtrim);
168 (*i)++;
169 } else {
170+ dielikemake(&pre->v[pre->n - 1], "missing 'endif'");
171 return -1;
172 }
173 addnode(out, state);
174@@ -1085,7 +1163,9 @@ parseblock(const struct Pre *pre, size_t *i, struct NodeList *out, struct Node *
175 }
176 free(trim);
177
178- state = parseline(line);
179+ state = parseline(line, targets);
180+ if (deadlikemake)
181+ return -1;
182 if (state.kind == NODE_ASSIGN)
183 updatespecialassign(targets, state.data.assign.lhs, state.data.assign.rhs);
184 addnode(out, state);
185@@ -1109,12 +1189,23 @@ buildast(const char *path, const struct Pre *pre, struct Ast *ast)
186 (void)path;
187 memset(ast, 0, sizeof(*ast));
188 parseerrs = 0;
189+ deadlikemake = 0;
190 initspecialtargets(&targets);
191 i = 0;
192 if (parseblock(pre, &i, (struct NodeList *)ast, 0, &targets) < 0)
193- return -1;
194+ return deadlikemake ? -2 : -1;
195+ if (i < pre->n) {
196+ char *trim;
197+
198+ trim = trimdup(pre->v[i].text, strlen(pre->v[i].text));
199+ if (haskw(trim, "endif"))
200+ dielikemake(&pre->v[i], "extraneous 'endif'");
201+ else if (haskw(trim, "else"))
202+ dielikemake(&pre->v[i], "extraneous 'else'");
203+ free(trim);
204+ }
205 if (parseerrs)
206- return -1;
207+ return deadlikemake ? -2 : -1;
208
209 return 0;
210 }
+16,
-2
1@@ -93,6 +93,10 @@ delayed_only() {
2 done
3 }
4
5+nowork_only() {
6+ [ "$1" = "ninja: no work to do." ]
7+}
8+
9 while [ "$#" -gt 0 ]; do
10 case "$1" in
11 -v|--version)
12@@ -208,8 +212,11 @@ if [ -n "$vars" ]; then
13 IFS=$oldifs
14 fi
15
16-if ! "$@"; then
17- exit $?
18+if "$@"; then
19+ :
20+else
21+ rc=$?
22+ exit "$rc"
23 fi
24
25 set -- "$ninja_bin" --quiet -f build.ninja
26@@ -247,6 +254,13 @@ if [ "$dryrun" -eq 0 ]; then
27 printf '%s' "$probe_out"
28 exit "$probe_status"
29 fi
30+ if nowork_only "$probe_out"; then
31+ target=$(uptodate_target)
32+ if [ -n "$target" ]; then
33+ printf "%s: '%s' is up to date.\n" "${0##*/}" "$target"
34+ fi
35+ exit 0
36+ fi
37 if delayed_only "$probe_out"; then
38 run_out=$("$@" 2>&1)
39 run_status=$?