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=$?