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