mirror of
https://github.com/oasislinux/oasis.git
synced 2025-12-14 20:35:43 +01:00
522 lines
11 KiB
Lua
522 lines
11 KiB
Lua
--
|
|
-- 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
|