commit b483835

shrub  ·  2026-05-14 12:37:08 +0000 UTC
parent 9782164
handle .DEFAULT, add 1 new test
11 files changed,  +126, -51
+0, -41
 1@@ -148,47 +148,6 @@ joinrecipes(const struct Target *t)
 2 	return s;
 3 }
 4 
 5-/*turn remaining automatic vars into ninja equivalents*/
 6-static char *
 7-localpath(const struct Target *t, const char *path)
 8-{
 9-	size_t n;
10-
11-	if (!t->owner || !t->owner[0])
12-		return xstrdup(path);
13-	n = strlen(t->owner);
14-	if (strncmp(path, t->owner, n) != 0 || path[n] != '/')
15-		return xstrdup(path);
16-	return xstrdup(path + n + 1);
17-}
18-
19-static char *
20-joinlocalprereqs(const struct Target *t)
21-{
22-	size_t i;
23-	struct StrList list;
24-	char *s;
25-
26-	memset(&list, 0, sizeof(list));
27-	for (i = 0; i < t->impprereqs.n; i++) {
28-		char *name;
29-
30-		name = localpath(t, t->impprereqs.v[i]);
31-		addstr(&list, name);
32-		free(name);
33-	}
34-	for (i = 0; i < t->prereqs.n; i++) {
35-		char *name;
36-
37-		name = localpath(t, t->prereqs.v[i]);
38-		addstr(&list, name);
39-		free(name);
40-	}
41-	s = joinstrs(&list, " ");
42-	freestrs(&list);
43-	return s;
44-}
45-
46 static char *
47 rulecmd(const struct Target *t)
48 {
+29, -3
  1@@ -219,9 +219,11 @@ main(int argc, char **argv)
  2 	int dump_ast;
  3 	int dump_graph;
  4 	int env_override;
  5+	char **goals;
  6 	int i;
  7 	char **assigns;
  8 	size_t nassigns;
  9+	size_t ngoals;
 10 	enum Generator gen;
 11 	struct Ast ast;
 12 	struct RuleSet rs;
 13@@ -235,6 +237,8 @@ main(int argc, char **argv)
 14 	gen = GEN_NINJA;
 15 	assigns = 0;
 16 	nassigns = 0;
 17+	goals = 0;
 18+	ngoals = 0;
 19 	memset(&sg, 0, sizeof(sg));
 20 	memset(&ast, 0, sizeof(ast));
 21 	memset(&rs, 0, sizeof(rs));
 22@@ -262,17 +266,20 @@ main(int argc, char **argv)
 23 				fprintf(stderr, "unknown generator: %s\n\n", argv[i]);
 24 				usage(stderr, argv[0]);
 25 				free(assigns);
 26+				free(goals);
 27 				return 2;
 28 			}
 29 		} else if (strcmp(argv[i], "-h") == 0 || strcmp(argv[i], "--help") == 0) {
 30 			usage(stdout, argv[0]);
 31 			free(assigns);
 32+			free(goals);
 33 			return 0;
 34 		} else if (strcmp(argv[i], "-C") == 0) {
 35 			if (i + 1 >= argc) {
 36 				fprintf(stderr, "specify a directory\n\n");
 37 				usage(stderr, argv[0]);
 38 				free(assigns);
 39+				free(goals);
 40 				return 2;
 41 			}
 42 			++i;
 43@@ -280,6 +287,7 @@ main(int argc, char **argv)
 44 			if (chdir(argv[i]) != 0) {
 45 				fprintf(stderr, "failed to chdir to %s", argv[i]);
 46 				free(assigns);
 47+				free(goals);
 48 				return 2;
 49 			}
 50 		} else if (strcmp(argv[i], "-f") == 0) {
 51@@ -287,6 +295,7 @@ main(int argc, char **argv)
 52 				fprintf(stderr, "specify a file\n\n");
 53 				usage(stderr, argv[0]);
 54 				free(assigns);
 55+				free(goals);
 56 				return 2;
 57 			}
 58 			++i;
 59@@ -300,6 +309,7 @@ main(int argc, char **argv)
 60 			if (!tmp) {
 61 				fprintf(stderr, "out of memory\n");
 62 				free(assigns);
 63+				free(goals);
 64 				return 2;
 65 			}
 66 			assigns = tmp;
 67@@ -307,15 +317,26 @@ main(int argc, char **argv)
 68 		} else if (argv[i][0] == '-') {
 69 			usage(stderr, argv[0]);
 70 			free(assigns);
 71+			free(goals);
 72 			return 2;
 73 		} else {
 74-			usage(stderr, argv[0]);
 75-			free(assigns);
 76-			return 2;
 77+			char **tmp;
 78+
 79+			tmp = realloc(goals, (ngoals + 1) * sizeof(goals[0]));
 80+			if (!tmp) {
 81+				fprintf(stderr, "out of memory\n");
 82+				free(assigns);
 83+				free(goals);
 84+				return 2;
 85+			}
 86+			goals = tmp;
 87+			goals[ngoals++] = argv[i];
 88 		}
 89 	}
 90 	for (i = 0; i < (int)nassigns; i++)
 91 		addstr(&sg.assigns, assigns[i]);
 92+	for (i = 0; i < (int)ngoals; i++)
 93+		addstr(&sg.goals, goals[i]);
 94 	if (dump_ast) {
 95 		char *mkpath;
 96 
 97@@ -326,6 +347,7 @@ main(int argc, char **argv)
 98 			else
 99 				fprintf(stderr, "could not find a makefile\n");
100 			free(assigns);
101+			free(goals);
102 			freesubgraph(&sg);
103 			return 2;
104 		}
105@@ -338,6 +360,7 @@ main(int argc, char **argv)
106 			rc = parse(path, src, &ast);
107 			if (rc < 0) {
108 				free(assigns);
109+				free(goals);
110 				free(pathbuf);
111 				free(src);
112 				freesubgraph(&sg);
113@@ -358,6 +381,7 @@ main(int argc, char **argv)
114 		}
115 		if (eval(path, &ast, 0, env_override, &rs) < 0) {
116 			free(assigns);
117+			free(goals);
118 			freeast(&ast);
119 			free(pathbuf);
120 			free(src);
121@@ -374,7 +398,9 @@ main(int argc, char **argv)
122 	if (env_override)
123 		addstr(&sg.flags, "-e");
124 	free(assigns);
125+	free(goals);
126 	assigns = 0;
127+	goals = 0;
128 	{
129 		int rc;
130 
+7, -0
 1@@ -448,6 +448,12 @@ evalnodes(const struct NodeList *in, struct RuleSet *out, struct EvalCtx *ctx)
 2 				ctx->export_all = 1;
 3 				break;
 4 			}
 5+			if (src->data.rule.targets.n == 1 &&
 6+			    strcmp(src->data.rule.targets.v[0], ".DEFAULT") == 0) {
 7+				freerecipes(&out->defaultrule);
 8+				addrecipes(&out->defaultrule, &src->data.rule.recipes);
 9+				break;
10+			}
11 			if (handlespecialrule(&targets, &src->data.rule))
12 				break;
13 			addrulesetrule(out, &src->data.rule, ctx);
14@@ -547,6 +553,7 @@ freeruleset(struct RuleSet *ruleset)
15 	freeassigns(ruleset->vars, ruleset->nvars);
16 	freeassigns(ruleset->tvars, ruleset->ntvars);
17 	freerules(ruleset->rules, ruleset->nrules);
18+	freerecipes(&ruleset->defaultrule);
19 	freestrs(&ruleset->phony);
20 	memset(ruleset, 0, sizeof(*ruleset));
21 }
+48, -1
 1@@ -22,6 +22,7 @@ struct GraphState {
 2 	struct Graph *graph;
 3 	struct Env env;
 4 	const struct StrList *phony;
 5+	const struct RecipeList *defaultrule;
 6 	const struct RuleNode *rules;
 7 	size_t nrules;
 8 	struct PatRules patterns;
 9@@ -234,12 +235,22 @@ addtassign(struct GraphState *gs, const struct AssignNode *assign)
10 	ta->origin = assign->origin;
11 }
12 
13+static void
14+instdefaultrule(const struct GraphState *gs, struct Target *t, struct EvalCtx *ctx)
15+{
16+	if (!gs->defaultrule || gs->defaultrule->n == 0 || t->defined || t->phony || t->recipes.n > 0)
17+		return;
18+	addrecipes(&t->recipes, gs->defaultrule);
19+	freeenv(&t->env);
20+	copyenv(&t->env, ctx->env);
21+}
22+
23 /*generate the graph from the evaluated rule set
24  * get the env and target specific assignments,
25  * then expand placeholder targets until all prereqs
26  * we can find are in the graph. */
27 int
28-buildgraph(const struct RuleSet *ruleset, struct Graph *graph)
29+buildgraph(const struct RuleSet *ruleset, const struct StrList *goals, struct Graph *graph)
30 {
31 	size_t i, j;
32 	struct GraphState gs;
33@@ -250,6 +261,7 @@ buildgraph(const struct RuleSet *ruleset, struct Graph *graph)
34 	memset(&ctx, 0, sizeof(ctx));
35 	gs.graph = graph;
36 	gs.phony = &ruleset->phony;
37+	gs.defaultrule = &ruleset->defaultrule;
38 	seedenv(&gs.env, ruleset->posix, ruleset->envoverride);
39 	gs.rules = ruleset->rules;
40 	gs.nrules = ruleset->nrules;
41@@ -291,6 +303,39 @@ buildgraph(const struct RuleSet *ruleset, struct Graph *graph)
42 		}
43 	}
44 
45+	if (goals) {
46+		/* explicitly requested goals need to exist in the generated graph before ninja runs.
47+		 * if a requested goal is missing but a .DEFAULT rule exists, create a
48+		 * placeholder target here so the default recipe will be attached later. */
49+		for (i = 0; i < goals->n; i++) {
50+			struct Target *t;
51+
52+			t = findtarget(graph, goals->v[i]);
53+			if (t)
54+				continue;
55+			if (ruleset->defaultrule.n == 0) {
56+				/* if there is no default rule but a target that doesn't exist
57+				 * is requested, die */
58+				char *detail;
59+
60+				detail = cat3("'", goals->v[i], "'");
61+				dielikemake(0, 0, "No rule to make target", detail);
62+				free(detail);
63+				freepatrule(&gs.patterns);
64+				freesufrules(&gs.sufs);
65+				freeenv(&gs.env);
66+				free(gs.tas);
67+				return -1;
68+			}
69+			graph->v = xrealloc(graph->v, (graph->n + 1) * sizeof(graph->v[0]));
70+			t = &graph->v[graph->n++];
71+			memset(t, 0, sizeof(*t));
72+			t->name = intern(goals->v[i]);
73+			if (hasword(gs.phony, t->name))
74+				t->phony = 1;
75+		}
76+	}
77+
78 	for (;;) {
79 		int changed;
80 
81@@ -318,6 +363,8 @@ buildgraph(const struct RuleSet *ruleset, struct Graph *graph)
82 				matched = t->recipes.n > 0;
83 				if (!matched) {
84 					instsufrule(&gs.sufs, graph, t, &targetctx);
85+					if (t->recipes.n == 0)
86+						instdefaultrule(&gs, t, &targetctx);
87 				}
88 				freeenv(&env);
89 				if (targetctx.errors)
+1, -1
1@@ -663,7 +663,7 @@ buildsubgraph0(struct SubGraph *sg, struct SubGraphStack *stack)
2 	rc = eval(path, &ast, sg->assigns.n ? &pre : 0, envoverride, &rs);
3 	if (rc < 0)
4 		goto out;
5-	rc = buildgraph(&rs, &sg->graph);
6+	rc = buildgraph(&rs, &sg->goals, &sg->graph);
7 	if (rc < 0)
8 		goto out;
9 	rc = expandgraph(&sg->graph);
+1, -4
 1@@ -150,10 +150,7 @@ matchsuf(const char *suf, const char *name, char **stem)
 2  *   $*  stem
 3  *   $$  literal $
 4  *
 5- *  this turns them into concrete strings,
 6- *  later during ninja generation
 7- *  translateauto() turns any remaining ones
 8- *  into ninja variables if possible
 9+ *  this turns them into concrete strings.
10  */
11 static char *
12 expandauto(const char *s, const struct Target *t, const char *stem)
+2, -1
 1@@ -182,6 +182,7 @@ struct RuleSet {
 2 	size_t ntvars;
 3 	struct RuleNode *rules;
 4 	size_t nrules;
 5+	struct RecipeList defaultrule;
 6 	struct StrList phony;
 7 	int export_all;
 8 	int posix;
 9@@ -242,7 +243,7 @@ int eval(const char *path, const struct Ast *ast, const struct Ast *pre, int env
10 int parse(const char *path, const char *src, struct Ast *ast);
11 void freeast(struct Ast *ast);
12 void freeruleset(struct RuleSet *ruleset);
13-int buildgraph(const struct RuleSet *ruleset, struct Graph *graph);
14+int buildgraph(const struct RuleSet *ruleset, const struct StrList *goals, struct Graph *graph);
15 int expandgraph(struct Graph *graph);
16 void freegraph(struct Graph *graph);
17 int buildsubgraph(struct SubGraph *sg);
+3, -0
1@@ -0,0 +1,3 @@
2+all:
3+	@echo top=$(CURDIR)
4+	@cd sub && $(MAKE) -f alt.mk show
+2, -0
1@@ -0,0 +1,2 @@
2+top=#PWD#
3+sub=#PWD#/sub
+24, -0
 1@@ -0,0 +1,24 @@
 2+{
 3+  "case": "t002",
 4+  "category": "recursion",
 5+  "compare_output": true,
 6+  "description": "recursive make preserves CURDIR across different directories",
 7+  "details": "",
 8+  "env": {},
 9+  "expected_exit": 0,
10+  "options": "",
11+  "options_mode": "argv",
12+  "output_mode": "exact",
13+  "setup": [
14+    {
15+      "kind": "file",
16+      "mode": "0644",
17+      "mtime": 0,
18+      "path": "sub/alt.mk",
19+      "content": "show:\n\t@echo sub=$(CURDIR)\n"
20+    }
21+  ],
22+  "stdin": "",
23+  "suite": "shin",
24+  "timeout_seconds": 60
25+}
+9, -0
 1@@ -239,6 +239,15 @@ if [ -n "$vars" ]; then
 2 	done
 3 	IFS=$oldifs
 4 fi
 5+if [ -n "$targets" ]; then
 6+	oldifs=$IFS
 7+	IFS='
 8+'
 9+	for target in $targets; do
10+		set -- "$@" "$target"
11+	done
12+	IFS=$oldifs
13+fi
14 
15 if "$@"; then
16 	: