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 (11180B)


  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('cflags', tc.cflags)
  222. set('ldflags', tc.ldflags)
  223. end
  224. function phony(name, inputs)
  225. build('phony', '$gendir/'..name, inputs)
  226. end
  227. function cflags(flags)
  228. set('cflags', '$cflags '..table.concat(flags, ' '))
  229. end
  230. function nasmflags(flags)
  231. set('nasmflags', '$nasmflags '..table.concat(flags, ' '))
  232. end
  233. function compile(rule, src, deps, args)
  234. local obj = src..'.o'
  235. if not src:match('^[$/]') then
  236. src = '$srcdir/'..src
  237. obj = '$outdir/'..obj
  238. end
  239. if not deps and pkg.deps then
  240. deps = '$gendir/deps'
  241. end
  242. if deps then
  243. src = {src, '||', deps}
  244. end
  245. build(rule, obj, src, args)
  246. return obj
  247. end
  248. function cc(src, deps, args)
  249. return compile('cc', src, deps, args)
  250. end
  251. function objects(srcs, deps, args)
  252. local objs, nobjs = {}, 0
  253. local rules = {
  254. c='cc',
  255. s='cc',
  256. S='cc',
  257. cc='cc',
  258. cpp='cc',
  259. asm='nasm',
  260. }
  261. local fn
  262. if type(srcs) == 'string' then
  263. fn = coroutine.wrap(pathsgen)
  264. else
  265. fn = coroutine.wrap(stringsgen)
  266. end
  267. for src in fn, srcs do
  268. local rule = rules[src:match('[^.]*$')]
  269. local obj = src
  270. if rule then
  271. obj = compile(rule, src, deps, args)
  272. end
  273. nobjs = nobjs + 1
  274. objs[nobjs] = obj
  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, '||')
  362. table.insert(deps, '$builddir/pkg/pax/host/pax')
  363. end
  364. build('fetch', '$dir/fetch', deps, {script=script})
  365. if basedir ~= '.' then
  366. build('phony', '$gendir/fetch', '$dir/fetch')
  367. end
  368. if next(pkg.inputs.fetch) then
  369. build('phony', table.keys(pkg.inputs.fetch), '$dir/fetch')
  370. end
  371. end
  372. local function findany(path, pats)
  373. for _, pat in pairs(pats) do
  374. if path:find(pat) then
  375. return true
  376. end
  377. end
  378. return false
  379. end
  380. local function specmatch(spec, path)
  381. if spec.include and not findany(path, spec.include) then
  382. return false
  383. end
  384. if spec.exclude and findany(path, spec.exclude) then
  385. return false
  386. end
  387. return true
  388. end
  389. local function fs(name, path)
  390. for _, spec in ipairs(config.fs) do
  391. for specname in iterstrings(spec) do
  392. if name == specname then
  393. return specmatch(spec, path)
  394. end
  395. end
  396. end
  397. return (config.fs.include or config.fs.exclude) and specmatch(config.fs, path)
  398. end
  399. function gitfile(path, mode, src)
  400. local out = '$builddir/root.hash/'..path
  401. local perm = ('10%04o %s'):format(tonumber(mode, 8), path)
  402. build('git-hash', out, {src, '|', '$basedir/scripts/hash.sh', '||', '$builddir/root.stamp'}, {
  403. args=perm,
  404. })
  405. table.insert(pkg.inputs.index, out)
  406. end
  407. function file(path, mode, src)
  408. if pkg.gendir:hasprefix('pkg/') and not fs(pkg.name, path) then
  409. return
  410. end
  411. mode = ('%04o'):format(tonumber(mode, 8))
  412. pkg.fspec[path] = {
  413. type='reg',
  414. mode=mode,
  415. source=src:gsub('^%$(%w+)', pkg, 1),
  416. }
  417. gitfile(path, mode, src)
  418. end
  419. function dir(path, mode)
  420. if pkg.gendir:hasprefix('pkg/') and not fs(pkg.name, path) then
  421. return
  422. end
  423. pkg.fspec[path] = {
  424. type='dir',
  425. mode=('%04o'):format(tonumber(mode, 8)),
  426. }
  427. end
  428. function sym(path, target)
  429. if pkg.gendir:hasprefix('pkg/') and not fs(pkg.name, path) then
  430. return
  431. end
  432. pkg.fspec[path] = {
  433. type='sym',
  434. mode='0777',
  435. target=target,
  436. }
  437. local out = '$builddir/root.hash/'..path
  438. build('git-hash', out, {'|', '$basedir/scripts/hash.sh', '||', '$builddir/root.stamp'}, {
  439. args=string.format('120000 %s %s', path, target),
  440. })
  441. table.insert(pkg.inputs.index, out)
  442. end
  443. function man(srcs, section)
  444. for _, src in ipairs(srcs) do
  445. local out = src..'.gz'
  446. if not src:match('^[$/]') then
  447. src = '$srcdir/'..src
  448. out = '$outdir/'..out
  449. end
  450. local base = src:match('[^/]*$')
  451. local ext = base:match('%.([^.]*)$')
  452. if ext then base = base:sub(1, -(#ext + 2)) end
  453. if section then ext = section end
  454. local path = 'share/man/man'..ext..'/'..base..'.'..ext
  455. if config.gzman ~= false then
  456. build('gzip', out, src)
  457. src = out
  458. path = path..'.gz'
  459. end
  460. file(path, '644', src)
  461. end
  462. end
  463. function copy(outdir, srcdir, files)
  464. local outs = {}
  465. for file in iterstrings(files) do
  466. local out = outdir..'/'..file
  467. table.insert(outs, out)
  468. build('copy', out, srcdir..'/'..file)
  469. end
  470. return outs
  471. end