ackbuilder.lua 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916
  1. local posix = require("posix")
  2. -- Targets:
  3. --
  4. -- {
  5. -- fullname = full name of target
  6. -- dir = target's build directory
  7. -- outs = target's object files
  8. -- is = { set of rule types which made the target }
  9. -- }
  10. local emitter = {}
  11. local rules = {}
  12. local targets = {}
  13. local buildfiles = {}
  14. local globals
  15. local cwd = "."
  16. local vars = {}
  17. local parente = {}
  18. local loadingstack = {}
  19. -- Forward references
  20. local loadtarget
  21. local function print(...)
  22. local function print_no_nl(list)
  23. for _, s in ipairs(list) do
  24. if (type(s) == "table") then
  25. io.stderr:write("{")
  26. for k, v in pairs(s) do
  27. print_no_nl({k})
  28. io.stderr:write("=")
  29. print_no_nl({v})
  30. io.stderr:write(" ")
  31. end
  32. io.stderr:write("}")
  33. else
  34. io.stderr:write(tostring(s))
  35. end
  36. end
  37. end
  38. print_no_nl({...})
  39. io.stderr:write("\n")
  40. end
  41. local function assertString(s, i)
  42. if (type(s) ~= "string") then
  43. error(string.format("parameter %d must be a string", i))
  44. end
  45. end
  46. local function concat(...)
  47. local r = {}
  48. local function process(list)
  49. for _, t in ipairs(list) do
  50. if (type(t) == "table") and not t.is then
  51. process(t)
  52. else
  53. r[#r+1] = t
  54. end
  55. end
  56. end
  57. process({...})
  58. return r
  59. end
  60. -- Test table membership (crudely).
  61. local function contains(needle, haystack)
  62. for _, k in ipairs(haystack) do
  63. if (k == needle) then
  64. return true
  65. end
  66. end
  67. return false
  68. end
  69. local function inherit(high, low)
  70. local o = {}
  71. setmetatable(o, {
  72. __index = function(self, k)
  73. local x = high[k]
  74. if x then
  75. return x
  76. end
  77. return low[k]
  78. end
  79. })
  80. for k, v in pairs(high) do
  81. local _, _, kk = k:find("^%+(.*)$")
  82. if kk then
  83. o[kk] = concat(low[kk], v)
  84. end
  85. end
  86. return o
  87. end
  88. local function asstring(o)
  89. local t = type(o)
  90. if (t == "nil") then
  91. return ""
  92. elseif (t == "string") then
  93. return o
  94. elseif (t == "number") then
  95. return o
  96. elseif (t == "table") then
  97. if o.is then
  98. return asstring(o.outs)
  99. else
  100. local s = {}
  101. for _, v in pairs(o) do
  102. s[#s+1] = asstring(v)
  103. end
  104. return table.concat(s, " ")
  105. end
  106. else
  107. error(string.format("can't turn values of type '%s' into strings", t))
  108. end
  109. end
  110. local function concatpath(...)
  111. local p = table.concat({...}, "/")
  112. return (p:gsub("/+", "/"):gsub("^%./", ""):gsub("/%./", "/"))
  113. end
  114. -- Returns a list of the targets within the given collection; the keys of any
  115. -- keyed items are lost. Lists and wildcards are expanded.
  116. local function targetsof(...)
  117. local o = {}
  118. local function process(items)
  119. for _, item in pairs(items) do
  120. if (type(item) == "table") then
  121. if item.is then
  122. -- This is a target.
  123. o[#o+1] = item
  124. else
  125. -- This is a list.
  126. process(item)
  127. end
  128. elseif (type(item) == "string") then
  129. -- Filename!
  130. if item:find("^%+") then
  131. item = cwd..item
  132. elseif item:find("^%./") then
  133. item = concatpath(cwd, item)
  134. end
  135. o[#o+1] = loadtarget(item)
  136. else
  137. error(string.format("member of target list is not a string or a target"))
  138. end
  139. end
  140. end
  141. process({...})
  142. return o
  143. end
  144. local function filenamesof(...)
  145. local targets = targetsof(...)
  146. local f = {}
  147. for _, r in ipairs(targets) do
  148. if (type(r) == "table") and r.is then
  149. if r.outs then
  150. for _, o in ipairs(r.outs) do
  151. f[#f+1] = o
  152. end
  153. end
  154. elseif (type(r) == "string") then
  155. f[#f+1] = r
  156. else
  157. error(string.format("list of targets contains a %s which isn't a target",
  158. type(r)))
  159. end
  160. end
  161. return f
  162. end
  163. local function targetnamesof(...)
  164. local targets = targetsof(...)
  165. local f
  166. for _, r in pairs(targets) do
  167. if (type(r) == "table") and r.is then
  168. f[#f+1] = r.fullname
  169. elseif (type(r) == "string") then
  170. f[#f+1] = r
  171. else
  172. error(string.format("list of targets contains a %s which isn't a target",
  173. type(r)))
  174. end
  175. end
  176. return f
  177. end
  178. local function dotocollection(files, callback)
  179. if (#files == 1) and (type(files[1]) == "string") then
  180. return callback(files[1])
  181. end
  182. local o = {}
  183. local function process(files)
  184. for _, s in ipairs(files) do
  185. if (type(s) == "table") then
  186. if s.is then
  187. error("passed target to a filename manipulation function")
  188. else
  189. process(s)
  190. end
  191. else
  192. local b = callback(s)
  193. if (b ~= "") then
  194. o[#o+1] = b
  195. end
  196. end
  197. end
  198. end
  199. process(files)
  200. return o
  201. end
  202. local function abspath(...)
  203. return dotocollection({...},
  204. function(filename)
  205. assertString(filename, 1)
  206. if not filename:find("^[/$]") then
  207. filename = concatpath(posix.getcwd(), filename)
  208. end
  209. return filename
  210. end
  211. )
  212. end
  213. local function basename(...)
  214. return dotocollection({...},
  215. function(filename)
  216. assertString(filename, 1)
  217. local _, _, b = filename:find("^.*/([^/]*)$")
  218. if not b then
  219. return filename
  220. end
  221. return b
  222. end
  223. )
  224. end
  225. local function dirname(...)
  226. return dotocollection({...},
  227. function(filename)
  228. assertString(filename, 1)
  229. local _, _, b = filename:find("^(.*)/[^/]*$")
  230. if not b then
  231. return ""
  232. end
  233. return b
  234. end
  235. )
  236. end
  237. local function replace(files, pattern, repl)
  238. return dotocollection({files},
  239. function(filename)
  240. return filename:gsub(pattern, repl)
  241. end
  242. )
  243. end
  244. local function fpairs(...)
  245. return ipairs(filenamesof(...))
  246. end
  247. local function matching(collection, pattern)
  248. local o = {}
  249. dotocollection(collection,
  250. function(filename)
  251. if filename:find(pattern) then
  252. o[#o+1] = filename
  253. end
  254. end
  255. )
  256. return o
  257. end
  258. -- Selects all targets containing at least one output file that matches
  259. -- the pattern (or all, if the pattern is nil).
  260. local function selectof(targets, pattern)
  261. local targets = targetsof(targets)
  262. local o = {}
  263. for k, v in pairs(targets) do
  264. if v.is and v.outs then
  265. local matches = false
  266. for _, f in pairs(v.outs) do
  267. if f:find(pattern) then
  268. matches = true
  269. break
  270. end
  271. end
  272. if matches then
  273. o[#o+1] = v
  274. end
  275. end
  276. end
  277. return o
  278. end
  279. local function uniquify(...)
  280. local s = {}
  281. return dotocollection({...},
  282. function(filename)
  283. if not s[filename] then
  284. s[filename] = true
  285. return filename
  286. end
  287. end
  288. )
  289. end
  290. local function startswith(needle, haystack)
  291. return haystack:sub(1, #needle) == needle
  292. end
  293. local function emit(...)
  294. local n = select("#", ...)
  295. local args = {...}
  296. for i=1, n do
  297. local s = asstring(args[i])
  298. io.stdout:write(s)
  299. if not s:find("\n$") then
  300. io.stdout:write(" ")
  301. end
  302. end
  303. end
  304. local function templateexpand(list, vars)
  305. vars = inherit(vars, globals)
  306. local o = {}
  307. for _, s in ipairs(list) do
  308. o[#o+1] = s:gsub("%%%b{}",
  309. function(expr)
  310. expr = expr:sub(3, -2)
  311. local chunk, e = loadstring("return ("..expr..")", expr)
  312. if e then
  313. error(string.format("error evaluating expression: %s", e))
  314. end
  315. setfenv(chunk, vars)
  316. local value = chunk()
  317. if (value == nil) then
  318. error(string.format("template expression '%s' expands to nil (probably an undefined variable)", expr))
  319. end
  320. return asstring(value)
  321. end
  322. )
  323. end
  324. return o
  325. end
  326. local function loadbuildfile(filename)
  327. if contains(filename, loadingstack) then
  328. error(string.format("build file cycle; '%s' refers to itself indirectly; stack is: %s %s",
  329. filename, asstring(loadingstack), filename))
  330. end
  331. loadingstack[#loadingstack+1] = filename
  332. if not buildfiles[filename] then
  333. buildfiles[filename] = true
  334. local fp, data, chunk, e
  335. io.stderr:write("loading ", filename, "\n")
  336. fp, e = io.open(filename)
  337. if not e then
  338. data, e = fp:read("*a")
  339. fp:close()
  340. if not e then
  341. local thisglobals = {}
  342. thisglobals._G = thisglobals
  343. setmetatable(thisglobals, {__index = globals})
  344. chunk, e = loadstring(data, "@"..filename)
  345. if not e then
  346. setfenv(chunk, thisglobals)
  347. end
  348. end
  349. end
  350. if e then
  351. error(string.format("couldn't load '%s': %s", filename, e))
  352. end
  353. local oldcwd = cwd
  354. cwd = dirname(filename)
  355. chunk()
  356. cwd = oldcwd
  357. end
  358. loadingstack[#loadingstack] = nil
  359. end
  360. local function loadbuildfilefor(filepart, targetpart)
  361. local normalname = concatpath(filepart, "/build.lua")
  362. if posix.access(normalname, "r") then
  363. loadbuildfile(normalname)
  364. return
  365. end
  366. local extendedname = concatpath(filepart, "/build-"..targetpart..".lua")
  367. if posix.access(extendedname, "r") then
  368. loadbuildfile(extendedname)
  369. return
  370. end
  371. error(string.format("could not access either '%s' or '%s'", normalname, extendedname))
  372. end
  373. loadtarget = function(targetname)
  374. if targets[targetname] then
  375. return targets[targetname]
  376. end
  377. local target
  378. if not targetname:find("%+") then
  379. local files
  380. if targetname:find("[?*]") then
  381. files = posix.glob(targetname)
  382. if not files then
  383. files = {}
  384. end
  385. else
  386. files = {targetname}
  387. end
  388. target = {
  389. outs = files,
  390. is = {
  391. __implicitfile = true
  392. }
  393. }
  394. targets[targetname] = target
  395. else
  396. local _, _, filepart, targetpart = targetname:find("^([^+]*)%+([%w-_]+)$")
  397. if not filepart or not targetpart then
  398. error(string.format("malformed target name '%s'", targetname))
  399. end
  400. if (filepart == "") then
  401. filepart = cwd
  402. end
  403. loadbuildfilefor(filepart, targetpart)
  404. target = targets[targetname]
  405. if not target then
  406. error(string.format("build file '%s' contains no target '%s'",
  407. filename, targetpart))
  408. end
  409. end
  410. return target
  411. end
  412. local typeconverters = {
  413. targets = function(propname, i)
  414. if (type(i) == "string") then
  415. i = {i}
  416. elseif (type(i) ~= "table") then
  417. error(string.format("property '%s' must be a target list", propname))
  418. end
  419. local m = {}
  420. for k, v in pairs(i) do
  421. local ts = targetsof(v)
  422. if (type(k) == "number") then
  423. for _, t in ipairs(ts) do
  424. m[#m+1] = t
  425. end
  426. else
  427. if (#ts ~= 1) then
  428. error(string.format("named target '%s' can only be assigned from a single target", k))
  429. else
  430. m[k] = ts[1]
  431. end
  432. end
  433. end
  434. return m
  435. end,
  436. strings = function(propname, i)
  437. if (type(i) == "string") then
  438. i = {i}
  439. elseif (type(i) ~= "table") then
  440. error(string.format("property '%s' must be a string list", propname))
  441. end
  442. return concat(i)
  443. end,
  444. boolean = function(propname, i)
  445. if (type(i) ~= "boolean") then
  446. error(string.format("property '%s' must be a boolean", propname))
  447. end
  448. return i
  449. end,
  450. string = function(propname, i)
  451. if (type(i) ~= "string") then
  452. error(string.format("property '%s' must be a string", propname))
  453. end
  454. return i
  455. end,
  456. table = function(propname, i)
  457. if (type(i) ~= "table") then
  458. error(string.format("property '%s' must be a table", propname))
  459. end
  460. return i
  461. end,
  462. object = function(propname, i)
  463. return i
  464. end,
  465. }
  466. local function definerule(rulename, types, cb)
  467. if rulename and rules[rulename] then
  468. error(string.format("rule '%s' is already defined", rulename))
  469. end
  470. types.name = { type="string" }
  471. types.cwd = { type="string", optional=true }
  472. types.vars = { type="table", default={} }
  473. for propname, typespec in pairs(types) do
  474. if not typeconverters[typespec.type] then
  475. error(string.format("property '%s' has unrecognised type '%s'",
  476. propname, typespec.type))
  477. end
  478. end
  479. local rulecwd = cwd
  480. local rule = function(e)
  481. local definedprops = {}
  482. for propname, _ in pairs(e) do
  483. definedprops[propname] = true
  484. end
  485. local args = {}
  486. for propname, typespec in pairs(types) do
  487. if not e[propname] then
  488. if not typespec.optional and (typespec.default == nil) then
  489. error(string.format("missing mandatory property '%s'", propname))
  490. end
  491. args[propname] = typespec.default
  492. else
  493. args[propname] = typeconverters[typespec.type](propname, e[propname])
  494. definedprops[propname] = nil
  495. end
  496. end
  497. local propname, _ = next(definedprops)
  498. if propname then
  499. error(string.format("don't know what to do with property '%s'", propname))
  500. end
  501. if not args.cwd then
  502. args.cwd = cwd
  503. end
  504. args.fullname = args.cwd.."+"..args.name
  505. local oldparente = parente
  506. parente = args
  507. args.vars = inherit(args.vars, oldparente.vars)
  508. local result = cb(args) or {}
  509. parente = oldparente
  510. result.is = result.is or {}
  511. if rulename then
  512. result.is[rulename] = true
  513. end
  514. result.fullname = args.fullname
  515. if targets[arg.fullname] and (targets[arg.fullname] ~= result) then
  516. error(string.format("target '%s' is already defined", args.fullname))
  517. end
  518. targets[result.fullname] = result
  519. return result
  520. end
  521. if rulename then
  522. if rules[rulename] then
  523. error(string.format("rule '%s' is already defined", rulename))
  524. end
  525. rules[rulename] = rule
  526. end
  527. return rule
  528. end
  529. -----------------------------------------------------------------------------
  530. -- DEFAULT RULES --
  531. -----------------------------------------------------------------------------
  532. local function install_make_emitter()
  533. emit("hide = @\n")
  534. function emitter:var(name, value)
  535. -- Don't let emit insert spaces.
  536. emit(name.."="..value.."\n")
  537. end
  538. function emitter:rule(name, ins, outs)
  539. if (#outs == 0) then
  540. local n = name.."-IMAGINARY-OUT"
  541. emit(".INTERMEDIATE:", n, "\n")
  542. outs = {n}
  543. end
  544. local impl = name.."-IMPL"
  545. emit(".INTERMEDIATE:", name, "\n")
  546. emit(".INTERMEDIATE:", impl, "\n")
  547. for i = 1, #outs do
  548. emit(name..":", outs[i], "\n")
  549. end
  550. for i = 1, #outs do
  551. emit(outs[i]..":", impl, ";\n")
  552. end
  553. for i = 1, #ins do
  554. emit(impl..":", ins[i], "\n")
  555. end
  556. emit(impl..":", "\n")
  557. local dirs = uniquify(dirname(outs))
  558. if (#dirs > 0) then
  559. emit("\t@mkdir -p", dirs, "\n")
  560. end
  561. end
  562. function emitter:phony(name, ins, outs)
  563. emit(".PHONY:", name, "\n")
  564. self:rule(name, ins, outs)
  565. end
  566. function emitter:label(...)
  567. local s = table.concat({...}, " ")
  568. emit("\t@echo", s, "\n")
  569. end
  570. function emitter:exec(commands)
  571. for _, s in ipairs(commands) do
  572. emit("\t$(hide)", s, "\n")
  573. end
  574. end
  575. function emitter:endrule()
  576. emit("\n")
  577. end
  578. end
  579. local function install_ninja_emitter()
  580. emit("rule build\n")
  581. emit(" command = $command\n")
  582. emit("\n")
  583. local function unmake(collection)
  584. return dotocollection({collection},
  585. function(s)
  586. return s:gsub("%$%b()",
  587. function(expr)
  588. return "${"..expr:sub(3, -2).."}"
  589. end
  590. )
  591. end
  592. )
  593. end
  594. function emitter:var(name, value)
  595. -- Don't let emit insert spaces.
  596. emit(name.."="..unmake(value).."\n")
  597. end
  598. function emitter:rule(name, ins, outs)
  599. if (#outs == 0) then
  600. emit("build", name, ": phony", unmake(ins), "\n")
  601. else
  602. emit("build", name, ": phony", unmake(outs), "\n")
  603. emit("build", unmake(outs), ": build", unmake(ins), "\n")
  604. end
  605. end
  606. function emitter:label(...)
  607. end
  608. function emitter:exec(commands)
  609. emit(" command =", table.concat(unmake(commands), " && "), "\n")
  610. end
  611. function emitter:endrule()
  612. emit("\n")
  613. end
  614. end
  615. definerule("simplerule",
  616. {
  617. ins = { type="targets" },
  618. outs = { type="strings" },
  619. deps = { type="targets", default={} },
  620. label = { type="string", optional=true },
  621. commands = { type="strings" },
  622. vars = { type="table", default={} },
  623. },
  624. function (e)
  625. emitter:rule(e.fullname, filenamesof(e.ins, e.deps), e.outs)
  626. emitter:label(e.fullname, " ", e.label or "")
  627. local vars = inherit(e.vars, {
  628. ins = filenamesof(e.ins),
  629. outs = filenamesof(e.outs)
  630. })
  631. emitter:exec(templateexpand(e.commands, vars))
  632. emitter:endrule()
  633. return {
  634. outs = e.outs
  635. }
  636. end
  637. )
  638. definerule("installable",
  639. {
  640. map = { type="targets", default={} },
  641. },
  642. function (e)
  643. local deps = {}
  644. local commands = {}
  645. local srcs = {}
  646. local outs = {}
  647. local dests = {}
  648. for dest, src in pairs(e.map) do
  649. if src.is.installable then
  650. if (type(dest) ~= "number") then
  651. error("can't specify a destination filename when installing an installable")
  652. end
  653. deps[#deps+1] = src.fullname
  654. outs = concat(outs, filenamesof(src))
  655. elseif (type(dest) == "number") then
  656. error("only references to other installables can be missing a destination")
  657. else
  658. local f = filenamesof(src)
  659. if (#f ~= 1) then
  660. error("installable can only cope with targets emitting single files")
  661. end
  662. deps[#deps+1] = f
  663. dests[#dests+1] = dest
  664. outs[#outs+1] = dest
  665. commands[#commands+1] = "cp "..f[1].." "..dest
  666. end
  667. end
  668. emitter:rule(e.fullname, deps, dests)
  669. emitter:label(e.fullname, " ", e.label or "")
  670. if (#commands > 0) then
  671. emitter:exec(commands)
  672. end
  673. emitter:endrule()
  674. return {
  675. outs = outs
  676. }
  677. end
  678. )
  679. -----------------------------------------------------------------------------
  680. -- MAIN PROGRAM --
  681. -----------------------------------------------------------------------------
  682. local function parse_arguments(argmap, arg)
  683. local i = 1
  684. local files = {}
  685. local function unrecognisedarg(arg)
  686. argmap[" unrecognised"](arg)
  687. end
  688. while (i <= #arg) do
  689. local o = arg[i]
  690. local op
  691. if (o:byte(1) == 45) then
  692. -- This is an option.
  693. if (o:byte(2) == 45) then
  694. -- ...with a -- prefix.
  695. o = o:sub(3)
  696. local fn = argmap[o]
  697. if not fn then
  698. unrecognisedarg("--"..o)
  699. end
  700. i = i + fn(arg[i+1], arg[i+2])
  701. else
  702. -- ...without a -- prefix.
  703. local od = o:sub(2, 2)
  704. local fn = argmap[od]
  705. if not fn then
  706. unrecognisedarg("-"..od)
  707. end
  708. op = o:sub(3)
  709. if (op == "") then
  710. i = i + fn(arg[i+1], arg[i+2])
  711. else
  712. fn(op)
  713. end
  714. end
  715. else
  716. files[#files+1] = o
  717. end
  718. i = i + 1
  719. end
  720. argmap[" files"](files)
  721. end
  722. globals = {
  723. posix = posix,
  724. abspath = abspath,
  725. asstring = asstring,
  726. basename = basename,
  727. concat = concat,
  728. concatpath = concatpath,
  729. cwd = function() return cwd end,
  730. definerule = definerule,
  731. dirname = dirname,
  732. emit = emit,
  733. filenamesof = filenamesof,
  734. fpairs = fpairs,
  735. include = loadbuildfile,
  736. inherit = inherit,
  737. print = print,
  738. replace = replace,
  739. matching = matching,
  740. selectof = selectof,
  741. startswith = startswith,
  742. uniquify = uniquify,
  743. vars = vars,
  744. }
  745. setmetatable(globals,
  746. {
  747. __index = function(self, k)
  748. local rule = rules[k]
  749. if rule then
  750. return rule
  751. else
  752. return _G[k]
  753. end
  754. end
  755. }
  756. )
  757. vars.cflags = {}
  758. parente.vars = vars
  759. setmetatable(_G,
  760. {
  761. __index = function(self, k)
  762. local value = rawget(_G, k)
  763. if not value then
  764. error(string.format("access of undefined variable '%s'", k))
  765. end
  766. return value
  767. end
  768. }
  769. )
  770. do
  771. local emitter_type = install_make_emitter
  772. parse_arguments(
  773. {
  774. ["make"] = function()
  775. emitter_type = install_make_emitter
  776. return 0
  777. end,
  778. ["ninja"] = function()
  779. emitter_type = install_ninja_emitter
  780. return 0
  781. end,
  782. [" unrecognised"] = function(arg)
  783. error(string.format("unrecognised argument '%s'", arg))
  784. end,
  785. [" files"] = function(files)
  786. emitter_type()
  787. for _, f in ipairs(files) do
  788. local _, _, name, value = f:find("^([%w_]+)=(.*)$")
  789. if name then
  790. emitter:var(name, value)
  791. end
  792. end
  793. for _, f in ipairs(files) do
  794. if not f:find("=") then
  795. loadbuildfile(f)
  796. end
  797. end
  798. end
  799. },
  800. {...}
  801. )
  802. end