1#include "ninqu.h"
2
3#include <stdio.h>
4#include <stdlib.h>
5#include <string.h>
6
7/* pull in every rule a group names, recursively */
8static struct StrList visited_groups;
9
10void
11include_ref(const char *name)
12{
13 struct Group *g = group_find(name);
14 int i;
15
16 if (g) {
17 if (sl_has(&visited_groups, name))
18 return;
19 sl_push(&visited_groups, name);
20 for (i = 0; i < g->refs.n; i++)
21 include_ref(g->refs.v[i]);
22 return;
23 }
24 if (rule_find(name)) {
25 expand_rule(name);
26 return;
27 }
28 eprintf("manifest: '%s' is neither a known group nor a known rule\n", name);
29}
30
31void
32build_default_group(void)
33{
34 struct Group *def = group_find("default");
35 int i;
36 if (!def)
37 eprintf("no group given, and manifest defines no (group default)\n");
38 for (i = 0; i < def->refs.n; i++)
39 include_ref(def->refs.v[i]);
40}
41
42/* match on ALIAS when the rule gave one, otherwise on the rule own
43 * bare name, so a member with no alias is still reachable under its
44 * existing name */
45static struct Rule *
46rule_find_member(const char *group, const char *alias)
47{
48 int i;
49 for (i = 0; i < nrules; i++) {
50 struct Rule *r = &rules[i];
51 if (!r->member_of[0] || strcmp(r->member_of, group) != 0)
52 continue;
53 if (r->member_alias[0]) {
54 if (strcmp(r->member_alias, alias) == 0)
55 return r;
56 } else if (strcmp(r->name, alias) == 0) {
57 return r;
58 }
59 }
60 return NULL;
61}
62
63/* GROUP/ALIAS qualified addressing. leading segments before the
64 * last two are ignored, so "posix/bc" and "cmd/posix/bc" resolve
65 * the same rule. returns NULL when w has no '/', so callers can
66 * fall through to plain group/rule resolution. a qualified path
67 * that does not resolve is a hard error, not a silent fallthrough */
68static struct Rule *
69resolve_member_path(const char *w)
70{
71 const char *last_slash;
72 const char *alias;
73 const char *group_start;
74 const char *p;
75 char group[128];
76 size_t glen;
77 struct Rule *r;
78
79 last_slash = strrchr(w, '/');
80 if (!last_slash)
81 return NULL;
82 alias = last_slash + 1;
83 if (!*alias)
84 eprintf("manifest: '%s' ends in '/', expected GROUP/ALIAS\n", w);
85
86 group_start = w;
87 for (p = w; p < last_slash; p++)
88 if (*p == '/')
89 group_start = p + 1;
90 glen = (size_t)(last_slash - group_start);
91 if (glen == 0 || glen >= sizeof group)
92 eprintf("manifest: '%s': bad GROUP/ALIAS path\n", w);
93 memcpy(group, group_start, glen);
94 group[glen] = '\0';
95
96 if (!group_find(group))
97 eprintf("manifest: '%s': no such group '%s'\n", w, group);
98 r = rule_find_member(group, alias);
99 if (!r)
100 eprintf("manifest: '%s' names no member of group '%s'\n", alias, group);
101 return r;
102}
103
104/* META.TARGET applies META overrides then resolves TARGET.
105 * META alone applies META then falls back to (group default).
106 * anything else is a plain group or rule name */
107void
108resolve_wanted(const char *w)
109{
110 const char *dot = strchr(w, '.');
111 struct Meta *m;
112 char meta_name[128];
113 struct Rule *member;
114
115 if (strchr(w, '/')) {
116 member = resolve_member_path(w);
117 expand_rule(member->name);
118 return;
119 }
120
121 if (dot) {
122 size_t len = (size_t)(dot - w);
123 if (len >= sizeof meta_name)
124 eprintf("manifest: meta name too long in '%s'\n", w);
125 memcpy(meta_name, w, len);
126 meta_name[len] = '\0';
127 m = meta_find(meta_name);
128 if (!m)
129 eprintf("manifest: '%s' names no known meta-target\n", meta_name);
130 meta_apply(m);
131 include_ref(dot + 1);
132 return;
133 }
134
135 m = meta_find(w);
136 if (m) {
137 meta_apply(m);
138 build_default_group();
139 return;
140 }
141
142 /* a rule that declared (member ...) does not resolve by bare name
143 * from the command line, only through GROUP/ALIAS or transitively
144 * via its owning group ref list */
145 {
146 struct Rule *br = rule_find(w);
147 if (br && br->member_of[0])
148 eprintf(
149 "manifest: '%s' is a member of group '%s', not a top-level target; "
150 "use '%s/%s'\n",
151 w,
152 br->member_of,
153 br->member_of,
154 br->member_alias[0] ? br->member_alias : br->name
155 );
156 }
157
158 include_ref(w);
159}
160
161/* a plain token in (dep) or (after) that names an existing rule
162 * means the same as (rule NAME). resolved once after the whole
163 * manifest loads, so a rule declared later can still be named
164 * earlier. a token with $, glob metachars, /, or . is left alone
165 * so this never shadows a real file dependency */
166static int
167looks_like_path(const char *s)
168{
169 return strpbrk(s, "$*?/.") != NULL;
170}
171
172static void
173resolve_bareword_deps(struct StrList *sl)
174{
175 int i;
176 for (i = 0; i < sl->n; i++) {
177 char *tok = sl->v[i];
178 char buf[256];
179 if (tok[0] == '@' || looks_like_path(tok) || !rule_find(tok))
180 continue;
181 snprintf(buf, sizeof buf, "@%s", tok);
182 free(sl->v[i]);
183 sl->v[i] = estrdup(buf);
184 }
185}
186
187void
188resolve_all_bareword_deps(void)
189{
190 int i;
191 for (i = 0; i < nrules; i++) {
192 resolve_bareword_deps(&rules[i].in);
193 resolve_bareword_deps(&rules[i].extra_deps);
194 }
195}