commit 23ba302

shrub  ·  2026-04-17 20:33:39 +0000 UTC
parent f1aa24f
+12 new test passes, conditional stuff, handle override, and sinclude + misc
9 files changed,  +207, -82
+40, -25
 1@@ -224,7 +224,7 @@ main(int argc, char **argv)
 2 	struct RuleSet rs;
 3 	struct Graph graph;
 4 
 5-	path = "Makefile";
 6+	path = 0;
 7 	dump_ast = 0;
 8 	dump_graph = 0;
 9 	gen = GEN_NINJA;
10@@ -291,42 +291,57 @@ main(int argc, char **argv)
11 		  return 1;
12 		}
13 	}
14-	src = readfile(path);
15-	if (!src) {
16-	  /* iterate through possible makefile names */
17-
18-	  char* mks[]={
19-	    "makefile",
20-	    "GNUmakefile",
21-	    NULL
22-	  };
23-
24-	    for(size_t mk=0; mks[mk]!=NULL; ++mk)
25-	      {
26-		path=mks[mk];
27-		src=readfile(path);
28+	if (path) {
29+		src = readfile(path);
30+		if (!src) {
31+			fprintf(stderr, "could not read %s\n", path);
32+			return 1;
33+		}
34+	} else {
35+		static const char *const defaults[] = {
36+			"GNUmakefile",
37+			"makefile",
38+			"Makefile",
39+			0,
40+		};
41+		size_t mk;
42 
43-		if(src) break;
44-		
45-		if(mks[mk+1]==NULL && !src)
46-		  {
47-		    fprintf(stderr, "could not find a makefile\n");
48-		    return 1;
49-		  }
50-	      }
51+		src = 0;
52+		for (mk = 0; defaults[mk]; mk++) {
53+			path = defaults[mk];
54+			src = readfile(path);
55+			if (src)
56+				break;
57+		}
58+		if (!src) {
59+			fprintf(stderr, "could not find a makefile\n");
60+			return 1;
61+		}
62 	}
63 	src = appendassigns(src, assigns, nassigns);
64-	free(assigns);
65 	if (!src) {
66+	  free(assigns);
67 	  fprintf(stderr, "out of memory\n");
68 	  return 1;
69 	}
70 	if (parse(path, src, &ast) < 0) {
71 		fprintf(stderr, "parse error in %s\n", path);
72+		free(assigns);
73 		free(src);
74 		return 1;
75-		
76 	}
77+	{
78+		size_t marked = 0;
79+		size_t k = ast.n;
80+		while (k > 0 && marked < nassigns) {
81+			k--;
82+			if (ast.v[k].kind == NODE_ASSIGN) {
83+				ast.v[k].data.assign.origin = ORIGIN_COMMAND;
84+				marked++;
85+			}
86+		}
87+	}
88+	free(assigns);
89 	if (eval(&ast, &rs) < 0) {
90 		fprintf(stderr, "eval error in %s\n", path);
91 		freeast(&ast);
+12, -5
 1@@ -49,6 +49,7 @@ copyenv(struct Env *dst, const struct Env *src)
 2 		dst->v[i].name = xstrdup(src->v[i].name);
 3 		dst->v[i].val = xstrdup(src->v[i].val);
 4 		dst->v[i].simple = src->v[i].simple;
 5+		dst->v[i].origin = src->v[i].origin;
 6 	}
 7 	dst->n = src->n;
 8 }
 9@@ -59,33 +60,38 @@ evalassign(struct EvalCtx *ctx, const struct AssignNode *in)
10 	struct Env *env;
11 	struct Var *v;
12 	char *rhs, *joined;
13+	enum Origin o;
14 
15 	env = ctx->env;
16+	o = in->origin ? in->origin : ORIGIN_FILE;
17 	switch (in->op) {
18 	case ASSIGN_EQ:
19-		envsetvar(env, in->lhs, xstrdup(in->rhs), 0);
20+		envsetvar(env, in->lhs, xstrdup(in->rhs), 0, o);
21 		break;
22 	case ASSIGN_COLON_EQ:
23-		envsetvar(env, in->lhs, expandstr(ctx, in->rhs), 1);
24+		envsetvar(env, in->lhs, expandstr(ctx, in->rhs), 1, o);
25 		break;
26 	case ASSIGN_QMARK_EQ:
27 		if (!findvar(env, in->lhs))
28-			envsetvar(env, in->lhs, xstrdup(in->rhs), 0);
29+			envsetvar(env, in->lhs, xstrdup(in->rhs), 0, o);
30 		break;
31 	case ASSIGN_PLUS_EQ:
32 		v = findvar(env, in->lhs);
33 		if (!v) {
34-			envsetvar(env, in->lhs, xstrdup(in->rhs), 0);
35+			envsetvar(env, in->lhs, xstrdup(in->rhs), 0, o);
36 			break;
37 		}
38+		if ((int)o < (int)v->origin)
39+			break;
40 		rhs = v->simple ? expandstr(ctx, in->rhs) : xstrdup(in->rhs);
41 		joined = cat3(v->val, " ", rhs);
42 		free(rhs);
43 		free(v->val);
44 		v->val = joined;
45+		v->origin = o;
46 		break;
47 	case ASSIGN_BANG_EQ:
48-		envsetvar(env, in->lhs, expandstr(ctx, in->rhs), 1);
49+		envsetvar(env, in->lhs, expandstr(ctx, in->rhs), 1, o);
50 		break;
51 	}
52 }
53@@ -158,6 +164,7 @@ addrulesetassign(struct AssignNode **vec, size_t *n, const struct Node *src, str
54 	memset(dst, 0, sizeof(*dst));
55 	dst->lhs = xstrdup(src->data.assign.lhs);
56 	dst->op = src->data.assign.op;
57+	dst->origin = src->data.assign.origin;
58 	dst->tspec = src->data.assign.tspec;
59 	if (src->data.assign.op == ASSIGN_COLON_EQ || src->data.assign.op == ASSIGN_BANG_EQ)
60 		dst->rhs = expandstr(ctx, src->data.assign.rhs);
+2, -1
 1@@ -7,6 +7,7 @@ struct Var {
 2 	char *name;
 3 	char *val;
 4 	int simple;
 5+	enum Origin origin;
 6 };
 7 
 8 struct Env {
 9@@ -31,7 +32,7 @@ void addrecipe(struct RecipeList *dest, const char *raw);
10 void addrecipes(struct RecipeList *dest, const struct RecipeList *src);
11 void freestrs(struct StrList *list);
12 void freerecipes(struct RecipeList *list);
13-void envsetvar(struct Env *env, const char *name, char *val, int simple);
14+void envsetvar(struct Env *env, const char *name, char *val, int simple, enum Origin origin);
15 struct Target *findtarget(struct Graph *graph, const char *name);
16 const struct Target *findctarget(const struct Graph *graph, const char *name);
17 
+85, -18
  1@@ -44,7 +44,7 @@ parseerr(const struct PreLine *line, const char *msg, const char *detail)
  2 }
  3 
  4 static const char *const unsupported_kws[] = {
  5-    "define", "endef", "override", "export", "unexport",
  6+    "define", "endef", "export", "unexport",
  7     "undefine", "vpath", "private", "load", 0};
  8 
  9 static char *
 10@@ -428,12 +428,14 @@ preprocfile(const char *path, const char *src_override, struct Pre *pre, struct
 11 
 12 			stripcomment(line.text);
 13 			trim = trimdup(line.text, strlen(line.text));
 14-			if (haskw(trim, "include") || haskw(trim, "-include")) {
 15+			if (haskw(trim, "include") || haskw(trim, "-include") || haskw(trim, "sinclude")) {
 16 				char *full;
 17 				int rc;
 18+				size_t kwlen;
 19 
 20-				opt = haskw(trim, "-include");
 21-				incarg = trimdup(trim + (opt ? 8 : 7), strlen(trim + (opt ? 8 : 7)));
 22+				opt = haskw(trim, "-include") || haskw(trim, "sinclude");
 23+				kwlen = haskw(trim, "-include") || haskw(trim, "sinclude") ? 8 : 7;
 24+				incarg = trimdup(trim + kwlen, strlen(trim + kwlen));
 25 				if (isplainpath(incarg)) {
 26 					full = incarg[0] == '/' ? xstrdup(incarg) : joinpath(dir, incarg);
 27 					free(line.path);
 28@@ -507,6 +509,9 @@ parseinclude(const struct PreLine *line, const char *s)
 29 	if (haskw(s, "-include")) {
 30 		state.data.include.optional = 1;
 31 		off = strlen("-include");
 32+	} else if (haskw(s, "sinclude")) {
 33+		state.data.include.optional = 1;
 34+		off = strlen("sinclude");
 35 	} else {
 36 		off = strlen("include");
 37 	}
 38@@ -557,21 +562,51 @@ parsecond(const struct PreLine *line, const char *s)
 39 		state.data.cond.arg2 = xstrndup("", 0);
 40 		return state;
 41 	}
 42-	if ((p[0] == '(' && p[n - 1] == ')') || (p[0] == '"' && p[n - 1] == '"')) {
 43-		mid = n / 2;
 44-		state.data.cond.arg1 = xstrndup(p, n);
 45+	if (p[0] == '(' && p[n - 1] == ')') {
 46+		state.data.cond.arg1 = xstrndup("", 0);
 47 		state.data.cond.arg2 = xstrndup("", 0);
 48-		if (p[0] == '(') {
 49-			mid = (size_t)(findtop(p + 1, n - 2, ','));
 50-			if (mid != (size_t)-1) {
 51-				free(state.data.cond.arg1);
 52-				free(state.data.cond.arg2);
 53-				state.data.cond.arg1 = trimdup(p + 1, mid);
 54-				state.data.cond.arg2 = trimdup(p + 2 + mid, n - 3 - mid);
 55-			} else {
 56-				parseerr(line, "malformed ifeq/ifneq arguments", p);
 57-			}
 58+		mid = (size_t)(findtop(p + 1, n - 2, ','));
 59+		if (mid != (size_t)-1) {
 60+			free(state.data.cond.arg1);
 61+			free(state.data.cond.arg2);
 62+			state.data.cond.arg1 = trimdup(p + 1, mid);
 63+			state.data.cond.arg2 = trimdup(p + 2 + mid, n - 3 - mid);
 64+		} else {
 65+			parseerr(line, "malformed ifeq/ifneq arguments", p);
 66+		}
 67+	} else if (p[0] == '"' || p[0] == '\'') {
 68+		size_t e1, s2, e2;
 69+		char q1 = p[0], q2;
 70+		e1 = 1;
 71+		while (e1 < n && p[e1] != q1)
 72+			e1++;
 73+		if (e1 >= n) {
 74+			parseerr(line, "malformed ifeq/ifneq arguments", p);
 75+			state.data.cond.arg1 = xstrndup("", 0);
 76+			state.data.cond.arg2 = xstrndup("", 0);
 77+			return state;
 78 		}
 79+		s2 = e1 + 1;
 80+		while (s2 < n && isspace((unsigned char)p[s2]))
 81+			s2++;
 82+		if (s2 >= n || (p[s2] != '"' && p[s2] != '\'')) {
 83+			parseerr(line, "malformed ifeq/ifneq arguments", p);
 84+			state.data.cond.arg1 = xstrndup("", 0);
 85+			state.data.cond.arg2 = xstrndup("", 0);
 86+			return state;
 87+		}
 88+		q2 = p[s2];
 89+		e2 = s2 + 1;
 90+		while (e2 < n && p[e2] != q2)
 91+			e2++;
 92+		if (e2 >= n || e2 != n - 1) {
 93+			parseerr(line, "malformed ifeq/ifneq arguments", p);
 94+			state.data.cond.arg1 = xstrndup("", 0);
 95+			state.data.cond.arg2 = xstrndup("", 0);
 96+			return state;
 97+		}
 98+		state.data.cond.arg1 = xstrndup(p + 1, e1 - 1);
 99+		state.data.cond.arg2 = xstrndup(p + s2 + 1, e2 - s2 - 1);
100 	} else {
101 		parseerr(line, "malformed ifeq/ifneq arguments", p);
102 		state.data.cond.arg1 = xstrndup("", 0);
103@@ -685,10 +720,20 @@ parseline(const struct PreLine *line)
104 	struct AssignScan as;
105 	char *trim;
106 	int dcolon;
107+	int is_override;
108 	size_t n;
109 	ptrdiff_t colon;
110 
111 	trim = trimdup(line->text, strlen(line->text));
112+	is_override = 0;
113+	if (haskw(trim, "override")) {
114+		char *rest;
115+
116+		rest = trimdup(trim + 8, strlen(trim + 8));
117+		free(trim);
118+		trim = rest;
119+		is_override = 1;
120+	}
121 	n = strlen(trim);
122 
123 	if (!n) {
124@@ -714,7 +759,7 @@ parseline(const struct PreLine *line)
125 		free(trim);
126 		return state;
127 	}
128-	if (haskw(trim, "include") || haskw(trim, "-include")) {
129+	if (haskw(trim, "include") || haskw(trim, "-include") || haskw(trim, "sinclude")) {
130 		state = parseinclude(line, trim);
131 		free(trim);
132 		return state;
133@@ -741,12 +786,16 @@ parseline(const struct PreLine *line)
134 		ptrdiff_t semi = findtop(trim + colon + off, as.pos - (size_t)colon - off, ';');
135 		if (semi < 0) {
136 			state = parseassign(line, trim, n, (size_t)colon + 1, as, 1, (size_t)colon);
137+			if (is_override)
138+				state.data.assign.origin = ORIGIN_OVERRIDE;
139 			free(trim);
140 			return state;
141 		}
142 	}
143 	if (as.ok && (colon < 0 || as.pos <= (size_t)colon)) {
144 		state = parseassign(line, trim, n, 0, as, 0, 0);
145+		if (is_override)
146+			state.data.assign.origin = ORIGIN_OVERRIDE;
147 		free(trim);
148 		return state;
149 	}
150@@ -805,6 +854,24 @@ parseblock(const struct Pre *pre, size_t *i, struct NodeList *out)
151 				endline = &pre->v[*i];
152 				endtrim = trimdup(endline->text, strlen(endline->text));
153 				if (haskw(endtrim, "else")) {
154+					const char *rest = endtrim + 4;
155+					while (*rest && isspace((unsigned char)*rest))
156+						rest++;
157+					if (*rest) {
158+						/* else-if form; rewrite the current line
159+						   to drop else and recurse, the inner
160+						   conditional's endif also closes us */
161+						char *rep = xstrndup(rest, strlen(rest));
162+						free(endline->text);
163+						endline->text = rep;
164+						free(endtrim);
165+						if (parseblock(pre, i, &state.data.cond.elsepart) < 0)
166+							return -1;
167+						state.loc.line1 = endline->line1;
168+						addnode(out, state);
169+						last_rule = 0;
170+						continue;
171+					}
172 					free(endtrim);
173 					(*i)++;
174 					if (parseblock(pre, i, &state.data.cond.elsepart) < 0)
+20, -20
 1@@ -232,26 +232,26 @@ expandauto(const char *s, const struct Target *t, const char *stem)
 2 void
 3 seedenv(struct Env *env)
 4 {
 5-	envsetvar(env, "CC", xstrdup("cc"), 1);
 6-	envsetvar(env, "CFLAGS", xstrdup(""), 1);
 7-	envsetvar(env, "CXX", xstrdup("c++"), 1);
 8-	envsetvar(env, "CPP", xstrdup("$(CC) -E"), 0);
 9-	envsetvar(env, "FC", xstrdup("fort77"), 1);
10-	envsetvar(env, "FFLAGS", xstrdup(""), 1);
11-	envsetvar(env, "AR", xstrdup("ar"), 1);
12-	envsetvar(env, "ARFLAGS", xstrdup("-rv"), 1);
13-	envsetvar(env, "AS", xstrdup("as"), 1);
14-	envsetvar(env, "GET", xstrdup("get"), 1);
15-	envsetvar(env, "GFLAGS", xstrdup(""), 1);
16-	envsetvar(env, "LD", xstrdup("ld"), 1);
17-	envsetvar(env, "LDFLAGS", xstrdup(""), 1);
18-	envsetvar(env, "LEX", xstrdup("lex"), 1);
19-	envsetvar(env, "LFLAGS", xstrdup(""), 1);
20-	envsetvar(env, "SCCSFLAGS", xstrdup(""), 1);
21-	envsetvar(env, "SCCSGETFLAGS", xstrdup("-s"), 1);
22-	envsetvar(env, "YACC", xstrdup("yacc"), 1);
23-	envsetvar(env, "YFLAGS", xstrdup(""), 1);
24-	envsetvar(env, "SHELL", xstrdup("/bin/sh"), 1);
25+	envsetvar(env, "CC", xstrdup("cc"), 1, ORIGIN_DEFAULT);
26+	envsetvar(env, "CFLAGS", xstrdup(""), 1, ORIGIN_DEFAULT);
27+	envsetvar(env, "CXX", xstrdup("c++"), 1, ORIGIN_DEFAULT);
28+	envsetvar(env, "CPP", xstrdup("$(CC) -E"), 0, ORIGIN_DEFAULT);
29+	envsetvar(env, "FC", xstrdup("fort77"), 1, ORIGIN_DEFAULT);
30+	envsetvar(env, "FFLAGS", xstrdup(""), 1, ORIGIN_DEFAULT);
31+	envsetvar(env, "AR", xstrdup("ar"), 1, ORIGIN_DEFAULT);
32+	envsetvar(env, "ARFLAGS", xstrdup("-rv"), 1, ORIGIN_DEFAULT);
33+	envsetvar(env, "AS", xstrdup("as"), 1, ORIGIN_DEFAULT);
34+	envsetvar(env, "GET", xstrdup("get"), 1, ORIGIN_DEFAULT);
35+	envsetvar(env, "GFLAGS", xstrdup(""), 1, ORIGIN_DEFAULT);
36+	envsetvar(env, "LD", xstrdup("ld"), 1, ORIGIN_DEFAULT);
37+	envsetvar(env, "LDFLAGS", xstrdup(""), 1, ORIGIN_DEFAULT);
38+	envsetvar(env, "LEX", xstrdup("lex"), 1, ORIGIN_DEFAULT);
39+	envsetvar(env, "LFLAGS", xstrdup(""), 1, ORIGIN_DEFAULT);
40+	envsetvar(env, "SCCSFLAGS", xstrdup(""), 1, ORIGIN_DEFAULT);
41+	envsetvar(env, "SCCSGETFLAGS", xstrdup("-s"), 1, ORIGIN_DEFAULT);
42+	envsetvar(env, "YACC", xstrdup("yacc"), 1, ORIGIN_DEFAULT);
43+	envsetvar(env, "YFLAGS", xstrdup(""), 1, ORIGIN_DEFAULT);
44+	envsetvar(env, "SHELL", xstrdup("/bin/sh"), 1, ORIGIN_DEFAULT);
45 }
46 
47 void
+8, -0
 1@@ -29,6 +29,13 @@ enum AssignOp {
 2 	ASSIGN_BANG_EQ,
 3 };
 4 
 5+enum Origin {
 6+	ORIGIN_DEFAULT,
 7+	ORIGIN_FILE,
 8+	ORIGIN_COMMAND,
 9+	ORIGIN_OVERRIDE,
10+};
11+
12 enum CondKind {
13 	COND_IFEQ,
14 	COND_IFNEQ,
15@@ -77,6 +84,7 @@ struct AssignNode {
16 	char *lhs;
17 	char *rhs;
18 	enum AssignOp op;
19+	enum Origin origin;
20 	int tspec;
21 	struct StrList targets;
22 };
+7, -1
 1@@ -183,20 +183,26 @@ freerecipes(struct RecipeList *list)
 2 }
 3 
 4 void
 5-envsetvar(struct Env *env, const char *name, char *val, int simple)
 6+envsetvar(struct Env *env, const char *name, char *val, int simple, enum Origin origin)
 7 {
 8 	struct Var *v;
 9 
10 	v = findvar(env, name);
11 	if (v) {
12+		if ((int)origin < (int)v->origin) {
13+			free(val);
14+			return;
15+		}
16 		free(v->val);
17 		v->val = val;
18 		v->simple = simple;
19+		v->origin = origin;
20 		return;
21 	}
22 	env->v = xrealloc(env->v, (env->n + 1) * sizeof(env->v[0]));
23 	env->v[env->n].name = xstrdup(name);
24 	env->v[env->n].val = val;
25 	env->v[env->n].simple = simple;
26+	env->v[env->n].origin = origin;
27 	env->n++;
28 }
+22, -3
 1@@ -7,7 +7,8 @@ repo_dir=$(CDPATH= cd -- "$self_dir/.." && pwd)
 2 shin_bin=${SHIN:-"$repo_dir/shin"}
 3 ninja_bin=${NINJA:-ninja}
 4 
 5-makefile=Makefile
 6+makefile=
 7+makefile_set=0
 8 workdir=
 9 dryrun=0
10 jobs=
11@@ -73,10 +74,12 @@ while [ "$#" -gt 0 ]; do
12 		;;
13 	-f)
14 		makefile=$2
15+		makefile_set=1
16 		shift 2
17 		;;
18 	-sf|-fs)
19 		makefile=$2
20+		makefile_set=1
21 		shift 2
22 		;;
23 	-C)
24@@ -130,11 +133,27 @@ if [ -n "${workdir}" ]; then
25 	cd -- "$workdir" || exit 2
26 fi
27 
28+if [ -z "$makefile" ]; then
29+	for cand in GNUmakefile makefile Makefile; do
30+		if [ -f "$cand" ]; then
31+			makefile=$cand
32+			break
33+		fi
34+	done
35+	if [ -z "$makefile" ]; then
36+		makefile=Makefile
37+	fi
38+fi
39+
40 if probemk; then
41 	exit 0
42 fi
43 
44-set -- "$shin_bin" -f "$makefile"
45+if [ "$makefile_set" -eq 1 ]; then
46+	set -- "$shin_bin" -f "$makefile"
47+else
48+	set -- "$shin_bin"
49+fi
50 if [ -n "$vars" ]; then
51 	oldifs=$IFS
52 	IFS='
53@@ -154,7 +173,7 @@ if [ -n "${jobs}" ]; then
54 	set -- "$@" -j "$jobs"
55 fi
56 if [ "$dryrun" -ne 0 ]; then
57-	set -- "$@" -n
58+	set -- "$@" -v -n
59 fi
60 if [ -n "$targets" ]; then
61 	oldifs=$IFS
+11, -9
 1@@ -389,11 +389,7 @@ sub toplevel
 2   $total_tests_failed = $total_tests_run - $total_tests_passed;
 3 
 4   if ($total_tests_failed) {
 5-    print "\n$total_tests_failed Test";
 6-    print "s" unless $total_tests_failed == 1;
 7-    print " in $categories_failed Categor";
 8-    print ($categories_failed == 1 ? "y" : "ies");
 9-    print " Failed :-(\n\n";
10+    print "\n";
11     my $i = 0;
12     for my $e (@failure_info) {
13         ++$i;
14@@ -403,21 +399,27 @@ sub toplevel
15         print "    diff: $e->{'diff'}\n" if exists($e->{'diff'});
16         print "    log:  $e->{'log'}\n" if exists($e->{'log'});
17     }
18+    print "\n$total_tests_passed/$total_tests_run Tests Passed";
19+    print " ($total_tests_failed failed";
20+    print ", $categories_failed categor";
21+    print ($categories_failed == 1 ? "y" : "ies");
22+    print " affected";
23+    print ") :-(\n\n";
24     return 0;
25   }
26 
27   if ($some_test_failed) {
28       # Something failed but no tests were marked failed... probably a syntax
29       # error in a test script
30-    print "\nSome tests failed (See output for details) :-(\n\n";
31+    print "\n$total_tests_passed/$total_tests_run Tests Passed";
32+    print " (See output for details) :-(\n\n";
33     return 0;
34   }
35 
36-  print "\n$total_tests_passed Test";
37-  print "s" unless $total_tests_passed == 1;
38+  print "\n$total_tests_passed/$total_tests_run Tests Passed";
39   print " in $categories_passed Categor";
40   print ($categories_passed == 1 ? "y" : "ies");
41-  print " Complete ... No Failures :-)\n\n";
42+  print " ... No Failures :-)\n\n";
43   return 1;
44 }
45