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