xref: /inferno-os/appl/cmd/mash/make.b (revision 37da2899f40661e3e9631e497da8dc59b971cbd0)
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