opkg

statically linked package installer
git clone anongit@rnpnr.xyz:opkg.git
Log | Files | Refs | Feed | Submodules | README | LICENSE

ninja.lua (11173B)


      1 --
      2 -- utility functions
      3 --
      4 
      5 function string.hasprefix(s, prefix)
      6 	return s:sub(1, #prefix) == prefix
      7 end
      8 
      9 function string.hassuffix(s, suffix)
     10 	return s:sub(-#suffix) == suffix
     11 end
     12 
     13 -- collects the results of an iterator into a table
     14 local function collect(f, s, i)
     15 	local t = {}
     16 	for v in f, s, i do
     17 		t[#t + 1] = v
     18 	end
     19 	return t
     20 end
     21 
     22 -- collects the keys of a table into a sorted table
     23 function table.keys(t, f)
     24 	local keys = collect(next, t)
     25 	table.sort(keys, f)
     26 	return keys
     27 end
     28 
     29 -- iterates over the sorted keys and values of a table
     30 function sortedpairs(t, f)
     31 	return function(s, i)
     32 		local k = s[i]
     33 		return k and i + 1, k, t[k]
     34 	end, table.keys(t, f), 1
     35 end
     36 
     37 -- yields string values of table or nested tables
     38 local function stringsgen(t)
     39 	for _, val in ipairs(t) do
     40 		if type(val) == 'string' then
     41 			coroutine.yield(val)
     42 		else
     43 			stringsgen(val)
     44 		end
     45 	end
     46 end
     47 
     48 function iterstrings(x)
     49 	return coroutine.wrap(stringsgen), x
     50 end
     51 
     52 function strings(s)
     53 	return collect(iterstrings(s))
     54 end
     55 
     56 -- yields strings generated by concateting all strings in a table, for every
     57 -- combination of strings in subtables
     58 local function expandgen(t, i)
     59 	while true do
     60 		local val
     61 		i, val = next(t, i)
     62 		if not i then
     63 			coroutine.yield(table.concat(t))
     64 			break
     65 		elseif type(val) == 'table' then
     66 			for opt in iterstrings(val) do
     67 				t[i] = opt
     68 				expandgen(t, i)
     69 			end
     70 			t[i] = val
     71 			break
     72 		end
     73 	end
     74 end
     75 
     76 function expand(t)
     77 	return collect(coroutine.wrap(expandgen), t)
     78 end
     79 
     80 -- yields expanded paths from the given path specification string
     81 local function pathsgen(s, i)
     82 	local results = {}
     83 	local first = not i
     84 	while true do
     85 		i = s:find('[^%s]', i)
     86 		local _, j, arch = s:find('^@([^%s()]*)%s*[^%s]?', i)
     87 		if arch then i = j end
     88 		if not i or s:sub(i, i) == ')' then
     89 			break
     90 		end
     91 		local parts, nparts = {}, 0
     92 		local c
     93 		while true do
     94 			local j = s:find('[%s()]', i)
     95 			if not j or j > i then
     96 				nparts = nparts + 1
     97 				parts[nparts] = s:sub(i, j and j - 1)
     98 			end
     99 			i = j
    100 			c = i and s:sub(i, i)
    101 			if c == '(' then
    102 				local opts, nopts = {}, 0
    103 				local fn = coroutine.wrap(pathsgen)
    104 				local opt
    105 				opt, i = fn(s, i + 1)
    106 				while opt do
    107 					nopts = nopts + 1
    108 					opts[nopts] = opt
    109 					opt, i = fn(s)
    110 				end
    111 				nparts = nparts + 1
    112 				parts[nparts] = opts
    113 				if not i or s:sub(i, i) ~= ')' then
    114 					error('unmatched (')
    115 				end
    116 				i = i + 1
    117 				c = s:sub(i, i)
    118 			else
    119 				break
    120 			end
    121 		end
    122 		if not arch or arch == config.target.platform:match('[^-]*') then
    123 			expandgen(parts)
    124 		end
    125 		if not c or c == ')' then
    126 			break
    127 		end
    128 	end
    129 	if first and i then
    130 		error('unmatched )')
    131 	end
    132 	return nil, i
    133 end
    134 
    135 function iterpaths(s)
    136 	return coroutine.wrap(pathsgen), s
    137 end
    138 
    139 function paths(s)
    140 	return collect(iterpaths(s))
    141 end
    142 
    143 -- yields non-empty non-comment lines in a file
    144 local function linesgen(file)
    145 	for line in io.lines(file) do
    146 		if #line > 0 and not line:hasprefix('#') then
    147 			coroutine.yield(line)
    148 		end
    149 	end
    150 end
    151 
    152 function iterlines(file, raw)
    153 	table.insert(pkg.inputs.gen, '$dir/'..file)
    154 	file = string.format('%s/%s/%s', basedir, pkg.gendir, file)
    155 	if raw then
    156 		return io.lines(file)
    157 	end
    158 	return coroutine.wrap(linesgen), file
    159 end
    160 
    161 function lines(file, raw)
    162 	return collect(iterlines(file, raw))
    163 end
    164 
    165 function load(file)
    166 	table.insert(pkg.inputs.gen, '$dir/'..file)
    167 	return dofile(string.format('%s/%s/%s', basedir, pkg.gendir, file))
    168 end
    169 
    170 --
    171 -- base constructs
    172 --
    173 
    174 function set(var, val, indent)
    175 	if type(val) == 'table' then
    176 		val = table.concat(val, ' ')
    177 	end
    178 	io.write(string.format('%s%s = %s\n', indent or '', var, val))
    179 end
    180 
    181 function subninja(file)
    182 	if not file:match('^[$/]') then
    183 		file = '$gendir/'..file
    184 	end
    185 	io.write(string.format('subninja %s\n', file))
    186 end
    187 
    188 function include(file)
    189 	io.write(string.format('include %s\n', file))
    190 end
    191 
    192 local function let(bindings)
    193 	for var, val in pairs(bindings) do
    194 		set(var, val, '  ')
    195 	end
    196 end
    197 
    198 function rule(name, cmd, bindings)
    199 	io.write(string.format('rule %s\n  command = %s\n', name, cmd))
    200 	if bindings then
    201 		let(bindings)
    202 	end
    203 end
    204 
    205 function build(rule, outputs, inputs, bindings)
    206 	if type(outputs) == 'table' then
    207 		outputs = table.concat(strings(outputs), ' ')
    208 	end
    209 	if not inputs then
    210 		inputs = ''
    211 	elseif type(inputs) == 'table' then
    212 		local srcs, nsrcs = {}, 0
    213 		for src in iterstrings(inputs) do
    214 			nsrcs = nsrcs + 1
    215 			srcs[nsrcs] = src
    216 			if src:hasprefix('$srcdir/') then
    217 				pkg.inputs.fetch[src] = true
    218 			end
    219 		end
    220 		inputs = table.concat(srcs, ' ')
    221 	elseif inputs:hasprefix('$srcdir/') then
    222 		pkg.inputs.fetch[inputs] = true
    223 	end
    224 	io.write(string.format('build %s: %s %s\n', outputs, rule, inputs))
    225 	if bindings then
    226 		let(bindings)
    227 	end
    228 end
    229 
    230 --
    231 -- higher-level rules
    232 --
    233 
    234 function sub(name, fn)
    235 	local old = io.output()
    236 	io.output(pkg.gendir..'/'..name)
    237 	fn()
    238 	io.output(old)
    239 	subninja(name)
    240 end
    241 
    242 function toolchain(tc)
    243 	set('ar', tc.ar or (tc.platform and tc.platform..'-ar') or 'ar')
    244 	set('as', tc.as or (tc.platform and tc.platform..'-as') or 'as')
    245 	set('cc', tc.cc or (tc.platform and tc.platform..'-cc') or 'cc')
    246 	set('ld', tc.ld or (tc.platform and tc.platform..'-ld') or 'ld')
    247 	set('objcopy', tc.objcopy or (tc.platform and tc.platform..'-objcopy') or 'objcopy')
    248 	set('mc', tc.mc or 'false')
    249 
    250 	set('cflags', tc.cflags)
    251 	set('ldflags', tc.ldflags)
    252 end
    253 
    254 function phony(name, inputs)
    255 	build('phony', '$gendir/'..name, inputs)
    256 end
    257 
    258 function cflags(flags)
    259 	set('cflags', '$cflags '..table.concat(flags, ' '))
    260 end
    261 
    262 function nasmflags(flags)
    263 	set('nasmflags', '$nasmflags '..table.concat(flags, ' '))
    264 end
    265 
    266 function compile(rule, src, deps, args)
    267 	local obj = src..'.o'
    268 	if not src:match('^[$/]') then
    269 		src = '$srcdir/'..src
    270 		obj = '$outdir/'..obj
    271 	end
    272 	if not deps and pkg.deps then
    273 		deps = '$gendir/deps'
    274 	end
    275 	if deps then
    276 		src = {src, '||', deps}
    277 	end
    278 	build(rule, obj, src, args)
    279 	return obj
    280 end
    281 
    282 function cc(src, deps, args)
    283 	return compile('cc', src, deps, args)
    284 end
    285 
    286 function objects(srcs, deps, args)
    287 	local objs, nobjs = {}, 0
    288 	local rules = {
    289 		c='cc',
    290 		s='cc',
    291 		S='cc',
    292 		cc='cc',
    293 		cpp='cc',
    294 		asm='nasm',
    295 	}
    296 	local fn
    297 	if type(srcs) == 'string' then
    298 		fn = coroutine.wrap(pathsgen)
    299 	else
    300 		fn = coroutine.wrap(stringsgen)
    301 	end
    302 	for src in fn, srcs do
    303 		local rule = rules[src:match('[^.]*$')]
    304 		if rule then
    305 			src = compile(rule, src, deps, args)
    306 		end
    307 		nobjs = nobjs + 1
    308 		objs[nobjs] = src
    309 	end
    310 	return objs
    311 end
    312 
    313 function link(out, files, args)
    314 	local objs = {}
    315 	local deps = {}
    316 	for _, file in ipairs(files) do
    317 		if not file:match('^[$/]') then
    318 			file = '$outdir/'..file
    319 		end
    320 		if file:hassuffix('.d') then
    321 			deps[#deps + 1] = file
    322 		else
    323 			objs[#objs + 1] = file
    324 		end
    325 	end
    326 	out = '$outdir/'..out
    327 	if not args then
    328 		args = {}
    329 	end
    330 	if next(deps) then
    331 		local rsp = out..'.rsp'
    332 		build('awk', rsp, {deps, '|', '$basedir/scripts/rsp.awk'}, {expr='-f $basedir/scripts/rsp.awk'})
    333 		objs[#objs + 1] = '|'
    334 		objs[#objs + 1] = rsp
    335 		args.ldlibs = '@'..rsp
    336 	end
    337 	build('link', out, objs, args)
    338 	return out
    339 end
    340 
    341 function ar(out, files)
    342 	out = '$outdir/'..out
    343 	local objs, nobjs = {}, 0
    344 	local deps, ndeps = {out}, 1
    345 	for _, file in ipairs(files) do
    346 		if not file:match('^[$/]') then
    347 			file = '$outdir/'..file
    348 		end
    349 		if file:find('%.[ad]$') then
    350 			ndeps = ndeps + 1
    351 			deps[ndeps] = file
    352 		else
    353 			nobjs = nobjs + 1
    354 			objs[nobjs] = file
    355 		end
    356 	end
    357 	build('ar', out, objs)
    358 	build('rsp', out..'.d', deps)
    359 end
    360 
    361 function lib(out, srcs, deps)
    362 	return ar(out, objects(srcs, deps))
    363 end
    364 
    365 function exe(out, srcs, deps, args)
    366 	return link(out, objects(srcs, deps), args)
    367 end
    368 
    369 function yacc(name, gram)
    370 	if not gram:match('^[$/]') then
    371 		gram = '$srcdir/'..gram
    372 	end
    373 	build('yacc', expand{'$outdir/', name, {'.tab.c', '.tab.h'}}, gram, {
    374 		yaccflags='-d -b $outdir/'..name,
    375 	})
    376 end
    377 
    378 function waylandproto(proto, outs, args)
    379 	proto = '$srcdir/'..proto
    380 	if outs.client then
    381 		build('wayland-proto', '$outdir/'..outs.client, proto, {type='client-header'})
    382 	end
    383 	if outs.server then
    384 		build('wayland-proto', '$outdir/'..outs.server, proto, {type='server-header'})
    385 	end
    386 	if outs.code then
    387 		local code = '$outdir/'..outs.code
    388 		build('wayland-proto', code, proto, {type='public-code'})
    389 		cc(code, {'pkg/wayland/headers'}, args)
    390 	end
    391 end
    392 
    393 function fetch(method)
    394 	local script
    395 	local deps = {'|', '$dir/ver', script}
    396 	if method == 'local' then
    397 		script = '$dir/fetch.sh'
    398 	else
    399 		script = '$basedir/scripts/fetch-'..method..'.sh'
    400 		if method == 'curl' then
    401 			table.insert(deps, '$builddir/pkg/arch/pax/host/pax')
    402 		end
    403 	end
    404 	build('fetch', '$dir/fetch', deps, {script=script})
    405 	if basedir ~= '.' then
    406 		build('phony', '$gendir/fetch', '$dir/fetch')
    407 	end
    408 	if next(pkg.inputs.fetch) then
    409 		build('phony', table.keys(pkg.inputs.fetch), '$dir/fetch')
    410 	end
    411 end
    412 
    413 local function findany(path, pats)
    414 	for _, pat in pairs(pats) do
    415 		if path:find(pat) then
    416 			return true
    417 		end
    418 	end
    419 	return false
    420 end
    421 
    422 local function specmatch(spec, path)
    423 	if spec.include and not findany(path, spec.include) then
    424 		return false
    425 	end
    426 	if spec.exclude and findany(path, spec.exclude) then
    427 		return false
    428 	end
    429 	return true
    430 end
    431 
    432 local function fs(name, path)
    433 	for _, spec in ipairs(config.fs) do
    434 		for specname in iterstrings(spec) do
    435 			if name == specname then
    436 				return specmatch(spec, path)
    437 			end
    438 		end
    439 	end
    440 	return (config.fs.include or config.fs.exclude) and specmatch(config.fs, path)
    441 end
    442 
    443 function gitfile(path, mode, src)
    444 	local out = '$builddir/root.hash/'..path
    445 	local perm = ('10%04o %s'):format(tonumber(mode, 8), path)
    446 	build('git-hash', out, {src, '|', '$basedir/scripts/hash.sh', '||', '$builddir/root.stamp'}, {
    447 		args=perm,
    448 	})
    449 	table.insert(pkg.inputs.index, out)
    450 end
    451 
    452 function file(path, mode, src)
    453 	if pkg.gendir:hasprefix('pkg/') and not fs(pkg.name, path) then
    454 		return
    455 	end
    456 	mode = ('%04o'):format(tonumber(mode, 8))
    457 	pkg.fspec[path] = {
    458 		type='reg',
    459 		mode=mode,
    460 		source=src:gsub('^%$(%w+)', pkg, 1),
    461 	}
    462 	gitfile(path, mode, src)
    463 end
    464 
    465 function dir(path, mode)
    466 	if pkg.gendir:hasprefix('pkg/') and not fs(pkg.name, path) then
    467 		return
    468 	end
    469 	pkg.fspec[path] = {
    470 		type='dir',
    471 		mode=('%04o'):format(tonumber(mode, 8)),
    472 	}
    473 end
    474 
    475 function sym(path, target)
    476 	if pkg.gendir:hasprefix('pkg/') and not fs(pkg.name, path) then
    477 		return
    478 	end
    479 	pkg.fspec[path] = {
    480 		type='sym',
    481 		mode='0777',
    482 		target=target,
    483 	}
    484 	local out = '$builddir/root.hash/'..path
    485 	build('git-hash', out, {'|', '$basedir/scripts/hash.sh', '||', '$builddir/root.stamp'}, {
    486 		args=string.format('120000 %s %s', path, target),
    487 	})
    488 	table.insert(pkg.inputs.index, out)
    489 end
    490 
    491 function man(srcs, section)
    492 	for _, src in ipairs(srcs) do
    493 		local out = src..'.gz'
    494 		if not src:match('^[$/]') then
    495 			src = '$srcdir/'..src
    496 			out = '$outdir/'..out
    497 		end
    498 
    499 		local base = src:match('[^/]*$')
    500 		local ext = base:match('%.([^.]*)$')
    501 		if ext then base = base:sub(1, -(#ext + 2)) end
    502 		if section then ext = section end
    503 		local path = 'share/man/man'..ext..'/'..base..'.'..ext
    504 		if config.gzman ~= false then
    505 			build('gzip', out, src)
    506 			src = out
    507 			path = path..'.gz'
    508 		end
    509 		file(path, '644', src)
    510 	end
    511 end
    512 
    513 function copy(outdir, srcdir, files)
    514 	local outs = {}
    515 	for file in iterstrings(files) do
    516 		local out = outdir..'/'..file
    517 		table.insert(outs, out)
    518 		build('copy', out, srcdir..'/'..file)
    519 	end
    520 	return outs
    521 end