ninja.lua (11164B)
- --
- -- utility functions
- --
- function string.hasprefix(s, prefix)
- return s:sub(1, #prefix) == prefix
- end
- function string.hassuffix(s, suffix)
- return s:sub(-#suffix) == suffix
- end
- -- collects the results of an iterator into a table
- local function collect(f, s, i)
- local t = {}
- for v in f, s, i do
- t[#t + 1] = v
- end
- return t
- end
- -- collects the keys of a table into a sorted table
- function table.keys(t, f)
- local keys = collect(next, t)
- table.sort(keys, f)
- return keys
- end
- -- iterates over the sorted keys and values of a table
- function sortedpairs(t, f)
- return function(s, i)
- local k = s[i]
- return k and i + 1, k, t[k]
- end, table.keys(t, f), 1
- end
- -- yields string values of table or nested tables
- local function stringsgen(t)
- for _, val in ipairs(t) do
- if type(val) == 'string' then
- coroutine.yield(val)
- else
- stringsgen(val)
- end
- end
- end
- function iterstrings(x)
- return coroutine.wrap(stringsgen), x
- end
- function strings(s)
- return collect(iterstrings(s))
- end
- -- yields strings generated by concateting all strings in a table, for every
- -- combination of strings in subtables
- local function expandgen(t, i)
- while true do
- local val
- i, val = next(t, i)
- if not i then
- coroutine.yield(table.concat(t))
- break
- elseif type(val) == 'table' then
- for opt in iterstrings(val) do
- t[i] = opt
- expandgen(t, i)
- end
- t[i] = val
- break
- end
- end
- end
- function expand(t)
- return collect(coroutine.wrap(expandgen), t)
- end
- -- yields expanded paths from the given path specification string
- local function pathsgen(s, i)
- local results = {}
- local first = not i
- while true do
- i = s:find('[^%s]', i)
- local _, j, arch = s:find('^@([^%s()]*)%s*[^%s]?', i)
- if arch then i = j end
- if not i or s:sub(i, i) == ')' then
- break
- end
- local parts, nparts = {}, 0
- local c
- while true do
- local j = s:find('[%s()]', i)
- if not j or j > i then
- nparts = nparts + 1
- parts[nparts] = s:sub(i, j and j - 1)
- end
- i = j
- c = i and s:sub(i, i)
- if c == '(' then
- local opts, nopts = {}, 0
- local fn = coroutine.wrap(pathsgen)
- local opt
- opt, i = fn(s, i + 1)
- while opt do
- nopts = nopts + 1
- opts[nopts] = opt
- opt, i = fn(s)
- end
- nparts = nparts + 1
- parts[nparts] = opts
- if not i or s:sub(i, i) ~= ')' then
- error('unmatched (')
- end
- i = i + 1
- c = s:sub(i, i)
- else
- break
- end
- end
- if not arch or arch == config.target.platform:match('[^-]*') then
- expandgen(parts)
- end
- if not c or c == ')' then
- break
- end
- end
- if first and i then
- error('unmatched )')
- end
- return nil, i
- end
- function iterpaths(s)
- return coroutine.wrap(pathsgen), s
- end
- function paths(s)
- return collect(iterpaths(s))
- end
- -- yields non-empty non-comment lines in a file
- local function linesgen(file)
- for line in io.lines(file) do
- if #line > 0 and not line:hasprefix('#') then
- coroutine.yield(line)
- end
- end
- end
- function iterlines(file, raw)
- table.insert(pkg.inputs.gen, '$dir/'..file)
- file = string.format('%s/%s/%s', basedir, pkg.gendir, file)
- if raw then
- return io.lines(file)
- end
- return coroutine.wrap(linesgen), file
- end
- function lines(file, raw)
- return collect(iterlines(file, raw))
- end
- function load(file)
- table.insert(pkg.inputs.gen, '$dir/'..file)
- return dofile(string.format('%s/%s/%s', basedir, pkg.gendir, file))
- end
- --
- -- base constructs
- --
- function set(var, val, indent)
- if type(val) == 'table' then
- val = table.concat(val, ' ')
- end
- io.write(string.format('%s%s = %s\n', indent or '', var, val))
- end
- function subninja(file)
- if not file:match('^[$/]') then
- file = '$gendir/'..file
- end
- io.write(string.format('subninja %s\n', file))
- end
- function include(file)
- io.write(string.format('include %s\n', file))
- end
- local function let(bindings)
- for var, val in pairs(bindings) do
- set(var, val, ' ')
- end
- end
- function rule(name, cmd, bindings)
- io.write(string.format('rule %s\n command = %s\n', name, cmd))
- if bindings then
- let(bindings)
- end
- end
- function build(rule, outputs, inputs, bindings)
- if type(outputs) == 'table' then
- outputs = table.concat(strings(outputs), ' ')
- end
- if not inputs then
- inputs = ''
- elseif type(inputs) == 'table' then
- local srcs, nsrcs = {}, 0
- for src in iterstrings(inputs) do
- nsrcs = nsrcs + 1
- srcs[nsrcs] = src
- if src:hasprefix('$srcdir/') then
- pkg.inputs.fetch[src] = true
- end
- end
- inputs = table.concat(srcs, ' ')
- elseif inputs:hasprefix('$srcdir/') then
- pkg.inputs.fetch[inputs] = true
- end
- io.write(string.format('build %s: %s %s\n', outputs, rule, inputs))
- if bindings then
- let(bindings)
- end
- end
- --
- -- higher-level rules
- --
- function sub(name, fn)
- local old = io.output()
- io.output(pkg.gendir..'/'..name)
- fn()
- io.output(old)
- subninja(name)
- end
- function toolchain(tc)
- set('ar', tc.ar or (tc.platform and tc.platform..'-ar') or 'ar')
- set('as', tc.as or (tc.platform and tc.platform..'-as') or 'as')
- set('cc', tc.cc or (tc.platform and tc.platform..'-cc') or 'cc')
- set('ld', tc.ld or (tc.platform and tc.platform..'-ld') or 'ld')
- set('objcopy', tc.objcopy or (tc.platform and tc.platform..'-objcopy') or 'objcopy')
- set('mc', tc.mc or 'false')
- set('cflags', tc.cflags)
- set('ldflags', tc.ldflags)
- end
- function phony(name, inputs)
- build('phony', '$gendir/'..name, inputs)
- end
- function cflags(flags)
- set('cflags', '$cflags '..table.concat(flags, ' '))
- end
- function nasmflags(flags)
- set('nasmflags', '$nasmflags '..table.concat(flags, ' '))
- end
- function compile(rule, src, deps, args)
- local obj = src..'.o'
- if not src:match('^[$/]') then
- src = '$srcdir/'..src
- obj = '$outdir/'..obj
- end
- if not deps and pkg.deps then
- deps = '$gendir/deps'
- end
- if deps then
- src = {src, '||', deps}
- end
- build(rule, obj, src, args)
- return obj
- end
- function cc(src, deps, args)
- return compile('cc', src, deps, args)
- end
- function objects(srcs, deps, args)
- local objs, nobjs = {}, 0
- local rules = {
- c='cc',
- s='cc',
- S='cc',
- cc='cc',
- cpp='cc',
- asm='nasm',
- }
- local fn
- if type(srcs) == 'string' then
- fn = coroutine.wrap(pathsgen)
- else
- fn = coroutine.wrap(stringsgen)
- end
- for src in fn, srcs do
- local rule = rules[src:match('[^.]*$')]
- if rule then
- src = compile(rule, src, deps, args)
- end
- nobjs = nobjs + 1
- objs[nobjs] = src
- end
- return objs
- end
- function link(out, files, args)
- local objs = {}
- local deps = {}
- for _, file in ipairs(files) do
- if not file:match('^[$/]') then
- file = '$outdir/'..file
- end
- if file:hassuffix('.d') then
- deps[#deps + 1] = file
- else
- objs[#objs + 1] = file
- end
- end
- out = '$outdir/'..out
- if not args then
- args = {}
- end
- if next(deps) then
- local rsp = out..'.rsp'
- build('awk', rsp, {deps, '|', '$basedir/scripts/rsp.awk'}, {expr='-f $basedir/scripts/rsp.awk'})
- objs[#objs + 1] = '|'
- objs[#objs + 1] = rsp
- args.ldlibs = '@'..rsp
- end
- build('link', out, objs, args)
- return out
- end
- function ar(out, files)
- out = '$outdir/'..out
- local objs, nobjs = {}, 0
- local deps, ndeps = {out}, 1
- for _, file in ipairs(files) do
- if not file:match('^[$/]') then
- file = '$outdir/'..file
- end
- if file:find('%.[ad]$') then
- ndeps = ndeps + 1
- deps[ndeps] = file
- else
- nobjs = nobjs + 1
- objs[nobjs] = file
- end
- end
- build('ar', out, objs)
- build('rsp', out..'.d', deps)
- end
- function lib(out, srcs, deps)
- return ar(out, objects(srcs, deps))
- end
- function exe(out, srcs, deps, args)
- return link(out, objects(srcs, deps), args)
- end
- function yacc(name, gram)
- if not gram:match('^[$/]') then
- gram = '$srcdir/'..gram
- end
- build('yacc', expand{'$outdir/', name, {'.tab.c', '.tab.h'}}, gram, {
- yaccflags='-d -b $outdir/'..name,
- })
- end
- function waylandproto(proto, outs, args)
- proto = '$srcdir/'..proto
- if outs.client then
- build('wayland-proto', '$outdir/'..outs.client, proto, {type='client-header'})
- end
- if outs.server then
- build('wayland-proto', '$outdir/'..outs.server, proto, {type='server-header'})
- end
- if outs.code then
- local code = '$outdir/'..outs.code
- build('wayland-proto', code, proto, {type='public-code'})
- cc(code, {'pkg/wayland/headers'}, args)
- end
- end
- function fetch(method)
- local script
- local deps = {'|', '$dir/ver', script}
- if method == 'local' then
- script = '$dir/fetch.sh'
- else
- script = '$basedir/scripts/fetch-'..method..'.sh'
- end
- if method ~= 'git' then
- table.insert(deps, '$builddir/pkg/pax/host/pax')
- end
- build('fetch', '$dir/fetch', deps, {script=script})
- if basedir ~= '.' then
- build('phony', '$gendir/fetch', '$dir/fetch')
- end
- if next(pkg.inputs.fetch) then
- build('phony', table.keys(pkg.inputs.fetch), '$dir/fetch')
- end
- end
- local function findany(path, pats)
- for _, pat in pairs(pats) do
- if path:find(pat) then
- return true
- end
- end
- return false
- end
- local function specmatch(spec, path)
- if spec.include and not findany(path, spec.include) then
- return false
- end
- if spec.exclude and findany(path, spec.exclude) then
- return false
- end
- return true
- end
- local function fs(name, path)
- for _, spec in ipairs(config.fs) do
- for specname in iterstrings(spec) do
- if name == specname then
- return specmatch(spec, path)
- end
- end
- end
- return (config.fs.include or config.fs.exclude) and specmatch(config.fs, path)
- end
- function gitfile(path, mode, src)
- local out = '$builddir/root.hash/'..path
- local perm = ('10%04o %s'):format(tonumber(mode, 8), path)
- build('git-hash', out, {src, '|', '$basedir/scripts/hash.sh', '||', '$builddir/root.stamp'}, {
- args=perm,
- })
- table.insert(pkg.inputs.index, out)
- end
- function file(path, mode, src)
- if pkg.gendir:hasprefix('pkg/') and not fs(pkg.name, path) then
- return
- end
- mode = ('%04o'):format(tonumber(mode, 8))
- pkg.fspec[path] = {
- type='reg',
- mode=mode,
- source=src:gsub('^%$(%w+)', pkg, 1),
- }
- gitfile(path, mode, src)
- end
- function dir(path, mode)
- if pkg.gendir:hasprefix('pkg/') and not fs(pkg.name, path) then
- return
- end
- pkg.fspec[path] = {
- type='dir',
- mode=('%04o'):format(tonumber(mode, 8)),
- }
- end
- function sym(path, target)
- if pkg.gendir:hasprefix('pkg/') and not fs(pkg.name, path) then
- return
- end
- pkg.fspec[path] = {
- type='sym',
- mode='0777',
- target=target,
- }
- local out = '$builddir/root.hash/'..path
- build('git-hash', out, {'|', '$basedir/scripts/hash.sh', '||', '$builddir/root.stamp'}, {
- args=string.format('120000 %s %s', path, target),
- })
- table.insert(pkg.inputs.index, out)
- end
- function man(srcs, section)
- for _, src in ipairs(srcs) do
- local out = src..'.gz'
- if not src:match('^[$/]') then
- src = '$srcdir/'..src
- out = '$outdir/'..out
- end
- local base = src:match('[^/]*$')
- local ext = base:match('%.([^.]*)$')
- if ext then base = base:sub(1, -(#ext + 2)) end
- if section then ext = section end
- local path = 'share/man/man'..ext..'/'..base..'.'..ext
- if config.gzman ~= false then
- build('gzip', out, src)
- src = out
- path = path..'.gz'
- end
- file(path, '644', src)
- end
- end
- function copy(outdir, srcdir, files)
- local outs = {}
- for file in iterstrings(files) do
- local out = outdir..'/'..file
- table.insert(outs, out)
- build('copy', out, srcdir..'/'..file)
- end
- return outs
- end