summaryrefslogtreecommitdiff
path: root/mbld
diff options
context:
space:
mode:
authorOri Bernstein <ori@eigenstate.org>2015-04-13 01:25:54 -0700
committerOri Bernstein <ori@eigenstate.org>2015-04-13 01:29:42 -0700
commit52585564bf5a46db0ae6c9fe4e20307e014de848 (patch)
treea0cbd5529dcb351d1c1c059d44379e8e5b85b1f8 /mbld
parent2f5a88d9b79271f78ec71cf7f796ca5fb74de89e (diff)
downloadmc-52585564bf5a46db0ae6c9fe4e20307e014de848.tar.gz
Move mbld to subdirectory of itself for merging.
Diffstat (limited to 'mbld')
-rw-r--r--mbld/Makefile20
-rw-r--r--mbld/bldfile22
-rw-r--r--mbld/build.myr412
-rw-r--r--mbld/clean.myr89
-rw-r--r--mbld/config+plan9-x64.myr11
-rwxr-xr-xmbld/configure65
-rw-r--r--mbld/deps.myr308
-rw-r--r--mbld/fsel.myr105
-rw-r--r--mbld/install.myr92
-rw-r--r--mbld/main.myr115
-rw-r--r--mbld/mbld.181
-rw-r--r--mbld/mk/myr.mk42
-rw-r--r--mbld/opts.myr85
-rw-r--r--mbld/parse.myr539
-rw-r--r--mbld/subdir.myr23
-rw-r--r--mbld/test.myr150
-rw-r--r--mbld/types.myr59
-rw-r--r--mbld/util.myr123
18 files changed, 2341 insertions, 0 deletions
diff --git a/mbld/Makefile b/mbld/Makefile
new file mode 100644
index 0000000..9e1ea95
--- /dev/null
+++ b/mbld/Makefile
@@ -0,0 +1,20 @@
+MYRBIN=mbld
+
+MYRSRC= \
+ build.myr \
+ clean.myr \
+ config.myr \
+ deps.myr \
+ fsel.myr \
+ install.myr \
+ main.myr \
+ opts.myr \
+ parse.myr \
+ subdir.myr \
+ util.myr \
+ test.myr \
+ types.myr
+
+include config.mk
+include mk/myr.mk
+
diff --git a/mbld/bldfile b/mbld/bldfile
new file mode 100644
index 0000000..45d21bd
--- /dev/null
+++ b/mbld/bldfile
@@ -0,0 +1,22 @@
+# the mbld binary
+bin mbld =
+ build.myr
+ clean.myr
+ config.myr # config, as generated by the ./configure script
+ config+plan9-x64.myr # config, as hardcoded for Plan 9
+ deps.myr
+ fsel.myr
+ install.myr
+ main.myr
+ opts.myr
+ parse.myr
+ subdir.myr
+ test.myr
+ types.myr
+ util.myr
+;;
+
+gen config.myr {durable} = ./configure ;;
+
+man = mbld.1;;
+
diff --git a/mbld/build.myr b/mbld/build.myr
new file mode 100644
index 0000000..74768db
--- /dev/null
+++ b/mbld/build.myr
@@ -0,0 +1,412 @@
+use std
+
+use "config.use"
+use "deps.use"
+use "opts.use"
+use "parse.use"
+use "types.use"
+use "util.use"
+
+pkg bld =
+ const buildall : (b : build# -> bool)
+ const genall : (b : build# -> bool)
+ const build : (b : build#, target : byte[:] -> bool)
+ const buildbin : (b : build#, bt : myrtarg#, addsrc : bool -> void)
+ const buildlib : (b : build#, lt : myrtarg# -> void)
+;;
+
+const buildall = {b
+ for tn in b.all
+ match gettarg(b.targs, tn)
+ | `Bin bt: buildbin(b, bt, false)
+ | `Lib lt: buildlib(b, lt)
+ | `Test tt: /* build on 'mbld test' by default */
+ | `Gen gt: genfiles(b, gt)
+ | `Man m: /* nothing needed */
+ ;;
+ ;;
+ -> true
+}
+
+const genall = {b
+ for tn in b.all
+ match gettarg(b.targs, tn)
+ | `Gen gt: run(gt.cmd)
+ | _: /* 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 (`Test tt): buildbin(b, tt, false)
+ | `std.Some (`Gen gt): run(gt.cmd)
+ | `std.Some (`Man m): /* nothing needed */
+ | `std.None: std.fatal(1, "invalid target %s\n", targ)
+ ;;
+ -> true
+}
+
+const buildbin = {b, targ, addsrc
+ var dg, src
+
+ if targ.built
+ ->
+ ;;
+ setdir(b, targ.dir)
+ addincludes(b, targ)
+ buildlibdeps(b, targ)
+ std.put("%s...\n", targ.name)
+ if !myrdeps(b, targ, false, false, addsrc, &dg)
+ std.fatal(1, "Could not load dependencies for %s\n", targ.name)
+ ;;
+ if !std.hthas(dg.deps, targ.name)
+ std.fatal(1, "no input files for %s\n", targ.name)
+ ;;
+ if builddep(b, &dg, targ.name, targ.incpath) || !freshlibs(targ, dg.libs)
+ src = std.htkeys(dg.sources)
+ linkbin(&dg, targ.name, src, targ.ldscript, targ.runtime, targ.incpath, targ.libdeps)
+ std.slfree(src)
+ ;;
+ targ.built = true
+}
+
+const buildlib = {b, targ
+ var archive
+ var u, l
+ var dg
+ var lib, src
+
+ if targ.built
+ ->
+ ;;
+ setdir(b, targ.dir)
+ addincludes(b, targ)
+ buildlibdeps(b, targ)
+ lib = targ.name
+ std.put("lib%s.a...\n", lib)
+ archive = std.fmt("lib%s.a", lib)
+ if !myrdeps(b, targ, true, false, false, &dg)
+ std.fatal(1, "Could not load dependencies for %s\n", lib)
+ ;;
+ if !std.hthas(dg.deps, lib)
+ std.fatal(1, "no target declared for %s\n", lib)
+ ;;
+ u = builddep(b, &dg, targ.name, targ.incpath)
+ l = builddep(b, &dg, archive, targ.incpath)
+ if u || l || !freshlibs(targ, 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)
+ targ.built = true
+}
+
+const genfiles = {b, gt
+ for f in gt.out
+ if !std.fexists(f)
+ run(gt.cmd)
+ break
+ ;;
+ ;;
+}
+
+const addincludes = {b, targ
+ for (inc, lib, subtarg) in targ.libdeps
+ if !hasinc(targ.incpath, inc)
+ targ.incpath = std.slpush(targ.incpath, inc)
+ ;;
+ ;;
+}
+
+const buildlibdeps = {b, targ
+ for (inc, lib, subtarg) in targ.libdeps
+ build(b, subtarg)
+ ;;
+}
+
+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
+ ;;
+ if !std.fexists(d)
+ match std.htget(b.gensrc, d)
+ | `std.Some gt: run(gt.cmd)
+ | `std.None: std.fatal(1, "no input file %s\n", d)
+ ;;
+ ;;
+ if !isfresh(d, out)
+ stale = true
+ ;;
+ ;;
+ | `std.None:
+ ;;
+
+ match std.htget(dg.input, out)
+ | `std.Some src:
+ if stale
+ compile(src, incs)
+ ;;
+ std.htput(dg.updated, out, true)
+ | `std.None:
+ ;;
+ -> stale
+}
+
+const compile = {src, incs
+ var o
+ var cmd
+
+ cmd = [][:]
+ if std.hassuffix(src, ".myr")
+ cmd = std.slpush(cmd, opt_mc)
+ for inc in incs
+ cmd = std.slpush(cmd, "-I")
+ cmd = std.slpush(cmd, inc)
+ ;;
+ if opt_genasm
+ cmd = std.slpush(cmd, "-S")
+ ;;
+ cmd = std.slpush(cmd, src)
+ run(cmd)
+ std.slfree(cmd)
+ elif std.hassuffix(src, ".s")
+ o = srcswapsuffix(src, config.Objsuffix)
+ for c in config.Ascmd
+ cmd = std.slpush(cmd, c)
+ ;;
+ cmd = std.slpush(cmd,"-o")
+ cmd = std.slpush(cmd, o)
+ cmd = std.slpush(cmd, src)
+ run(cmd)
+ std.slfree(o)
+ else
+ std.fatal(1, "Unknown file type for %s\n", src)
+ ;;
+}
+
+const linkbin = {dg, bin, srcfiles, ldscript, rt, incs, extralibs
+ var cmd
+
+ cmd = [][:]
+
+ /* ld -o bin */
+ for c in config.Linkcmd
+ cmd = std.slpush(cmd, std.sldup(c))
+ ;;
+ cmd = std.slpush(cmd, std.sldup(bin))
+
+ /* [-T script] */
+ if ldscript.len > 0
+ cmd = std.slpush(cmd, std.sldup("-T"))
+ cmd = std.slpush(cmd, std.sldup(ldscript))
+ ;;
+
+ if rt.len != 0
+ cmd = std.slpush(cmd, std.sldup(rt))
+ else
+ cmd = std.slpush(cmd, std.sldup(opt_runtime))
+ ;;
+
+ /* input.o list.o... */
+ for f in srcfiles
+ cmd = std.slpush(cmd, srcswapsuffix(f, config.Objsuffix))
+ ;;
+
+ /* -L path -l lib... */
+ cmd = addlibs(cmd, dg.libs, incs)
+ for (d, l, t) in extralibs
+ cmd = std.slpush(cmd, std.fmt("-l%s", l))
+ ;;
+
+
+ /* special for OSX: it warns if we don't add this */
+ if std.sleq(opt_sys, "osx")
+ cmd = std.slpush(cmd, std.sldup("-macosx_version_min"))
+ cmd = std.slpush(cmd, std.sldup("10.6"))
+ ;;
+
+ run(cmd)
+ strlistfree(cmd)
+}
+
+const archivelib = {dg, lib, files, incs
+ var cmd
+ var obj
+
+ cmd = [][:]
+ for c in config.Arcmd
+ cmd = std.slpush(cmd, std.sldup(c))
+ ;;
+ cmd = std.slpush(cmd, std.fmt("lib%s.a", lib))
+ for f in files
+ obj = srcswapsuffix(f, config.Objsuffix)
+ cmd = std.slpush(cmd, obj)
+ ;;
+ run(cmd)
+ strlistfree(cmd)
+}
+
+const mergeuse = {dg, lib, files, incs
+ var cmd
+
+ cmd = [][:]
+ cmd = std.slpush(cmd, std.sldup(opt_muse))
+ cmd = std.slpush(cmd, std.sldup("-o"))
+ cmd = std.slpush(cmd, std.sldup(lib))
+ for f in files
+ if std.hassuffix(f, ".myr")
+ cmd = std.slpush(cmd, srcswapsuffix(f, ".use"))
+ elif !std.hassuffix(f, ".s")
+ std.fatal(1, "unknown file type for %s\n", f)
+ ;;
+ ;;
+ 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
+ cmd = std.slpush(cmd, std.fmt("-L%s", inc))
+ ;;
+ cmd = std.slpush(cmd, std.fmt("-L%s%s", opt_instroot, "/lib/myr"))
+ ;;
+
+ 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(1, "cycle in library graph involving \"%s\"\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
+ if !config.Directlib
+ -> std.slput(cmd, head, std.fmt("-l%s", lib))
+ else
+ match findlib(lib, incs)
+ | `std.None:
+ std.fatal(1, "could not find library lib%s.a", 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%s.a", lib)
+ for i in incs
+ p = std.pathjoin([i, sl][:])
+ if std.fexists(p)
+ -> `std.Some p
+ ;;
+ std.slfree(p)
+ ;;
+ p = std.pathjoin([opt_instroot, "lib/myr", sl][:])
+ if std.fexists(p)
+ -> `std.Some p
+ ;;
+ std.slfree(p)
+ -> `std.None
+}
+
+const freshlibs = {targ, libgraph
+ var libs
+
+ libs = std.htkeys(libgraph)
+ for l in libs
+ match findlib(l, targ.incpath)
+ | `std.Some lib:
+ if !isfresh(lib, targ.name)
+ std.slfree(lib)
+ -> false
+ ;;
+ std.slfree(lib)
+ | `std.None:
+ std.fatal(1, "%s: could not find library lib%s.a", l)
+ ;;
+ ;;
+ std.slfree(libs)
+ -> 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.Some mt: srcmt = mt
+ | `std.None: std.fatal(1, "could not stat %s\n", src)
+ ;;
+ match std.fmtime(dst)
+ | `std.Some mt: dstmt = mt
+ | `std.None: -> false
+ ;;
+ -> srcmt <= dstmt
+}
+
diff --git a/mbld/clean.myr b/mbld/clean.myr
new file mode 100644
index 0000000..89e595b
--- /dev/null
+++ b/mbld/clean.myr
@@ -0,0 +1,89 @@
+use std
+
+use "config.use"
+use "deps.use"
+use "opts.use"
+use "parse.use"
+use "subdir.use"
+use "types.use"
+use "util.use"
+
+pkg bld =
+ const cleanall : (b : build# -> bool)
+ const clean : (b : build#, targ : byte[:] -> bool)
+;;
+
+const cleanall = {b
+ for tn in b.all
+ match gettarg(b.targs, tn)
+ | `Bin bt:
+ cleanup(b, bt, bt.inputs, true)
+ | `Lib lt:
+ cleanup(b, lt, lt.inputs, true)
+ | `Test tt:
+ cleanup(b, tt, tt.inputs, true)
+ | `Gen gt:
+ for f in gt.out
+ if !gt.durable && std.remove(f)
+ std.put("\tclean %s\n", f)
+ ;;
+ ;;
+ | `Man m:
+ ;;
+ ;;
+ -> true
+}
+
+const clean = {b, targ
+ for tn in b.all
+ match gettarg(b.targs, tn)
+ | `Bin bt:
+ if std.sleq(bt.name, targ)
+ cleanup(b, bt, bt.inputs, true)
+ ;;
+ | `Lib lt:
+ if std.sleq(lt.name, targ)
+ cleanup(b, lt, lt.inputs, true)
+ ;;
+ | `Test tt:
+ if std.sleq(tt.name, targ)
+ cleanup(b, tt, tt.inputs, true)
+ ;;
+ | `Gen gt:
+ for f in gt.out
+ if !gt.durable && std.remove(f)
+ std.put("\tclean %s\n", f)
+ ;;
+ ;;
+ | `Man m:
+ ;;
+ ;;
+ -> true
+}
+
+const cleanup = {b, targ, leaves, islib
+ var mchammer_files /* cant touch this */
+ var keys
+ var dg
+
+ /*
+ we want to automatically add 'clean' sources since otherwise,
+ mbld won't be able to clean code after changing a build file.
+ */
+ setdir(b, targ.dir)
+ if !myrdeps(b, targ, islib, true, true, &dg)
+ std.fatal(1, "Could not load dependencies for %s\n", targ.name)
+ ;;
+ mchammer_files = std.mkht(std.strhash, std.streq)
+ for l in leaves
+ std.htput(mchammer_files, l, true)
+ ;;
+
+ keys = std.htkeys(dg.deps)
+ for k in keys
+ if !std.htgetv(mchammer_files, k, false) && std.remove(k)
+ std.put("\tclean %s\n", k)
+ ;;
+ ;;
+}
+
diff --git a/mbld/config+plan9-x64.myr b/mbld/config+plan9-x64.myr
new file mode 100644
index 0000000..75b5081
--- /dev/null
+++ b/mbld/config+plan9-x64.myr
@@ -0,0 +1,11 @@
+pkg config =
+ const Instroot = "/amd64"
+ const Sys = "Plan9"
+ const Objsuffix = ".6"
+ const Linkcmd = ["6l", "-lo"]
+ const Arcmd = ["ar", "vu"]
+ const Ascmd = ["6a"]
+ const Directlib = true
+ const Runtime = "_myrrt.6"
+ const Manpath = "man/"
+;;
diff --git a/mbld/configure b/mbld/configure
new file mode 100755
index 0000000..d7b2227
--- /dev/null
+++ b/mbld/configure
@@ -0,0 +1,65 @@
+#!/bin/sh
+
+prefix="/usr/local"
+
+for i in `seq 300`; do
+ echo "Lots of output to emulate automake... ok"
+ echo "Testing for things you'll never use... fail"
+ echo "Satisfying the fortran77 lobby... ok"
+ echo "Burning CPU time checking for the bloody obvious... ok"
+done
+echo "Automake emulated successfully"
+
+INST_ROOT='/usr/local'
+
+for arg in $*; do
+ shift 1
+ case $arg in
+ "--prefix" | "-p")
+ prefix=shift $*
+ ;;
+ --prefix=*)
+ prefix=`echo $arg | sed 's/^--prefix=//g'`
+ ;;
+ "--help" | "-h")
+ echo "Usage:"
+ echo " --prefix | -p: The prefix to install to"
+ break;
+ ;;
+ *) echo "Unrecognized argument $arg";;
+ esac
+done
+
+OS=`uname`
+
+echo export INST_ROOT=$prefix > config.mk
+
+echo "pkg config = " > config.myr
+echo "const Instroot = \"$prefix\"" >> config.myr
+echo "const Objsuffix = \".o\"" >> config.myr
+echo "const Linkcmd = [\"ld\", \"-o\"]" >> config.myr
+echo "const Arcmd = [\"ar\", \"-rcs\"]" >> config.myr
+echo "const Ascmd = [\"as\", \"-g\"]" >> config.myr
+echo "const Directlib = false" >> config.myr
+echo "const Runtime = \"_myrrt.o\"" >> config.myr
+echo "const Manpath = \"share/man/man\"" >> config.myr
+case $OS in
+ *Linux*)
+ echo 'export SYS=linux' >> config.mk
+ echo 'const Sys = "Linux"' >> config.myr
+ ;;
+ *Darwin*)
+ echo 'export SYS=osx' >> config.mk
+ echo 'const Sys = "OSX"' >> config.myr
+ ;;
+ *)
+ echo 'Unknown architecture.'
+ ;;
+esac
+echo ";;" >> config.myr
+
+cat << EOF
+ Building with:
+ prefix=$prefix
+EOF
+
diff --git a/mbld/deps.myr b/mbld/deps.myr
new file mode 100644
index 0000000..b46a517
--- /dev/null
+++ b/mbld/deps.myr
@@ -0,0 +1,308 @@
+use std
+use regex
+use bio
+
+use "config.use"
+use "opts.use"
+use "types.use"
+use "util.use"
+
+pkg bld =
+ const myrdeps : (b : build#, mt : myrtarg#, islib : bool, isclean : bool, addsrc : bool, dg : depgraph# -> bool)
+
+ /* a bit ugly: initialized from main() */
+ var usepat : regex.regex#
+;;
+
+var usepat : regex.regex#
+
+type dep = union
+ `Local byte[:]
+ `Lib byte[:]
+;;
+
+const myrdeps = {b, mt, islib, isclean, addsrc, dg
+ var objs, uses, srcs, incs
+ var out, useout, depstk
+ var i
+
+ dg.deps = std.mkht(std.strhash, std.streq)
+ dg.libs = std.mkht(std.strhash, std.streq)
+ dg.input = std.mkht(std.strhash, std.streq)
+ dg.sources = std.mkht(std.strhash, std.streq)
+ dg.updated = std.mkht(std.strhash, std.streq)
+ dg.seen = std.mkht(std.strhash, std.streq)
+ dg.done = std.mkht(std.strhash, std.streq)
+
+ /* direct dependencies of binary */
+ if islib
+ out = std.fmt("lib%s.a", mt.name)
+ useout = std.sldup(mt.name)
+ else
+ out = std.sldup(mt.name)
+ useout = ""
+ ;;
+
+ srcs = mt.inputs
+ incs = mt.incpath
+ objs = swapall(srcs, config.Objsuffix)
+ uses = swapall(srcs, ".use")
+ for i = 0; i < srcs.len; i++
+ std.htput(dg.input, objs[i], srcs[i])
+ std.htput(dg.sources, srcs[i], true)
+ pushdep(dg, srcs[i], objs[i])
+ if std.hassuffix(srcs[i], ".myr")
+ std.htput(dg.input, uses[i], srcs[i])
+ pushdep(dg, srcs[i], uses[i])
+ ;;
+ ;;
+
+ for i = 0; i < srcs.len; i++
+ pushdep(dg, objs[i], out)
+ if islib && std.hassuffix(srcs[i], ".myr")
+ pushdep(dg, uses[i], useout)
+ ;;
+ ;;
+
+ for i = 0; i < srcs.len; i++
+ depstk = [][:]
+ srcdeps(b, dg, srcs[i], objs[i], uses[i], incs, &depstk, isclean, addsrc)
+ std.slfree(depstk)
+ ;;
+ dumpgraph(dg)
+ -> true
+}
+
+const swapall = {srcs, suff
+ var sl
+
+ sl = [][:]
+ for s in srcs
+ sl = std.slpush(sl, srcswapsuffix(s, suff))
+ ;;
+ -> sl
+}
+
+const dumpgraph = {dg
+ var keys
+
+ if !opt_debug
+ ->
+ ;;
+ keys = std.htkeys(dg.deps)
+ std.put("digraph dg {\n")
+ for k in keys
+ for v in std.htgetv(dg.deps, k, ["WTFUNKNOWN!"][:])
+ std.put("\t\"%s\" -> \"%s\";\n", k, v)
+ ;;
+ ;;
+ std.put("}\n")
+}
+
+const srcdeps = {b, g, path, obj, usefile, incs, depstk, isclean, addsrc
+ var deps
+
+ if std.hthas(g.done, path)
+ ->
+ ;;
+
+ depstk# = std.slpush(depstk#, path)
+ if std.htgetv(g.seen, path, false)
+ std.fput(1, "dependency loop involving %s:\n", path)
+ for d in depstk#
+ std.fput(1, "\t%s\n", d)
+ ;;
+ std.exit(1)
+ ;;
+ deps = getdeps(b, path)
+ std.htput(g.seen, path, true)
+ for d in deps
+ match d
+ | `Lib lib:
+ /*
+ If we're cleaning, we don't care about libraries; at best, this does nothing. At
+ worst, this will cause failure if the library is a local library that gets cleand.
+ */
+ if !isclean
+ scrapelibs(g, lib, incs)
+ ;;
+ | `Local l:
+ if !std.hassuffix(l, ".use")
+ std.fatal(1, "local dependency \"%s\" of \"%s\" should end with .use\n", l, path)
+ ;;
+ if obj.len != 0
+ pushdep(g, l, obj)
+ ;;
+ if usefile.len != 0
+ pushdep(g, l, usefile)
+ ;;
+ addusedep(b, g, path, l, incs, depstk, isclean, addsrc)
+ ;;
+ ;;
+ depstk# = std.slgrow(depstk#, depstk#.len - 1)
+ std.htput(g.seen, path, false)
+ std.htput(g.done, path, true)
+}
+
+const addusedep = {b, g, f, usefile, incs, depstk, isclean, addsrc
+ var src
+
+ if std.hthas(g.done, usefile)
+ if opt_debug
+ std.put("already loaded deps for %s\n", usefile)
+ ;;
+ ->
+ ;;
+ match std.htget(g.input, usefile)
+ | `std.Some path:
+ src = std.sldup(path)
+ | `std.None:
+ src = swapsuffix(usefile, ".use", ".myr")
+ if addsrc
+ std.htput(g.sources, src, true)
+ elif !std.hthas(g.input, usefile)
+ std.fatal(1, "%s: source file %s not listed in bldfile\n", f, src)
+ ;;
+ ;;
+ pushdep(g, src, usefile)
+ std.htput(g.input, usefile, src)
+ srcdeps(b, g, src, "", usefile, incs, depstk, isclean, addsrc)
+ std.htput(g.done, usefile, true)
+}
+
+const getdeps = {b, path
+ var f
+ var deps : dep[:]
+
+ deps = [][:]
+ if !std.fexists(path)
+ match std.htget(b.gensrc, path)
+ | `std.Some gt: run(gt.cmd)
+ | `std.None: std.fatal(1, "no input file %s\n", path)
+ ;;
+ ;;
+ match bio.open(path, bio.Rd)
+ | `std.Some fd: f = fd
+ | `std.None: std.fatal(1, "could not open %s\n", path)
+ ;;
+
+ while true
+ match bio.readln(f)
+ | `std.Some ln:
+ deps = depname(deps, ln)
+ std.slfree(ln)
+ | `std.None:
+ bio.close(f)
+ -> deps
+ ;;
+ ;;
+}
+
+const scrapelibs = {dg, lib, incs
+ var deps, d
+ var f
+ var done
+
+ if std.hthas(dg.libs, lib)
+ ->
+ ;;
+
+ deps = [][:]
+ f = openlib(lib, incs)
+ match bio.getc(f)
+ | `std.Some 'U': /* nothing */
+ | `std.Some _: std.fatal(1, "library %s: corrupt or invalid usefile\n", lib)
+ | `std.None: std.fatal(1, "library %s: could not read usefile\n", lib)
+ ;;
+ match bio.getbe32(f)
+ | `std.Some 1: /* nothing: version matches. */
+ | `std.Some 0: std.fput(1, "library %s: warning: old usefile version\n", lib)
+ | `std.Some _: std.fatal(1, "library %s: usefile version unknown\n", lib)
+ | `std.None: std.fatal(1, "library %s: corrutpt or invalid usefile\n", lib)
+ ;;
+ std.slfree(rdstr(f))
+ done = false
+ while !done
+ match bio.getc(f)
+ | `std.Some 'L':
+ d = rdstr(f)
+ deps = std.slpush(deps, d)
+ | `std.Some _: done = true
+ | `std.None: done = true
+ ;;
+ ;;
+ bio.close(f)
+ std.htput(dg.libs, lib, deps)
+ for dep in deps
+ scrapelibs(dg, dep, incs)
+ ;;
+}
+
+const openlib = {lib, incs
+ var path
+
+ for p in incs
+ path = std.pathjoin([p, lib][:])
+ match bio.open(path, bio.Rd)
+ | `std.Some file:
+ -> file
+ | `std.None:
+ /* nothing */
+ ;;
+ ;;
+ path = std.pathjoin([opt_instroot, "/lib/myr", lib][:])
+ match bio.open(path, bio.Rd)
+ | `std.Some file:
+ -> file
+ | `std.None:
+ /* nothing */
+ ;;
+ std.fatal(1, "could not find library %s.\n", lib)
+}
+
+const depname = {deps, ln
+ /*
+ the regex pattern does some contortions to either grab
+ an unquoted path and put it into uses[4], or a quoted
+ path, and put it (minus the quotes) into uses[2]
+ */
+ match regex.exec(usepat, ln)
+ | `std.Some uses:
+ if uses[2].len > 0
+ deps = std.slpush(deps, `Local std.sldup(uses[2]))
+ else
+ deps = std.slpush(deps, `Lib std.sldup(uses[4]))
+ ;;
+ | `std.None:
+ /* nothing to do */
+ ;;
+ -> deps
+}
+
+
+/* pushes a dep into the dependency list */
+const pushdep = {dg, src, dst
+ var sl
+
+ if opt_debug
+ std.put("%s <= %s\n", dst, src)
+ ;;
+ std.assert(dst.len < 200, "BUG!")
+ sl = std.htgetv(dg.deps, dst, [][:])
+ sl = std.slpush(sl, src)
+ std.htput(dg.deps, dst, sl)
+}
+
+const rdstr = {f
+ var len
+ var sl
+
+ match bio.getbe32(f)
+ | `std.Some l:
+ len = l
+ sl = std.slalloc(len)
+ | `std.None: std.die("string length not readable")
+ ;;
+ bio.read(f, sl)
+ -> sl
+}
diff --git a/mbld/fsel.myr b/mbld/fsel.myr
new file mode 100644
index 0000000..ea45b4f
--- /dev/null
+++ b/mbld/fsel.myr
@@ -0,0 +1,105 @@
+use std
+
+use "opts.use"
+
+pkg bld =
+ type fsel = struct
+ filematch : std.htab(byte[:], int)#
+ filebest : std.htab(byte[:], byte[:])#
+ sysattrs : std.htab(byte[:], bool)#
+ ;;
+
+ const mkfsel : (-> fsel#)
+ const fseladd : (fsel : fsel#, file : byte[:] -> void)
+ const fselfin : (fsel : fsel# -> byte[:][:])
+;;
+
+const mkfsel = {
+ var fsel
+
+ fsel = std.alloc()
+ fsel.filematch = std.mkht(std.strhash, std.streq)
+ fsel.filebest = std.mkht(std.strhash, std.streq)
+ fsel.sysattrs = std.mkht(std.strhash, std.streq)
+ addsysattrs(fsel.sysattrs)
+ -> fsel
+}
+
+const fseladd = {fsel, f
+ var basename, attrs
+ var nmatch, curbest
+ var attrlist
+
+ match std.strfind(f, "+")
+ | `std.Some i:
+ basename = f[:i]
+ match std.strrfind(f[i+1:], ".")
+ | `std.Some j: attrs = f[i+1:][:j]
+ | `std.None: std.fatal(1, "unrecognized type for file %s\n", f)
+ ;;
+ | `std.None:
+ match std.strrfind(f, ".")
+ | `std.None: std.fatal(1, "unrecognized type for file %s\n", f)
+ | `std.Some i:
+ basename = f[:i]
+ attrs = ""
+ ;;
+ ;;
+
+ nmatch = 0
+ attrlist = std.strsplit(attrs, "-")
+ for a in attrlist
+ if std.hthas(fsel.sysattrs, a)
+ nmatch++
+ else
+ nmatch = -1
+ break
+ ;;
+ ;;
+ std.slfree(attrlist)
+ curbest = std.htgetv(fsel.filematch, basename, -1)
+ if curbest < nmatch
+ std.htput(fsel.filematch, basename, nmatch)
+ std.htput(fsel.filebest, basename, f)
+ ;;
+}
+
+const fselfin = {fsel
+ var keys, nmatch, ret
+
+ keys = std.htkeys(fsel.filematch)
+ ret = [][:]
+ for k in keys
+ nmatch = std.htgetv(fsel.filematch, k, -1)
+ if nmatch == -1
+ std.fatal(1, "no applicable file for '%s'\n", k)
+ ;;
+ ret = std.slpush(ret, std.htgetv(fsel.filebest, k, ""))
+ ;;
+ std.htfree(fsel.filematch)
+ std.htfree(fsel.filebest)
+ std.htfree(fsel.sysattrs)
+ -> ret
+}
+
+const addsysattrs = {sa
+ var attrs
+ match opt_sys
+ | "freebsd": attrs = ["freebsd", "posixy"][:]
+ | "osx": attrs = ["osx", "posixy"][:]
+ | "linux": attrs = ["linux", "posixy"][:]
+ | "plan9": attrs = ["plan9"][:]
+ | unknown: std.fatal(1, "unknown system \"%s\"\n", unknown)
+ ;;
+ for a in attrs
+ std.htput(sa, a, true)
+ ;;
+
+ match opt_arch
+ | "x64": attrs = ["x64"][:]
+ | unknown: std.fatal(1, "unknown arch %s\n", unknown)
+ ;;
+ for a in attrs
+ std.htput(sa, a, true)
+ ;;
+}
diff --git a/mbld/install.myr b/mbld/install.myr
new file mode 100644
index 0000000..a895166
--- /dev/null
+++ b/mbld/install.myr
@@ -0,0 +1,92 @@
+use std
+
+use "config.use"
+use "deps.use"
+use "opts.use"
+use "parse.use"
+use "subdir.use"
+use "types.use"
+use "util.use"
+use "build.use"
+
+pkg bld =
+ const install : (p : build# -> bool)
+ const uninstall : (p : build# -> bool)
+;;
+
+const install = {b
+ buildall(b)
+ -> movetargs(b, false)
+}
+
+const uninstall = {b
+ -> movetargs(b, true)
+}
+
+const movetargs = {b, delete
+ var libarchive
+
+ for tn in b.all
+ match gettarg(b.targs, tn)
+ | `Bin bt:
+ movefile(delete, bt.name, opt_instroot, opt_destdir, "bin", 0o755)
+ | `Lib lt:
+ movefile(delete, lt.name, opt_instroot, opt_destdir, "lib/myr", 0o644)
+ libarchive = std.fmt("lib%s.a", lt.name)
+ movefile(delete, libarchive, opt_instroot, opt_destdir, "lib/myr", 0o644)
+ std.slfree(libarchive)
+ | `Gen gt:
+ /* nothing to do (?) */
+ | `Man mans:
+ /* FIXME: figure out man section by number */
+ for m in mans
+ moveman(delete, m)
+ ;;
+ | `Test tt: /* nothing */
+ ;;
+ ;;
+ -> true
+}
+
+
+const movefile = {delete, file, instdir, destdir, prefix, perm
+ var path
+
+ path = std.pathjoin([destdir, instdir, prefix, file][:])
+ if delete
+ std.put("\tdelete %s\n", path)
+ if !std.remove(path)
+ std.put("\t\tno such file %s\n", file)
+ ;;
+ else
+ std.put("\t%s => %s\n", file, path)
+ std.remove(path)
+ match std.slurp(file)
+ | `std.Fail m: std.fatal(1, "Could not open %s for reading\n", file)
+ | `std.Ok buf:
+ if !std.blat(path, buf, perm)
+ std.put("Could not write %s\n", file)
+ ;;
+ std.slfree(buf)
+ ;;
+ ;;
+ std.slfree(path)
+}
+
+const moveman = {delete, man
+ var sect, manrel
+
+ match std.strrfind(man, ".")
+ | `std.None:
+ std.fatal(1, "manpage %s has no section\n", man)
+ | `std.Some s:
+ sect = s + 1
+ if s + 1 == man.len
+ std.fatal(1, "manpage %s missing suffix\n", man)
+ ;;
+ ;;
+
+ manrel = std.fmt("%s%s", opt_manpath, man[sect:])
+ movefile(delete, man, opt_instroot, opt_destdir, manrel, 0o644)
+ std.slfree(manrel)
+}
diff --git a/mbld/main.myr b/mbld/main.myr
new file mode 100644
index 0000000..caab00a
--- /dev/null
+++ b/mbld/main.myr
@@ -0,0 +1,115 @@
+use std
+use regex
+
+use "build.use"
+use "clean.use"
+use "config.use"
+use "deps.use"
+use "install.use"
+use "opts.use"
+use "parse.use"
+use "test.use"
+use "types.use"
+
+const main = {args : byte[:][:]
+ var b : bld.build#
+ var mt : bld.myrtarg
+ var targname
+ var bintarg
+ var optctx
+
+ optctx = std.optinit("hb:l:s:Sr:I:C:A:M:L:R:d", args)
+ bld.initopts()
+ while !std.optdone(optctx)
+ match std.optnext(optctx)
+ | ('h', arg): usage(args[0])
+ | ('s', arg): bld.opt_ldscript = arg
+ | ('f', arg): bld.opt_bldfile = arg
+ | ('I', arg): bld.opt_incpaths = std.slpush(bld.opt_incpaths, arg)
+ | ('S', _): bld.opt_genasm = true
+ | ('R', arg): bld.opt_instroot = arg
+ | ('b', arg):
+ targname = arg
+ bintarg = true
+ | ('l', arg):
+ targname = arg
+ bintarg = false
+ | ('r', arg):
+ if std.sleq(arg, "none")
+ bld.opt_runtime = ""
+ else
+ bld.opt_runtime = arg
+ ;;
+ /*
+ internal undocumented args; used by compiler suite for
+ building with an uninstalled compiler.
+ */
+ | ('d', arg): bld.opt_debug = true
+ | ('C', arg): bld.opt_mc = arg
+ | ('M', arg): bld.opt_muse = arg
+ | _: std.die("got invalid arg\n")
+ ;;
+ ;;
+
+ match regex.compile("^\\s*use\\s+((\\<\\S+\\>)|(\"(\\S+)\")).*")
+ | `std.Ok re: bld.usepat = re
+ | `std.Fail f: std.fatal(1, "Failed to compile use pattern regex\n")
+ ;;
+
+ b = mkbuild()
+ if targname.len != 0
+ mt = [
+ .name=targname,
+ .inputs=optctx.args,
+ .runtime=bld.opt_runtime,
+ .incpath=bld.opt_incpaths,
+ .ldscript=bld.opt_ldscript,
+ .libdeps=[][:]
+ ]
+ if bintarg
+ bld.buildbin(b, &mt, true)
+ else
+ bld.buildlib(b, &mt)
+ ;;
+ else
+ bld.load(b, bld.opt_bldfile)
+ /*bld.configure()*/
+ /* default: buildall */
+ if optctx.args.len == 0
+ bld.buildall(b)
+ else
+ for cmd in optctx.args
+ match cmd
+ | "all": bld.buildall(b)
+ | "gen": bld.genall(b)
+ | "clean": bld.cleanall(b)
+ | "install": bld.install(b)
+ | "uninstall": bld.uninstall(b)
+ | "test": bld.test(b)
+ | target: bld.build(b, target)
+ ;;
+ ;;
+ ;;
+ ;;
+}
+
+const mkbuild = {
+ var b
+
+ b = std.zalloc()
+ b.targs = std.mkht(std.strhash, std.streq)
+ b.gensrc = std.mkht(std.strhash, std.streq)
+ b.basedir = std.getcwd()
+ b.curdir = ""
+ -> b
+}
+
+const usage = {prog
+ std.put("%s [-h] [-I path] [-l lib] [-b bin] inputs...\n", prog)
+ std.put("\t-h\tprint this help\n")
+ std.put("\t-b bin\tBuild a binary called 'bin'\n")
+ std.put("\t-l lib\tBuild a library called 'name'\n")
+ std.put("\t-s script\tUse the linker script 'script' when linking\n")
+ std.put("\t-I path\tAdd 'path' to use search path\n")
+ std.exit(0)
+}
diff --git a/mbld/mbld.1 b/mbld/mbld.1
new file mode 100644
index 0000000..52066b3
--- /dev/null
+++ b/mbld/mbld.1
@@ -0,0 +1,81 @@
+.TH MBLD 1
+.SH NAME
+mbld
+.SH SYNOPSIS
+.B mbld
+.I [all | clean | install | uninstall | test]
+.I -[hblIsfrR]
+.I [file...]
+.br
+.SH DESCRIPTION
+.PP
+The 'mbld' tool takes as input a list of Myrddin or assembly sources,
+and compiles them in the correct dependency order into either a library or
+an executable.
+
+.PP
+By default, it reads from an input file called 'bldfile', but if given the
+option '-b' or '-l', it will build a binary or library, respectively, from
+the arguments specified on the command lines.
+
+.PP
+Myrbuild will default to building for the current architecture.
+
+.PP
+The myrbuild options are:
+
+.TP
+.B -h
+Print a summary of the available options.
+
+.TP
+.B -b name
+Compile source into a binary named 'name'. If neither this option nor
+the '-l' option are given, myrbuild will create a binary called 'a.out'.
+
+.TP
+.B -l 'name'
+Compile source given into a library called 'lib<name>.a', and a matching
+usefile called 'name'. Only static libraries are currently supported.
+
+.TP
+.B -s 'script'
+Pass the linker script 'script' to the linker. If this option is not
+provided, no script is passed to the linker.
+
+.TP
+.B -r 'rt'
+Compile a binary using the runtime 'rt'. If the runtime name given
+is 'none', then no runtime will be linked. If this option is not provided,
+then the default runtime in '$INSTALL_ROOT/myr/lib/_myrrt.o' will be
+used.
+
+.TP
+.B -I path
+Add 'path' to the search path for unquoted use statments. This option
+does not affect the search path for local usefiles, which are always
+searched relative to the compiler's current working directory. Without
+any options, the search path defaults to /usr/include/myr.
+
+.SH EXAMPLE
+.EX
+ mbld
+ mbld -l foo bar.myr baz.myr
+.EE
+
+.SH FILES
+The source for muse is available from
+.B git://git.eigenstate.org/git/ori/mc.git
+and lives in the
+.I myrbuild/
+directory within the source tree.
+
+.SH SEE ALSO
+.IR mc(1)
+.IR muse(1)
+.IR ld(1)
+.IR as(1)
+
+.SH BUGS
+.PP
+None known.
diff --git a/mbld/mk/myr.mk b/mbld/mk/myr.mk
new file mode 100644
index 0000000..68e4c41
--- /dev/null
+++ b/mbld/mk/myr.mk
@@ -0,0 +1,42 @@
+ifneq ($(MYRLIB),)
+ _LIBNAME=lib$(MYRLIB).a
+endif
+
+all: $(_LIBNAME) $(MYRBIN)
+
+$(_LIBNAME): $(MYRSRC) $(ASMSRC)
+ myrbuild -l $(MYRLIB) $^
+
+$(MYRBIN): $(MYRSRC) $(ASMSRC)
+ myrbuild -b $(MYRBIN) $^
+
+OBJ=$(MYRSRC:.myr=.o) $(ASMSRC:.s=.o)
+JUNKASM=$(MYRSRC:.myr=.s)
+USE=$(MYRSRC:.myr=.use) $(MYRLIB)
+.PHONY: clean install install-bin install-lib
+clean:
+ rm -f $(OBJ)
+ rm -f $(USE)
+ rm -f $(JUNKASM) $(CLEANEXTRA)
+ rm -f $(_LIBNAME) $(MYRBIN)
+
+install: install-bin install-lib
+
+install-bin: $(MYRBIN)
+ @if [ ! -z "$(MYRBIN)" ]; then \
+ echo install $(MYRBIN) $(INST_ROOT)/bin; \
+ mkdir -p $(INST_ROOT)/bin; \
+ install $(MYRBIN) $(INST_ROOT)/bin; \
+ fi
+
+install-lib: $(_LIBNAME)
+ @if [ ! -z "$(_LIBNAME)" ]; then \
+ echo install -m 644 $(_LIBNAME) $(INST_ROOT)/lib/myr; \
+ echo install -m 644 $(MYRLIB) $(INST_ROOT)/lib/myr; \
+ mkdir -p $(INST_ROOT)/lib/myr; \
+ install -m 644 $(_LIBNAME) $(INST_ROOT)/lib/myr; \
+ install -m 644 $(MYRLIB) $(INST_ROOT)/lib/myr; \
+ fi
+
+config.mk:
+ ./configure
diff --git a/mbld/opts.myr b/mbld/opts.myr
new file mode 100644
index 0000000..1adfab9
--- /dev/null
+++ b/mbld/opts.myr
@@ -0,0 +1,85 @@
+use std
+
+use "config.use"
+
+pkg bld =
+ var opt_arch : byte[:]
+ var opt_sys : byte[:]
+ var opt_runtime : byte[:]
+ var opt_genasm : bool
+ var opt_ldscript : byte[:]
+ var opt_incpaths : byte[:][:]
+ var opt_instroot : byte[:]
+ var opt_manpath : byte[:]
+ var opt_destdir : byte[:]
+ var opt_outdir : byte[:]
+ var opt_bldfile : byte[:]
+ var opt_debug : bool
+
+ /* undocumented/unsupported opts */
+ var opt_mc : byte[:]
+ var opt_muse : byte[:]
+
+ var sysarchstr : byte[:]
+ var archstr : byte[:]
+ var sysstr : byte[:]
+
+ const initopts : (-> void)
+;;
+
+var opt_arch = ""
+var opt_sys = ""
+var opt_binname = ""
+var opt_libname = ""
+var opt_runtime = ""
+var opt_ldscript = ""
+var opt_incpaths /* FIXME: taking a constant slice is a nonconstant initializer */
+var opt_instroot = ""
+var opt_manpath = ""
+var opt_destdir = ""
+var opt_debug = false
+var opt_bldfile = "bldfile"
+var opt_mc = "6m"
+var opt_as = "as"
+var opt_muse = "muse"
+var opt_ld = "ld"
+var opt_ar = "ar"
+var opt_genasm = false
+
+/* derived */
+var sysarchstr = ""
+var archstr = ""
+var sysstr = ""
+
+const initopts = {
+ var si
+
+ std.getsysinfo(&si)
+ match si.system
+ | "Linux": opt_sys = "linux"
+ | "Darwin": opt_sys = "osx"
+ | "FreeBSD": opt_sys = "freebsd"
+ | "Plan9": opt_sys = "plan9"
+ | unknown: std.fatal(1, "unknown system \"%s\"\n", unknown)
+ ;;
+
+ match si.arch
+ | "x86_64": opt_arch = "x64"
+ | "amd64": opt_arch = "x64"
+ | unknown: std.fatal(1, "unknown architecture \"%s\"\n", unknown)
+ ;;
+
+ opt_incpaths = [][:]
+ opt_instroot = config.Instroot
+ opt_manpath = config.Manpath
+ opt_destdir = std.getenvv("DESTDIR", "")
+ opt_mc = std.getenvv("MYR_MC", "6m")
+ opt_muse = std.getenvv("MYR_MUSE", "muse")
+
+ sysarchstr = std.fmt("+%s-%s", opt_sys, opt_arch)
+ sysstr = std.fmt("+%s", opt_sys)
+ archstr = std.fmt("+%s", opt_arch)
+
+ opt_runtime = std.pathjoin([opt_instroot, "lib/myr", config.Runtime][:])
+}
+
diff --git a/mbld/parse.myr b/mbld/parse.myr
new file mode 100644
index 0000000..75efe3d
--- /dev/null
+++ b/mbld/parse.myr
@@ -0,0 +1,539 @@
+use std
+
+use "types.use"
+use "util.use"
+use "opts.use"
+use "fsel.use"
+
+pkg bld =
+ const load : (b : build#, path : byte[:] -> bool)
+;;
+
+type parser = struct
+ /* parse input */
+ data : byte[:]
+ rest : byte[:]
+ fname : byte[:]
+ fdir : byte[:] /* directory relative to base */
+ basedir : byte[:]
+ line : int
+
+ /* extracted data for further parsing */
+ subdirs : byte[:][:]
+;;
+
+const load = {b, path
+ -> loadall(b, path, "")
+}
+
+
+const loadall = {b, path, dir
+ var p : parser#
+ var subpath, subbld, ok
+
+ p = mkparser(path, dir, b.basedir)
+ ok = bld.parse(b, p, "")
+ for s in p.subdirs
+ subpath = std.pathcat(p.fdir, s)
+ subbld = std.pathcat(subpath, "bldfile")
+ loadall(b, subbld, subpath)
+ std.slfree(subpath)
+ std.slfree(subbld)
+ ;;
+ freeparser(p)
+ -> ok
+}
+
+const mkparser = {path, dir, basedir
+ var p
+
+ p = std.zalloc()
+ p.line = 1
+ p.fname = std.sldup(path)
+ p.fdir = std.sldup(dir)
+ p.basedir = std.sldup(basedir)
+ match std.slurp(path)
+ | `std.Ok d: p.data = d
+ | `std.Fail _: std.fatal(1, "could not open '%s'\n", path)
+ ;;
+ p.rest = p.data
+ -> p
+}
+
+const freeparser = {p
+ std.slfree(p.fname)
+ std.slfree(p.fdir)
+ std.slfree(p.basedir)
+ std.slfree(p.subdirs)
+ std.slfree(p.data)
+ std.free(p)
+}
+
+const failparse = {p, msg, args : ...
+ var buf : byte[1024]
+ var ap
+ var sl
+
+ ap = std.vastart(&args)
+ sl = std.bfmtv(buf[:], msg, ap)
+ std.fput(1, "%s:%i: %s", p.fname, p.line, sl)
+ std.exit(1)
+}
+
+const parse = {b, p, path
+ while true
+ skipspace(p)
+ if !target(b, p)
+ break
+ ;;
+ ;;
+ skipspace(p)
+ if p.rest.len > 0
+ failparse(p, "junk in file near %s\n", p.rest[:std.min(p.rest.len, 10)])
+ -> false
+ ;;
+ -> true
+}
+
+const target = {b, p
+ match word(p)
+ | `std.Some "bin": bintarget(b, p)
+ | `std.Some "test": testtarget(b, p)
+ | `std.Some "lib": libtarget(b, p)
+ | `std.Some "gen": gentarget(b, p)
+ | `std.Some "man": mantarget(b, p)
+ | `std.Some "sub": subtarget(b, p)
+ | `std.Some targtype: failparse(p, "unknown targtype type %s\n", targtype)
+ | `std.None: -> false
+ ;;
+ -> true
+}
+
+/* bintarget: myrtarget */
+const bintarget = {b, p
+ var t
+ t = myrtarget(p, "bin")
+ addtarg(p, b, t.name, `Bin t)
+}
+
+/* libtarget: myrtarget */
+const libtarget = {b, p
+ var t
+ t = myrtarget(p, "lib")
+ addtarg(p, b, t.name, `Lib t)
+}
+
+/* testtarget: myrtarget */
+const testtarget = {b, p
+ var t
+ t = myrtarget(p, "test")
+ addtarg(p, b, t.name, `Test myrtarget(p, "test"))
+}
+
+/* mantarget: anontarget */
+const mantarget = {b, p
+ addtarg(p, b, "__man__", `Man anontarget(p, "man"))
+}
+
+/* subtarget : anontarget */
+const subtarget = {b, p
+ var subs
+
+ subs = anontarget(p, "sub")
+ for s in subs
+ p.subdirs = std.slpush(p.subdirs, std.pathcat(p.fdir, s))
+ ;;
+}
+
+
+/* gentarget: wordlist {attrs} = wordlist ;; */
+const gentarget = {b, p
+ var outlist, cmdlist
+ var durable
+ var attrs
+ var gt
+
+ match wordlist(p)
+ | `std.None: failparse(p, "gen target missing output files\n")
+ | `std.Some out:
+ outlist = out
+ ;;
+
+ skipspace(p)
+ if matchc(p, '{')
+ match attrlist(p)
+ | `std.Some al: attrs = al
+ | `std.None: failparse(p, "invalid attr list for %s\n", outlist[0])
+ ;;
+ else
+ attrs = [][:]
+ ;;
+
+ skipspace(p)
+ if !matchc(p, '=')
+ failparse(p, "expected '=' after '%s %s'\n", cmdlist, outlist[outlist.len-1])
+ ;;
+
+ match wordlist(p)
+ | `std.None: failparse(p, "gen target missing command\n")
+ | `std.Some cmd:
+ cmdlist = cmd
+ ;;
+
+ if !matchc(p, ';') || !matchc(p, ';')
+ failparse(p, "expected ';;' terminating genfile command, got %c\n", peekc(p))
+ ;;
+
+ durable = false
+ for elt in attrs
+ match elt
+ | ("durable", ""): durable = true
+ | ("durable", val): failparse(p, "durable attr does not take argument\n")
+ ;;
+ ;;
+
+ gt = std.mk([
+ .dir=std.sldup(p.fdir),
+ .out=outlist,
+ .durable=durable,
+ .cmd=cmdlist
+ ])
+ for o in outlist
+ std.htput(b.gensrc, o, gt)
+ addtarg(p, b, o, `Gen gt)
+ ;;
+}
+
+/*
+myrtarget: name '=' inputlist ';;'
+ | name attrlist = inputlist ';;'
+*/
+const myrtarget = {p, targ
+ var ldscript, runtime, inst, incpath
+ var name, inputs, libdeps, attrs
+ var fsel
+
+ match word(p)
+ | `std.Some n: name = n
+ | `std.None: failparse(p, "expected target name after '%s'\n", targ)
+ ;;
+
+ skipspace(p)
+ if matchc(p, '{')
+ match attrlist(p)
+ | `std.Some al: attrs = al
+ | `std.None: failparse(p, "invalid attr list for %s %s\n", targ, name)
+ ;;
+ else
+ attrs = [][:]
+ ;;
+
+ skipspace(p)
+ if !matchc(p, '=')
+ failparse(p, "expected '=' after '%s %s'\n", targ, name)
+ ;;
+
+ match inputlist(p)
+ | `std.Some (wl, libs):
+ fsel = mkfsel()
+ libdeps = libs
+ for w in wl
+ fseladd(fsel, w)
+ ;;
+ inputs = fselfin(fsel)
+ std.slfree(wl)
+ | `std.None: failparse(p, "expected list of file names after '%s %s'\n", targ, name)
+ ;;
+
+ skipspace(p)
+ if !matchc(p, ';') || !matchc(p, ';')
+ failparse(p, "expected ';;' terminating input list, got %c\n", peekc(p))
+ ;;
+
+ inst = true
+ ldscript = ""
+ runtime = ""
+ incpath = [][:]
+ for elt in attrs
+ match elt
+ | ("ldscript", lds): ldscript = std.sldup(lds)
+ | ("runtime", rt): runtime = std.sldup(rt)
+ | ("inc", path): incpath = std.slpush(incpath, std.sldup(path))
+ | ("noinst", ""): inst = false
+ | ("noinst", val): failparse(p, "noinst attr does not take argument\n")
+ | (invalid, _):
+ std.fatal(1, "%s: got invalid attr '%s'\n", targ, invalid)
+ ;;
+ ;;
+ -> std.mk([
+ /* target */
+ .dir=std.sldup(p.fdir),
+ .name=name,
+ .inputs=inputs,
+ .libdeps=libdeps,
+ /* attrs */
+ .install=inst,
+ .ldscript=ldscript,
+ .runtime=runtime,
+ .incpath=incpath,
+ .built=false
+ ])
+}
+
+/* anontarget: '=' wordlist ';;' */
+const anontarget = {p, targ
+ var inputs
+
+ inputs = [][:]
+ skipspace(p)
+ if !matchc(p, '=')
+ failparse(p, "expected '=' after '%s' target\n", targ)
+ ;;
+
+ match wordlist(p)
+ | `std.None: failparse(p, "expected list of file names after '%s' target\n", targ)
+ | `std.Some wl: inputs = wl
+ ;;
+ skipspace(p)
+ if !matchc(p, ';') || !matchc(p, ';')
+ failparse(p, "expected ';;' terminating input list\n")
+ ;;
+ -> inputs
+}
+
+/*
+attrlist: attrs '}'
+
+attrs : EMPTY
+ | attrs attr
+
+attr : name
+ | name '=' name
+*/
+const attrlist = {p
+ var al
+
+ al = [][:]
+ while true
+ match word(p)
+ | `std.Some k:
+ skipspace(p)
+ if matchc(p, '=')
+ match word(p)
+ | `std.Some v:
+ al = std.slpush(al, (k, v))
+ | `std.None:
+ failparse(p, "invalid attr in attribute list\n")
+ ;;
+ else
+ al = std.slpush(al, (k, [][:]))
+ ;;
+ if !matchc(p, ',')
+ break
+ ;;
+ | `std.None: break
+ ;;
+ ;;
+ if !matchc(p, '}')
+ failparse(p, "expected '}' at end of attr list, got '%c'\n", peekc(p))
+ ;;
+ if al.len == 0
+ -> `std.None
+ else
+ -> `std.Some al
+ ;;
+}
+
+/*
+inputlist: EMPTY
+ | inputlist input
+
+input : word
+ | "lib" word
+*/
+const inputlist = {p
+ var dir, lib, targ
+ var wl, libs
+
+ wl = [][:]
+ libs = [][:]
+ while true
+ match word(p)
+ | `std.Some "lib":
+ match word(p)
+ | `std.Some l:
+ (dir, lib, targ) = libpath(p, l)
+ libs = std.slpush(libs, (dir, lib, targ))
+ | `std.None:
+ failparse(p, "expected lib name after 'lib'\n")
+ ;;
+ | `std.Some w: wl = std.slpush(wl, w)
+ | `std.None: break
+ ;;
+ ;;
+ if wl.len == 0
+ -> `std.None
+ else
+ -> `std.Some (wl, libs)
+ ;;
+}
+
+/* wordlist: EMPTY | wordlist word */
+const wordlist = {p
+ var wl
+
+ wl = [][:]
+ while true
+ match word(p)
+ | `std.Some w: wl = std.slpush(wl, w)
+ | `std.None: break
+ ;;
+ ;;
+ if wl.len == 0
+ -> `std.None
+ else
+ -> `std.Some wl
+ ;;
+}
+
+/* word: /wordchar*/
+const word = {p
+ var c, n
+ var start
+
+ skipspace(p)
+
+ c = peekc(p)
+ if c == '"'
+ n = 0
+ getc(p)
+ start = p.rest
+ while p.rest.len > 0
+ c = peekc(p)
+ if c == '"'
+ getc(p)
+ goto done
+ elif c == '\\'
+ c = getc(p)
+ ;;
+ getc(p)
+ n += std.charlen(c)
+ ;;
+ failparse(p, "input ended within quoted word\n")
+ else
+ n = 0
+ start = p.rest
+ while p.rest.len > 0
+ c = peekc(p)
+ if wordchar(c)
+ getc(p)
+ n += std.charlen(c)
+ else
+ break
+ ;;
+ ;;
+ ;;
+:done
+ if n > 0
+ -> `std.Some std.sldup(start[:n])
+ else
+ -> `std.None
+ ;;
+}
+
+const wordchar = {c
+ -> std.isalnum(c) || \
+ c == '.' || c == '_' || c == '$' || c == '-' || \
+ c == '/' || c == ':' || c == '!' || c == '~' || \
+ c == '+'
+}
+
+const skipspace = {p
+ var c, r
+
+ r = p.rest
+ while r.len > 0
+ c = peekc(p)
+ match c
+ | ' ': getc(p)
+ | '\t': getc(p)
+ | '\n':
+ getc(p)
+ p.line++
+ | '#':
+ while p.rest.len > 0 && peekc(p) != '\n'
+ getc(p)
+ ;;
+ | _:
+ break
+ ;;
+ ;;
+}
+
+const matchc = {p, c
+ var chr, s
+
+ if p.rest.len == 0
+ -> false
+ ;;
+ (chr, s) = std.striter(p.rest)
+ if c == chr
+ p.rest = s
+ -> true
+ else
+ -> false
+ ;;
+}
+
+const peekc = {p
+ -> std.decode(p.rest)
+}
+
+const getc = {p
+ var c, s
+
+ (c, s) = std.striter(p.rest)
+ p.rest = s
+ -> c
+}
+
+const addtarg = {p, b, name, targ
+ var tn
+
+ tn = std.fmt("%s:%s", p.fdir, name)
+ if std.hthas(b.targs, tn)
+ failparse(p, "duplicate target %s\n", tn)
+ ;;
+ b.all = std.slpush(b.all, tn)
+ std.htput(b.targs, tn, targ)
+}
+
+const libpath = {p, libpath
+ var dir, lib, targ
+
+ match(std.strrfind(libpath, ":"))
+ | `std.None:
+ dir = std.sldup(".")
+ lib = std.sldup(libpath)
+ targ = std.fmt("%s:%s", p.fdir, lib)
+ | `std.Some idx:
+ if idx == libpath.len
+ std.fatal(1, "libdep %s missing library after ':'\n")
+ ;;
+ /* absolute path */
+ if std.hasprefix(libpath, "@/") || std.hasprefix(libpath, "@:")
+ dir = std.pathcat(p.basedir, libpath[2:idx])
+ lib = std.sldup(libpath[idx+1:])
+ targ = std.sldup(libpath[2:])
+ /* relative path */
+ else
+ dir = std.sldup(libpath[:idx])
+ lib = std.sldup(libpath[idx+1:])
+ targ = std.pathcat(p.fdir, libpath)
+ if std.hasprefix(targ, "../")
+ std.fatal(1, "library %s outside of project\n", libpath)
+ ;;
+ ;;
+ ;;
+ -> (dir, lib, targ)
+}
diff --git a/mbld/subdir.myr b/mbld/subdir.myr
new file mode 100644
index 0000000..0efd958
--- /dev/null
+++ b/mbld/subdir.myr
@@ -0,0 +1,23 @@
+use std
+
+use "types.use"
+use "util.use"
+
+pkg bld =
+ const subdirs : (p : build#, subs : byte[:][:], targ : std.option(byte[:]) -> void)
+;;
+
+const subdirs = {p, subs, targ
+ for s in subs
+ std.put("Entering directory '%s'\n", s)
+ if !std.chdir(s)
+ std.fatal(1, "unable to enter directory %s\n", s)
+ ;;
+ run(p.cmd)
+ std.put("Leaving directory '%s'\n", s)
+ if !std.chdir("..")
+ std.fatal(1, "unable to leave directory %s\n", s)
+ ;;
+ ;;
+}
+
diff --git a/mbld/test.myr b/mbld/test.myr
new file mode 100644
index 0000000..77c6c25
--- /dev/null
+++ b/mbld/test.myr
@@ -0,0 +1,150 @@
+use std
+
+use "build.use"
+use "clean.use"
+use "deps.use"
+use "opts.use"
+use "parse.use"
+use "types.use"
+use "util.use"
+use "subdir.use"
+
+use "config.use"
+
+pkg bld =
+ const test : (b : build# -> void)
+;;
+
+const test = {b
+ var ok/*, bin */
+
+ /* no implicit tests to run */
+ ok = true
+ if std.fexists("test")
+ for tn in b.all
+ match gettarg(b.targs, tn)
+ | `Bin bt:
+ if !dotest(b, bt, ok)
+ ok = false
+ ;;
+ | `Lib lt:
+ if !dotest(b, lt, ok)
+ ok = false
+ ;;
+ | _: /* ignore */
+ ;;
+ ;;
+ ;;
+ /*
+ FIXME: reenable test binaries
+ for `Test t in targs
+ for s in t.incpath
+ if std.sleq(".", s)
+ goto founddot
+ ;;
+ ;;
+ t.incpath = std.slpush(t.incpath, std.sldup("."))
+
+:founddot
+ buildbin(b, t, false)
+ bin = std.strcat("./", t.name)
+ if !runtest(bin)
+ ok = false
+ ;;
+ std.slfree(bin)
+ ;;
+ */
+ if ok
+ std.put("TESTS PASSED\n")
+ else
+ std.put("TESTS FAILED\n")
+ std.exit(1)
+ ;;
+}
+
+const dotest = {b, targ, ok
+ var tt, bin ,path, tests
+
+ tests = [][:]
+ setdir(b, targ.dir)
+ for s in targ.inputs
+ path = std.pathcat("./test", s)
+ if std.fexists(path)
+ bin = srcswapsuffix(path, "")
+ tt = [
+ .name = bin,
+ .dir = targ.dir,
+ .inputs = [path, s][:],
+ .install = false,
+ .libdeps = targ.libdeps,
+ .incpath = targ.incpath,
+ .built = false,
+ ]
+
+ cleantest(b, path)
+ buildbin(b, &tt, true)
+ tests = std.slpush(tests, bin)
+ ;;
+ std.slfree(path)
+ ;;
+
+ ok = true
+ for t in tests
+ if !runtest(t)
+ ok = false
+ ;;
+ std.slfree(t)
+ ;;
+ std.slfree(tests)
+ -> ok
+}
+
+const cleantest = {b, src
+ var obj, bin, log, usef
+
+ obj = srcswapsuffix(src, config.Objsuffix)
+ log = srcswapsuffix(src, ".log")
+ usef = srcswapsuffix(src, ".use")
+ bin = srcswapsuffix(src, "")
+
+ std.remove(obj)
+ std.remove(usef)
+ std.remove(log)
+ std.remove(bin)
+
+ std.slfree(obj)
+ std.slfree(usef)
+ std.slfree(log)
+ std.slfree(bin)
+}
+
+const runtest = {bin
+ var r, log
+
+ std.put("run %s:\t", bin)
+ log = std.strcat(bin, ".log")
+ match std.spork([bin][:])
+ | `std.Fail m:
+ std.fatal(1, "unable to run test: %s\n", m)
+ | `std.Ok (pid, infd, outfd):
+ match std.fslurp(outfd)
+ | `std.Ok "": /* empty output; nothing to log */
+ | `std.Ok buf:
+ std.blat(log, buf, 0o644)
+ | `std.Fail m:
+ ;;
+ std.slfree(log)
+
+ r = false
+ match std.wait(pid)
+ | `std.Wfailure: std.put("FAIL\n")
+ | `std.Wsignalled: std.put("CRASH\n")
+ | `std.Wsuccess:
+ std.put("PASS\n")
+ r = true
+ | _: std.put("???\n")
+ ;;
+ ;;
+ -> r
+}
+
diff --git a/mbld/types.myr b/mbld/types.myr
new file mode 100644
index 0000000..90d1d0c
--- /dev/null
+++ b/mbld/types.myr
@@ -0,0 +1,59 @@
+use std
+
+pkg bld =
+ type build = struct
+ cmd : byte[:][:] /* command that we ran */
+ /* build state */
+ basedir : byte[:]
+ curdir : byte[:]
+ /* build params */
+ all : byte[:][:]
+ targs : std.htab(byte[:], targ)# /* dir => target mapping */
+ tdeps : std.htab(byte[:], byte[:][:]) /* targname => depname[:] mapping */
+ gensrc : std.htab(byte[:], gentarg#)# /* generated src => generating target mapping */
+ prefix : byte[:]
+ system : byte[:]
+ arch : byte[:]
+ ;;
+
+
+ type depgraph = struct
+ roots : byte[:][:]
+ deps : std.htab(byte[:], byte[:][:])#
+ libs : std.htab(byte[:], byte[:][:])#
+ input : std.htab(byte[:], byte[:])#
+ sources : std.htab(byte[:], bool)#
+ updated : std.htab(byte[:], bool)#
+ seen : std.htab(byte[:], bool)#
+ done : std.htab(byte[:], bool)#
+ ;;
+
+ type myrtarg = struct
+ dir : byte[:]
+ name : byte[:]
+ inputs : byte[:][:]
+ libdeps : (byte[:], byte[:], byte[:])[:] /* dir, lib pairs */
+ built : bool
+ install : bool
+ runtime : byte[:]
+ incpath : byte[:][:]
+ ldscript : byte[:]
+ ;;
+
+ type gentarg = struct
+ dir : byte[:]
+ out : byte[:][:]
+ cmd : byte[:][:]
+ durable : bool
+ /* we can have multiple outputs, but we only want to run once for each */
+ done : bool
+ ;;
+
+ type targ = union
+ `Bin myrtarg#
+ `Lib myrtarg#
+ `Test myrtarg#
+ `Gen gentarg#
+ `Man byte[:][:]
+ ;;
+;;
diff --git a/mbld/util.myr b/mbld/util.myr
new file mode 100644
index 0000000..57872b1
--- /dev/null
+++ b/mbld/util.myr
@@ -0,0 +1,123 @@
+use std
+
+use "opts.use"
+use "types.use"
+
+pkg bld =
+ const run : (cmd : byte[:][:] -> void)
+ const printcmd
+ const srcsplit : (src : byte[:] -> (byte[:], byte[:], byte[:]))
+ const swapsuffix : (f : byte[:], suff : byte[:], newsuff : byte[:] -> byte[:])
+ const srcswapsuffix : (f : byte[:], newsuff : byte[:] -> byte[:])
+ const strlistfree : (sl : byte[:][:] -> void)
+ const gettarg : (tab : std.htab(byte[:], targ)#, n : byte[:] -> targ)
+ const setdir : (b : build#, dir : byte[:] -> void)
+;;
+
+const run = {cmd
+ var pid
+
+ printcmd(cmd)
+ pid = std.fork()
+ if pid == -1
+ std.fatal(1, "could not fork command\n")
+ elif pid == 0
+ if std.execvp(cmd[0], cmd) < 0
+ std.fatal(1, "failed to exec %s\n", cmd[0])
+ ;;
+ else
+ match std.wait(pid)
+ | `std.Wsuccess: /* nothing */
+ | `std.Wfailure: std.fatal(1, "FAIL: \"%s\"\n", std.strjoin(cmd, " "))
+ | `std.Wsignalled: std.fatal(1, "CRASH: \"%s\"\n", std.strjoin(cmd, " "))
+ | `std.Waiterror: std.fatal(1, "WAT: \"%s\"\n", std.strjoin(cmd, " "))
+ ;;
+ ;;
+}
+
+const printcmd = {lst
+ if lst.len > 0
+ std.put("\t")
+ std.put("%s\t", lst[0])
+ for l in lst[1:]
+ std.put("%s ", l)
+ ;;
+ ;;
+ std.put("\n")
+}
+
+const srcsplit = {src
+ var platf, suff
+
+ platf = ""
+ suff = ""
+ match std.strrfind(src, ".")
+ | `std.Some i:
+ suff = src[i:]
+ src = src[:i]
+ | `std.None:
+ /* no suffix to trim */
+ ;;
+
+ match std.strrfind(src, "+")
+ | `std.Some i:
+ platf = src[i:]
+ src = src[:i]
+ | `std.None:
+ /* no platform to trim */
+ ;;
+ -> (src, platf, suff)
+}
+
+const swapsuffix = {f, suff, newsuff
+ if std.hassuffix(f, suff)
+ f = f[:f.len - suff.len]
+ ;;
+ -> std.fmt("%s%s", f, newsuff)
+}
+
+const srcswapsuffix = {src, new
+ var base, platf, suff
+
+ (base, platf, suff) = srcsplit(src)
+ if std.sleq(suff, ".myr")
+ -> std.strcat(base, new)
+ elif std.sleq(suff, ".s")
+ -> std.strcat(base, new)
+ else
+ std.fatal(1, "unrecognized source %s\n", src)
+ ;;
+}
+
+const strlistfree = {sl
+ for s in sl
+ std.slfree(s)
+ ;;
+ std.slfree(sl)
+}
+
+const gettarg = {tab, n
+ match std.htget(tab, n)
+ | `std.None: std.fatal(1, "internal: nonexistent %s\n", n)
+ | `std.Some t: -> t
+ ;;
+}
+
+const setdir = {b, dir
+ var p
+
+ if !std.sleq(b.curdir, dir)
+ p = std.pathcat(b.basedir, dir)
+ if b.curdir.len != 0
+ std.put("Leaving directory %s\n", b.curdir)
+ ;;
+
+ std.put("Entering directory '%s'\n", dir)
+ if !std.chdir(p)
+ std.fatal(1, "could not cd into %s\n")
+ ;;
+ b.curdir = dir
+ std.slfree(p)
+ ;;
+}
+