commit 4f51eb3
shrub
·
2026-04-20 18:31:41 +0000 UTC
parent 088da53
keep target env so we can do lazy recipe expansion, and lower info into proper ninja commands. also some wrapper plumbing to preserve gmake semantics. +380 test passes
11 files changed,
+211,
-58
+1,
-1
1@@ -175,7 +175,7 @@ genninjafile(const struct Graph *graph, const char *path, const char *prefix, in
2 fprintf(fp, " %s", graph->v[i].order_only.v[j]);
3 }
4 fprintf(fp, "\n\n");
5- } else if (graph->v[i].prereqs.n > 0 || graph->v[i].order_only.n > 0) {
6+ } else if (graph->v[i].defined || graph->v[i].prereqs.n > 0 || graph->v[i].order_only.n > 0) {
7 fprintf(fp, "build %s: phony", graph->v[i].name);
8 for (j = 0; j < graph->v[i].prereqs.n; j++)
9 fprintf(fp, " %s", graph->v[i].prereqs.v[j]);
+6,
-0
1@@ -357,6 +357,12 @@ main(int argc, char **argv)
2 freesubgraph(&sg);
3 return 1;
4 }
5+ if (expandgraph(&sg.graph) < 0) {
6+ fprintf(stderr, "expand error in %s\n", path ? path : "(default)");
7+ free(pathbuf);
8+ freesubgraph(&sg);
9+ return 1;
10+ }
11 if (dump_graph)
12 dumpgraph(&sg.graph);
13 if (gen == GEN_DOT) {
+1,
-1
1@@ -325,7 +325,7 @@ static const struct func funcs[] = {
2 {"wildcard", FNEXP1, {.f1 = fnwildcard}},
3 {"shell", FNEXP1, {.f1 = fnshell}},
4 {"sort", FNEXP1, {.f1 = fnsort}},
5- {"info", FNEXP1, {.f1 = fninfo}},
6+ {"info", FNCTX, {.ctx = fninfo}},
7 {"notdir", FNEXP1, {.f1 = fnnotdir}},
8 {"dir", FNEXP1, {.f1 = fndir}},
9 {"basename", FNEXP1, {.f1 = fnbasename}},
+15,
-3
1@@ -421,10 +421,22 @@ fnsort(const char *text)
2 }
3
4 char *
5-fninfo(const char *text)
6+fninfo(struct EvalCtx *ctx, const char *args)
7 {
8- fputs(text, stderr);
9- fputc('\n', stderr);
10+ char *text;
11+
12+ text = expandstr(ctx, args);
13+ if (ctx->avoid_io && ctx->side_effects) {
14+ char *cmd;
15+
16+ cmd = cat3("echo ", text, "");
17+ addstr(ctx->side_effects, cmd);
18+ free(cmd);
19+ } else {
20+ fputs(text, stdout);
21+ fputc('\n', stdout);
22+ }
23+ free(text);
24 return xstrdup("");
25 }
26
+4,
-6
1@@ -192,13 +192,9 @@ instpatrule(const struct PatRules *rules, const struct Graph *graph, struct Targ
2 t->order_only.v[t->order_only.n++] = s;
3 }
4 for (j = 0; j < rules->v[i].recipes.n; j++) {
5- char *s, *vars, *exp;
6+ char *exp;
7
8- s = applystem(rules->v[i].recipes.v[j].body, stem);
9- vars = expandstr(ctx, s);
10- exp = expandstem(vars, stem);
11- free(vars);
12- free(s);
13+ exp = expandstem(rules->v[i].recipes.v[j].body, stem);
14 if (!exp[0]) {
15 free(exp);
16 continue;
17@@ -212,6 +208,8 @@ instpatrule(const struct PatRules *rules, const struct Graph *graph, struct Targ
18 copysubmake(&t->recipes.v[t->recipes.n].sm, &rules->v[i].recipes.v[j].sm);
19 t->recipes.n++;
20 }
21+ freeenv(&t->env);
22+ copyenv(&t->env, ctx->env);
23 free(stem);
24 return 1;
25 }
+75,
-24
1@@ -14,6 +14,7 @@ struct TAssign {
2 char *lhs;
3 char *rhs;
4 enum AssignOp op;
5+ enum Origin origin;
6 };
7
8 struct GraphState {
9@@ -70,29 +71,6 @@ targetmatches(const char *pat, const char *name)
10 return patmatches(pat, name);
11 }
12
13-static void
14-addexprecipes(struct RecipeList *dest, const struct RecipeList *src, struct EvalCtx *ctx)
15-{
16- size_t i;
17-
18- for (i = 0; i < src->n; i++) {
19- char *exp;
20-
21- exp = expandstr(ctx, src->v[i].body);
22- if (!exp[0]) {
23- free(exp);
24- continue;
25- }
26- dest->v = xrealloc(dest->v, (dest->n + 1) * sizeof(dest->v[0]));
27- dest->v[dest->n].body = exp;
28- dest->v[dest->n].silent = src->v[i].silent;
29- dest->v[dest->n].ignore = src->v[i].ignore;
30- dest->v[dest->n].recursive = src->v[i].recursive;
31- dest->v[dest->n].submake = src->v[i].submake;
32- copysubmake(&dest->v[dest->n].sm, &src->v[i].sm);
33- dest->n++;
34- }
35-}
36
37 /* we apply target assignments from the graphstate (gs.tas) to those specific
38 * targets, for some target like
39@@ -114,6 +92,7 @@ applytassigns(struct GraphState *gs, struct EvalCtx *ctx, const char *name)
40 in.lhs = gs->tas[i].lhs;
41 in.rhs = gs->tas[i].rhs;
42 in.op = gs->tas[i].op;
43+ in.origin = gs->tas[i].origin;
44 evalassign(ctx, &in);
45 }
46 }
47@@ -156,7 +135,9 @@ addrule(struct GraphState *gs, const char *name, const struct RuleNode *rule)
48 addwords(&t->prereqs, &rule->prereqs);
49 addwords(&t->order_only, &rule->order_only);
50 targetenv(gs, &ctx, name);
51- addexprecipes(&t->recipes, &rule->recipes, &ctx);
52+ addrecipes(&t->recipes, &rule->recipes);
53+ freeenv(&t->env);
54+ copyenv(&t->env, &env);
55 freeenv(&env);
56
57 for (i = 0; i < rule->prereqs.n; i++) {
58@@ -182,6 +163,7 @@ addtassign(struct GraphState *gs, const struct AssignNode *assign)
59 ta->lhs = xstrdup(assign->lhs);
60 ta->rhs = xstrdup(assign->rhs);
61 ta->op = assign->op;
62+ ta->origin = assign->origin;
63 }
64
65 /*generate the graph from the evaluated rule set
66@@ -295,6 +277,74 @@ buildgraph(const struct RuleSet *ruleset, struct Graph *graph)
67 return ctx.errors ? -1 : 0;
68 }
69
70+int
71+expandgraph(struct Graph *graph)
72+{
73+ size_t i, k, j;
74+
75+ for (i = 0; i < graph->n; i++) {
76+ struct Target *t = &graph->v[i];
77+ struct EvalCtx ctx;
78+ struct StrList side_effects;
79+ struct RecipeList new_recipes;
80+
81+ if (!t->env.n) {
82+ freeenv(&t->env);
83+ continue;
84+ }
85+ memset(&ctx, 0, sizeof(ctx));
86+ memset(&side_effects, 0, sizeof(side_effects));
87+ memset(&new_recipes, 0, sizeof(new_recipes));
88+ ctx.env = &t->env;
89+ ctx.avoid_io = 1;
90+ ctx.side_effects = &side_effects;
91+
92+ for (k = 0; k < t->recipes.n; k++) {
93+ struct Recipe *r = &t->recipes.v[k];
94+ char *exp;
95+
96+ freestrs(&side_effects);
97+ exp = expandstr(&ctx, r->body);
98+ for (j = 0; j < side_effects.n; j++) {
99+ struct Recipe *nr;
100+
101+ new_recipes.v = xrealloc(new_recipes.v,
102+ (new_recipes.n + 1) * sizeof(new_recipes.v[0]));
103+ nr = &new_recipes.v[new_recipes.n++];
104+ memset(nr, 0, sizeof(*nr));
105+ nr->body = xstrdup(side_effects.v[j]);
106+ nr->silent = r->silent;
107+ nr->ignore = r->ignore;
108+ }
109+ if (exp[0]) {
110+ struct Recipe *nr;
111+
112+ new_recipes.v = xrealloc(new_recipes.v,
113+ (new_recipes.n + 1) * sizeof(new_recipes.v[0]));
114+ nr = &new_recipes.v[new_recipes.n++];
115+ *nr = *r;
116+ nr->body = exp;
117+ free(r->body);
118+ r->body = 0;
119+ memset(&r->sm, 0, sizeof(r->sm));
120+ } else {
121+ free(exp);
122+ }
123+ }
124+
125+ freestrs(&side_effects);
126+ freerecipes(&t->recipes);
127+ t->recipes = new_recipes;
128+ freeenv(&t->env);
129+ }
130+
131+ for (i = 0; i < graph->nsubs; i++) {
132+ if (expandgraph(&graph->subs[i].graph) < 0)
133+ return -1;
134+ }
135+ return 0;
136+}
137+
138 void
139 freegraph(struct Graph *graph)
140 {
141@@ -308,6 +358,7 @@ freegraph(struct Graph *graph)
142 freestrs(&graph->v[i].prereqs);
143 freestrs(&graph->v[i].order_only);
144 freerecipes(&graph->v[i].recipes);
145+ freeenv(&graph->v[i].env);
146 }
147 for (i = 0; i < graph->nsubs; i++)
148 freesubgraph(&graph->subs[i]);
+3,
-13
1@@ -3,18 +3,6 @@
2
3 #include "shinobi.h"
4
5-struct Var {
6- char *name;
7- char *val;
8- int simple;
9- enum Origin origin;
10-};
11-
12-struct Env {
13- struct Var *v;
14- size_t n;
15-};
16-
17 struct CallFrame {
18 char **args;
19 size_t nargs;
20@@ -26,6 +14,8 @@ struct EvalCtx {
21 struct RuleSet *out;
22 struct CallFrame *call;
23 int errors;
24+ int avoid_io;
25+ struct StrList *side_effects;
26 };
27
28 void *xmalloc(size_t n);
29@@ -72,7 +62,7 @@ char *fnfilterout(const char *patterns, const char *text);
30 char *fnaddprefix(const char *prefix, const char *names);
31 char *fnaddsuffix(const char *suffix, const char *names);
32 char *fnsort(const char *text);
33-char *fninfo(const char *text);
34+char *fninfo(struct EvalCtx *ctx, const char *args);
35 char *fnnotdir(const char *names);
36 char *fndir(const char *names);
37 char *fnbasename(const char *names);
+13,
-2
1@@ -733,10 +733,21 @@ parseline(const struct PreLine *line)
2 if (colon >= 0 && as.ok && (size_t)colon < as.pos) {
3 /* some inline rule like 'all: ; @echo hi' */
4 size_t off = dcolon ? 2 : 1;
5+ size_t base = (size_t)colon + off;
6+ int assign_override = is_override;
7 ptrdiff_t semi = findtop(trim + colon + off, as.pos - (size_t)colon - off, ';');
8+
9+ while (base < n && isspace((unsigned char)trim[base]))
10+ base++;
11+ if (haskw(trim + base, "override")) {
12+ base += 8;
13+ while (base < n && isspace((unsigned char)trim[base]))
14+ base++;
15+ assign_override = 1;
16+ }
17 if (semi < 0) {
18- state = parseassign(line, trim, n, (size_t)colon + 1, as, 1, (size_t)colon);
19- if (is_override)
20+ state = parseassign(line, trim, n, base, as, 1, (size_t)colon);
21+ if (assign_override)
22 state.data.assign.origin = ORIGIN_OVERRIDE;
23 free(trim);
24 return state;
+8,
-8
1@@ -424,11 +424,9 @@ instsufrule(const struct SufRules *rules,
2 t->prereqs.v[0] = src;
3 t->prereqs.n++;
4 for (k = 0; k < rules->sufs[r].recipes.n; k++) {
5- char *vars, *exp;
6+ char *exp;
7
8- vars = expandstr(ctx, rules->sufs[r].recipes.v[k].body);
9- exp = expandauto(vars, t, stem);
10- free(vars);
11+ exp = expandauto(rules->sufs[r].recipes.v[k].body, t, stem);
12 if (!exp[0]) {
13 free(exp);
14 continue;
15@@ -442,6 +440,8 @@ instsufrule(const struct SufRules *rules,
16 copysubmake(&t->recipes.v[t->recipes.n].sm, &rules->sufs[r].recipes.v[k].sm);
17 t->recipes.n++;
18 }
19+ freeenv(&t->env);
20+ copyenv(&t->env, ctx->env);
21 free(stem);
22 return 1;
23 }
24@@ -468,11 +468,9 @@ instsufrule(const struct SufRules *rules,
25 t->prereqs.v[0] = src;
26 t->prereqs.n++;
27 for (k = 0; k < rules->singlesuf[j].recipes.n; k++) {
28- char *vars, *exp;
29+ char *exp;
30
31- vars = expandstr(ctx, rules->singlesuf[j].recipes.v[k].body);
32- exp = expandauto(vars, t, t->name);
33- free(vars);
34+ exp = expandauto(rules->singlesuf[j].recipes.v[k].body, t, t->name);
35 if (!exp[0]) {
36 free(exp);
37 continue;
38@@ -486,6 +484,8 @@ instsufrule(const struct SufRules *rules,
39 copysubmake(&t->recipes.v[t->recipes.n].sm, &rules->singlesuf[j].recipes.v[k].sm);
40 t->recipes.n++;
41 }
42+ freeenv(&t->env);
43+ copyenv(&t->env, ctx->env);
44 return 1;
45 }
46 }
+14,
-0
1@@ -159,12 +159,25 @@ struct RuleSet {
2 size_t nrules;
3 };
4
5+struct Var {
6+ char *name;
7+ char *val;
8+ int simple;
9+ enum Origin origin;
10+};
11+
12+struct Env {
13+ struct Var *v;
14+ size_t n;
15+};
16+
17 struct Target {
18 char *name;
19 char *owner;
20 struct StrList prereqs;
21 struct StrList order_only;
22 struct RecipeList recipes;
23+ struct Env env;
24 int dcolon;
25 int defined;
26 };
27@@ -197,6 +210,7 @@ int parse(const char *path, const char *src, struct Ast *ast);
28 void freeast(struct Ast *ast);
29 void freeruleset(struct RuleSet *ruleset);
30 int buildgraph(const struct RuleSet *ruleset, struct Graph *graph);
31+int expandgraph(struct Graph *graph);
32 void freegraph(struct Graph *graph);
33 int buildsubgraph(struct SubGraph *sg);
34 void freesubgraph(struct SubGraph *sg);
+71,
-0
1@@ -63,6 +63,35 @@ probemk() {
2 return 1
3 }
4
5+default_target() {
6+ sed -n 's/^default //p' build.ninja 2>/dev/null | sed -n '1p'
7+}
8+
9+uptodate_target() {
10+ if [ -n "$targets" ]; then
11+ printf '%s\n' "$targets" | sed -n '1p'
12+ else
13+ default_target
14+ fi
15+}
16+
17+delayed_only() {
18+ printf '%s\n' "$1" | while IFS= read -r line; do
19+ case $line in
20+ '') continue ;;
21+ '['*'] '*) line=${line#*] } ;;
22+ esac
23+ case $line in
24+ echo\ *|echo\ -e\ *)
25+ :
26+ ;;
27+ *)
28+ return 1
29+ ;;
30+ esac
31+ done
32+}
33+
34 while [ "$#" -gt 0 ]; do
35 case "$1" in
36 -v|--version)
37@@ -185,4 +214,46 @@ if [ -n "$targets" ]; then
38 IFS=$oldifs
39 fi
40
41+if [ "$dryrun" -eq 0 ]; then
42+ probe_cmd="$ninja_bin -f build.ninja"
43+ if [ -n "${jobs}" ]; then
44+ probe_cmd="$probe_cmd -j $jobs"
45+ fi
46+ oldifs=$IFS
47+ IFS='
48+'
49+ for target in $targets; do
50+ probe_cmd="$probe_cmd '$target'"
51+ done
52+ IFS=$oldifs
53+ probe_out=$(eval "$probe_cmd -n" 2>&1)
54+ probe_status=$?
55+ if [ "$probe_status" -ne 0 ]; then
56+ printf '%s' "$probe_out"
57+ exit "$probe_status"
58+ fi
59+ if [ -z "$probe_out" ]; then
60+ target=$(uptodate_target)
61+ if [ -n "$target" ]; then
62+ printf "%s: '%s' is up to date.\n" "${0##*/}" "$target"
63+ exit 0
64+ fi
65+ fi
66+ if delayed_only "$probe_out"; then
67+ run_out=$("$@" 2>&1)
68+ run_status=$?
69+ if [ -n "$run_out" ]; then
70+ printf '%s\n' "$run_out"
71+ fi
72+ if [ "$run_status" -ne 0 ]; then
73+ exit "$run_status"
74+ fi
75+ target=$(uptodate_target)
76+ if [ -n "$target" ]; then
77+ printf "%s: '%s' is up to date.\n" "${0##*/}" "$target"
78+ fi
79+ exit 0
80+ fi
81+fi
82+
83 NINJA_STATUS= exec "$@"