definerule("normalrule", { ins = { type="targets" }, outleaves = { type="strings" }, label = { type="string", optional=true }, commands = { type="strings" }, vars = { type="table", default={} }, }, function (e) local objpath = "$(OBJDIR)/"..e.name local realouts = {} for k, v in pairs(e.outleaves) do realouts[k] = concatpath(objpath, v) end local result = simplerule { name = e.name, ins = e.ins, outs = realouts, label = e.label, commands = e.commands, vars = e.vars, } result.dir = objpath return result end ) definerule("cfile", { srcs = { type="targets" }, hdrs = { type="targets", default={} }, commands = { type="strings", default={ "$CC -c -o %{outs[1]} %{ins[1]} %{hdrpaths}" }, } }, function (e) if (#e.srcs ~= 1) then error("you must have exactly one .c file") end hdrpaths = {} for _, t in pairs(e.hdrs) do hdrpaths[#hdrpaths+1] = "-I"..t.dir end local outleaf = basename(filenamesof(e.srcs)[1]):gsub("%.c$", ".o") return normalrule { name = e.name, ins = {e.srcs[1], unpack(e.hdrs)}, outleaves = {outleaf}, label = e.label, commands = e.commands, vars = { hdrpaths = hdrpaths } } end ) normalrule { name = "mkheader", ins = {}, outleaves = {"foo.h"}, commands = { "echo 1 >> %{outs}" } } cfile { name = "testfile-foo", srcs = "foo.c", } cfile { name = "testfile-bar", srcs = "bar.c", hdrs = ":mkheader", } --[[ -- -- Targets: -- -- { -- dir = target's build directory -- outs = target's object files -- is = { set of rule types which made the target } -- } function M.subenv(p) local e = p[1] setmetable(p, {__index = e}) return p end local function check_filename(fn) if type(fn) == "table" then for _, f in ipairs(fn) do check_filename(f) end else if fn:find("%s") then error("Filename '"+fn+"' contains spaces. This will make the build system sad.") end end end function M.basename(fn) if type(fn) == "table" then local nfn = {} for _, f in ipairs(fn) do nfn[#nfn+1] = M.basename(f) end return nfn else local _, _, base = fn:find("([^/]*)$") return base end end function M.flatten(t) if t == nil then return {} end local tt = {} for _, subt in ipairs(t) do if type(subt) == "table" then for _, subt in ipairs(M.flatten(subt)) do tt[#tt+1] = subt end else tt[#tt+1] = subt end end return tt end local function append(...) local ts = {} for _, t in ipairs({...}) do for _, v in ipairs(t) do ts[#ts+1] = v end end return ts end local function settotable(s) local t = {} for k in pairs(s) do t[#t+1] = k end return t end local function asstring(t) return table.concat(M.flatten(t), " ") end local function emit(...) for _, s in ipairs({...}) do io.stdout:write(s) end end function M.rawtarget(p) local description = p.description or error("no description supplied") local ins = M.flatten(p.ins) local outs = M.flatten(p.outs) local cmd = p.command if type(cmd) ~= "table" then cmd = {cmd} end for _, s in ipairs(ins) do check_filename(s) end for _, s in ipairs(outs) do check_filename(s) end emit(outs[1], ":") for _, s in ipairs(ins) do emit(" ", s) end emit("\n") emit("\t@echo ", p.description, "\n") emit("\t$(hide) ", table.concat(cmd, " && "), "\n") for i = 2, #outs do emit(outs[i], ": ", outs[1], "\n") end emit("\n") end function M.export(p) local e = p[1] local dest = p.dest or error("no export destination provided") local deps = p.deps or error("nothing to export") local fdeps = M.flatten(deps) if #fdeps ~= 1 then error("you can only export one thing at a time") end return M.rawtarget {e, ins=deps, outs={dest}, command="cp "..fdeps[1].." "..dest, description="EXPORT "..dest } end function M.hermetic(p) local e = p[1] local ins = M.flatten(p.ins) local deps = M.flatten(p.deps) local baseouts = p.baseouts or error("you must specify some baseouts") local description = p.description local absouts = {} local path = e.PATH .. "/" .. p.baseouts[1] .. ".env" for _, s in ipairs(M.flatten(p.baseouts)) do absouts[#absouts+1] = path .. "/" .. s end local dirset = {} for _, s in ipairs(absouts) do local d = s:gsub("^(.*/).*$", "%1") if d then dirset[d] = true end end local newcmd = { "rm -rf "..path, "mkdir -p "..asstring(settotable(dirset)), "ln -srf "..asstring(append(ins, deps)).." "..path, "cd "..path } for _, s in ipairs(p.command) do newcmd[#newcmd+1] = "(" .. s .. ")" end M.rawtarget {e, ins={ins, unpack(deps)}, outs=absouts, command=newcmd, description=description } return absouts end function M.cfile(p) local e = p[1] local src = p.src local deps = p.deps or {} local outfile = p.src:gsub("%.c$", ".o") local basesrc = M.basename(p.src) return M.hermetic {e, ins={src}, deps=deps, baseouts={outfile}, command={e.CC.." "..e.CFLAGS.." -c -o "..outfile.." "..basesrc}, description="CC "..src } end function M.cprogram(p) local e = p[1] local name = p.name or error("cprogram must have a name specified") local deps = p.deps or {} local libs = p.libraries or {} local headers = p.headers or {} if p.srcs then local mainlib = M.clibrary {e, name=name..".a", deps=deps, srcs=p.srcs, headers=headers } deps = append(deps, {mainlib}) end local libflags = {} for _, s in ipairs(libs) do libflags[#libflags+1] = "-l"..s end return M.hermetic {e, ins=deps, baseouts={name}, command={e.CC.." "..e.CFLAGS.." -o "..name.." "..asstring(M.basename(deps)).." "..asstring(libflags)}, description="CLINK "..name } end function M.clibrary(p) local e = p[1] local name = p.name or error("clibrary must have a name specified") local deps = M.flatten(p.deps) local srcs = M.flatten(p.srcs) local headers = M.flatten(p.headers) local baseouts = {name} local objs = deps for _, f in ipairs(srcs) do objs[#objs+1] = M.cfile {e, src=f, deps=headers } end return M.hermetic {e, ins=append(objs, headers), baseouts=baseouts, command={ e.AR.." q "..name.." "..asstring(M.basename(objs)), e.RANLIB.." "..name, }, description="CLIBRARY "..name } end emit("hide = @\n") return M --]]