commit a98ab69

shrub  ·  2026-05-06 09:04:26 +0000 UTC
parent 75ca578
handle 'export var' and fix test wrapper regression
11 files changed,  +262, -95
M README
M README
+1, -1
1@@ -17,6 +17,6 @@ if you want to help out, there's lots to do. feel free to make a
2 contibution. some high-priority tasks are listed in the TODO file.
3 
4 it is implemented in C99. the perl in this repo is just the gnu make 
5-test suite (of which 2613/3865 tests pass, at the time of writing)
6+test suite (of which 2606/3865 tests pass, at the time of writing)
7 
8 this is public domain software
+23, -2
 1@@ -5,7 +5,23 @@
 2 #include <stdlib.h>
 3 #include <string.h>
 4 
 5-/* ninja backend */
 6+/* check so we dont export special vars */
 7+static int
 8+isvarname(const char *s)
 9+{
10+	size_t i;
11+
12+	if (!s || !s[0])
13+		return 0;
14+	for (i = 0; s[i]; i++) {
15+		if (!(('a' <= s[i] && s[i] <= 'z') ||
16+		      ('A' <= s[i] && s[i] <= 'Z') ||
17+		      ('0' <= s[i] && s[i] <= '9') ||
18+		      s[i] == '_'))
19+			return 0;
20+	}
21+	return 1;
22+}
23 
24 struct Rule {
25 	char *cmd;
26@@ -58,6 +74,8 @@ exportenv(const struct Target *t)
27 
28 		if (!t->env.v[i].exported)
29 			continue;
30+		if (!isvarname(t->env.v[i].name))
31+			continue;
32 		quoted = shellquote(t->env.v[i].val);
33 		nname = strlen(t->env.v[i].name);
34 		nquoted = strlen(quoted);
35@@ -93,7 +111,10 @@ joinrecipes(const struct Target *t)
36 	for (i = 0; i < t->recipes.n; i++) {
37 		char *body;
38 
39-		body = cat3("( ", t->recipes.v[i].body, " )");
40+		if (t->recipes.n > 1)
41+			body = cat3("( ", t->recipes.v[i].body, " )");
42+		else
43+			body = xstrdup(t->recipes.v[i].body);
44 		bodies.v = xrealloc(bodies.v, (bodies.n + 1) * sizeof(bodies.v[0]));
45 		bodies.v[bodies.n++] = body;
46 	}
+64, -64
  1@@ -242,12 +242,12 @@ main(int argc, char **argv)
  2 		} else if (strcmp(argv[i], "-g") == 0) {
  3 			dump_graph = 1;
  4 		} else if (strcmp(argv[i], "-G") == 0) {
  5-				if (i + 1 >= argc) {
  6-					fprintf(stderr, "specify a generator\n\n");
  7-					usage(stderr, argv[0]);
  8-					free(assigns);
  9-					return 2;
 10-				}
 11+			if (i + 1 >= argc) {
 12+				fprintf(stderr, "specify a generator\n\n");
 13+				usage(stderr, argv[0]);
 14+				free(assigns);
 15+				return 2;
 16+			}
 17 			++i;
 18 			if (strcmp(argv[i], "ninja") == 0) {
 19 				gen = GEN_NINJA;
 20@@ -255,61 +255,61 @@ main(int argc, char **argv)
 21 				gen = GEN_DOT;
 22 			} else if (strcmp(argv[i], "compcmd") == 0) {
 23 				gen = GEN_COMPCMD;
 24-				} else {
 25-					fprintf(stderr, "unknown generator: %s\n\n", argv[i]);
 26-					usage(stderr, argv[0]);
 27-					free(assigns);
 28-					return 2;
 29-				}
 30+			} else {
 31+				fprintf(stderr, "unknown generator: %s\n\n", argv[i]);
 32+				usage(stderr, argv[0]);
 33+				free(assigns);
 34+				return 2;
 35+			}
 36 		} else if (strcmp(argv[i], "-h") == 0 || strcmp(argv[i], "--help") == 0) {
 37 			usage(stdout, argv[0]);
 38 			free(assigns);
 39 			return 0;
 40-			} else if (strcmp(argv[i], "-C") == 0) {
 41-				if (i + 1 >= argc) {
 42-					fprintf(stderr, "specify a directory\n\n");
 43-					usage(stderr, argv[0]);
 44-					free(assigns);
 45-					return 2;
 46-				}
 47+		} else if (strcmp(argv[i], "-C") == 0) {
 48+			if (i + 1 >= argc) {
 49+				fprintf(stderr, "specify a directory\n\n");
 50+				usage(stderr, argv[0]);
 51+				free(assigns);
 52+				return 2;
 53+			}
 54 			++i;
 55 
 56-				if (chdir(argv[i]) != 0) {
 57-					fprintf(stderr, "failed to chdir to %s", argv[i]);
 58-					free(assigns);
 59-					return 2;
 60-				}
 61-			} else if (strcmp(argv[i], "-f") == 0) {
 62-				if (i + 1 >= argc) {
 63-					fprintf(stderr, "specify a file\n\n");
 64-					usage(stderr, argv[0]);
 65-					free(assigns);
 66-					return 2;
 67-				}
 68+			if (chdir(argv[i]) != 0) {
 69+				fprintf(stderr, "failed to chdir to %s", argv[i]);
 70+				free(assigns);
 71+				return 2;
 72+			}
 73+		} else if (strcmp(argv[i], "-f") == 0) {
 74+			if (i + 1 >= argc) {
 75+				fprintf(stderr, "specify a file\n\n");
 76+				usage(stderr, argv[0]);
 77+				free(assigns);
 78+				return 2;
 79+			}
 80 			++i;
 81 			path = argv[i];
 82 		} else if (strcmp(argv[i], "-e") == 0) {
 83 			env_override = 1;
 84-			} else if (isvarassign(argv[i])) {
 85-				char **tmp;
 86+		} else if (isvarassign(argv[i])) {
 87+			char **tmp;
 88 
 89-				tmp = realloc(assigns, (nassigns + 1) * sizeof(assigns[0]));
 90-				if (!tmp) {
 91-					fprintf(stderr, "out of memory\n");
 92-					free(assigns);
 93-					return 2;
 94-				}
 95-				assigns = tmp;
 96-				assigns[nassigns++] = argv[i];
 97-			} else if (argv[i][0] == '-') {
 98-				usage(stderr, argv[0]);
 99-				free(assigns);
100-				return 2;
101-			} else {
102-				usage(stderr, argv[0]);
103+			tmp = realloc(assigns, (nassigns + 1) * sizeof(assigns[0]));
104+			if (!tmp) {
105+				fprintf(stderr, "out of memory\n");
106 				free(assigns);
107 				return 2;
108 			}
109+			assigns = tmp;
110+			assigns[nassigns++] = argv[i];
111+		} else if (argv[i][0] == '-') {
112+			usage(stderr, argv[0]);
113+			free(assigns);
114+			return 2;
115+		} else {
116+			usage(stderr, argv[0]);
117+			free(assigns);
118+			return 2;
119+		}
120 	}
121 	for (i = 0; i < (int)nassigns; i++)
122 		addstr(&sg.assigns, assigns[i]);
123@@ -317,15 +317,15 @@ main(int argc, char **argv)
124 		char *mkpath;
125 
126 		mkpath = 0;
127-			if (loadmakefile(path, &mkpath, &src) < 0) {
128-				if (path)
129-					fprintf(stderr, "could not read %s\n", path);
130-				else
131-					fprintf(stderr, "could not find a makefile\n");
132-				free(assigns);
133-				freesubgraph(&sg);
134-				return 2;
135-			}
136+		if (loadmakefile(path, &mkpath, &src) < 0) {
137+			if (path)
138+				fprintf(stderr, "could not read %s\n", path);
139+			else
140+				fprintf(stderr, "could not find a makefile\n");
141+			free(assigns);
142+			freesubgraph(&sg);
143+			return 2;
144+		}
145 		path = mkpath;
146 		pathbuf = mkpath;
147 		src = appendassigns(src, &sg.assigns);
148@@ -353,14 +353,14 @@ main(int argc, char **argv)
149 				}
150 			}
151 		}
152-			if (eval(path, &ast, 0, env_override, &rs) < 0) {
153-				free(assigns);
154-				freeast(&ast);
155-				free(pathbuf);
156-				free(src);
157-				freesubgraph(&sg);
158-				return 2;
159-			}
160+		if (eval(path, &ast, 0, env_override, &rs) < 0) {
161+			free(assigns);
162+			freeast(&ast);
163+			free(pathbuf);
164+			free(src);
165+			freesubgraph(&sg);
166+			return 2;
167+		}
168 		dumpruleset(&rs);
169 		freeruleset(&rs);
170 		freeast(&ast);
+102, -6
  1@@ -103,6 +103,35 @@ runshellassign(const char *cmd)
  2 	return out;
  3 }
  4 
  5+static void
  6+removeexportname(struct StrList *list, const char *name)
  7+{
  8+	size_t i;
  9+
 10+	for (i = 0; i < list->n; i++) {
 11+		if (strcmp(list->v[i], name) != 0)
 12+			continue;
 13+		free(list->v[i]);
 14+		memmove(&list->v[i], &list->v[i + 1], (list->n - i - 1) * sizeof(list->v[0]));
 15+		list->n--;
 16+		return;
 17+	}
 18+}
 19+
 20+static int
 21+assignexported(const struct EvalCtx *ctx, const struct AssignNode *in)
 22+{
 23+	if (in->exported > 0)
 24+		return 1;
 25+	if (in->exported < 0)
 26+		return 0;
 27+	if (hasword(&ctx->unexports, in->lhs))
 28+		return 0;
 29+	if (hasword(&ctx->exports, in->lhs))
 30+		return 1;
 31+	return ctx->export_all;
 32+}
 33+
 34 void
 35 evalassign(struct EvalCtx *ctx, const struct AssignNode *in)
 36 {
 37@@ -110,19 +139,21 @@ evalassign(struct EvalCtx *ctx, const struct AssignNode *in)
 38 	struct Var *v;
 39 	char *rhs, *joined;
 40 	enum Origin o;
 41+	int exported;
 42 
 43 	env = ctx->env;
 44 	o = in->origin ? in->origin : ORIGIN_FILE;
 45+	exported = assignexported(ctx, in);
 46 	switch (in->op) {
 47 	case ASSIGN_EQ:
 48-		envsetvar(env, in->lhs, xstrdup(in->rhs), 0, o, in->exported);
 49+		envsetvar(env, in->lhs, xstrdup(in->rhs), 0, o, exported);
 50 		break;
 51 	case ASSIGN_COLON_EQ:
 52-		envsetvar(env, in->lhs, expandstr(ctx, in->rhs), 1, o, in->exported);
 53+		envsetvar(env, in->lhs, expandstr(ctx, in->rhs), 1, o, exported);
 54 		break;
 55 	case ASSIGN_QMARK_EQ:
 56 		if (!findvar(env, in->lhs))
 57-			envsetvar(env, in->lhs, xstrdup(in->rhs), 0, o, in->exported);
 58+			envsetvar(env, in->lhs, xstrdup(in->rhs), 0, o, exported);
 59 		break;
 60 	case ASSIGN_PLUS_EQ:
 61 		v = findvar(env, in->lhs);
 62@@ -138,18 +169,68 @@ evalassign(struct EvalCtx *ctx, const struct AssignNode *in)
 63 		free(v->val);
 64 		v->val = joined;
 65 		v->origin = o;
 66-		if (in->exported)
 67+		if (exported)
 68 			v->exported = 1;
 69 		break;
 70 	case ASSIGN_BANG_EQ:
 71 		rhs = expandstr(ctx, in->rhs);
 72 		joined = runshellassign(rhs);
 73 		free(rhs);
 74-		envsetvar(env, in->lhs, joined, 1, o, in->exported);
 75+		envsetvar(env, in->lhs, joined, 1, o, exported);
 76 		break;
 77 	}
 78 }
 79 
 80+static void
 81+evalexport(struct EvalCtx *ctx, const struct ExportNode *exp)
 82+{
 83+	struct StrList names;
 84+	struct Var *v;
 85+	size_t i, j;
 86+
 87+	if (exp->all) {
 88+		ctx->export_all = exp->exported;
 89+		for (i = 0; i < ctx->env->n; i++)
 90+			ctx->env->v[i].exported = exp->exported;
 91+		return;
 92+	}
 93+	memset(&names, 0, sizeof(names));
 94+	for (i = 0; i < exp->names.n; i++) {
 95+		char *s;
 96+
 97+		s = expandstr(ctx, exp->names.v[i]);
 98+		splitwords(&names, s, strlen(s));
 99+		free(s);
100+	}
101+	for (i = 0; i < names.n; i++) {
102+		if (exp->exported) {
103+			removeexportname(&ctx->unexports, names.v[i]);
104+			if (!hasword(&ctx->exports, names.v[i]))
105+				addstr(&ctx->exports, names.v[i]);
106+		} else {
107+			removeexportname(&ctx->exports, names.v[i]);
108+			if (!hasword(&ctx->unexports, names.v[i]))
109+				addstr(&ctx->unexports, names.v[i]);
110+		}
111+		v = findvar(ctx->env, names.v[i]);
112+		if (v)
113+			v->exported = exp->exported;
114+	}
115+	for (j = 0; j < ctx->out->nvars; j++) {
116+		if (hasword(&ctx->exports, ctx->out->vars[j].lhs))
117+			ctx->out->vars[j].exported = 1;
118+		if (hasword(&ctx->unexports, ctx->out->vars[j].lhs))
119+			ctx->out->vars[j].exported = 0;
120+	}
121+	for (j = 0; j < ctx->out->ntvars; j++) {
122+		if (hasword(&ctx->exports, ctx->out->tvars[j].lhs))
123+			ctx->out->tvars[j].exported = 1;
124+		if (hasword(&ctx->unexports, ctx->out->tvars[j].lhs))
125+			ctx->out->tvars[j].exported = 0;
126+	}
127+	freestrs(&names);
128+}
129+
130 static int
131 testcond(struct EvalCtx *ctx, const struct CondNode *cond)
132 {
133@@ -203,7 +284,7 @@ addrulesetassign(struct AssignNode **vec, size_t *n, const struct Node *src, str
134 	dst->lhs = xstrdup(src->data.assign.lhs);
135 	dst->op = src->data.assign.op;
136 	dst->origin = src->data.assign.origin;
137-	dst->exported = src->data.assign.exported;
138+	dst->exported = assignexported(ctx, &src->data.assign);
139 	dst->tspec = src->data.assign.tspec;
140 	if (src->data.assign.op == ASSIGN_COLON_EQ || src->data.assign.op == ASSIGN_BANG_EQ)
141 		dst->rhs = expandstr(ctx, src->data.assign.rhs);
142@@ -309,7 +390,15 @@ evalnodes(const struct NodeList *in, struct RuleSet *out, struct EvalCtx *ctx)
143 				evalassign(ctx, &src->data.assign);
144 			}
145 			break;
146+		case NODE_EXPORT:
147+			evalexport(ctx, &src->data.export);
148+			break;
149 		case NODE_RULE:
150+			if (src->data.rule.targets.n == 1 &&
151+			    strcmp(src->data.rule.targets.v[0], ".EXPORT_ALL_VARIABLES") == 0) {
152+				ctx->export_all = 1;
153+				break;
154+			}
155 			if (handlespecialrule(&targets, &src->data.rule))
156 				break;
157 			addrulesetrule(out, &src->data.rule, ctx);
158@@ -318,6 +407,8 @@ evalnodes(const struct NodeList *in, struct RuleSet *out, struct EvalCtx *ctx)
159 	}
160 	if (out)
161 		addwords(&out->phony, &targets.phony);
162+	if (out)
163+		out->export_all = targets.export_all;
164 	if (out)
165 		out->posix = targets.posix;
166 	freestrs(&targets.phony);
167@@ -365,6 +456,8 @@ eval(const char *path, const struct Ast *ast, const struct Ast *pre, int envover
168 	}
169 	rc = evalnodes((const struct NodeList *)ast, out, &ctx);
170 	freeenv(&env);
171+	freestrs(&ctx.exports);
172+	freestrs(&ctx.unexports);
173 	if (rc == 0 && ctx.errors)
174 		return -1;
175 	return rc;
176@@ -424,6 +517,9 @@ freenodes(struct NodeList *list)
177 		case NODE_RAW:
178 		case NODE_INCLUDE:
179 			break;
180+		case NODE_EXPORT:
181+			free(list->v[i].data.export.names.v);
182+			break;
183 		case NODE_ASSIGN:
184 			free(list->v[i].data.assign.targets.v);
185 			break;
+4, -0
 1@@ -132,6 +132,10 @@ resolvegoals(const struct SubGraph *sg, struct StrList *out)
 2 	return -1;
 3 }
 4 
 5+/* when expanding a submake, start from a target name and walk the graph backwards
 6+ * only for that target. if we expand the entire subgraph from the beginning,
 7+ * we get a lot of cases where the subgraph will recurse and try to create a new
 8+ * subgraph of itself */
 9 static void
10 markreachable(const struct Graph *graph, const char *name, unsigned char *seen)
11 {
+6, -2
 1@@ -21,12 +21,16 @@ struct EvalCtx {
 2 	struct StrList *side_effects;
 3 	const char *cur_path;
 4 	int cur_line;
 5+	struct StrList exports;
 6+	struct StrList unexports;
 7+	int export_all;
 8 };
 9 
10 struct SpecialTargets {
11 	int posix;
12 	struct StrList phony;
13 	char recipeprefix;
14+	int export_all;
15 };
16 
17 void *xmalloc(size_t n);
18@@ -34,11 +38,11 @@ void dielikemake(const char *path, int line, const char *msg, const char *detail
19 void *xrealloc(void *p, size_t n);
20 char *xstrndup(const char *s, size_t n);
21 char *xstrdup(const char *s);
22-void  arena_init(struct Arena *a, size_t block_size);
23+void arena_init(struct Arena *a, size_t block_size);
24 void *arena_alloc(struct Arena *a, size_t n);
25 char *arena_strndup(struct Arena *a, const char *s, size_t n);
26 char *arena_strdup(struct Arena *a, const char *s);
27-void  arena_free(struct Arena *a);
28+void arena_free(struct Arena *a);
29 const char *intern(const char *s);
30 char *readfile(const char *path);
31 int loadmakefile(const char *path, char **path_out, char **src_out);
+46, -18
  1@@ -90,7 +90,6 @@ parsewarn(const struct PreLine *line, const char *msg)
  2 }
  3 
  4 static const char *const unsupported_kws[] = {
  5-    "unexport",
  6     "undefine", "vpath", "private", "load", 0};
  7 
  8 static char *
  9@@ -578,20 +577,20 @@ preprocfile0(const char *path, const char *src_override, struct Pre *pre, struct
 10 				opt = haskw(trim, "-include") || haskw(trim, "sinclude");
 11 				kwlen = haskw(trim, "-include") || haskw(trim, "sinclude") ? 8 : 7;
 12 				incarg = trimdup(trim + kwlen, strlen(trim + kwlen));
 13-			if (isplainpath(incarg)) {
 14-				free(trim);
 15-				rc = preprocinclude(dir, incarg, pre, inc, targets);
 16-				if (rc < 0 && !opt)
 17-					parseerr(&line, "include not found", 0);
 18-				free(incarg);
 19-				free(line.path);
 20-				free(line.text);
 21-				if (rc < 0 && !opt) {
 22-					free(dir);
 23-					free(src);
 24-					popinc(inc);
 25-					return -1;
 26-				}
 27+				if (isplainpath(incarg)) {
 28+					free(trim);
 29+					rc = preprocinclude(dir, incarg, pre, inc, targets);
 30+					if (rc < 0 && !opt)
 31+						parseerr(&line, "include not found", 0);
 32+					free(incarg);
 33+					free(line.path);
 34+					free(line.text);
 35+					if (rc < 0 && !opt) {
 36+						free(dir);
 37+						free(src);
 38+						popinc(inc);
 39+						return -1;
 40+					}
 41 					continue;
 42 				}
 43 				free(incarg);
 44@@ -855,6 +854,22 @@ parseexpr(const struct PreLine *line, const char *s)
 45 	return state;
 46 }
 47 
 48+static struct Node
 49+parseexport(const struct PreLine *line, const char *s, int exported)
 50+{
 51+	struct Node state;
 52+
 53+	memset(&state, 0, sizeof(state));
 54+	state.kind = NODE_EXPORT;
 55+	state.loc.line0 = line->line0;
 56+	state.loc.line1 = line->line1;
 57+	state.data.export.exported = exported;
 58+	state.data.export.all = *s == 0;
 59+	if (!state.data.export.all)
 60+		astsplitwords(&state.data.export.names, s, strlen(s));
 61+	return state;
 62+}
 63+
 64 /* unclassified line, treat as unsupported syntax */
 65 static struct Node
 66 parseraw(const struct PreLine *line, const char *s, char recipeprefix)
 67@@ -992,12 +1007,14 @@ parseline(const struct PreLine *line, const struct SpecialTargets *targets)
 68 	int dcolon;
 69 	int is_override;
 70 	int is_export;
 71+	int is_unexport;
 72 	size_t n;
 73 	ptrdiff_t colon;
 74 
 75 	trim = trimdup(line->text, strlen(line->text));
 76 	is_override = 0;
 77 	is_export = 0;
 78+	is_unexport = 0;
 79 	if (haskw(trim, "override")) {
 80 		char *rest;
 81 
 82@@ -1014,6 +1031,14 @@ parseline(const struct PreLine *line, const struct SpecialTargets *targets)
 83 		trim = rest;
 84 		is_export = 1;
 85 	}
 86+	if (haskw(trim, "unexport")) {
 87+		char *rest;
 88+
 89+		rest = trimdup(trim + 8, strlen(trim + 8));
 90+		free(trim);
 91+		trim = rest;
 92+		is_unexport = 1;
 93+	}
 94 	n = strlen(trim);
 95 
 96 	if (!n) {
 97@@ -1087,6 +1112,8 @@ parseline(const struct PreLine *line, const struct SpecialTargets *targets)
 98 				state.data.assign.origin = ORIGIN_OVERRIDE;
 99 			if (is_export)
100 				state.data.assign.exported = 1;
101+			if (is_unexport)
102+				state.data.assign.exported = -1;
103 			free(trim);
104 			return state;
105 		}
106@@ -1107,12 +1134,13 @@ parseline(const struct PreLine *line, const struct SpecialTargets *targets)
107 			state.data.assign.origin = ORIGIN_OVERRIDE;
108 		if (is_export)
109 			state.data.assign.exported = 1;
110+		if (is_unexport)
111+			state.data.assign.exported = -1;
112 		free(trim);
113 		return state;
114 	}
115-	if (is_export) {
116-		parseerr(line, "directive", "export");
117-		state = blanknode(line);
118+	if (is_export || is_unexport) {
119+		state = parseexport(line, trim, is_export && !is_unexport);
120 		free(trim);
121 		return state;
122 	}
+9, -0
 1@@ -25,6 +25,7 @@ enum NodeKind {
 2 	NODE_RULE,
 3 	NODE_INCLUDE,
 4 	NODE_COND,
 5+	NODE_EXPORT,
 6 	NODE_RAW,
 7 };
 8 
 9@@ -130,6 +131,12 @@ struct RawNode {
10 	char *text;
11 };
12 
13+struct ExportNode {
14+	int all;
15+	int exported;
16+	struct StrList names;
17+};
18+
19 struct Node;
20 
21 struct NodeList {
22@@ -155,6 +162,7 @@ struct Node {
23 		struct RuleNode rule;
24 		struct IncludeNode include;
25 		struct CondNode cond;
26+		struct ExportNode export;
27 		struct RawNode raw;
28 	} data;
29 };
30@@ -174,6 +182,7 @@ struct RuleSet {
31 	struct RuleNode *rules;
32 	size_t nrules;
33 	struct StrList phony;
34+	int export_all;
35 	int posix;
36 	int envoverride;
37 };
+1, -1
1@@ -239,7 +239,7 @@ parsesubmake(struct SubMake *dst, const char *cmd)
2 		if (strcmp(ntok, "&&") == 0) {
3 			free(ntok);
4 			break;
5- 		}
6+		}
7 		if ((strcmp(ntok, "-C") == 0 || strcmp(ntok, "--directory") == 0) && i + 1 < toks.n) {
8 			addstr(&dst->flags, ntok);
9 			free(dst->dir);
+5, -0
 1@@ -9,6 +9,7 @@ initspecialtargets(struct SpecialTargets *targets)
 2 	targets->posix = 0;
 3 	memset(&targets->phony, 0, sizeof(targets->phony));
 4 	targets->recipeprefix = '\t';
 5+	targets->export_all = 0;
 6 }
 7 
 8 void
 9@@ -40,5 +41,9 @@ handlespecialrule(struct SpecialTargets *targets, const struct RuleNode *rule)
10 		}
11 		return 1;
12 	}
13+	if (rule->targets.n == 1 && strcmp(rule->targets.v[0], ".EXPORT_ALL_VARIABLES") == 0) {
14+		targets->export_all = 1;
15+		return 1;
16+	}
17 	return 0;
18 }
+1, -1
1@@ -276,7 +276,7 @@ if [ "$dryrun" -eq 0 ]; then
2 		probe_cmd="$probe_cmd '$target'"
3 	done
4 	IFS=$oldifs
5-	probe_out=$(eval "$probe_cmd -n" 2>&1)
6+	probe_out=$(eval "$probe_cmd -v -n" 2>&1)
7 	probe_status=$?
8 	if [ "$probe_status" -ne 0 ]; then
9 		if ! rewrite_ninja_error "$probe_out"; then