2016-06-07 02:13:56 +00:00
|
|
|
local posix = require("posix")
|
|
|
|
|
2016-06-06 15:18:19 +00:00
|
|
|
-- Targets:
|
|
|
|
--
|
|
|
|
-- {
|
2016-06-14 05:34:14 +00:00
|
|
|
-- fullname = full name of target
|
2016-06-06 15:18:19 +00:00
|
|
|
-- dir = target's build directory
|
|
|
|
-- outs = target's object files
|
|
|
|
-- is = { set of rule types which made the target }
|
|
|
|
-- }
|
|
|
|
|
2016-07-26 21:43:31 +00:00
|
|
|
local emitter = {}
|
2016-06-06 15:18:19 +00:00
|
|
|
local rules = {}
|
2016-06-06 18:50:48 +00:00
|
|
|
local targets = {}
|
|
|
|
local buildfiles = {}
|
|
|
|
local globals
|
|
|
|
local cwd = "."
|
2016-07-26 22:10:15 +00:00
|
|
|
local vars = {}
|
|
|
|
local parente = {}
|
2016-08-13 23:38:36 +00:00
|
|
|
local loadingstack = {}
|
2016-07-26 22:10:15 +00:00
|
|
|
|
2016-08-04 21:51:19 +00:00
|
|
|
-- Forward references
|
|
|
|
local loadtarget
|
|
|
|
|
2016-07-28 22:22:22 +00:00
|
|
|
local function print(...)
|
2016-08-04 21:51:19 +00:00
|
|
|
local function print_no_nl(list)
|
|
|
|
for _, s in ipairs(list) do
|
|
|
|
if (type(s) == "table") then
|
|
|
|
io.stderr:write("{")
|
|
|
|
for k, v in pairs(s) do
|
|
|
|
print_no_nl({k})
|
|
|
|
io.stderr:write("=")
|
|
|
|
print_no_nl({v})
|
|
|
|
io.stderr:write(" ")
|
|
|
|
end
|
|
|
|
io.stderr:write("}")
|
|
|
|
else
|
|
|
|
io.stderr:write(tostring(s))
|
|
|
|
end
|
2016-07-28 22:22:22 +00:00
|
|
|
end
|
|
|
|
end
|
2016-08-04 21:51:19 +00:00
|
|
|
|
|
|
|
print_no_nl({...})
|
2016-07-28 22:22:22 +00:00
|
|
|
io.stderr:write("\n")
|
|
|
|
end
|
|
|
|
|
2016-08-04 21:51:19 +00:00
|
|
|
local function assertString(s, i)
|
|
|
|
if (type(s) ~= "string") then
|
|
|
|
error(string.format("parameter %d must be a string", i))
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2016-07-26 22:10:15 +00:00
|
|
|
local function concat(...)
|
|
|
|
local r = {}
|
2016-08-04 21:51:19 +00:00
|
|
|
|
|
|
|
local function process(list)
|
|
|
|
for _, t in ipairs(list) do
|
|
|
|
if (type(t) == "table") and not t.is then
|
|
|
|
process(t)
|
|
|
|
else
|
|
|
|
r[#r+1] = t
|
2016-07-29 22:39:22 +00:00
|
|
|
end
|
2016-07-26 22:10:15 +00:00
|
|
|
end
|
|
|
|
end
|
2016-08-04 21:51:19 +00:00
|
|
|
process({...})
|
|
|
|
|
2016-07-26 22:10:15 +00:00
|
|
|
return r
|
|
|
|
end
|
2016-06-06 15:18:19 +00:00
|
|
|
|
2016-08-13 23:38:36 +00:00
|
|
|
-- Test table membership (crudely).
|
|
|
|
local function contains(needle, haystack)
|
|
|
|
for _, k in ipairs(haystack) do
|
|
|
|
if (k == needle) then
|
|
|
|
return true
|
|
|
|
end
|
|
|
|
end
|
|
|
|
return false
|
|
|
|
end
|
|
|
|
|
2016-06-09 04:55:44 +00:00
|
|
|
local function inherit(high, low)
|
|
|
|
local o = {}
|
|
|
|
setmetatable(o, {
|
|
|
|
__index = function(self, k)
|
|
|
|
local x = high[k]
|
|
|
|
if x then
|
|
|
|
return x
|
|
|
|
end
|
|
|
|
return low[k]
|
|
|
|
end
|
|
|
|
})
|
2016-07-26 22:10:15 +00:00
|
|
|
for k, v in pairs(high) do
|
|
|
|
local _, _, kk = k:find("^%+(.*)$")
|
|
|
|
if kk then
|
|
|
|
o[kk] = concat(low[kk], v)
|
|
|
|
end
|
|
|
|
end
|
2016-06-09 04:55:44 +00:00
|
|
|
return o
|
2016-06-05 08:39:29 +00:00
|
|
|
end
|
|
|
|
|
2016-06-06 15:18:19 +00:00
|
|
|
local function asstring(o)
|
|
|
|
local t = type(o)
|
2016-06-07 02:13:56 +00:00
|
|
|
if (t == "nil") then
|
|
|
|
return ""
|
|
|
|
elseif (t == "string") then
|
2016-06-06 15:18:19 +00:00
|
|
|
return o
|
|
|
|
elseif (t == "number") then
|
|
|
|
return o
|
|
|
|
elseif (t == "table") then
|
2016-06-07 02:13:56 +00:00
|
|
|
if o.is then
|
2016-06-06 22:10:22 +00:00
|
|
|
return asstring(o.outs)
|
|
|
|
else
|
|
|
|
local s = {}
|
|
|
|
for _, v in pairs(o) do
|
|
|
|
s[#s+1] = asstring(v)
|
|
|
|
end
|
|
|
|
return table.concat(s, " ")
|
2016-06-05 08:39:29 +00:00
|
|
|
end
|
|
|
|
else
|
2016-06-06 15:18:19 +00:00
|
|
|
error(string.format("can't turn values of type '%s' into strings", t))
|
2016-06-05 08:39:29 +00:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2016-06-06 20:13:30 +00:00
|
|
|
local function concatpath(...)
|
|
|
|
local p = table.concat({...}, "/")
|
2016-07-18 21:16:27 +00:00
|
|
|
return (p:gsub("/+", "/"):gsub("^%./", ""):gsub("/%./", "/"))
|
2016-06-06 20:13:30 +00:00
|
|
|
end
|
|
|
|
|
2016-08-08 21:55:23 +00:00
|
|
|
-- Returns a list of the targets within the given collection; the keys of any
|
|
|
|
-- keyed items are lost. Lists and wildcards are expanded.
|
2016-08-04 21:51:19 +00:00
|
|
|
local function targetsof(...)
|
|
|
|
local o = {}
|
|
|
|
|
|
|
|
local function process(items)
|
2016-08-08 21:55:23 +00:00
|
|
|
for _, item in pairs(items) do
|
2016-08-04 21:51:19 +00:00
|
|
|
if (type(item) == "table") then
|
|
|
|
if item.is then
|
|
|
|
-- This is a target.
|
|
|
|
o[#o+1] = item
|
|
|
|
else
|
|
|
|
-- This is a list.
|
|
|
|
process(item)
|
|
|
|
end
|
|
|
|
elseif (type(item) == "string") then
|
|
|
|
-- Filename!
|
|
|
|
if item:find("^%+") then
|
|
|
|
item = cwd..item
|
|
|
|
elseif item:find("^%./") then
|
|
|
|
item = concatpath(cwd, item)
|
2016-06-07 02:13:56 +00:00
|
|
|
end
|
2016-08-04 21:51:19 +00:00
|
|
|
o[#o+1] = loadtarget(item)
|
2016-06-08 01:21:53 +00:00
|
|
|
else
|
2016-08-04 21:51:19 +00:00
|
|
|
error(string.format("member of target list is not a string or a target"))
|
2016-06-07 02:13:56 +00:00
|
|
|
end
|
2016-06-06 20:13:30 +00:00
|
|
|
end
|
|
|
|
end
|
2016-08-04 21:51:19 +00:00
|
|
|
|
|
|
|
process({...})
|
|
|
|
return o
|
2016-06-06 20:13:30 +00:00
|
|
|
end
|
|
|
|
|
2016-08-04 21:51:19 +00:00
|
|
|
local function filenamesof(...)
|
|
|
|
local targets = targetsof(...)
|
2016-07-14 21:53:34 +00:00
|
|
|
|
2016-08-04 21:51:19 +00:00
|
|
|
local f = {}
|
|
|
|
for _, r in ipairs(targets) do
|
|
|
|
if (type(r) == "table") and r.is then
|
|
|
|
if r.outs then
|
|
|
|
for _, o in ipairs(r.outs) do
|
|
|
|
f[#f+1] = o
|
|
|
|
end
|
2016-07-14 21:53:34 +00:00
|
|
|
end
|
2016-08-04 21:51:19 +00:00
|
|
|
elseif (type(r) == "string") then
|
|
|
|
f[#f+1] = r
|
|
|
|
else
|
|
|
|
error(string.format("list of targets contains a %s which isn't a target",
|
|
|
|
type(r)))
|
2016-07-14 21:53:34 +00:00
|
|
|
end
|
|
|
|
end
|
|
|
|
return f
|
|
|
|
end
|
|
|
|
|
2016-08-04 21:51:19 +00:00
|
|
|
local function targetnamesof(...)
|
|
|
|
local targets = targetsof(...)
|
|
|
|
|
|
|
|
local f
|
|
|
|
for _, r in pairs(targets) do
|
|
|
|
if (type(r) == "table") and r.is then
|
|
|
|
f[#f+1] = r.fullname
|
|
|
|
elseif (type(r) == "string") then
|
|
|
|
f[#f+1] = r
|
|
|
|
else
|
|
|
|
error(string.format("list of targets contains a %s which isn't a target",
|
|
|
|
type(r)))
|
2016-06-12 18:59:43 +00:00
|
|
|
end
|
2016-08-04 21:51:19 +00:00
|
|
|
end
|
|
|
|
return f
|
|
|
|
end
|
|
|
|
|
|
|
|
local function dotocollection(files, callback)
|
|
|
|
if (#files == 1) and (type(files[1]) == "string") then
|
2016-06-12 18:59:43 +00:00
|
|
|
return callback(files[1])
|
|
|
|
end
|
|
|
|
|
|
|
|
local o = {}
|
2016-08-04 21:51:19 +00:00
|
|
|
local function process(files)
|
|
|
|
for _, s in ipairs(files) do
|
|
|
|
if (type(s) == "table") then
|
|
|
|
if s.is then
|
|
|
|
error("passed target to a filename manipulation function")
|
|
|
|
else
|
|
|
|
process(s)
|
|
|
|
end
|
|
|
|
else
|
|
|
|
local b = callback(s)
|
|
|
|
if (b ~= "") then
|
|
|
|
o[#o+1] = b
|
|
|
|
end
|
2016-06-12 18:59:43 +00:00
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
2016-08-04 21:51:19 +00:00
|
|
|
process(files)
|
2016-06-12 18:59:43 +00:00
|
|
|
return o
|
|
|
|
end
|
|
|
|
|
2016-08-04 21:51:19 +00:00
|
|
|
|
|
|
|
local function abspath(...)
|
|
|
|
return dotocollection({...},
|
2016-06-12 18:59:43 +00:00
|
|
|
function(filename)
|
2016-08-04 21:51:19 +00:00
|
|
|
assertString(filename, 1)
|
2016-07-16 12:58:29 +00:00
|
|
|
if not filename:find("^[/$]") then
|
|
|
|
filename = concatpath(posix.getcwd(), filename)
|
|
|
|
end
|
|
|
|
return filename
|
2016-06-12 18:59:43 +00:00
|
|
|
end
|
|
|
|
)
|
|
|
|
end
|
2016-07-18 21:16:27 +00:00
|
|
|
|
2016-08-04 21:51:19 +00:00
|
|
|
|
|
|
|
local function basename(...)
|
|
|
|
return dotocollection({...},
|
2016-06-12 18:59:43 +00:00
|
|
|
function(filename)
|
2016-08-04 21:51:19 +00:00
|
|
|
assertString(filename, 1)
|
2016-06-12 18:59:43 +00:00
|
|
|
local _, _, b = filename:find("^.*/([^/]*)$")
|
|
|
|
if not b then
|
|
|
|
return filename
|
|
|
|
end
|
|
|
|
return b
|
|
|
|
end
|
|
|
|
)
|
|
|
|
end
|
|
|
|
|
2016-08-04 21:51:19 +00:00
|
|
|
|
|
|
|
local function dirname(...)
|
|
|
|
return dotocollection({...},
|
2016-06-12 18:59:43 +00:00
|
|
|
function(filename)
|
2016-08-04 21:51:19 +00:00
|
|
|
assertString(filename, 1)
|
2016-06-12 18:59:43 +00:00
|
|
|
local _, _, b = filename:find("^(.*)/[^/]*$")
|
|
|
|
if not b then
|
|
|
|
return ""
|
|
|
|
end
|
|
|
|
return b
|
|
|
|
end
|
|
|
|
)
|
|
|
|
end
|
|
|
|
|
2016-08-04 21:51:19 +00:00
|
|
|
local function replace(files, pattern, repl)
|
2016-08-11 22:19:30 +00:00
|
|
|
return dotocollection({files},
|
2016-06-29 11:28:22 +00:00
|
|
|
function(filename)
|
|
|
|
return filename:gsub(pattern, repl)
|
|
|
|
end
|
|
|
|
)
|
|
|
|
end
|
|
|
|
|
2016-08-04 21:51:19 +00:00
|
|
|
local function fpairs(...)
|
|
|
|
return ipairs(filenamesof(...))
|
2016-06-12 18:59:43 +00:00
|
|
|
end
|
|
|
|
|
2016-08-04 21:51:19 +00:00
|
|
|
local function matching(collection, pattern)
|
|
|
|
local o = {}
|
|
|
|
dotocollection(collection,
|
|
|
|
function(filename)
|
|
|
|
if filename:find(pattern) then
|
|
|
|
o[#o+1] = filename
|
|
|
|
end
|
|
|
|
end
|
|
|
|
)
|
|
|
|
return o
|
|
|
|
end
|
|
|
|
|
2016-06-08 01:21:53 +00:00
|
|
|
-- Selects all targets containing at least one output file that matches
|
2016-06-12 18:59:43 +00:00
|
|
|
-- the pattern (or all, if the pattern is nil).
|
2016-06-08 01:21:53 +00:00
|
|
|
local function selectof(targets, pattern)
|
2016-08-04 21:51:19 +00:00
|
|
|
local targets = targetsof(targets)
|
2016-06-07 03:00:26 +00:00
|
|
|
local o = {}
|
|
|
|
for k, v in pairs(targets) do
|
|
|
|
if v.is and v.outs then
|
2016-06-08 01:21:53 +00:00
|
|
|
local matches = false
|
2016-06-07 03:00:26 +00:00
|
|
|
for _, f in pairs(v.outs) do
|
2016-06-08 01:21:53 +00:00
|
|
|
if f:find(pattern) then
|
|
|
|
matches = true
|
|
|
|
break
|
2016-06-07 03:00:26 +00:00
|
|
|
end
|
|
|
|
end
|
2016-06-08 01:21:53 +00:00
|
|
|
if matches then
|
2016-06-07 03:00:26 +00:00
|
|
|
o[#o+1] = v
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
return o
|
|
|
|
end
|
|
|
|
|
2016-08-04 21:51:19 +00:00
|
|
|
local function uniquify(...)
|
2016-06-06 20:13:30 +00:00
|
|
|
local s = {}
|
2016-08-04 21:51:19 +00:00
|
|
|
return dotocollection({...},
|
|
|
|
function(filename)
|
|
|
|
if not s[filename] then
|
|
|
|
s[filename] = true
|
|
|
|
return filename
|
|
|
|
end
|
2016-06-06 20:13:30 +00:00
|
|
|
end
|
2016-08-04 21:51:19 +00:00
|
|
|
)
|
2016-06-06 20:13:30 +00:00
|
|
|
end
|
|
|
|
|
2016-06-09 04:55:44 +00:00
|
|
|
local function startswith(needle, haystack)
|
|
|
|
return haystack:sub(1, #needle) == needle
|
|
|
|
end
|
|
|
|
|
2016-06-05 08:39:29 +00:00
|
|
|
local function emit(...)
|
2016-06-06 15:18:19 +00:00
|
|
|
local n = select("#", ...)
|
|
|
|
local args = {...}
|
2016-06-05 08:39:29 +00:00
|
|
|
|
2016-06-06 15:18:19 +00:00
|
|
|
for i=1, n do
|
|
|
|
local s = asstring(args[i])
|
|
|
|
io.stdout:write(s)
|
|
|
|
if not s:find("\n$") then
|
|
|
|
io.stdout:write(" ")
|
2016-06-05 08:39:29 +00:00
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2016-06-06 22:10:22 +00:00
|
|
|
local function templateexpand(list, vars)
|
2016-06-09 04:55:44 +00:00
|
|
|
vars = inherit(vars, globals)
|
2016-06-06 22:10:22 +00:00
|
|
|
|
|
|
|
local o = {}
|
|
|
|
for _, s in ipairs(list) do
|
|
|
|
o[#o+1] = s:gsub("%%%b{}",
|
|
|
|
function(expr)
|
|
|
|
expr = expr:sub(3, -2)
|
2016-08-04 21:51:19 +00:00
|
|
|
local chunk, e = load("return ("..expr..")", expr, "text", vars)
|
2016-06-06 22:10:22 +00:00
|
|
|
if e then
|
|
|
|
error(string.format("error evaluating expression: %s", e))
|
|
|
|
end
|
2016-07-19 21:42:42 +00:00
|
|
|
local value = chunk()
|
|
|
|
if (value == nil) then
|
2016-08-04 21:51:19 +00:00
|
|
|
error(string.format("template expression '%s' expands to nil (probably an undefined variable)", expr))
|
2016-07-19 21:42:42 +00:00
|
|
|
end
|
|
|
|
return asstring(value)
|
2016-06-06 22:10:22 +00:00
|
|
|
end
|
|
|
|
)
|
2016-06-06 18:50:48 +00:00
|
|
|
end
|
2016-06-06 22:10:22 +00:00
|
|
|
return o
|
|
|
|
end
|
|
|
|
|
|
|
|
local function loadbuildfile(filename)
|
2016-08-13 23:38:36 +00:00
|
|
|
if contains(filename, loadingstack) then
|
|
|
|
error(string.format("build file cycle; '%s' refers to itself indirectly; stack is: %s %s",
|
|
|
|
filename, asstring(loadingstack), filename))
|
|
|
|
end
|
|
|
|
|
|
|
|
loadingstack[#loadingstack+1] = filename
|
2016-06-06 22:10:22 +00:00
|
|
|
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
|
2016-08-04 22:01:55 +00:00
|
|
|
local thisglobals = {}
|
|
|
|
thisglobals._G = thisglobals
|
2016-06-06 22:10:22 +00:00
|
|
|
setmetatable(thisglobals, {__index = globals})
|
2016-08-04 21:51:19 +00:00
|
|
|
chunk, e = load(data, "@"..filename, "text", thisglobals)
|
2016-06-06 22:10:22 +00:00
|
|
|
end
|
|
|
|
end
|
|
|
|
if e then
|
|
|
|
error(string.format("couldn't load '%s': %s", filename, e))
|
|
|
|
end
|
2016-06-06 18:50:48 +00:00
|
|
|
|
2016-06-06 22:10:22 +00:00
|
|
|
local oldcwd = cwd
|
|
|
|
cwd = dirname(filename)
|
|
|
|
chunk()
|
|
|
|
cwd = oldcwd
|
|
|
|
end
|
2016-08-13 23:38:36 +00:00
|
|
|
loadingstack[#loadingstack] = nil
|
2016-06-06 18:50:48 +00:00
|
|
|
end
|
|
|
|
|
2016-08-04 21:51:19 +00:00
|
|
|
loadtarget = function(targetname)
|
2016-06-06 22:10:22 +00:00
|
|
|
if targets[targetname] then
|
2016-06-06 18:50:48 +00:00
|
|
|
return targets[targetname]
|
|
|
|
end
|
|
|
|
|
|
|
|
local target
|
2016-06-14 05:34:14 +00:00
|
|
|
if not targetname:find("%+") then
|
2016-06-09 04:55:44 +00:00
|
|
|
local files
|
|
|
|
if targetname:find("[?*]") then
|
|
|
|
files = posix.glob(targetname)
|
|
|
|
if not files then
|
|
|
|
error(string.format("glob '%s' matches no files", targetname))
|
|
|
|
end
|
|
|
|
else
|
|
|
|
files = {targetname}
|
|
|
|
end
|
|
|
|
|
2016-06-06 18:50:48 +00:00
|
|
|
target = {
|
2016-06-09 04:55:44 +00:00
|
|
|
outs = files,
|
2016-06-06 18:50:48 +00:00
|
|
|
is = {
|
|
|
|
__implicitfile = true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
targets[targetname] = target
|
|
|
|
else
|
2016-06-14 05:34:14 +00:00
|
|
|
local _, _, filepart, targetpart = targetname:find("^([^+]*)%+([%w-_]+)$")
|
2016-06-06 18:50:48 +00:00
|
|
|
if not filepart or not targetpart then
|
|
|
|
error(string.format("malformed target name '%s'", targetname))
|
|
|
|
end
|
2016-06-06 22:10:22 +00:00
|
|
|
if (filepart == "") then
|
|
|
|
filepart = cwd
|
2016-06-06 18:50:48 +00:00
|
|
|
end
|
2016-06-06 22:10:22 +00:00
|
|
|
local filename = concatpath(filepart, "/build.lua")
|
2016-08-13 23:38:36 +00:00
|
|
|
if posix.access(filename, "r") then
|
|
|
|
loadbuildfile(filename)
|
|
|
|
else
|
|
|
|
filename = concatpath(filepart, "/build-"..targetpart..".lua")
|
|
|
|
loadbuildfile(filename)
|
|
|
|
end
|
2016-06-06 18:50:48 +00:00
|
|
|
|
|
|
|
target = targets[targetname]
|
|
|
|
if not target then
|
2016-08-07 19:56:53 +00:00
|
|
|
error(string.format("build file '%s' contains no target '%s'",
|
2016-06-06 22:10:22 +00:00
|
|
|
filename, targetpart))
|
2016-06-06 18:50:48 +00:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
return target
|
|
|
|
end
|
|
|
|
|
|
|
|
local typeconverters = {
|
|
|
|
targets = function(propname, i)
|
|
|
|
if (type(i) == "string") then
|
|
|
|
i = {i}
|
|
|
|
elseif (type(i) ~= "table") then
|
|
|
|
error(string.format("property '%s' must be a target list", propname))
|
|
|
|
end
|
2016-08-08 21:55:23 +00:00
|
|
|
|
|
|
|
local m = {}
|
|
|
|
for k, v in pairs(i) do
|
|
|
|
local ts = targetsof(v)
|
|
|
|
if (type(k) == "number") then
|
|
|
|
for _, t in ipairs(ts) do
|
|
|
|
m[#m+1] = t
|
|
|
|
end
|
|
|
|
else
|
|
|
|
if (#ts ~= 1) then
|
|
|
|
error(string.format("named target '%s' can only be assigned from a single target", k))
|
|
|
|
else
|
|
|
|
m[k] = ts[1]
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
return m
|
2016-06-06 18:50:48 +00:00
|
|
|
end,
|
|
|
|
|
|
|
|
strings = function(propname, i)
|
|
|
|
if (type(i) == "string") then
|
|
|
|
i = {i}
|
|
|
|
elseif (type(i) ~= "table") then
|
|
|
|
error(string.format("property '%s' must be a string list", propname))
|
|
|
|
end
|
2016-08-04 21:51:19 +00:00
|
|
|
return concat(i)
|
2016-06-06 18:50:48 +00:00
|
|
|
end,
|
|
|
|
|
2016-07-23 22:50:02 +00:00
|
|
|
boolean = function(propname, i)
|
|
|
|
if (type(i) ~= "boolean") then
|
|
|
|
error(string.format("property '%s' must be a boolean", propname))
|
|
|
|
end
|
|
|
|
return i
|
|
|
|
end,
|
|
|
|
|
2016-06-06 18:50:48 +00:00
|
|
|
string = function(propname, i)
|
|
|
|
if (type(i) ~= "string") then
|
|
|
|
error(string.format("property '%s' must be a string", propname))
|
|
|
|
end
|
2016-06-06 20:13:30 +00:00
|
|
|
return i
|
2016-06-06 18:50:48 +00:00
|
|
|
end,
|
2016-06-07 03:00:26 +00:00
|
|
|
|
|
|
|
table = function(propname, i)
|
|
|
|
if (type(i) ~= "table") then
|
|
|
|
error(string.format("property '%s' must be a table", propname))
|
|
|
|
end
|
|
|
|
return i
|
|
|
|
end,
|
2016-07-29 22:39:22 +00:00
|
|
|
|
|
|
|
object = function(propname, i)
|
|
|
|
return i
|
|
|
|
end,
|
2016-06-06 18:50:48 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
local function definerule(rulename, types, cb)
|
2016-06-29 11:28:22 +00:00
|
|
|
if rulename and rules[rulename] then
|
2016-06-06 18:50:48 +00:00
|
|
|
error(string.format("rule '%s' is already defined", rulename))
|
|
|
|
end
|
|
|
|
|
|
|
|
types.name = { type="string" }
|
2016-06-29 11:58:38 +00:00
|
|
|
types.cwd = { type="string", optional=true }
|
2016-07-26 22:10:15 +00:00
|
|
|
types.vars = { type="table", default={} }
|
2016-06-06 18:50:48 +00:00
|
|
|
|
|
|
|
for propname, typespec in pairs(types) do
|
|
|
|
if not typeconverters[typespec.type] then
|
|
|
|
error(string.format("property '%s' has unrecognised type '%s'",
|
|
|
|
propname, typespec.type))
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2016-06-29 11:58:38 +00:00
|
|
|
local rulecwd = cwd
|
2016-06-29 11:28:22 +00:00
|
|
|
local rule = function(e)
|
2016-07-26 22:10:15 +00:00
|
|
|
local definedprops = {}
|
|
|
|
for propname, _ in pairs(e) do
|
|
|
|
definedprops[propname] = true
|
|
|
|
end
|
|
|
|
|
2016-06-06 18:50:48 +00:00
|
|
|
local args = {}
|
|
|
|
for propname, typespec in pairs(types) do
|
2016-06-06 20:13:30 +00:00
|
|
|
if not e[propname] then
|
2016-07-23 22:50:02 +00:00
|
|
|
if not typespec.optional and (typespec.default == nil) then
|
2016-06-06 20:13:30 +00:00
|
|
|
error(string.format("missing mandatory property '%s'", propname))
|
|
|
|
end
|
2016-06-06 18:50:48 +00:00
|
|
|
|
2016-06-06 20:13:30 +00:00
|
|
|
args[propname] = typespec.default
|
|
|
|
else
|
|
|
|
args[propname] = typeconverters[typespec.type](propname, e[propname])
|
2016-07-26 22:10:15 +00:00
|
|
|
definedprops[propname] = nil
|
2016-06-06 20:13:30 +00:00
|
|
|
end
|
2016-06-06 18:50:48 +00:00
|
|
|
end
|
|
|
|
|
2016-07-26 22:10:15 +00:00
|
|
|
local propname, _ = next(definedprops)
|
2016-06-06 18:50:48 +00:00
|
|
|
if propname then
|
|
|
|
error(string.format("don't know what to do with property '%s'", propname))
|
|
|
|
end
|
2016-06-06 20:13:30 +00:00
|
|
|
|
2016-06-29 11:58:38 +00:00
|
|
|
if not args.cwd then
|
|
|
|
args.cwd = cwd
|
|
|
|
end
|
2016-07-14 21:53:34 +00:00
|
|
|
args.fullname = args.cwd.."+"..args.name
|
2016-06-06 22:10:22 +00:00
|
|
|
|
2016-07-26 22:10:15 +00:00
|
|
|
local oldparente = parente
|
|
|
|
parente = args
|
|
|
|
args.vars = inherit(args.vars, oldparente.vars)
|
2016-06-06 22:10:22 +00:00
|
|
|
local result = cb(args) or {}
|
2016-07-26 22:10:15 +00:00
|
|
|
parente = oldparente
|
2016-06-29 11:58:38 +00:00
|
|
|
|
2016-06-06 22:10:22 +00:00
|
|
|
result.is = result.is or {}
|
2016-07-28 22:22:22 +00:00
|
|
|
if rulename then
|
|
|
|
result.is[rulename] = true
|
|
|
|
end
|
2016-06-14 05:34:14 +00:00
|
|
|
result.fullname = args.fullname
|
2016-07-14 21:53:34 +00:00
|
|
|
|
|
|
|
if targets[arg.fullname] and (targets[arg.fullname] ~= result) then
|
|
|
|
error(string.format("target '%s' is already defined", args.fullname))
|
|
|
|
end
|
2016-06-14 05:34:14 +00:00
|
|
|
targets[result.fullname] = result
|
2016-06-07 02:13:56 +00:00
|
|
|
return result
|
2016-06-05 08:39:29 +00:00
|
|
|
end
|
2016-06-29 11:28:22 +00:00
|
|
|
|
|
|
|
if rulename then
|
2016-07-14 21:53:34 +00:00
|
|
|
if rules[rulename] then
|
|
|
|
error(string.format("rule '%s' is already defined", rulename))
|
|
|
|
end
|
2016-06-29 11:28:22 +00:00
|
|
|
rules[rulename] = rule
|
|
|
|
end
|
|
|
|
return rule
|
2016-06-05 08:39:29 +00:00
|
|
|
end
|
|
|
|
|
2016-06-06 18:50:48 +00:00
|
|
|
-----------------------------------------------------------------------------
|
|
|
|
-- DEFAULT RULES --
|
|
|
|
-----------------------------------------------------------------------------
|
|
|
|
|
2016-07-26 21:43:31 +00:00
|
|
|
local function install_make_emitter()
|
2016-06-19 06:55:02 +00:00
|
|
|
emit("hide = @\n")
|
2016-07-26 21:43:31 +00:00
|
|
|
function emitter:rule(name, ins, outs)
|
2016-06-19 06:55:02 +00:00
|
|
|
emit(".INTERMEDIATE:", name, "\n")
|
|
|
|
for i = 1, #ins do
|
|
|
|
emit(name..":", ins[i], "\n")
|
|
|
|
end
|
|
|
|
for i = 1, #outs do
|
2016-06-30 11:19:10 +00:00
|
|
|
emit(outs[i]..":", name, ";\n")
|
2016-06-19 06:55:02 +00:00
|
|
|
end
|
|
|
|
emit(name..":\n")
|
|
|
|
|
|
|
|
local dirs = uniquify(dirname(outs))
|
|
|
|
if (#dirs > 0) then
|
|
|
|
emit("\t@mkdir -p", dirs, "\n")
|
|
|
|
end
|
2016-06-16 03:26:44 +00:00
|
|
|
end
|
|
|
|
|
2016-07-26 21:43:31 +00:00
|
|
|
function emitter:phony(name, ins, outs)
|
2016-06-19 06:55:02 +00:00
|
|
|
emit(".PHONY:", name, "\n")
|
|
|
|
self:rule(name, ins, outs)
|
2016-06-06 20:13:30 +00:00
|
|
|
end
|
|
|
|
|
2016-07-26 21:43:31 +00:00
|
|
|
function emitter:label(...)
|
2016-06-19 06:55:02 +00:00
|
|
|
local s = table.concat({...}, " ")
|
|
|
|
emit("\t@echo", s, "\n")
|
|
|
|
end
|
2016-06-14 05:34:14 +00:00
|
|
|
|
2016-07-26 21:43:31 +00:00
|
|
|
function emitter:exec(commands)
|
2016-06-19 06:55:02 +00:00
|
|
|
for _, s in ipairs(commands) do
|
|
|
|
emit("\t$(hide)", s, "\n")
|
|
|
|
end
|
|
|
|
end
|
2016-06-06 20:13:30 +00:00
|
|
|
|
2016-07-26 21:43:31 +00:00
|
|
|
function emitter:endrule()
|
2016-06-19 06:55:02 +00:00
|
|
|
emit("\n")
|
2016-06-14 05:34:14 +00:00
|
|
|
end
|
2016-06-06 22:10:22 +00:00
|
|
|
end
|
|
|
|
|
2016-07-26 21:43:31 +00:00
|
|
|
local function install_ninja_emitter()
|
2016-06-19 06:55:02 +00:00
|
|
|
emit("rule build\n")
|
|
|
|
emit(" command = $command\n")
|
2016-06-06 22:10:22 +00:00
|
|
|
emit("\n")
|
2016-06-19 06:55:02 +00:00
|
|
|
|
|
|
|
local function unmake(collection)
|
2016-08-04 21:51:19 +00:00
|
|
|
return dotocollection({collection},
|
2016-06-19 06:55:02 +00:00
|
|
|
function(s)
|
|
|
|
return s:gsub("%$%b()",
|
|
|
|
function(expr)
|
|
|
|
return "${"..expr:sub(3, -2).."}"
|
|
|
|
end
|
|
|
|
)
|
|
|
|
end
|
|
|
|
)
|
|
|
|
end
|
|
|
|
|
2016-07-26 21:43:31 +00:00
|
|
|
function emitter:rule(name, ins, outs)
|
2016-06-19 06:55:02 +00:00
|
|
|
if (#outs == 0) then
|
|
|
|
emit("build", name, ": phony", unmake(ins), "\n")
|
|
|
|
else
|
|
|
|
emit("build", name, ": phony", unmake(outs), "\n")
|
|
|
|
emit("build", unmake(outs), ": build", unmake(ins), "\n")
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2016-07-26 21:43:31 +00:00
|
|
|
function emitter:label(...)
|
2016-06-19 06:55:02 +00:00
|
|
|
end
|
|
|
|
|
2016-07-26 21:43:31 +00:00
|
|
|
function emitter:exec(commands)
|
2016-06-19 06:55:02 +00:00
|
|
|
emit(" command =", table.concat(unmake(commands), " && "), "\n")
|
|
|
|
end
|
|
|
|
|
2016-07-26 21:43:31 +00:00
|
|
|
function emitter:endrule()
|
2016-06-19 06:55:02 +00:00
|
|
|
emit("\n")
|
|
|
|
end
|
2016-06-06 20:13:30 +00:00
|
|
|
end
|
|
|
|
|
2016-06-06 18:50:48 +00:00
|
|
|
definerule("simplerule",
|
|
|
|
{
|
|
|
|
ins = { type="targets" },
|
|
|
|
outs = { type="strings" },
|
2016-07-14 21:53:34 +00:00
|
|
|
deps = { type="targets", default={} },
|
2016-06-06 20:13:30 +00:00
|
|
|
label = { type="string", optional=true },
|
|
|
|
commands = { type="strings" },
|
2016-06-07 03:00:26 +00:00
|
|
|
vars = { type="table", default={} },
|
2016-06-06 18:50:48 +00:00
|
|
|
},
|
|
|
|
function (e)
|
2016-08-04 21:51:19 +00:00
|
|
|
emitter:rule(e.fullname, filenamesof(e.ins, e.deps), e.outs)
|
2016-07-26 21:43:31 +00:00
|
|
|
emitter:label(e.fullname, " ", e.label or "")
|
2016-06-07 03:00:26 +00:00
|
|
|
|
2016-06-09 04:55:44 +00:00
|
|
|
local vars = inherit(e.vars, {
|
2016-08-04 21:51:19 +00:00
|
|
|
ins = filenamesof(e.ins),
|
|
|
|
outs = filenamesof(e.outs)
|
2016-06-09 04:55:44 +00:00
|
|
|
})
|
2016-06-07 03:00:26 +00:00
|
|
|
|
2016-07-26 21:43:31 +00:00
|
|
|
emitter:exec(templateexpand(e.commands, vars))
|
|
|
|
emitter:endrule()
|
2016-06-06 22:10:22 +00:00
|
|
|
|
|
|
|
return {
|
|
|
|
outs = e.outs
|
|
|
|
}
|
2016-06-06 18:50:48 +00:00
|
|
|
end
|
|
|
|
)
|
|
|
|
|
2016-06-14 05:34:14 +00:00
|
|
|
definerule("installable",
|
|
|
|
{
|
2016-08-08 21:55:23 +00:00
|
|
|
map = { type="targets", default={} },
|
2016-06-14 05:34:14 +00:00
|
|
|
},
|
|
|
|
function (e)
|
|
|
|
local deps = {}
|
|
|
|
local commands = {}
|
|
|
|
local srcs = {}
|
|
|
|
local dests = {}
|
|
|
|
for dest, src in pairs(e.map) do
|
|
|
|
if src.is.installable then
|
|
|
|
if (type(dest) ~= "number") then
|
|
|
|
error("can't specify a destination filename when installing an installable")
|
|
|
|
end
|
|
|
|
deps[#deps+1] = src.fullname
|
|
|
|
elseif (type(dest) == "number") then
|
|
|
|
error("only references to other installables can be missing a destination")
|
|
|
|
else
|
|
|
|
local f = filenamesof(src)
|
|
|
|
if (#f ~= 1) then
|
|
|
|
error("installable can only cope with targets emitting single files")
|
|
|
|
end
|
|
|
|
|
2016-08-04 21:51:19 +00:00
|
|
|
deps[#deps+1] = src.fullname
|
2016-06-14 05:34:14 +00:00
|
|
|
dests[#dests+1] = dest
|
|
|
|
commands[#commands+1] = "cp "..f[1].." "..dest
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2016-07-26 21:43:31 +00:00
|
|
|
emitter:rule(e.fullname, deps, dests)
|
|
|
|
emitter:label(e.fullname, " ", e.label or "")
|
2016-06-16 03:26:44 +00:00
|
|
|
if (#commands > 0) then
|
2016-07-26 21:43:31 +00:00
|
|
|
emitter:exec(commands)
|
2016-06-14 05:34:14 +00:00
|
|
|
end
|
2016-07-26 21:43:31 +00:00
|
|
|
emitter:endrule()
|
2016-07-28 22:22:22 +00:00
|
|
|
|
|
|
|
return {
|
|
|
|
outs = dests
|
|
|
|
}
|
2016-06-14 05:34:14 +00:00
|
|
|
end
|
|
|
|
)
|
|
|
|
|
2016-06-06 15:18:19 +00:00
|
|
|
-----------------------------------------------------------------------------
|
|
|
|
-- MAIN PROGRAM --
|
|
|
|
-----------------------------------------------------------------------------
|
2016-06-05 08:39:29 +00:00
|
|
|
|
2016-06-19 06:55:02 +00:00
|
|
|
local function parse_arguments(argmap, arg)
|
|
|
|
local i = 1
|
|
|
|
local files = {}
|
|
|
|
|
|
|
|
local function unrecognisedarg(arg)
|
|
|
|
argmap[" unrecognised"](arg)
|
|
|
|
end
|
|
|
|
|
|
|
|
while (i <= #arg) do
|
|
|
|
local o = arg[i]
|
|
|
|
local op
|
|
|
|
|
|
|
|
if (o:byte(1) == 45) then
|
|
|
|
-- This is an option.
|
|
|
|
if (o:byte(2) == 45) then
|
|
|
|
-- ...with a -- prefix.
|
|
|
|
o = o:sub(3)
|
|
|
|
local fn = argmap[o]
|
|
|
|
if not fn then
|
|
|
|
unrecognisedarg("--"..o)
|
|
|
|
end
|
|
|
|
i = i + fn(arg[i+1], arg[i+2])
|
|
|
|
else
|
|
|
|
-- ...without a -- prefix.
|
|
|
|
local od = o:sub(2, 2)
|
|
|
|
local fn = argmap[od]
|
|
|
|
if not fn then
|
|
|
|
unrecognisedarg("-"..od)
|
|
|
|
end
|
|
|
|
op = o:sub(3)
|
|
|
|
if (op == "") then
|
|
|
|
i = i + fn(arg[i+1], arg[i+2])
|
|
|
|
else
|
|
|
|
fn(op)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
else
|
|
|
|
files[#files+1] = o
|
|
|
|
end
|
|
|
|
i = i + 1
|
|
|
|
end
|
|
|
|
|
|
|
|
argmap[" files"](files)
|
|
|
|
end
|
|
|
|
|
2016-06-06 18:50:48 +00:00
|
|
|
globals = {
|
2016-07-18 21:16:27 +00:00
|
|
|
posix = posix,
|
|
|
|
|
2016-06-12 18:59:43 +00:00
|
|
|
abspath = abspath,
|
2016-06-06 15:18:19 +00:00
|
|
|
asstring = asstring,
|
2016-06-07 03:00:26 +00:00
|
|
|
basename = basename,
|
2016-07-14 21:53:34 +00:00
|
|
|
concat = concat,
|
2016-06-07 02:13:56 +00:00
|
|
|
concatpath = concatpath,
|
2016-06-12 18:59:43 +00:00
|
|
|
cwd = function() return cwd end,
|
2016-06-06 15:18:19 +00:00
|
|
|
definerule = definerule,
|
2016-06-07 03:00:26 +00:00
|
|
|
dirname = dirname,
|
2016-06-06 15:18:19 +00:00
|
|
|
emit = emit,
|
2016-06-07 03:00:26 +00:00
|
|
|
filenamesof = filenamesof,
|
2016-07-14 21:53:34 +00:00
|
|
|
fpairs = fpairs,
|
2016-06-29 11:28:22 +00:00
|
|
|
include = loadbuildfile,
|
2016-06-09 04:55:44 +00:00
|
|
|
inherit = inherit,
|
2016-07-28 22:22:22 +00:00
|
|
|
print = print,
|
2016-06-29 11:28:22 +00:00
|
|
|
replace = replace,
|
2016-08-04 21:51:19 +00:00
|
|
|
matching = matching,
|
2016-06-07 03:00:26 +00:00
|
|
|
selectof = selectof,
|
2016-06-09 04:55:44 +00:00
|
|
|
startswith = startswith,
|
2016-06-08 01:21:53 +00:00
|
|
|
uniquify = uniquify,
|
2016-07-26 22:10:15 +00:00
|
|
|
vars = vars,
|
2016-06-06 15:18:19 +00:00
|
|
|
}
|
2016-06-06 18:50:48 +00:00
|
|
|
setmetatable(globals,
|
|
|
|
{
|
|
|
|
__index = function(self, k)
|
|
|
|
local rule = rules[k]
|
|
|
|
if rule then
|
|
|
|
return rule
|
|
|
|
else
|
|
|
|
return _G[k]
|
|
|
|
end
|
|
|
|
end
|
|
|
|
}
|
|
|
|
)
|
2016-06-05 08:39:29 +00:00
|
|
|
|
2016-07-26 22:10:15 +00:00
|
|
|
vars.cflags = {}
|
|
|
|
parente.vars = vars
|
|
|
|
|
2016-08-04 22:01:55 +00:00
|
|
|
setmetatable(_G,
|
|
|
|
{
|
|
|
|
__index = function(self, k)
|
|
|
|
local value = rawget(_G, k)
|
|
|
|
if not value then
|
|
|
|
error(string.format("access of undefined variable '%s'", k))
|
|
|
|
end
|
|
|
|
return value
|
|
|
|
end
|
|
|
|
}
|
|
|
|
)
|
|
|
|
|
2016-06-19 06:55:02 +00:00
|
|
|
do
|
2016-07-26 21:43:31 +00:00
|
|
|
local emitter_type = install_make_emitter
|
2016-06-19 06:55:02 +00:00
|
|
|
parse_arguments(
|
|
|
|
{
|
|
|
|
["make"] = function()
|
2016-07-26 21:43:31 +00:00
|
|
|
emitter_type = install_make_emitter
|
2016-06-19 06:55:02 +00:00
|
|
|
return 1
|
|
|
|
end,
|
|
|
|
|
|
|
|
["ninja"] = function()
|
2016-07-26 21:43:31 +00:00
|
|
|
emitter_type = install_ninja_emitter
|
2016-06-19 06:55:02 +00:00
|
|
|
return 1
|
|
|
|
end,
|
|
|
|
|
|
|
|
[" unrecognised"] = function(arg)
|
|
|
|
error(string.format("unrecognised argument '%s'", arg))
|
|
|
|
end,
|
|
|
|
|
|
|
|
[" files"] = function(files)
|
2016-07-26 21:43:31 +00:00
|
|
|
emitter_type()
|
2016-06-19 06:55:02 +00:00
|
|
|
for _, f in ipairs(files) do
|
|
|
|
loadbuildfile(f)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
},
|
|
|
|
{...}
|
|
|
|
)
|
2016-06-05 08:39:29 +00:00
|
|
|
end
|
|
|
|
|