logo

oasis

Own branch of Oasis Linux (upstream: <https://git.sr.ht/~mcf/oasis/>) git clone https://anongit.hacktivis.me/git/oasis.git

ninja.lua (11164B)


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