master xplshn/aruu / cmd / dev / ninqu / resolve.c
  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}