shithub: mc

ref: 2be6300162a3b32e4d82f71ed6772aa5feced897
dir: /mbld/build.myr/

View raw version
use std

use "config"
use "deps"
use "opts"
use "parse"
use "types"
use "util"

pkg bld =
	const buildall	: (b : build# -> bool)
	const genall	: (b : build# -> bool)
	const buildtarg	: (b : build#, target : byte[:] -> bool)
	const buildbin	: (b : build#, bt : myrtarg#, addsrc : bool -> bool)
	const buildlib	: (b : build#, lt : myrtarg# -> bool)
;;

const buildall = {b
	for tn in b.all
		if std.hthas(b.built, tn)
			continue
		;;
		std.htput(b.built, tn, true)
		match gettarg(b.targs, tn)
		| `Bin bt:
			if !bt.istest
				buildbin(b, bt, false)
			;;
		| `Lib lt:	buildlib(b, lt)
		| `Gen gt:	genfiles(b, gt)
		| `Data _:	/* nothing needed */
		| `Man _:	/* nothing needed */
		| `Cmd _:	/* these are for manual commands or tests */
		;;
	;;
	setdir(b, "")
	-> true
}

const buildtarg = {b, targ
	var depset

	depset = std.mkht(std.strhash, std.streq)
	addeps(b, targ, depset)
	for tn in b.all
		if std.hthas(b.built, tn) || !std.hthas(depset, tn)
			continue
		;;
		if std.sleq(tn, targ)
			break
		;;
		std.htput(b.built, tn, true)
		match gettarg(b.targs, tn)
		| `Bin bt:
			if !bt.istest
				buildbin(b, bt, false)
			;;
		| `Lib lt:	buildlib(b, lt)
		| `Gen gt:	genfiles(b, gt)
		| `Data _:	/* nothing needed */
		| `Man _:	/* nothing needed */
		| `Cmd _:	/* these are for manual commands or tests */
		;;
	;;
	build(b, targ)
	std.htfree(depset)
	-> true
}

const addeps = {b, targ, depset
	if std.hthas(depset, targ)
		-> void
	;;

	std.htput(depset, targ, true)
	match gettarg(b.targs, targ)
	| `Bin bt:
		for (dir, lib, targname) in bt.libdeps
			addeps(b, targname, depset)
		;;
	| `Lib lt:
		for (dir, lib, targname) in lt.libdeps
			addeps(b, targname, depset)
		;;
	| _:
	;;
}

const genall = {b
	for tn in b.all
		match gettarg(b.targs, tn)
		| `Gen gt:	runin(b, gt.cmd, gt.dir)
		| _:	/* skip */
		;;
	;;
	/* genfiles will exit if the build fails; always return true */
	-> true
}

const build = {b, targ
	match std.htget(b.targs, targ)
	| `std.Some (`Bin bt):	buildbin(b, bt, false)
	| `std.Some (`Lib lt):	buildlib(b, lt)
	| `std.Some (`Gen gt):	runin(b, gt.cmd, gt.dir)
	| `std.Some (`Cmd ct):	runin(b, ct.cmd, ct.dir)
	| `std.Some (`Data _):	/* nothing needed */
	| `std.Some (`Man _):	/* nothing needed */
	| `std.None:	std.fatal("invalid target {}\n", targ)
	;;
	-> true
}

const runin = {b, cmd, dir
	setdir(b, dir)
	run(cmd)
}

const buildbin = {b, targ, addsrc
	var dg, src, path
	var libpath, incs

	setdir(b, targ.dir)
	addincludes(b, targ)
	path = std.pathcat(b.curdir, targ.name)
	mbldput("{}...\n", path)
	std.slfree(path)
	dg = myrdeps(b, targ, false, addsrc)
	if !std.hthas(dg.deps, targ.name)
		std.fatal("no input files for {}\n", targ.name)
	;;
	if builddep(b, dg, targ.name, targ.incpath) || !freshlibs(targ, targ.name, dg.libs)
		src = std.htkeys(dg.sources)

		incs = std.sldup(targ.incpath)
		if opt_instbase.len > 0 && !std.sleq(opt_instbase, "none")
			libpath = std.pathcat(bld.opt_instbase, config.Libpath)
			std.slpush(&incs, libpath)
		;;
		linkbin(dg, targ.name, src, targ.ldscript, targ.runtime, incs, targ.libdeps)
		std.slfree(incs)
		std.slfree(src)
	;;
	-> true
}

const buildlib = {b, targ
	var archive, usefile
	var u, l
	var dg
	var lib, src

	setdir(b, targ.dir)
	addincludes(b, targ)
	lib = targ.name
	mbldput("{}/lib{}.a...\n", b.curdir, lib)
	archive = std.fmt("lib{}.a", lib)
	usefile = std.fmt("lib{}.use", lib)
	dg = myrdeps(b, targ, false, false)
	if !std.hthas(dg.deps, lib)
		std.fatal("no target declared for {}\n", lib)
	;;
	u = builddep(b, dg, usefile, targ.incpath)
	l = builddep(b, dg, archive, targ.incpath)
	if  u || l || !freshlibs(targ, usefile, dg.libs)
		src = std.htkeys(dg.sources)
		mergeuse(dg, lib, src, targ.incpath)
		archivelib(dg, lib, src, targ.incpath)
		std.slfree(src)
	;;
	std.slfree(archive)
	-> true
}

const genfiles = {b, gt
	setdir(b, gt.dir)
	for out in gt.out
		if !std.fexists(out) || !allfresh(gt.deps, out)
			run(gt.cmd)
			break
		;;
	;;
}

const addincludes = {b, targ
	for (inc, lib, subtarg) in targ.libdeps
		if !hasinc(targ.incpath, inc)
			std.slput(&targ.incpath, 0, inc)
		;;
	;;
}

const hasinc = {path, t
	for e in path
		if std.sleq(e, t)
			-> true
		;;
	;;
	-> false
}


const builddep = {b, dg, out, incs
	var stale

	stale = false
	/* short circuit walking the dep tree if we've already built this. */
	if std.htgetv(dg.updated, out, false)
		-> false
	;;

	match std.htget(dg.deps, out)
	| `std.Some deps:
		for d in deps
			if builddep(b, dg, d, incs)
				stale = true
			;;
			match std.htget(b.gensrc, d)
			| `std.Some gt:	
				if !std.fexists(d) || !allfresh(gt.deps, d)
					run(gt.cmd)
				;;
			| `std.None:
				if !std.fexists(d)
					std.fatal("no input file {}\n", d)
				;;
			;;
			if !isfresh(d, out)
				stale = true
			;;
		;;
	| `std.None:
	;;

	match std.htget(dg.input, out)
	| `std.Some src:
		if stale
			compile(dg, src, incs)
		;;
		std.htput(dg.updated, out, true)
	| `std.None:
	;;
	-> stale
}

const compile = {dg, src, incs
	var o
	var cmd

	cmd = [][:]
	if std.hassuffix(src, ".myr")
		std.slpush(&cmd, opt_mc)
		for inc in incs 
			std.slpush(&cmd, "-I")
			std.slpush(&cmd, inc)
		;;
		if opt_genasm
			std.slpush(&cmd, "-S")
		;;
		std.slpush(&cmd, src)
		run(cmd)
		std.slfree(cmd)
	elif std.hassuffix(src, ".s")
		o = srcswapsuffix(src, config.Objsuffix)
		for c in config.Ascmd
			std.slpush(&cmd, c)
		;;
		std.slpush(&cmd,"-o")
		std.slpush(&cmd, o)
		std.slpush(&cmd, src)
		run(cmd)
		std.slfree(o)
	elif std.hassuffix(src, ".glue.c")
		o = srcswapsuffix(src, config.Objsuffix)
		std.slpush(&cmd, "cc")
		std.slpush(&cmd,"-c")
		std.slpush(&cmd,"-o")
		std.slpush(&cmd, o)
		std.slpush(&cmd, src)
		for flg in std.htgetv(dg.cflags, src, [][:])
			std.slpush(&cmd, flg)
		;;
		run(cmd)
	else
		std.fatal("Unknown file type for {}\n", src)
	;;
}

const linkbin = {dg, bin, srcfiles, ldscript, rt, incs, extlibs
	var cmd

	cmd = [][:]

	/* ld -o bin */
	for c in config.Linkcmd
		std.slpush(&cmd, std.sldup(c))
	;;
	std.slpush(&cmd, std.sldup(bin))

	/* [-T script] */
	if ldscript.len > 0
		std.slpush(&cmd, std.sldup("-T"))
		std.slpush(&cmd, std.sldup(ldscript))
	;;

	if rt.len != 0
		if !std.sleq(rt, "none")
			std.slpush(&cmd, std.sldup(rt))
		;;
	else
		std.slpush(&cmd, std.sldup(opt_runtime))
	;;

	/* input.o list.o... */
	for f in srcfiles
		std.slpush(&cmd, srcswapsuffix(f, config.Objsuffix))
	;;

	/* -L path -l lib... */
	cmd = addlibs(cmd, dg.libs, incs)


	/* add extra libs */
	for l in dg.extlibs
		std.slpush(&cmd, std.fmt("-l{}", l))
	;;

	/* special for OSX: it warns if we don't add this */
	if std.sleq(opt_sys, "osx")
		std.slpush(&cmd, std.sldup("-macosx_version_min"))
		std.slpush(&cmd, std.sldup("10.6"))
	elif std.sleq(opt_sys, "linux") && dg.dynamic
		std.slpush(&cmd, std.sldup("-dynamic-linker"))
		std.slpush(&cmd, std.sldup("/lib64/ld-linux-x86-64.so.2"))
	;;

	run(cmd)
	strlistfree(cmd)
}

const archivelib = {dg, lib, files, incs
	var cmd
	var obj

	cmd = [][:]
	for c in config.Arcmd
		std.slpush(&cmd, std.sldup(c))
	;;
	std.slpush(&cmd, std.fmt("lib{}.a", lib))
	for f in files
		obj = srcswapsuffix(f, config.Objsuffix)
		std.slpush(&cmd, obj)
	;;
	run(cmd)
	strlistfree(cmd)
}

const mergeuse = {dg, lib, files, incs
	var cmd

	cmd = [][:]
	std.slpush(&cmd, std.sldup(opt_muse))
	std.slpush(&cmd, std.sldup("-o"))
	std.slpush(&cmd, std.fmt("lib{}.use", lib))
	std.slpush(&cmd, std.sldup("-p"))
	std.slpush(&cmd, std.sldup(lib))
	for f in files
		if std.hassuffix(f, ".myr")
			std.slpush(&cmd, srcswapsuffix(f, ".use"))
		elif !std.hassuffix(f, ".s") && !std.hassuffix(f, ".glue.c")
			std.fatal("unknown file type for {}\n", f)
		;;
	;;
	for l in dg.extlibs
		std.slpush(&cmd, std.fmt("-l{}", l))
	;;
	run(cmd)
	strlistfree(cmd)
}

const addlibs = {cmd, libgraph, incs
	var looped : std.htab(byte[:], bool)#
	var marked : std.htab(byte[:], bool)#
	var libs
	var head

	/* -L incpath... */
	if !config.Directlib
		for inc in incs
			std.slpush(&cmd, std.fmt("-L{}", inc))
		;;
	;;

	libs = std.htkeys(libgraph)
	looped = std.mkht(std.strhash, std.streq)
	marked = std.mkht(std.strhash, std.streq)
	head = cmd.len

	for lib in libs
		cmd = visit(cmd, head, libgraph, lib, looped, marked, incs)
	;;

	-> cmd
}

const visit = {cmd, head, g, lib, looped, marked, incs
	if std.hthas(looped, lib)
		std.fatal("cycle in library graph involving \"{}\"\n", lib)
	elif std.hthas(marked, lib)
		-> cmd
	;;

	std.htput(looped, lib, true)
	for dep in std.htgetv(g, lib, [][:])
		cmd = visit(cmd, head, g, dep, looped, marked, incs)
	;;
	std.htdel(looped, lib)
	std.htput(marked, lib, true)
	-> putlib(cmd, head, lib, incs)
}

const putlib = {cmd, head, lib, incs
	var sep

	if !config.Directlib
		-> std.slput(&cmd, head, std.fmt("-l{}", lib))
	else
		match findlib(lib, incs)
		| `std.None:
			mbldput("in path: ")
			sep = ""
			for inc in incs
				mbldput("\t{}{}\n", sep, inc)
				sep = ", "
			;;
			std.fatal("could not find library lib{}.a.\n", lib)
		| `std.Some p:
			-> std.slput(&cmd, head, p)
		;;
	;;
}

const findlib = {lib, incs
	var buf : byte[512]
	var sl, p

	sl = std.bfmt(buf[:], "lib{}.a", lib)
	for i in incs
		p = std.pathcat(i, sl)
		if std.fexists(p)
			-> `std.Some p
		;;
		std.slfree(p)
	;;

	p = std.pathjoin([opt_instbase, config.Libpath, sl][:])
	if std.fexists(p)
		-> `std.Some p
	;;
	std.slfree(p)
	-> `std.None
}

const freshlibs = {targ, output, libgraph
	var libs

	libs = std.htkeys(libgraph)
	for l in libs
		match findlib(l, targ.incpath)
		| `std.Some lib:
			if !isfresh(lib, output)
				std.slfree(lib)
				-> false
			;;
			std.slfree(lib)
		| `std.None:
			std.fput(1, "{}: could not find library lib{}.a\n", targ.name, l)
			std.fput(1, "searched:\n")
			for inc in targ.incpath
				std.fput(1, "\t{}\n", inc)
			;;
			std.fput(1, "\t{}/{}\n", opt_instbase, config.Libpath)
			std.exit(1)
		;;
	;;
	std.slfree(libs)
	-> true
}

const allfresh = {deps, out
	for d in deps
		if !isfresh(d, out)
			-> false
		;;
	;;
	-> true
}

const isfresh = {src, dst
	var srcmt, dstmt

	/*
	OSX only has single second resolution on modification
	times. Since most builds happen within one second of each
	other, if we treat equal times as outdated, we do a lot of
	spurious rebuilding.

	So, we treat times where both secs and nsecs are equal as
	up to date.
	*/
	match std.fmtime(src)
	| `std.Ok mt:	srcmt = mt
	| `std.Err e:	std.fatal("could not stat {}: {}\n", src, e)
	;;
	match std.fmtime(dst)
	| `std.Ok mt:	dstmt = mt
	| `std.Err e:	-> false
	;;
	-> srcmt <= dstmt
}