master xplshn/aruu / cmd / dev / ninqu / expand.c
  1#include "ninqu.h"
  2
  3#include <fnmatch.h>
  4#include <stdio.h>
  5#include <stdlib.h>
  6#include <string.h>
  7
  8void
  9dep_push(struct Inst *inst, int idx)
 10{
 11  int i;
 12  for (i = 0; i < inst->n_dep; i++)
 13    if (inst->dep_inst[i] == idx)
 14      return;
 15  if (inst->n_dep >= inst->cap_dep) {
 16    inst->cap_dep  = inst->cap_dep ? inst->cap_dep * 2 : 4;
 17    inst->dep_inst = erealloc(inst->dep_inst, (size_t)inst->cap_dep * sizeof *inst->dep_inst);
 18  }
 19  inst->dep_inst[inst->n_dep++] = idx;
 20}
 21
 22void
 23resolve_deps(void)
 24{
 25  int i, j, k;
 26  for (i = 0; i < ninsts; i++) {
 27    struct Inst *inst = &insts[i];
 28    for (j = 0; j < inst->dep_rule_names.n; j++) {
 29      struct Rule *dep = rule_find(inst->dep_rule_names.v[j]);
 30      if (!dep)
 31        continue;
 32      for (k = 0; k < dep->n_inst; k++)
 33        dep_push(inst, dep->inst_idx[k]);
 34    }
 35  }
 36}
 37
 38int
 39inst_stale(struct Inst *inst)
 40{
 41  int i;
 42  if (inst->phony)
 43    return 1;
 44  if (!file_exists(inst->out))
 45    return 1;
 46  for (i = 0; i < inst->in.n; i++)
 47    if (mtime_of(inst->in.v[i]) > mtime_of(inst->out))
 48      return 1;
 49  for (i = 0; i < inst->stale_extra.n; i++)
 50    if (file_exists(inst->stale_extra.v[i])
 51        && mtime_of(inst->stale_extra.v[i]) > mtime_of(inst->out))
 52      return 1;
 53  return 0;
 54}
 55
 56/* pure $(NAME) splits on whitespace so $(CFLAGS) becomes many flags, -I$(INCDIR) stays one token */
 57static int
 58is_pure_ref(const char *tok)
 59{
 60  size_t      len;
 61  int         depth;
 62  const char *p;
 63
 64  if (tok[0] != '$' || tok[1] != '(')
 65    return 0;
 66  len = strlen(tok);
 67  if (tok[len - 1] != ')')
 68    return 0;
 69  depth = 1;
 70  for (p = tok + 2; *p; p++) {
 71    if (p[0] == '$' && p[1] == '(') {
 72      depth++;
 73      p++;
 74      continue;
 75    }
 76    if (*p == ')') {
 77      depth--;
 78      if (depth == 0)
 79        return (size_t)(p - tok) == len - 1;
 80    }
 81  }
 82  return 0;
 83}
 84
 85static void
 86expand_cmd_token(struct StrList *out, const char *raw)
 87{
 88  char *exp = kv_expand(raw);
 89  if (is_pure_ref(raw)) {
 90    struct StrList pieces = {0};
 91    int            j;
 92    sl_split(&pieces, exp);
 93    for (j = 0; j < pieces.n; j++)
 94      sl_push(out, pieces.v[j]);
 95  } else {
 96    sl_push(out, exp);
 97  }
 98  free(exp);
 99}
100
101static void
102build_cmd(struct Inst *inst, struct Rule *r)
103{
104  int i;
105  if (r->cmd.is_pipe) {
106    inst->cmd.is_pipe = 1;
107    inst->cmd.nstages = r->cmd.nstages;
108    inst->cmd.stages  = emalloc((size_t)r->cmd.nstages * sizeof *inst->cmd.stages);
109    for (i = 0; i < r->cmd.nstages; i++) {
110      int             j;
111      struct StrList *slot = emalloc(sizeof *slot);
112      memset(slot, 0, sizeof *slot);
113      for (j = 0; j < r->cmd.stages[i]->n; j++)
114        expand_cmd_token(slot, r->cmd.stages[i]->v[j]);
115      inst->cmd.stages[i] = slot;
116    }
117    for (i = 0; i < inst->cmd.nstages; i++) {
118      char *joined = sl_join(inst->cmd.stages[i]);
119      if (i > 0)
120        sl_push(&inst->cmd.argv, "|");
121      sl_push(&inst->cmd.argv, joined);
122      free(joined);
123    }
124  } else {
125    for (i = 0; i < r->cmd.argv.n; i++)
126      expand_cmd_token(&inst->cmd.argv, r->cmd.argv.v[i]);
127  }
128}
129
130/* if path does not exist, try to find a producer and run it first */
131static void
132resolve_input_token(int inst_idx, const char *raw, int only_dep)
133{
134  char *exp = kv_expand(raw);
135
136  if (exp[0] == '@') {
137    const char     *rulename = exp + 1;
138    struct Rule    *dep;
139    struct KvStore *saved = local_overlay;
140    struct Inst    *inst;
141    int             i;
142
143    /* expand_rule may set/clear local_overlay for its own loop */
144    local_overlay = NULL;
145    expand_rule(rulename);
146    local_overlay = saved;
147
148    /* insts[] may have moved during expand_rule */
149    inst = &insts[inst_idx];
150    dep  = rule_find(rulename);
151    if (!dep)
152      eprintf(
153          "manifest: '%s' references unknown rule '@%s'\n", rules[inst->rule_idx].name, rulename
154      );
155    if (!sl_has(&inst->dep_rule_names, rulename))
156      sl_push(&inst->dep_rule_names, rulename);
157    if (!only_dep)
158      for (i = 0; i < dep->n_inst; i++)
159        sl_push(&inst->in, insts[dep->inst_idx[i]].out);
160  } else if (strncmp(exp, "glob:", 5) == 0) {
161    struct StrList matches = {0};
162    int            i;
163    glob_expand(exp + 5, &matches);
164    for (i = 0; i < matches.n; i++) {
165      if (only_dep)
166        sl_push(&insts[inst_idx].stale_extra, matches.v[i]);
167      else
168        sl_push(&insts[inst_idx].in, matches.v[i]);
169    }
170  } else {
171    struct StrList pieces = {0};
172    int            i;
173    sl_split(&pieces, exp);
174    for (i = 0; i < pieces.n; i++) {
175      const char *path = pieces.v[i];
176      if (!file_exists(path))
177        materialize_if_missing(path);
178      if (only_dep)
179        sl_push(&insts[inst_idx].stale_extra, path);
180      else
181        sl_push(&insts[inst_idx].in, path);
182    }
183  }
184  free(exp);
185}
186
187static void
188setup_inst(struct Inst *inst, struct Rule *r)
189{
190  inst->phony    = r->phony;
191  inst->redirect = r->redirect;
192  if (r->workdir[0]) {
193    char *w = kv_expand(r->workdir);
194    strlcpy(inst->workdir, w, sizeof inst->workdir);
195    free(w);
196  }
197}
198
199/* caller must have local_overlay set if needed */
200static void
201finalize_inst(int idx, struct Rule *r, struct KvStore *ov)
202{
203  struct Inst *inst = &insts[idx];
204  int          k;
205
206  for (k = 0; k < r->in.n; k++)
207    resolve_input_token(idx, r->in.v[k], 0);
208  for (k = 0; k < r->extra_deps.n; k++)
209    resolve_input_token(idx, r->extra_deps.v[k], 1);
210
211  inst = &insts[idx]; /* may have moved */
212  {
213    char *o = kv_expand(r->out);
214    strlcpy(inst->out, o, sizeof inst->out);
215    free(o);
216  }
217  kv_set_raw(ov, "IN", sl_join(&inst->in));
218  kv_set_raw(ov, "OUT", inst->out);
219  build_cmd(inst, r);
220}
221
222/* true when deps (out) matches selfs glob. glob_all() needs bytes on disk
223 * so dep must run synchronously before enumerating instances */
224static int
225dep_feeds_glob(struct Rule *self, struct Rule *dep)
226{
227  int j;
228
229  if (!dep->out[0])
230    return 0;
231  for (j = 0; j < self->globs.n; j++) {
232    char *pat = kv_expand(self->globs.v[j]);
233    char *out = kv_expand(dep->out);
234    int   hit = fnmatch(pat, out, 0) == 0;
235    free(pat);
236    free(out);
237    if (hit)
238      return 1;
239  }
240  return 0;
241}
242
243void
244expand_rule(const char *name)
245{
246  struct Rule *r = rule_find(name);
247  int          i;
248
249  if (!r)
250    eprintf("manifest: unknown rule '%s'\n", name);
251  if (r->expanded)
252    return;
253  r->expanded = 1;
254
255  /* static gate checked once, if false rule produces zero instances */
256  if (r->gate && !gate_has_dyn(r->gate) && !gate_eval(r->gate))
257    return;
258
259  if (r->require_file[0] && r->globs.n == 0) {
260    char *rf = kv_expand(r->require_file);
261    int   ok = file_exists(rf);
262    free(rf);
263    if (!ok)
264      return;
265  }
266
267  if (r->globs.n > 0) {
268    struct StrList matches = {0};
269    int            j;
270    int            r_literal = rule_is_literal(r);
271
272    /* run a codegen producer before globbing only when its own
273     * output feeds this rules glob, so a step like POSIX_BC_C
274     * (produces cmd/posix/bc.c) finishes before glob_all() looks
275     * for cmd/posix/bc.c. anything else in extra_deps (a library to
276     * link, a header some other rule includes) never needs to run
277     * here, expand_rule() on it just registers its instances so
278     * the resolve_input_token() pass in finalize_inst() can wire up
279     * dep_rule_names for backend scheduling. skipped in
280     * summary mode since -S must not execute */
281    if (!summary_mode) {
282      for (j = 0; j < r->extra_deps.n; j++) {
283        char *tok = kv_expand(r->extra_deps.v[j]);
284        if (tok[0] == '@') {
285          struct Rule *dep = rule_find(tok + 1);
286          if (dep) {
287            expand_rule(dep->name);
288            if (dep_feeds_glob(r, dep) && dep->n_inst > 0) {
289              int k;
290              for (k = 0; k < dep->n_inst; k++)
291                run_inst_with_deps(dep->inst_idx[k]);
292            }
293          }
294        }
295        free(tok);
296      }
297    }
298
299    glob_all(r, &matches);
300
301    for (i = 0; i < matches.n; i++) {
302      const char    *match = matches.v[i];
303      const char    *base  = base_of(match);
304      char           dir[1024], stem[512], basestem[512], ext[64], unused[64];
305      char           ident[512];
306      struct KvStore ov;
307      int            idx;
308      struct Inst   *inst;
309
310      if (sl_has(&r->skip, base))
311        continue;
312      if (!r_literal && path_claimed_elsewhere(match, r))
313        continue;
314      if (r->require_marker[0] && !file_contains(match, r->require_marker))
315        continue;
316
317      if (strrchr(match, '/')) {
318        size_t dl = (size_t)(strrchr(match, '/') - match);
319        if (dl >= sizeof dir)
320          dl = sizeof dir - 1;
321        memcpy(dir, match, dl);
322        dir[dl] = '\0';
323      } else {
324        strlcpy(dir, ".", sizeof dir);
325      }
326      splitext(base, basestem, sizeof basestem, ext, sizeof ext);
327      splitext(match, stem, sizeof stem, unused, sizeof unused);
328      to_ident(basestem, ident, sizeof ident);
329
330      memset(&ov, 0, sizeof ov);
331      kv_set_raw(&ov, "MATCH", match);
332      kv_set_raw(&ov, "DIR", dir);
333      kv_set_raw(&ov, "BASE", base);
334      kv_set_raw(&ov, "EXT", ext);
335      kv_set_raw(&ov, "STEM", stem);
336      kv_set_raw(&ov, "BASESTEM", basestem);
337      kv_set_raw(&ov, "IDENT", ident);
338      local_overlay = &ov;
339
340      if (r->gate && gate_has_dyn(r->gate)) {
341        struct Gate *mg = gate_materialize(r->gate);
342        int          ok = gate_eval(mg);
343        gate_free(mg);
344        if (!ok) {
345          local_overlay = NULL;
346          continue;
347        }
348      }
349      if (r->require_file[0]) {
350        char *rf = kv_expand(r->require_file);
351        int   ok = file_exists(rf);
352        free(rf);
353        if (!ok) {
354          local_overlay = NULL;
355          continue;
356        }
357      }
358
359      idx  = inst_new((int)(r - rules));
360      inst = &insts[idx];
361      setup_inst(inst, r);
362      finalize_inst(idx, r, &ov);
363      local_overlay = NULL;
364    }
365  } else {
366    int            idx  = inst_new((int)(r - rules));
367    struct Inst   *inst = &insts[idx];
368    struct KvStore ov;
369
370    /* non-glob rules have no MATCH/DIR/BASE to expose, so the
371     * overlay only carries IN/OUT for build_cmd */
372    setup_inst(inst, r);
373    memset(&ov, 0, sizeof ov);
374    local_overlay = &ov;
375    finalize_inst(idx, r, &ov);
376    local_overlay = NULL;
377  }
378}