diff --git a/first/ackbuilder.lua b/first/ackbuilder.lua index 4a0888c22..413389a98 100644 --- a/first/ackbuilder.lua +++ b/first/ackbuilder.lua @@ -30,11 +30,15 @@ local function asstring(o) elseif (t == "number") then return o elseif (t == "table") then - local s = {} - for _, v in pairs(o) do - s[#s+1] = asstring(v) + if o.outs then + return asstring(o.outs) + else + local s = {} + for _, v in pairs(o) do + s[#s+1] = asstring(v) + end + return table.concat(s, " ") end - return table.concat(s, " ") else error(string.format("can't turn values of type '%s' into strings", t)) end @@ -99,23 +103,54 @@ local function emit(...) end end -local function loadbuildfile(filename) - local data, chunk, e - data = io.open(filename):read("*a") - if not e then - local thisglobals = {_G = thisglobals} - setmetatable(thisglobals, {__index = globals}) - chunk, e = loadstring(data, filename, "text", thisglobals) - end - if e then - error(string.format("couldn't load '%s': %s", filename, e)) - end +local function templateexpand(list, vars) + setmetatable(vars, { __index = globals }) - chunk() + local o = {} + for _, s in ipairs(list) do + o[#o+1] = s:gsub("%%%b{}", + function(expr) + expr = expr:sub(3, -2) + local chunk, e = loadstring("return "..expr, expr, "text", vars) + if e then + error(string.format("error evaluating expression: %s", e)) + end + return asstring(chunk()) + end + ) + end + return o +end + +local function loadbuildfile(filename) + if not buildfiles[filename] then + buildfiles[filename] = true + + local fp, data, chunk, e + io.stderr:write("loading ", filename, "\n") + fp, e = io.open(filename) + if not e then + data, e = fp:read("*a") + fp:close() + if not e then + local thisglobals = {_G = thisglobals} + setmetatable(thisglobals, {__index = globals}) + chunk, e = loadstring(data, filename, "text", thisglobals) + end + end + if e then + error(string.format("couldn't load '%s': %s", filename, e)) + end + + local oldcwd = cwd + cwd = dirname(filename) + chunk() + cwd = oldcwd + end end local function loadtarget(targetname) - if targets[target] then + if targets[targetname] then return targets[targetname] end @@ -129,23 +164,20 @@ local function loadtarget(targetname) } targets[targetname] = target else - local _, _, filepart, targetpart = targetname:find("^([^:]+):(%w+)$") + local _, _, filepart, targetpart = targetname:find("^([^:]*):(%w+)$") if not filepart or not targetpart then error(string.format("malformed target name '%s'", targetname)) end - if not buildfiles[filepart] then - buildfiles[filepart] = true - - local oldcwd = cwd - cwd = filepart - loadbuildfile(filepart.."/build.lua") - cwd = oldcwd + if (filepart == "") then + filepart = cwd end + local filename = concatpath(filepart, "/build.lua") + loadbuildfile(concatpath(filename)) target = targets[targetname] if not target then error(string.format("build file '%s' contains no rule '%s'", - filepart, targetpart)) + filename, targetpart)) end end @@ -168,6 +200,8 @@ local typeconverters = { if s:find("^//") then s = s:gsub("^//", "") + elseif s:find("^:") then + s = cwd..s elseif s:find("^[^/]") then s = concatpath(cwd, s) end @@ -228,7 +262,11 @@ local function definerule(rulename, types, cb) end args.environment = environment - cb(args) + + local result = cb(args) or {} + result.is = result.is or {} + result.is[rulename] = true + targets[cwd..":"..args.name] = result end end @@ -260,11 +298,11 @@ function environment:mkdirs(dirs) end function environment:exec(commands) - local o = {} - for _, s in ipairs(commands) do - o[#o+1] = "("..s..")" - end - emit("\t$(hide)", table.concat(o, " && "), "\n") + emit("\t$(hide)", table.concat(commands, " && "), "\n") +end + +function environment:endrule() + emit("\n") end definerule("simplerule", @@ -278,7 +316,19 @@ definerule("simplerule", e.environment:rule(filenamesof(e.ins), e.outs) e.environment:label(e.name, " ", e.label or "") e.environment:mkdirs(dirnames(e.outs)) - e.environment:exec(e.commands) + e.environment:exec( + templateexpand(e.commands, + { + ins = e.ins, + outs = e.outs + } + ) + ) + e.environment:endrule() + + return { + outs = e.outs + } end ) diff --git a/first/build.lua b/first/build.lua new file mode 100644 index 000000000..ded146587 --- /dev/null +++ b/first/build.lua @@ -0,0 +1,300 @@ +simplerule { + name = "random", + ins = {}, + outs = {"out"}, + commands = { + "dd if=/dev/random of=%{outs} bs=1024 count=1" + } +} + +simplerule { + name = "sorted", + ins = { ":random" }, + outs = { "sorted" }, + commands = { + "sort %{ins} > %{outs}" + } +} + +--[[ +function environment:cfileflags() + emit("$CFLAGS") +end + +function environment:cfile(srcs, obj, includes) + emit("$CC -o", obj, srcs) + emit(ab.expand(includes, "-I%")) + self:cflags() + emit("\n") +end + +function environment:clinkflags() + emit("$LDFLAGS") +end + +function environment:clink(objs, exe, libraryFlags) + emit("$CC -o", exe, objs, libraryFlags) + self:clinkflags() + emit("\n") +end + +-- +-- 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 +--]]