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