1implement Mashbuiltin; 2 3# 4# "make" builtin, defines: 5# 6# depends - print dependencies 7# make - make-like command 8# match - print details of rule matches 9# rules - print rules 10# 11 12include "mash.m"; 13include "mashparse.m"; 14 15verbose: con 0; # debug output 16 17mashlib: Mashlib; 18 19Cmd, Env, Item, Stab: import mashlib; 20Depend, Rule, Target: import mashlib; 21sys, bufio, hash: import mashlib; 22 23Iobuf: import bufio; 24 25# 26# Interface to catch the use as a command. 27# 28init(nil: ref Draw->Context, args: list of string) 29{ 30 raise "fail: " + hd args + " not loaded"; 31} 32 33# 34# Used by whatis. 35# 36name(): string 37{ 38 return "make"; 39} 40 41# 42# Install commands. 43# 44mashinit(nil: list of string, lib: Mashlib, this: Mashbuiltin, e: ref Env) 45{ 46 mashlib = lib; 47 e.defbuiltin("depends", this); 48 e.defbuiltin("make", this); 49 e.defbuiltin("match", this); 50 e.defbuiltin("rules", this); 51} 52 53# 54# Execute a builtin. 55# 56mashcmd(e: ref Env, l: list of string) 57{ 58 s := hd l; 59 l = tl l; 60 case s { 61 "depends" => 62 out := e.outfile(); 63 if (out == nil) 64 return; 65 if (l == nil) 66 alldeps(out); 67 else 68 depends(out, l); 69 out.close(); 70 "make" => 71 domake(e, l); 72 "match" => 73 domatch(e, l); 74 "rules" => 75 out := e.outfile(); 76 if (out == nil) 77 return; 78 if (l == nil) 79 allrules(out); 80 else 81 rules(out, l); 82 out.close(); 83 } 84} 85 86# 87# Node states. 88# 89SUnknown, SNoexist, SExist, SStale, SMade, SDir, SDirload 90 : con iota; 91 92# 93# Node flags. 94# 95# FMark - marked as in progress 96# 97FMark 98 : con 1 << iota; 99 100Node: adt 101{ 102 name: string; 103 state: int; 104 flags: int; 105 mtime: int; 106}; 107 108# 109# Step in implicit chain. 110# 111Step: type (ref Rule, array of string, ref Node); 112 113# 114# Implicit match. 115# 116Match: adt 117{ 118 node: ref Node; 119 path: list of Step; 120}; 121 122NSIZE: con 127; # node hash size 123DSIZE: con 32; # number of dir entries for read 124 125ntab: array of list of ref Node; # node hash table 126 127initnodes() 128{ 129 ntab = array[NSIZE] of list of ref Node; 130} 131 132# 133# Find node for a pathname. 134# 135getnode(s: string): ref Node 136{ 137 h := hash->fun1(s, NSIZE); 138 for (l := ntab[h]; l != nil; l = tl l) { 139 n := hd l; 140 if (n.name == s) 141 return n; 142 } 143 r := ref Node(s, SUnknown, 0, 0); 144 ntab[h] = r :: ntab[h]; 145 return r; 146} 147 148# 149# Make a pathname from a dir and an entry. 150# 151mkpath(d, s: string): string 152{ 153 if (d == ".") 154 return s; 155 else if (d == "/") 156 return "/" + s; 157 else 158 return d + "/" + s; 159} 160 161# 162# Load a directory. 163# 164loaddir(s: string) 165{ 166 if (verbose) 167 sys->print("loaddir %s\n", s); 168 fd := sys->open(s, Sys->OREAD); 169 if (fd == nil) 170 return; 171 for (;;) { 172 (c, dbuf) := sys->dirread(fd); 173 if(c <= 0) 174 break; 175 for (i := 0; i < c; i++) { 176 n := getnode(mkpath(s, dbuf[i].name)); 177 if (dbuf[i].mode & Sys->DMDIR) 178 n.state = SDir; 179 else 180 n.state = SExist; 181 n.mtime = dbuf[i].mtime; 182 } 183 } 184} 185 186# 187# Load a file. Get its node, maybe stat it or loaddir. 188# 189loadfile(s: string): ref Node 190{ 191 n := getnode(s); 192 if (n.state == SUnknown) { 193 if (verbose) 194 sys->print("stat %s\n", s); 195 (ok, d) := sys->stat(s); 196 if (ok >= 0) { 197 n.mtime = d.mtime; 198 if (d.mode & Sys->DMDIR) { 199 loaddir(s); 200 n.state = SDirload; 201 } else 202 n.state = SExist; 203 } else 204 n.state = SNoexist; 205 } else if (n.state == SDir) { 206 loaddir(s); 207 n.state = SDirload; 208 } 209 return n; 210} 211 212# 213# Get the node for a file and load the directories in its path. 214# 215getfile(s: string): ref Node 216{ 217 d: string; 218 n := len s; 219 while (n >= 2 && s[0:2] == "./") { 220 n -= 2; 221 s = s[2:]; 222 } 223 if (n > 0 && s[0] == '/') { 224 d = "/"; 225 s = s[1:]; 226 } else 227 d = "."; 228 (nil, l) := sys->tokenize(s, "/"); 229 for (;;) { 230 w := loadfile(d); 231 if (l == nil) 232 return w; 233 s = hd l; 234 l = tl l; 235 d = mkpath(d, s); 236 } 237} 238 239# 240# If a dependency rule makes more than one target propogate SMade. 241# 242propagate(l: list of string) 243{ 244 if (tl l == nil) 245 return ; 246 while (l != nil) { 247 s := hd l; 248 if (verbose) 249 sys->print("propogate to %s\n", s); 250 getfile(s).state = SMade; 251 l = tl l; 252 } 253} 254 255# 256# Try to make a node, or mark it as stale. 257# Return -1 on (reported) error, 0 on fail, 1 on success. 258# 259explicit(e: ref Env, t: ref Target, n: ref Node): int 260{ 261 d: ref Depend; 262 for (l := t.depends; l != nil ; l = tl l) { 263 if ((hd l).op != Cnop) { 264 if (d != nil) { 265 e.report(sys->sprint("make: too many rules for %s", t.target)); 266 return -1; 267 } 268 d = hd l; 269 } 270 } 271 for (l = t.depends; l != nil ; l = tl l) { 272 for (u := (hd l).depends; u != nil; u = tl u) { 273 s := hd u; 274 m := getfile(s); 275 x := make(e, m, s); 276 if (x < 0) { 277 sys->print("don't know how to make %s\n", s); 278 return x; 279 } 280 if (m.state == SMade || m.mtime > n.mtime) { 281 if (verbose) 282 sys->print("%s makes %s stale\n", s, t.target); 283 n.state = SStale; 284 } 285 } 286 } 287 if (d != nil) { 288 if (n.state == SNoexist || n.state == SStale) { 289 if (verbose) 290 sys->print("build %s with explicit rule\n", t.target); 291 e = e.copy(); 292 e.flags |= mashlib->EEcho | Mashlib->ERaise; 293 e.flags &= ~mashlib->EInter; 294 d.cmd.xeq(e); 295 propagate(d.targets); 296 n.state = SMade; 297 } else if (verbose) 298 sys->print("%s up to date\n", t.target); 299 return 1; 300 } 301 return 0; 302} 303 304# 305# Report multiple implicit chains of equal length. 306# 307multimatch(e: ref Env, n: ref Node, l: list of Match) 308{ 309 e.report(sys->sprint("%d rules match for %s", len l, n.name)); 310 f := e.stderr; 311 while (l != nil) { 312 m := hd l; 313 sys->fprint(f, "%s", m.node.name); 314 for (p := m.path; p != nil; p = tl p) { 315 (nil, nil, t) := hd p; 316 sys->fprint(f, " -> %s", t.name); 317 } 318 sys->fprint(f, "\n"); 319 l = tl l; 320 } 321} 322 323cycle(e: ref Env, n: ref Node) 324{ 325 e.report(sys->sprint("make: cycle in dependencies for target %s", n.name)); 326} 327 328# 329# Mark the nodes in an implicit chain. 330# 331markchain(e: ref Env, l: list of Step): int 332{ 333 while (tl l != nil) { 334 (nil, nil, n) := hd l; 335 if (n.flags & FMark) { 336 cycle(e, n); 337 return 0; 338 } 339 n.flags |= FMark; 340 l = tl l; 341 } 342 return 1; 343} 344 345# 346# Unmark the nodes in an implicit chain. 347# 348unmarkchain(l: list of Step): int 349{ 350 while (tl l != nil) { 351 (nil, nil, n) := hd l; 352 n.flags &= ~FMark; 353 l = tl l; 354 } 355 return 1; 356} 357 358# 359# Execute an implicit rule chain. 360# 361xeqmatch(e: ref Env, b, n: ref Node, l: list of Step): int 362{ 363 if (!markchain(e, l)) 364 return -1; 365 if (verbose) 366 sys->print("making %s for implicit rule chain\n", n.name); 367 e.args = nil; 368 x := make(e, n, n.name); 369 if (x < 0) { 370 sys->print("don't know how to make %s\n", n.name); 371 return x; 372 } 373 if (n.state == SMade || n.mtime > b.mtime || b.state == SStale) { 374 e = e.copy(); 375 e.flags |= mashlib->EEcho | Mashlib->ERaise; 376 e.flags &= ~mashlib->EInter; 377 for (;;) { 378 (r, a, t) := hd l; 379 if (verbose) 380 sys->print("making %s with implicit rule\n", t.name); 381 e.args = a; 382 r.cmd.xeq(e); 383 t.state = SMade; 384 l = tl l; 385 if (l == nil) 386 break; 387 t.flags &= ~FMark; 388 } 389 } else 390 unmarkchain(l); 391 return 1; 392} 393 394# 395# Find the shortest implicit rule chain. 396# 397implicit(e: ref Env, base: ref Node): int 398{ 399 win, lose: list of Match; 400 l: list of ref Rule; 401 cand := Match(base, nil) :: nil; 402 do { 403 # cand - list of candidate chains 404 # lose - list of extended chains that lose 405 # win - list of extended chains that win 406 lose = nil; 407 match: 408 # for each candidate 409 for (c := cand; c != nil; c = tl c) { 410 (b, x) := hd c; 411 s := b.name; 412 # find rules that match end of chain 413 m := mashlib->rulematch(s); 414 l = nil; 415 # exclude rules already in the chain 416 exclude: 417 for (n := m; n != nil; n = tl n) { 418 r := hd n; 419 for (y := x; y != nil; y = tl y) { 420 (u, nil, nil) := hd y; 421 if (u == r) 422 continue exclude; 423 } 424 l = r :: l; 425 } 426 if (l == nil) 427 continue match; 428 (nil, t) := sys->tokenize(s, "/"); 429 # for each new rule that matched 430 for (n = l; n != nil; n = tl n) { 431 r := hd n; 432 a := r.matches(t); 433 if (a == nil) { 434 e.report("rule match cock up"); 435 return -1; 436 } 437 a[0] = s; 438 e.args = a; 439 # eval rhs 440 (v, nil, nil) := r.rhs.ieval2(e); 441 if (v == nil) 442 continue; 443 y := (r, a, b) :: x; 444 z := getfile(v); 445 # winner or loser 446 if (z.state != SNoexist || Target.find(v) != nil) 447 win = (z, y) :: win; 448 else 449 lose = (z, y) :: lose; 450 } 451 } 452 # winner should be unique 453 if (win != nil) { 454 if (tl win != nil) { 455 multimatch(e, base, win); 456 return -1; 457 } else { 458 (a, p) := hd win; 459 return xeqmatch(e, base, a, p); 460 } 461 } 462 # losers are candidates in next round 463 cand = lose; 464 } while (cand != nil); 465 return 0; 466} 467 468# 469# Make a node (recursive). 470# Return -1 on (reported) error, 0 on fail, 1 on success. 471# 472make(e: ref Env, n: ref Node, s: string): int 473{ 474 if (n == nil) 475 n = getfile(s); 476 if (verbose) 477 sys->print("making %s\n", n.name); 478 if (n.state == SMade) 479 return 1; 480 if (n.flags & FMark) { 481 cycle(e, n); 482 return -1; 483 } 484 n.flags |= FMark; 485 t := Target.find(s); 486 if (t != nil) { 487 x := explicit(e, t, n); 488 if (x != 0) { 489 n.flags &= ~FMark; 490 return x; 491 } 492 } 493 x := implicit(e, n); 494 n.flags &= ~FMark; 495 if (x != 0) 496 return x; 497 if (n.state == SExist) 498 return 0; 499 return -1; 500} 501 502makelevel: int = 0; # count recursion 503 504# 505# Make driver routine. Maybe initialize and handle exceptions. 506# 507domake(e: ref Env, l: list of string) 508{ 509 if ((e.flags & mashlib->ETop) == 0) { 510 e.report("make not at top level"); 511 return; 512 } 513 inited := 0; 514 if (makelevel > 0) 515 inited = 1; 516 makelevel++; 517 if (l == nil) 518 l = "default" :: nil; 519 while (l != nil) { 520 s := hd l; 521 l = tl l; 522 if (s[0] == '-') { 523 case s { 524 "-clear" => 525 mashlib->initdep(); 526 * => 527 e.report("make: unknown option: " + s); 528 } 529 } else { 530 if (!inited) { 531 initnodes(); 532 inited = 1; 533 } 534 { 535 if (make(e, nil, s) < 0) { 536 sys->print("don't know how to make %s\n", s); 537 raise "fail: make error"; 538 } 539 }exception x{ 540 mashlib->FAILPAT => 541 makelevel--; 542 raise x; 543 } 544 } 545 } 546 makelevel--; 547} 548 549# 550# Print dependency/rule command. 551# 552prcmd(out: ref Iobuf, op: int, c: ref Cmd) 553{ 554 if (op == Clistgroup) 555 out.putc(':'); 556 if (c != nil) { 557 out.puts("{ "); 558 out.puts(c.text()); 559 out.puts(" }"); 560 } else 561 out.puts("{}"); 562} 563 564# 565# Print details of rule matches. 566# 567domatch(e: ref Env, l: list of string) 568{ 569 out := e.outfile(); 570 if (out == nil) 571 return; 572 e = e.copy(); 573 while (l != nil) { 574 s := hd l; 575 out.puts(sys->sprint("%s:\n", s)); 576 m := mashlib->rulematch(s); 577 (nil, t) := sys->tokenize(s, "/"); 578 while (m != nil) { 579 r := hd m; 580 out.puts(sys->sprint("\tlhs %s\n", r.lhs.text)); 581 a := r.matches(t); 582 if (a != nil) { 583 a[0] = s; 584 n := len a; 585 for (i := 0; i < n; i++) 586 out.puts(sys->sprint("\t$%d '%s'\n", i, a[i])); 587 e.args = a; 588 (v, w, nil) := r.rhs.ieval2(e); 589 if (v != nil) 590 out.puts(sys->sprint("\trhs '%s'\n", v)); 591 else 592 out.puts(sys->sprint("\trhs list %d\n", len w)); 593 if (r.cmd != nil) { 594 out.putc('\t'); 595 prcmd(out, r.op, r.cmd); 596 out.puts(";\n"); 597 } 598 } else 599 out.puts("\tcock up\n"); 600 m = tl m; 601 } 602 l = tl l; 603 } 604 out.close(); 605} 606 607# 608# Print word list. 609# 610prwords(out: ref Iobuf, l: list of string, pre: int) 611{ 612 while (l != nil) { 613 if (pre) 614 out.putc(' '); 615 out.puts(mashlib->quote(hd l)); 616 if (!pre) 617 out.putc(' '); 618 l = tl l; 619 } 620} 621 622# 623# Print dependency. 624# 625prdep(out: ref Iobuf, d: ref Depend) 626{ 627 prwords(out, d.targets, 0); 628 out.putc(':'); 629 prwords(out, d.depends, 1); 630 if (d.op != Cnop) { 631 out.putc(' '); 632 prcmd(out, d.op, d.cmd); 633 } 634 out.puts(";\n"); 635} 636 637# 638# Print all dependencies, avoiding duplicates. 639# 640alldep(out: ref Iobuf, d: ref Depend, pass: int) 641{ 642 case pass { 643 0 => 644 d.mark = 0; 645 1 => 646 if (!d.mark) { 647 prdep(out, d); 648 d.mark = 1; 649 } 650 } 651} 652 653# 654# Print all dependencies. 655# 656alldeps(out: ref Iobuf) 657{ 658 a := mashlib->dephash; 659 n := len a; 660 for (p := 0; p < 2; p++) 661 for (i := 0; i < n; i++) 662 for (l := a[i]; l != nil; l = tl l) 663 for (d := (hd l).depends; d != nil; d = tl d) 664 alldep(out, hd d, p); 665} 666 667# 668# Print dependencies. 669# 670depends(out: ref Iobuf, l: list of string) 671{ 672 while (l != nil) { 673 s := hd l; 674 out.puts(s); 675 out.puts(":\n"); 676 t := Target.find(s); 677 if (t != nil) { 678 for (d := t.depends; d != nil; d = tl d) 679 prdep(out, hd d); 680 } 681 l = tl l; 682 } 683} 684 685# 686# Print rule. 687# 688prrule(out: ref Iobuf, r: ref Rule) 689{ 690 out.puts(r.lhs.text); 691 out.puts(" :~ "); 692 out.puts(r.rhs.text()); 693 out.putc(' '); 694 prcmd(out, r.op, r.cmd); 695 out.puts(";\n"); 696} 697 698# 699# Print all rules. 700# 701allrules(out: ref Iobuf) 702{ 703 for (l := mashlib->rules; l != nil; l = tl l) 704 prrule(out, hd l); 705} 706 707# 708# Print matching rules. 709# 710rules(out: ref Iobuf, l: list of string) 711{ 712 while (l != nil) { 713 s := hd l; 714 out.puts(s); 715 out.puts(":\n"); 716 r := mashlib->rulematch(s); 717 while (r != nil) { 718 prrule(out, hd r); 719 r = tl r; 720 } 721 l = tl l; 722 } 723} 724