summaryrefslogtreecommitdiff
path: root/appl/cmd/mash/make.b
diff options
context:
space:
mode:
Diffstat (limited to 'appl/cmd/mash/make.b')
-rw-r--r--appl/cmd/mash/make.b723
1 files changed, 723 insertions, 0 deletions
diff --git a/appl/cmd/mash/make.b b/appl/cmd/mash/make.b
new file mode 100644
index 00000000..4d566c00
--- /dev/null
+++ b/appl/cmd/mash/make.b
@@ -0,0 +1,723 @@
+implement Mashbuiltin;
+
+#
+# "make" builtin, defines:
+#
+# depends - print dependencies
+# make - make-like command
+# match - print details of rule matches
+# rules - print rules
+#
+
+include "mash.m";
+include "mashparse.m";
+
+verbose: con 0; # debug output
+
+mashlib: Mashlib;
+
+Cmd, Env, Item, Stab: import mashlib;
+Depend, Rule, Target: import mashlib;
+sys, bufio, hash: import mashlib;
+
+Iobuf: import bufio;
+
+#
+# Interface to catch the use as a command.
+#
+init(nil: ref Draw->Context, args: list of string)
+{
+ raise "fail: " + hd args + " not loaded";
+}
+
+#
+# Used by whatis.
+#
+name(): string
+{
+ return "make";
+}
+
+#
+# Install commands.
+#
+mashinit(nil: list of string, lib: Mashlib, this: Mashbuiltin, e: ref Env)
+{
+ mashlib = lib;
+ e.defbuiltin("depends", this);
+ e.defbuiltin("make", this);
+ e.defbuiltin("match", this);
+ e.defbuiltin("rules", this);
+}
+
+#
+# Execute a builtin.
+#
+mashcmd(e: ref Env, l: list of string)
+{
+ s := hd l;
+ l = tl l;
+ case s {
+ "depends" =>
+ out := e.outfile();
+ if (out == nil)
+ return;
+ if (l == nil)
+ alldeps(out);
+ else
+ depends(out, l);
+ out.close();
+ "make" =>
+ domake(e, l);
+ "match" =>
+ domatch(e, l);
+ "rules" =>
+ out := e.outfile();
+ if (out == nil)
+ return;
+ if (l == nil)
+ allrules(out);
+ else
+ rules(out, l);
+ out.close();
+ }
+}
+
+#
+# Node states.
+#
+SUnknown, SNoexist, SExist, SStale, SMade, SDir, SDirload
+ : con iota;
+
+#
+# Node flags.
+#
+# FMark - marked as in progress
+#
+FMark
+ : con 1 << iota;
+
+Node: adt
+{
+ name: string;
+ state: int;
+ flags: int;
+ mtime: int;
+};
+
+#
+# Step in implicit chain.
+#
+Step: type (ref Rule, array of string, ref Node);
+
+#
+# Implicit match.
+#
+Match: adt
+{
+ node: ref Node;
+ path: list of Step;
+};
+
+NSIZE: con 127; # node hash size
+DSIZE: con 32; # number of dir entries for read
+
+ntab: array of list of ref Node; # node hash table
+
+initnodes()
+{
+ ntab = array[NSIZE] of list of ref Node;
+}
+
+#
+# Find node for a pathname.
+#
+getnode(s: string): ref Node
+{
+ h := hash->fun1(s, NSIZE);
+ for (l := ntab[h]; l != nil; l = tl l) {
+ n := hd l;
+ if (n.name == s)
+ return n;
+ }
+ r := ref Node(s, SUnknown, 0, 0);
+ ntab[h] = r :: ntab[h];
+ return r;
+}
+
+#
+# Make a pathname from a dir and an entry.
+#
+mkpath(d, s: string): string
+{
+ if (d == ".")
+ return s;
+ else if (d == "/")
+ return "/" + s;
+ else
+ return d + "/" + s;
+}
+
+#
+# Load a directory.
+#
+loaddir(s: string)
+{
+ if (verbose)
+ sys->print("loaddir %s\n", s);
+ fd := sys->open(s, Sys->OREAD);
+ if (fd == nil)
+ return;
+ for (;;) {
+ (c, dbuf) := sys->dirread(fd);
+ if(c <= 0)
+ break;
+ for (i := 0; i < c; i++) {
+ n := getnode(mkpath(s, dbuf[i].name));
+ if (dbuf[i].mode & Sys->DMDIR)
+ n.state = SDir;
+ else
+ n.state = SExist;
+ n.mtime = dbuf[i].mtime;
+ }
+ }
+}
+
+#
+# Load a file. Get its node, maybe stat it or loaddir.
+#
+loadfile(s: string): ref Node
+{
+ n := getnode(s);
+ if (n.state == SUnknown) {
+ if (verbose)
+ sys->print("stat %s\n", s);
+ (ok, d) := sys->stat(s);
+ if (ok >= 0) {
+ n.mtime = d.mtime;
+ if (d.mode & Sys->DMDIR) {
+ loaddir(s);
+ n.state = SDirload;
+ } else
+ n.state = SExist;
+ } else
+ n.state = SNoexist;
+ } else if (n.state == SDir) {
+ loaddir(s);
+ n.state = SDirload;
+ }
+ return n;
+}
+
+#
+# Get the node for a file and load the directories in its path.
+#
+getfile(s: string): ref Node
+{
+ d: string;
+ n := len s;
+ while (n >= 2 && s[0:2] == "./") {
+ n -= 2;
+ s = s[2:];
+ }
+ if (n > 0 && s[0] == '/') {
+ d = "/";
+ s = s[1:];
+ } else
+ d = ".";
+ (nil, l) := sys->tokenize(s, "/");
+ for (;;) {
+ w := loadfile(d);
+ if (l == nil)
+ return w;
+ s = hd l;
+ l = tl l;
+ d = mkpath(d, s);
+ }
+}
+
+#
+# If a dependency rule makes more than one target propogate SMade.
+#
+propagate(l: list of string)
+{
+ if (tl l == nil)
+ return ;
+ while (l != nil) {
+ s := hd l;
+ if (verbose)
+ sys->print("propogate to %s\n", s);
+ getfile(s).state = SMade;
+ l = tl l;
+ }
+}
+
+#
+# Try to make a node, or mark it as stale.
+# Return -1 on (reported) error, 0 on fail, 1 on success.
+#
+explicit(e: ref Env, t: ref Target, n: ref Node): int
+{
+ d: ref Depend;
+ for (l := t.depends; l != nil ; l = tl l) {
+ if ((hd l).op != Cnop) {
+ if (d != nil) {
+ e.report(sys->sprint("make: too many rules for %s", t.target));
+ return -1;
+ }
+ d = hd l;
+ }
+ }
+ for (l = t.depends; l != nil ; l = tl l) {
+ for (u := (hd l).depends; u != nil; u = tl u) {
+ s := hd u;
+ m := getfile(s);
+ x := make(e, m, s);
+ if (x < 0) {
+ sys->print("don't know how to make %s\n", s);
+ return x;
+ }
+ if (m.state == SMade || m.mtime > n.mtime) {
+ if (verbose)
+ sys->print("%s makes %s stale\n", s, t.target);
+ n.state = SStale;
+ }
+ }
+ }
+ if (d != nil) {
+ if (n.state == SNoexist || n.state == SStale) {
+ if (verbose)
+ sys->print("build %s with explicit rule\n", t.target);
+ e = e.copy();
+ e.flags |= mashlib->EEcho | Mashlib->ERaise;
+ e.flags &= ~mashlib->EInter;
+ d.cmd.xeq(e);
+ propagate(d.targets);
+ n.state = SMade;
+ } else if (verbose)
+ sys->print("%s up to date\n", t.target);
+ return 1;
+ }
+ return 0;
+}
+
+#
+# Report multiple implicit chains of equal length.
+#
+multimatch(e: ref Env, n: ref Node, l: list of Match)
+{
+ e.report(sys->sprint("%d rules match for %s", len l, n.name));
+ f := e.stderr;
+ while (l != nil) {
+ m := hd l;
+ sys->fprint(f, "%s", m.node.name);
+ for (p := m.path; p != nil; p = tl p) {
+ (nil, nil, t) := hd p;
+ sys->fprint(f, " -> %s", t.name);
+ }
+ sys->fprint(f, "\n");
+ l = tl l;
+ }
+}
+
+cycle(e: ref Env, n: ref Node)
+{
+ e.report(sys->sprint("make: cycle in dependencies for target %s", n.name));
+}
+
+#
+# Mark the nodes in an implicit chain.
+#
+markchain(e: ref Env, l: list of Step): int
+{
+ while (tl l != nil) {
+ (nil, nil, n) := hd l;
+ if (n.flags & FMark) {
+ cycle(e, n);
+ return 0;
+ }
+ n.flags |= FMark;
+ l = tl l;
+ }
+ return 1;
+}
+
+#
+# Unmark the nodes in an implicit chain.
+#
+unmarkchain(l: list of Step): int
+{
+ while (tl l != nil) {
+ (nil, nil, n) := hd l;
+ n.flags &= ~FMark;
+ l = tl l;
+ }
+ return 1;
+}
+
+#
+# Execute an implicit rule chain.
+#
+xeqmatch(e: ref Env, b, n: ref Node, l: list of Step): int
+{
+ if (!markchain(e, l))
+ return -1;
+ if (verbose)
+ sys->print("making %s for implicit rule chain\n", n.name);
+ e.args = nil;
+ x := make(e, n, n.name);
+ if (x < 0) {
+ sys->print("don't know how to make %s\n", n.name);
+ return x;
+ }
+ if (n.state == SMade || n.mtime > b.mtime || b.state == SStale) {
+ e = e.copy();
+ e.flags |= mashlib->EEcho | Mashlib->ERaise;
+ e.flags &= ~mashlib->EInter;
+ for (;;) {
+ (r, a, t) := hd l;
+ if (verbose)
+ sys->print("making %s with implicit rule\n", t.name);
+ e.args = a;
+ r.cmd.xeq(e);
+ t.state = SMade;
+ l = tl l;
+ if (l == nil)
+ break;
+ t.flags &= ~FMark;
+ }
+ } else
+ unmarkchain(l);
+ return 1;
+}
+
+#
+# Find the shortest implicit rule chain.
+#
+implicit(e: ref Env, base: ref Node): int
+{
+ win, lose: list of Match;
+ l: list of ref Rule;
+ cand := Match(base, nil) :: nil;
+ do {
+ # cand - list of candidate chains
+ # lose - list of extended chains that lose
+ # win - list of extended chains that win
+ lose = nil;
+ match:
+ # for each candidate
+ for (c := cand; c != nil; c = tl c) {
+ (b, x) := hd c;
+ s := b.name;
+ # find rules that match end of chain
+ m := mashlib->rulematch(s);
+ l = nil;
+ # exclude rules already in the chain
+ exclude:
+ for (n := m; n != nil; n = tl n) {
+ r := hd n;
+ for (y := x; y != nil; y = tl y) {
+ (u, nil, nil) := hd y;
+ if (u == r)
+ continue exclude;
+ }
+ l = r :: l;
+ }
+ if (l == nil)
+ continue match;
+ (nil, t) := sys->tokenize(s, "/");
+ # for each new rule that matched
+ for (n = l; n != nil; n = tl n) {
+ r := hd n;
+ a := r.matches(t);
+ if (a == nil) {
+ e.report("rule match cock up");
+ return -1;
+ }
+ a[0] = s;
+ e.args = a;
+ # eval rhs
+ (v, nil, nil) := r.rhs.ieval2(e);
+ if (v == nil)
+ continue;
+ y := (r, a, b) :: x;
+ z := getfile(v);
+ # winner or loser
+ if (z.state != SNoexist || Target.find(v) != nil)
+ win = (z, y) :: win;
+ else
+ lose = (z, y) :: lose;
+ }
+ }
+ # winner should be unique
+ if (win != nil) {
+ if (tl win != nil) {
+ multimatch(e, base, win);
+ return -1;
+ } else {
+ (a, p) := hd win;
+ return xeqmatch(e, base, a, p);
+ }
+ }
+ # losers are candidates in next round
+ cand = lose;
+ } while (cand != nil);
+ return 0;
+}
+
+#
+# Make a node (recursive).
+# Return -1 on (reported) error, 0 on fail, 1 on success.
+#
+make(e: ref Env, n: ref Node, s: string): int
+{
+ if (n == nil)
+ n = getfile(s);
+ if (verbose)
+ sys->print("making %s\n", n.name);
+ if (n.state == SMade)
+ return 1;
+ if (n.flags & FMark) {
+ cycle(e, n);
+ return -1;
+ }
+ n.flags |= FMark;
+ t := Target.find(s);
+ if (t != nil) {
+ x := explicit(e, t, n);
+ if (x != 0) {
+ n.flags &= ~FMark;
+ return x;
+ }
+ }
+ x := implicit(e, n);
+ n.flags &= ~FMark;
+ if (x != 0)
+ return x;
+ if (n.state == SExist)
+ return 0;
+ return -1;
+}
+
+makelevel: int = 0; # count recursion
+
+#
+# Make driver routine. Maybe initialize and handle exceptions.
+#
+domake(e: ref Env, l: list of string)
+{
+ if ((e.flags & mashlib->ETop) == 0) {
+ e.report("make not at top level");
+ return;
+ }
+ inited := 0;
+ if (makelevel > 0)
+ inited = 1;
+ makelevel++;
+ if (l == nil)
+ l = "default" :: nil;
+ while (l != nil) {
+ s := hd l;
+ l = tl l;
+ if (s[0] == '-') {
+ case s {
+ "-clear" =>
+ mashlib->initdep();
+ * =>
+ e.report("make: unknown option: " + s);
+ }
+ } else {
+ if (!inited) {
+ initnodes();
+ inited = 1;
+ }
+ {
+ if (make(e, nil, s) < 0) {
+ sys->print("don't know how to make %s\n", s);
+ raise "fail: make error";
+ }
+ }exception x{
+ mashlib->FAILPAT =>
+ makelevel--;
+ raise x;
+ }
+ }
+ }
+ makelevel--;
+}
+
+#
+# Print dependency/rule command.
+#
+prcmd(out: ref Iobuf, op: int, c: ref Cmd)
+{
+ if (op == Clistgroup)
+ out.putc(':');
+ if (c != nil) {
+ out.puts("{ ");
+ out.puts(c.text());
+ out.puts(" }");
+ } else
+ out.puts("{}");
+}
+
+#
+# Print details of rule matches.
+#
+domatch(e: ref Env, l: list of string)
+{
+ out := e.outfile();
+ if (out == nil)
+ return;
+ e = e.copy();
+ while (l != nil) {
+ s := hd l;
+ out.puts(sys->sprint("%s:\n", s));
+ m := mashlib->rulematch(s);
+ (nil, t) := sys->tokenize(s, "/");
+ while (m != nil) {
+ r := hd m;
+ out.puts(sys->sprint("\tlhs %s\n", r.lhs.text));
+ a := r.matches(t);
+ if (a != nil) {
+ a[0] = s;
+ n := len a;
+ for (i := 0; i < n; i++)
+ out.puts(sys->sprint("\t$%d '%s'\n", i, a[i]));
+ e.args = a;
+ (v, w, nil) := r.rhs.ieval2(e);
+ if (v != nil)
+ out.puts(sys->sprint("\trhs '%s'\n", v));
+ else
+ out.puts(sys->sprint("\trhs list %d\n", len w));
+ if (r.cmd != nil) {
+ out.putc('\t');
+ prcmd(out, r.op, r.cmd);
+ out.puts(";\n");
+ }
+ } else
+ out.puts("\tcock up\n");
+ m = tl m;
+ }
+ l = tl l;
+ }
+ out.close();
+}
+
+#
+# Print word list.
+#
+prwords(out: ref Iobuf, l: list of string, pre: int)
+{
+ while (l != nil) {
+ if (pre)
+ out.putc(' ');
+ out.puts(mashlib->quote(hd l));
+ if (!pre)
+ out.putc(' ');
+ l = tl l;
+ }
+}
+
+#
+# Print dependency.
+#
+prdep(out: ref Iobuf, d: ref Depend)
+{
+ prwords(out, d.targets, 0);
+ out.putc(':');
+ prwords(out, d.depends, 1);
+ if (d.op != Cnop) {
+ out.putc(' ');
+ prcmd(out, d.op, d.cmd);
+ }
+ out.puts(";\n");
+}
+
+#
+# Print all dependencies, avoiding duplicates.
+#
+alldep(out: ref Iobuf, d: ref Depend, pass: int)
+{
+ case pass {
+ 0 =>
+ d.mark = 0;
+ 1 =>
+ if (!d.mark) {
+ prdep(out, d);
+ d.mark = 1;
+ }
+ }
+}
+
+#
+# Print all dependencies.
+#
+alldeps(out: ref Iobuf)
+{
+ a := mashlib->dephash;
+ n := len a;
+ for (p := 0; p < 2; p++)
+ for (i := 0; i < n; i++)
+ for (l := a[i]; l != nil; l = tl l)
+ for (d := (hd l).depends; d != nil; d = tl d)
+ alldep(out, hd d, p);
+}
+
+#
+# Print dependencies.
+#
+depends(out: ref Iobuf, l: list of string)
+{
+ while (l != nil) {
+ s := hd l;
+ out.puts(s);
+ out.puts(":\n");
+ t := Target.find(s);
+ if (t != nil) {
+ for (d := t.depends; d != nil; d = tl d)
+ prdep(out, hd d);
+ }
+ l = tl l;
+ }
+}
+
+#
+# Print rule.
+#
+prrule(out: ref Iobuf, r: ref Rule)
+{
+ out.puts(r.lhs.text);
+ out.puts(" :~ ");
+ out.puts(r.rhs.text());
+ out.putc(' ');
+ prcmd(out, r.op, r.cmd);
+ out.puts(";\n");
+}
+
+#
+# Print all rules.
+#
+allrules(out: ref Iobuf)
+{
+ for (l := mashlib->rules; l != nil; l = tl l)
+ prrule(out, hd l);
+}
+
+#
+# Print matching rules.
+#
+rules(out: ref Iobuf, l: list of string)
+{
+ while (l != nil) {
+ s := hd l;
+ out.puts(s);
+ out.puts(":\n");
+ r := mashlib->rulematch(s);
+ while (r != nil) {
+ prrule(out, hd r);
+ r = tl r;
+ }
+ l = tl l;
+ }
+}