diff options
author | Ori Bernstein <ori@eigenstate.org> | 2015-04-13 01:25:54 -0700 |
---|---|---|
committer | Ori Bernstein <ori@eigenstate.org> | 2015-04-13 01:29:42 -0700 |
commit | 52585564bf5a46db0ae6c9fe4e20307e014de848 (patch) | |
tree | a0cbd5529dcb351d1c1c059d44379e8e5b85b1f8 /mbld | |
parent | 2f5a88d9b79271f78ec71cf7f796ca5fb74de89e (diff) | |
download | mc-52585564bf5a46db0ae6c9fe4e20307e014de848.tar.gz |
Move mbld to subdirectory of itself for merging.
Diffstat (limited to 'mbld')
-rw-r--r-- | mbld/Makefile | 20 | ||||
-rw-r--r-- | mbld/bldfile | 22 | ||||
-rw-r--r-- | mbld/build.myr | 412 | ||||
-rw-r--r-- | mbld/clean.myr | 89 | ||||
-rw-r--r-- | mbld/config+plan9-x64.myr | 11 | ||||
-rwxr-xr-x | mbld/configure | 65 | ||||
-rw-r--r-- | mbld/deps.myr | 308 | ||||
-rw-r--r-- | mbld/fsel.myr | 105 | ||||
-rw-r--r-- | mbld/install.myr | 92 | ||||
-rw-r--r-- | mbld/main.myr | 115 | ||||
-rw-r--r-- | mbld/mbld.1 | 81 | ||||
-rw-r--r-- | mbld/mk/myr.mk | 42 | ||||
-rw-r--r-- | mbld/opts.myr | 85 | ||||
-rw-r--r-- | mbld/parse.myr | 539 | ||||
-rw-r--r-- | mbld/subdir.myr | 23 | ||||
-rw-r--r-- | mbld/test.myr | 150 | ||||
-rw-r--r-- | mbld/types.myr | 59 | ||||
-rw-r--r-- | mbld/util.myr | 123 |
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) + ;; +} + |