opkg

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

ninja.lua (11064B)


      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 	if method == 'local' then
    396 		script = '$dir/fetch.sh'
    397 	else
    398 		script = '$basedir/scripts/fetch-'..method..'.sh'
    399 	end
    400 	build('fetch', '$dir/fetch', {'|', '$dir/ver', script}, {script=script})
    401 	if basedir ~= '.' then
    402 		build('phony', '$gendir/fetch', '$dir/fetch')
    403 	end
    404 	if next(pkg.inputs.fetch) then
    405 		build('phony', table.keys(pkg.inputs.fetch), '$dir/fetch')
    406 	end
    407 end
    408 
    409 local function findany(path, pats)
    410 	for _, pat in pairs(pats) do
    411 		if path:find(pat) then
    412 			return true
    413 		end
    414 	end
    415 	return false
    416 end
    417 
    418 local function specmatch(spec, path)
    419 	if spec.include and not findany(path, spec.include) then
    420 		return false
    421 	end
    422 	if spec.exclude and findany(path, spec.exclude) then
    423 		return false
    424 	end
    425 	return true
    426 end
    427 
    428 local function fs(name, path)
    429 	for _, spec in ipairs(config.fs) do
    430 		for specname in iterstrings(spec) do
    431 			if name == specname then
    432 				return specmatch(spec, path)
    433 			end
    434 		end
    435 	end
    436 	return (config.fs.include or config.fs.exclude) and specmatch(config.fs, path)
    437 end
    438 
    439 function gitfile(path, mode, src)
    440 	local out = '$builddir/root.hash/'..path
    441 	local perm = ('10%04o %s'):format(tonumber(mode, 8), path)
    442 	build('git-hash', out, {src, '|', '$basedir/scripts/hash.sh', '||', '$builddir/root.stamp'}, {
    443 		args=perm,
    444 	})
    445 	table.insert(pkg.inputs.index, out)
    446 end
    447 
    448 function file(path, mode, src)
    449 	if pkg.gendir:hasprefix('pkg/') and not fs(pkg.name, path) then
    450 		return
    451 	end
    452 	mode = ('%04o'):format(tonumber(mode, 8))
    453 	pkg.fspec[path] = {
    454 		type='reg',
    455 		mode=mode,
    456 		source=src:gsub('^%$(%w+)', pkg, 1),
    457 	}
    458 	gitfile(path, mode, src)
    459 end
    460 
    461 function dir(path, mode)
    462 	if pkg.gendir:hasprefix('pkg/') and not fs(pkg.name, path) then
    463 		return
    464 	end
    465 	pkg.fspec[path] = {
    466 		type='dir',
    467 		mode=('%04o'):format(tonumber(mode, 8)),
    468 	}
    469 end
    470 
    471 function sym(path, target)
    472 	if pkg.gendir:hasprefix('pkg/') and not fs(pkg.name, path) then
    473 		return
    474 	end
    475 	pkg.fspec[path] = {
    476 		type='sym',
    477 		mode='0777',
    478 		target=target,
    479 	}
    480 	local out = '$builddir/root.hash/'..path
    481 	build('git-hash', out, {'|', '$basedir/scripts/hash.sh', '||', '$builddir/root.stamp'}, {
    482 		args=string.format('120000 %s %s', path, target),
    483 	})
    484 	table.insert(pkg.inputs.index, out)
    485 end
    486 
    487 function man(srcs, section)
    488 	for _, src in ipairs(srcs) do
    489 		local out = src..'.gz'
    490 		if not src:match('^[$/]') then
    491 			src = '$srcdir/'..src
    492 			out = '$outdir/'..out
    493 		end
    494 
    495 		local base = src:match('[^/]*$')
    496 		local ext = base:match('%.([^.]*)$')
    497 		if ext then base = base:sub(1, -(#ext + 2)) end
    498 		if section then ext = section end
    499 		local path = 'share/man/man'..ext..'/'..base..'.'..ext
    500 		if config.gzman ~= false then
    501 			build('gzip', out, src)
    502 			src = out
    503 			path = path..'.gz'
    504 		end
    505 		file(path, '644', src)
    506 	end
    507 end
    508 
    509 function copy(outdir, srcdir, files)
    510 	local outs = {}
    511 	for file in iterstrings(files) do
    512 		local out = outdir..'/'..file
    513 		table.insert(outs, out)
    514 		build('copy', out, srcdir..'/'..file)
    515 	end
    516 	return outs
    517 end