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