shithub: mc

ref: 1d6cb9bf584e35be0786467e1786e60ac909ae21
dir: /mbld/deps.myr/

View raw version
use std
use regex
use bio

use "config"
use "opts"
use "types"
use "util"
use "libs"

pkg bld =
	const deps	: (b : build# -> void)
	const testdeps	: (b : build# -> void)
	const resolve	: (b : build# -> void)
;;

type dep = union
	`Xdep	byte[:]
	`Ldep	byte[:]
;;

var usepat	: regex.regex#
var cflagpat	: regex.regex#
var ldflagpat	: regex.regex#
var clibpat	: regex.regex#

const __init__ = {
	usepat = std.try(regex.compile("^\\s*use\\s+((\\<\\S+\\>)|\"(\\S+)\").*"))
	cflagpat = std.try(regex.compile("/\\*\\s*CFLAGS:\\s*(.*)\\s*\\*/"))
	ldflagpat = std.try(regex.compile("/\\*\\s*LDFLAGS:\\s*(.*)\\s*\\*/"))
	clibpat = std.try(regex.compile("/\\*\\s*LIBS:\\s*(.*)\\s*\\*/"))
}

const deps = {b
	for (name, `Gen gt) : std.byhtkeyvals(b.targs)
		cmddeps(b, name, gt)
	;;
	for name : b.all
		match gettarg(b.targs, name)
		| `Bin bt:	myrdeps(b, name, bt)
		| `Lib lt:	myrdeps(b, name, lt)
		| `Cmd ct:	cmddeps(b, name, ct)
		| `Man mt:	mandeps(b, name, mt)
		| `Data dt:	datdeps(b, name, dt)
		| `Gen gt:	/* gen was dealt with earlier */
		;;
	;;
}

const testdeps = {b
	for name : b.all
		match gettarg(b.targs, name)
		| `Bin bt:	addtests(b, name, bt)
		| `Lib lt:	addtests(b, name, lt)
		| _:		/* skip */
		;;
	;;
}

const myrdeps = {b, name, mt
	var t, p, o, u, n, to, tu
	var libs, dynlibs, ldflags
	var cflags, ll, lf
	var g, a, deps
	var gu, go

	g = b.deps
	if mt.islib
		u = std.fmt("lib{}.use", mt.name)
		o = std.fmt("lib{}.a", mt.name)
		tu = std.pathjoin([opt_objdir, mt.dir, u][:])
		to = std.pathjoin([opt_objdir, mt.dir, o][:])

		gu = node(g, tu)
		go = node(g, to)
		if mt.install
			go.instdir = config.Libpath
			gu.instdir = config.Libpath
		;;
		go.instmode = 0o644
		gu.instmode = 0o644
		generates(g, gu, tu)
		generates(g, go, to)

		a = std.htgetv(g.targs, "all", [][:])
		std.slpush(&a, gu)
		std.slpush(&a, go)
		std.htput(g.targs, "all", a)
		std.htput(g.targs, name, std.sldup([gu, go][:]))

		std.slfree(o)
		std.slfree(u)
	else
		u = std.fmt("{}.use", mt.name)
		tu = std.pathjoin([opt_objdir, mt.dir, u][:])
		to = std.pathjoin([opt_objdir, mt.dir, mt.name][:])

		go = node(g, to)
		gu = node(g, tu)

		generates(g, go, to)
		std.htput(g.targs, name, std.sldup([go][:]))
		if mt.istest
			n = node(g, mt.name)
			depends(g, n, to)
			addnode(g, "test", n)

			n.wdir = std.sldup(mt.dir)
			std.slpush(&n.cmd, std.pathjoin([b.basedir, opt_objdir, mt.dir, mt.name][:]))
		elif mt.isbench
			n = node(g, mt.name)
			depends(g, n, to)
			addnode(g, "bench", n)

			n.wdir = std.sldup(mt.dir)
			std.slpush(&n.cmd, std.pathjoin([b.basedir, opt_objdir, mt.dir, mt.name][:]))
		else
			addnode(g, "all", go)
			if mt.install
				go.instdir = config.Binpath
				go.instmode = 0o755
			;;
		;;
		std.slfree(u)
	;;

	libs = [][:]
	ldflags = [][:]
	dynlibs = [][:]
	for f : mt.inputs
		p = std.pathcat(mt.dir, f)
		leaf(g, p)
		if std.hassuffix(f, ".myr")
			t = changesuffix(p, config.Objsuffix)
			o = std.pathcat(opt_objdir, t)
			std.slfree(t)

			t = changesuffix(p, ".use")
			u = std.pathcat(opt_objdir, t)
			std.slfree(t)

			depends(g, go, o)
			depends(g, gu, u)

			n = node(g, o)
			generates(g, n, o)
			generates(g, n, u)
			depends(g, n, p)

			deps = scrapedeps(b, mt, p)
			for `Ldep d : deps
				depends(g, n, d)
			;;
			for `Xdep d : deps
				scrapelib(b, mt.name, d, mt.incpath)
				xdepends(b, g, n, d)
				std.slpush(&libs, d)
			;;
			myrcmd(b, n, mt, p, false)
			std.slfree(deps)
		elif std.hassuffix(f, ".s")
			t = changesuffix(p, config.Objsuffix)
			o = std.pathcat(opt_objdir, t)
			std.slfree(t)

			depends(g, go, o)
			n = node(g, o)
			generates(g, n, o)
			depends(g, n, p)
			ascmd(b, n, mt, o, p)
		elif std.hassuffix(f, ".glue.c")
			t = changesuffix(p, config.Objsuffix)
			o = std.pathcat(opt_objdir, t)
			std.slfree(t)

			(cflags, ll, lf) = scrapecflags(b, mt, p)
			depends(g, go, o)
			n = node(g, o)
			generates(g, n, o)
			depends(g, n, p)
			ccmd(b, n, mt, o, p, cflags)
			for l : ll
				std.slpush(&dynlibs, l)
			;;
			for l : lf
				std.slpush(&ldflags, l)
			;;
			mt.isdyn = true
		elif std.hassuffix(f, config.Objsuffix)
			depends(g, go, p)
		else
			std.fatal("don't know how to build {}/{}\n", mt.dir, f)
		;;
	;;

	if mt.islib
		arcmd(b, go, mt, to)
		musecmd(b, gu, mt, tu, dynlibs)
		builtlib(b, mt, libs, dynlibs)
	else
		linkcmd(b, go, mt, to, libs, dynlibs, ldflags, false)
		std.slfree(libs)
	;;
}

const cmddeps = {b, name, ct
	var n, a, p, gen, pid

	n = node(b.deps, std.strjoin(ct.cmd, " "))
	n.wdir = std.sldup(ct.dir)
	n.durable = ct.durable

	for c : ct.cmd
		std.slpush(&n.cmd, std.sldup(c))
	;;

	if ct.deps.len == 0
		std.slpush(&b.deps.leaves, n)
	else
		for d : ct.deps
			leaf(b.deps, d)
			depends(b.deps, n, d)
		;;
	;;

	gen = false
	for g : ct.gen
		p = std.pathcat(ct.dir, g)
		gen = gen || !std.fexists(p)
		generates(b.deps, n, p)
	;;

	if ct.istest
		a = std.htgetv(b.deps.targs, "test", [][:])
		std.slpush(&a, n)
		std.htput(b.deps.targs, "test", a)
	elif ct.isbench
		a = std.htgetv(b.deps.targs, "bench", [][:])
		std.slpush(&a, n)
		std.htput(b.deps.targs, "bench", a)
	elif gen 
		pid = run(ct.cmd, ct.dir)
		match std.wait(pid)
		| `std.Wfailure:	std.fatal("FAIL: {j= }\n", ct.cmd)
		| `std.Wsignalled:	std.fatal("CRASH: {j= }\n", ct.cmd)
		| `std.Waiterror:	std.fatal("WAT: {j= }\n", ct.cmd)
		| `std.Wsuccess:	/* ok */
		;;
	;;
}

const mandeps = {b, name, mt
	var p, r, n

	for pg : mt.pages
		p = std.pathcat(mt.dir, pg)
		n = leaf(b.deps, p)
		match std.strrfind(pg, ".")
		| `std.None:	std.fatal("manpage {} missing section\n", pg)
		| `std.Some i:
			r = std.strcat(config.Manpath, pg[i + 1:])
			if config.Stripman
				n.instname = std.sldup(pg[:i])
			;;
		;;
		n.instdir = r
		n.instmode = 0o644
		addnode(b.deps, "all", n)
	;;
}

const datdeps = {b, name, dt
	var p, n

	for db : dt.blobs
		p = std.pathcat(dt.dir, db)
		n = leaf(b.deps, p)
		n.instdir = dt.path
		if dt.path.len == 0
			n.instdir = config.Sharepath
		;;
		n.instmode = 0o644
		addnode(b.deps, "all", n)
	;;
}

const addtests = {b, name, mt
	for f : mt.inputs
		addtest(b, mt, f)
		addbench(b, mt, f)
	;;
}

const addtest = {b, mt, f
	addalt(b, mt, "test", f)
}

const addbench = {b, mt, f
	addalt(b, mt, "bench", f)
}

const addalt = {b, mt, kind, f
	var libs, deps
	var sp, tp, op
	var s, t, o
	var g, n
	var testinc

	/*
	change of suffix is to support testing assembly,
	C glue, and foo+sys.myr forms.
	*/
	g = b.deps
	s = changesuffix(f, ".myr")
	sp = std.pathjoin([mt.dir, kind, s][:])
	std.slfree(s)
	if !std.fexists(sp)
		std.slfree(sp)
		-> void
	;;

	libs = [][:]
	leaf(g, sp)

	t = changesuffix(f, "")
	tp = std.pathjoin([opt_objdir, mt.dir, kind, t][:])
	std.slfree(t)

	o = changesuffix(f, config.Objsuffix)
	op = std.pathjoin([opt_objdir, mt.dir, kind, o][:])
	std.slfree(o)

	n = node(g, sp)
	generates(g, n, op)
	depends(g, n, sp)
	testinc = [][:]
	std.slpush(&testinc, mt.dir)
	std.sljoin(&testinc, mt.incpath)
	deps = scrapedeps(b, mt, sp)
	for `Ldep d : deps
		depends(g, n, d)
	;;
	for `Xdep d : deps
		scrapelib(b, mt.name, d, mt.incpath)
		xdepends(b, g, n, d)
		std.slpush(&libs, d)
	;;
	myrcmd(b, n, mt, sp, true)

	n = node(g, tp)
	generates(g, n, tp)
	depends(g, n, op)
	linkcmd(b, n, mt, tp, libs, [][:], [][:], true)
	std.slfree(libs)

	n = node(g, tp)
	depends(g, n, tp)
	n.wdir = std.sldup(std.dirname(std.dirname(sp)))
	std.slpush(&n.cmd, std.pathjoin([b.basedir, opt_objdir, mt.dir, kind, std.basename(tp)][:]))

	addnode(g, kind, n)
}

const resolve = {b
	var visited, looped, stk
	var g

	g = b.deps
	for n : g.nodes
		for e : n.dep
			edge(g, n, e)
		;;
	;;

	stk = [][:]
	visited = std.mkht()
	looped = std.mkht()
	for n : g.nodes
		checkloop(g, n, visited, looped, &stk)
	;;
	std.htfree(visited)
	std.htfree(looped)
}

const edge = {g, n, e
	match std.htget(g.gen, e)
	| `std.None:
		std.fatal("{}: missing build rule for {}\n", n.lbl, e)
	| `std.Some d:	
		std.slpush(&n.ndep, d)
		std.slpush(&d.ngen, n)
		n.nblock++
	;;
}

const checkloop = {g, n, visited, looped, stk
	if std.hthas(looped, n)
		std.slpush(stk, n.lbl)
		std.fatal("dependency loop: {j= -> }\n", stk#)
	;;
	if std.hthas(visited, n)
		-> void
	;;
	std.slpush(stk, n.lbl)
	std.htput(visited, n, void)
	std.htput(looped, n, void)
	for d : n.ndep
		checkloop(g, d, visited, looped, stk)
	;;
	std.slpop(stk)
	std.htdel(looped, n)
}

const musecmd = {b, n, mt, mu, dynlibs
	std.slpush(&n.cmd, std.sldup(opt_muse))
	for o : opt_museflags
		std.slpush(&n.cmd, o)
	;;
	for l : dynlibs
		std.slpush(&n.cmd, std.fmt("-l{}", l))
	;;
	std.slpush(&n.cmd, std.sldup("-o"))
	std.slpush(&n.cmd, std.sldup(mu))
	std.slpush(&n.cmd, std.sldup("-p"))
	std.slpush(&n.cmd, std.sldup(mt.name))
	for u : n.dep
		std.slpush(&n.cmd, std.sldup(u))
	;;
}

const arcmd = {b, n, mt, ar
	for c : config.Arcmd
		std.slpush(&n.cmd, std.sldup(c))
	;;
	std.slpush(&n.cmd, std.sldup(ar))
	for obj : n.dep
		std.slpush(&n.cmd, std.sldup(obj))
	;;
}

const linkcmd = {b, n, mt, bin, libs, dynlibs, ldflags, istest
	var dynlink

	for c : config.Linkcmd
		std.slpush(&n.cmd, std.sldup(c))
	;;
	for o : opt_ldflags
		std.slpush(&n.cmd, o)
	;;

	std.slpush(&n.cmd, "-o")
	std.slpush(&n.cmd, std.sldup(bin))
	if mt.ldscript.len > 0
		std.slpush(&n.cmd, std.sldup("-T"))
		std.slpush(&n.cmd, std.sldup(mt.ldscript))
	;;

	if mt.runtime.len == 0 || std.eq(mt.runtime, "none")
		std.slpush(&n.cmd, std.sldup(opt_runtime))
	else
		std.slpush(&n.cmd, std.sldup(mt.runtime))
	;;

	for o : n.dep
		std.slpush(&n.cmd, std.sldup(o))
	;;

	dynlink = addlibs(b, &n.cmd, libs, mt.incpath) || mt.isdyn
	for f : ldflags
		std.slpush(&n.cmd, std.sldup(f))
	;;
	for l : dynlibs
		std.slpush(&n.cmd, std.fmt("-l{}", l))
	;;

	if dynlink
		for f : config.Dlflags
			std.slpush(&n.cmd, std.sldup(f))
		;;
	;;

	/* OSX warns if we don't add a version */
        /* OSX now hates this, and then breaks everything */
        /*
	if std.eq(opt_sys, "osx")
		std.slpush(&n.cmd, std.sldup("-macosx_version_min"))
		std.slpush(&n.cmd, std.sldup("10.6"))
	;;
        */
}

const myrcmd = {b, n, mt, src, istest
	std.slpush(&n.cmd, std.sldup(opt_mc))
	for o : opt_mcflags
		std.slpush(&n.cmd, o)
	;;
	if opt_objdir.len > 0
		pushopt(&n.cmd, "-O", std.sldup(opt_objdir))
	;;
	for inc : mt.incpath[:mt.incpath.len - 1]
		pushopt(&n.cmd, "-I", inc)
	;;
	if istest
		std.slpush(&n.cmd, "-T")
		for (dir, _, _) : mt.tstdeps
			pushopt(&n.cmd, "-I", std.pathcat(opt_objdir, dir))
		;;
		pushopt(&n.cmd, "-I", std.pathcat(opt_objdir, mt.dir))
	;;
	for (dir, _, _) : mt.libdeps
		pushopt(&n.cmd, "-I", std.pathcat(opt_objdir, dir))
	;;
	if opt_genasm
		std.slpush(&n.cmd, "-S")
	;;
	std.slpush(&n.cmd, src)
}

const ascmd = {b, n, mt, out, src
	for c : config.Ascmd
		std.slpush(&n.cmd, c)
	;;
	std.slpush(&n.cmd,"-o")
	std.slpush(&n.cmd, out)
	std.slpush(&n.cmd, src)
}

const ccmd = {b, n, mt, out, src, cflags
	std.slpush(&n.cmd, "cc")
	std.slpush(&n.cmd,"-c")
	std.slpush(&n.cmd,"-o")
	std.slpush(&n.cmd, out)
	std.slpush(&n.cmd, src)
	for flg : cflags
		std.slpush(&n.cmd, flg)
	;;
}

const scrapedeps = {b : build#, mt, path
	var p, f, l

	match bio.open(path, bio.Rd)
	| `std.Ok fd:	f = fd
	| `std.Err e:	std.fatal("error opening {}: {}\n", path, e)
	;;

	l = [][:]
	for ln : bio.byline(f)
		match std.strfind(ln, "use")
		| `std.None:	continue
		| `std.Some _:	/* ok */
		;;
		match regex.exec(usepat, ln)
		| `std.None:
		| `std.Some uses:
			if uses[2].len > 0
				/* external library */
				p = std.sldup(uses[2])
				std.slpush(&l, `Xdep p)
			else
				/* internal library */
				p = std.pathjoin([opt_objdir, mt.dir, uses[3]][:])
				std.sljoin(&p, ".use")
				std.slpush(&l, `Ldep p)
			;;
			regex.matchfree(uses)
		;;
	;;
	bio.close(f)
	-> l
}

const collectflags = {pat, ln, dst
	var fl

	match regex.exec(pat, ln)
	| `std.None:	/* skip */
	| `std.Some m:
		fl = std.strtok(m[1])
		for s : fl
			std.slpush(dst, std.sldup(s))
		;;
		std.slfree(fl)
	;;
}

const scrapecflags = {b, mt, path
	var f, cflags, libs, ldflags

	match bio.open(path, bio.Rd)
	| `std.Ok fd:	f = fd
	| `std.Err e:	std.fatal("error opening {}: {}\n", path, e)
	;;

	cflags = [][:]
	libs = [][:]
	ldflags = [][:]
	for ln : bio.byline(f)
		collectflags(cflagpat, ln, &cflags)
		collectflags(ldflagpat, ln, &ldflags)
		collectflags(clibpat, ln, &libs)
	;;
	-> (cflags, libs, ldflags)
}

const generates = {g, n, dep
	std.mkpath(std.dirname(dep))
	std.slpush(&n.gen, dep)
	std.htput(g.gen, dep, n)
}

const depends = {g, n, dep
	std.slpush(&n.dep, dep)
}

const xdepends = {b, g, n, dep
	match std.htget(b.libs, dep)
	| `std.Some ldep:
		if ldep.genuse.len != 0
			depends(g, n, ldep.genuse)
			depends(g, n, ldep.genar)
		;;
		n.deptime = std.max(n.deptime, ldep.mtime)
	| `std.None:
		std.fatal("unknown xdep {} (known: {})\n", dep, std.htkeys(b.libs))
	;;
}

const leaf = {g, f
	var nod

	match std.htget(g.gen, f)
	| `std.Some n:
		-> n
	| `std.None:	
		nod = node(g, f)
		nod.durable = true
		generates(g, nod, f)
		std.slpush(&g.leaves, nod)
		-> nod
	;;
}

const addnode = {g, targ, n
	var nl

	nl = std.htgetv(g.targs, targ, [][:])
	std.slpush(&nl, n)
	std.htput(g.targs, targ, nl)
}

const pushopt = {lst, pfx, dir
	std.slpush(lst, std.sldup(pfx))
	std.slpush(lst, std.pathnorm(dir))
}

const node = {g, lbl
	var nod

	nod = std.mk([
		.lbl=lbl,
		.cmd=[][:],
		.gen=[][:],
		.dep=[][:],

		.nblock=0,
		.mtime=0,
	])
	std.slpush(&g.nodes, nod)
	-> nod
}