build.lua 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848
  1. -- eLua build system
  2. module( ..., package.seeall )
  3. local lfs = require "lfs"
  4. local sf = string.format
  5. utils = require "tools.utils"
  6. -------------------------------------------------------------------------------
  7. -- Various helpers
  8. -- Return the time of the last modification of the file
  9. local function get_ftime( path )
  10. local t = lfs.attributes( path, 'modification' )
  11. return t or -1
  12. end
  13. -- Check if a given target name is phony
  14. local function is_phony( target )
  15. return target:find( "#phony" ) == 1
  16. end
  17. -- Return a string with $(key) replaced with 'value'
  18. local function expand_key( s, key, value )
  19. if not value then return s end
  20. local fmt = sf( "%%$%%(%s%%)", key )
  21. return ( s:gsub( fmt, value ) )
  22. end
  23. -- Return a target name considering phony targets
  24. local function get_target_name( s )
  25. if not is_phony( s ) then return s end
  26. end
  27. -- 'Liniarize' a file name by replacing its path separators indicators with '_'
  28. local function linearize_fname( s )
  29. return ( s:gsub( "[\\/]", "__" ) )
  30. end
  31. -- Helper: transform a table into a string if needed
  32. local function table_to_string( t )
  33. if not t then return nil end
  34. if type( t ) == "table" then t = table.concat( t, " " ) end
  35. return t
  36. end
  37. -- Helper: return the extended type of an object (takes into account __type)
  38. local function exttype( o )
  39. local t = type( o )
  40. if t == "table" and o.__type then t = o:__type() end
  41. return t
  42. end
  43. ---------------------------------------
  44. -- Table utils
  45. -- (from http://lua-users.org/wiki/TableUtils)
  46. function table.val_to_str( v )
  47. if "string" == type( v ) then
  48. v = string.gsub( v, "\n", "\\n" )
  49. if string.match( string.gsub(v,"[^'\"]",""), '^"+$' ) then
  50. return "'" .. v .. "'"
  51. end
  52. return '"' .. string.gsub(v,'"', '\\"' ) .. '"'
  53. else
  54. return "table" == type( v ) and table.tostring( v ) or tostring( v )
  55. end
  56. end
  57. function table.key_to_str ( k )
  58. if "string" == type( k ) and string.match( k, "^[_%a][_%a%d]*$" ) then
  59. return k
  60. else
  61. return "[" .. table.val_to_str( k ) .. "]"
  62. end
  63. end
  64. function table.tostring( tbl )
  65. local result, done = {}, {}
  66. for k, v in ipairs( tbl ) do
  67. table.insert( result, table.val_to_str( v ) )
  68. done[ k ] = true
  69. end
  70. for k, v in pairs( tbl ) do
  71. if not done[ k ] then
  72. table.insert( result,
  73. table.key_to_str( k ) .. "=" .. table.val_to_str( v ) )
  74. end
  75. end
  76. return "{" .. table.concat( result, "," ) .. "}"
  77. end
  78. -------------------------------------------------------------------------------
  79. -- Dummy 'builder': simply checks the date of a file
  80. local _fbuilder = {}
  81. _fbuilder.new = function( target, dep )
  82. local self = {}
  83. setmetatable( self, { __index = _fbuilder } )
  84. self.target = target
  85. self.dep = dep
  86. return self
  87. end
  88. _fbuilder.build = function( self )
  89. -- Doesn't build anything but returns 'true' if the dependency is newer than
  90. -- the target
  91. if is_phony( self.target ) then
  92. return true
  93. else
  94. return get_ftime( self.dep ) > get_ftime( self.target )
  95. end
  96. end
  97. _fbuilder.target_name = function( self )
  98. return get_target_name( self.dep )
  99. end
  100. -- Object type
  101. _fbuilder.__type = function()
  102. return "_fbuilder"
  103. end
  104. -------------------------------------------------------------------------------
  105. -- Target object
  106. local _target = {}
  107. _target.new = function( target, dep, command, builder, ttype )
  108. local self = {}
  109. setmetatable( self, { __index = _target } )
  110. self.target = target
  111. self.command = command
  112. self.builder = builder
  113. builder:register_target( target, self )
  114. self:set_dependencies( dep )
  115. self.dep = self:_build_dependencies( self.origdep )
  116. self.dont_clean = false
  117. self.can_substitute_cmdline = false
  118. self._force_rebuild = #self.dep == 0
  119. builder.runlist[ target ] = false
  120. self:set_type( ttype )
  121. return self
  122. end
  123. -- Set dependencies as a string; actual dependencies are computed by _build_dependencies
  124. -- (below) when 'build' is called
  125. _target.set_dependencies = function( self, dep )
  126. self.origdep = dep
  127. end
  128. -- Set the target type
  129. -- This is only for displaying actions
  130. _target.set_type = function( self, ttype )
  131. local atable = { comp = { "[COMPILE]", 'blue' } , dep = { "[DEPENDS]", 'magenta' }, link = { "[LINK]", 'yellow' }, asm = { "[ASM]", 'white' } }
  132. local tdata = atable[ ttype ]
  133. if not tdata then
  134. self.dispstr = is_phony( self.target ) and "[PHONY]" or "[TARGET]"
  135. self.dispcol = 'green'
  136. else
  137. self.dispstr = tdata[ 1 ]
  138. self.dispcol = tdata[ 2 ]
  139. end
  140. end
  141. -- Set dependencies
  142. -- This uses a proxy table and returns string deps dynamically according
  143. -- to the targets currently registered in the builder
  144. _target._build_dependencies = function( self, dep )
  145. -- Step 1: start with an array
  146. if type( dep ) == "string" then dep = utils.string_to_table( dep ) end
  147. -- Step 2: linearize "dep" array keeping targets
  148. local filter = function( e )
  149. local t = exttype( e )
  150. return t ~= "_ftarget" and t ~= "_target"
  151. end
  152. dep = utils.linearize_array( dep, filter )
  153. -- Step 3: strings are turned into _fbuilder objects if not found as targets;
  154. -- otherwise the corresponding target object is used
  155. for i = 1, #dep do
  156. if type( dep[ i ] ) == 'string' then
  157. local t = self.builder:get_registered_target( dep[ i ] )
  158. dep[ i ] = t or _fbuilder.new( self.target, dep[ i ] )
  159. end
  160. end
  161. return dep
  162. end
  163. -- Set pre-build function
  164. _target.set_pre_build_function = function( self, f )
  165. self._pre_build_function = f
  166. end
  167. -- Set post-build function
  168. _target.set_post_build_function = function( self, f )
  169. self._post_build_function = f
  170. end
  171. -- Force rebuild
  172. _target.force_rebuild = function( self, flag )
  173. self._force_rebuild = flag
  174. end
  175. -- Set additional arguments to send to the builder function if it is a callable
  176. _target.set_target_args = function( self, args )
  177. self._target_args = args
  178. end
  179. -- Function to execute in clean mode
  180. _target._cleaner = function( target, deps, tobj, disp_mode )
  181. -- Clean the main target if it is not a phony target
  182. local dprint = function( ... )
  183. if disp_mode ~= "minimal" then print( ... ) end
  184. end
  185. if not is_phony( target ) then
  186. if tobj.dont_clean then
  187. dprint( sf( "[builder] Target '%s' will not be deleted", target ) )
  188. return 0
  189. end
  190. if disp_mode ~= "minimal" then io.write( sf( "[builder] Removing %s ... ", target ) ) end
  191. if os.remove( target ) then dprint "done." else dprint "failed!" end
  192. end
  193. return 0
  194. end
  195. -- Build the given target
  196. _target.build = function( self )
  197. if self.builder.runlist[ self.target ] then return end
  198. local docmd = self:target_name() and lfs.attributes( self:target_name(), "mode" ) ~= "file"
  199. docmd = docmd or self.builder.global_force_rebuild
  200. local initdocmd = docmd
  201. self.dep = self:_build_dependencies( self.origdep )
  202. local depends, dep, previnit = '', self.dep, self.origdep
  203. -- Iterate through all dependencies, execute each one in turn
  204. local deprunner = function()
  205. for i = 1, #dep do
  206. local res = dep[ i ]:build()
  207. docmd = docmd or res
  208. local t = dep[ i ]:target_name()
  209. if exttype( dep[ i ] ) == "_target" and t and not is_phony( self.target ) then
  210. docmd = docmd or get_ftime( t ) > get_ftime( self.target )
  211. end
  212. if t then depends = depends .. t .. " " end
  213. end
  214. end
  215. deprunner()
  216. -- Execute the preb-build function if needed
  217. if self._pre_build_function then self._pre_build_function( self, docmd ) end
  218. -- If the dependencies changed as a result of running the pre-build function
  219. -- run through them again
  220. if previnit ~= self.origdep then
  221. self.dep = self:_build_dependencies( self.origdep )
  222. depends, dep, docmd = '', self.dep, initdocmd
  223. deprunner()
  224. end
  225. -- If at least one dependency is new rebuild the target
  226. docmd = docmd or self._force_rebuild or self.builder.clean_mode
  227. local keep_flag = true
  228. if docmd and self.command then
  229. if self.builder.disp_mode ~= 'all' and self.builder.disp_mode ~= "minimal" and not self.builder.clean_mode then
  230. io.write( utils.col_funcs[ self.dispcol ]( self.dispstr ) .. " " )
  231. end
  232. local cmd, code = self.command
  233. if self.builder.clean_mode then cmd = _target._cleaner end
  234. if type( cmd ) == 'string' then
  235. cmd = expand_key( cmd, "TARGET", self.target )
  236. cmd = expand_key( cmd, "DEPENDS", depends )
  237. cmd = expand_key( cmd, "FIRST", dep[ 1 ]:target_name() )
  238. if self.builder.disp_mode == 'all' then
  239. print( cmd )
  240. elseif self.builder.disp_mode ~= "minimal" then
  241. print( self.target )
  242. end
  243. code = self:execute( cmd )
  244. else
  245. if not self.builder.clean_mode and self.builder.disp_mode ~= "all" and self.builder.disp_mode ~= "minimal" then
  246. print( self.target )
  247. end
  248. code = cmd( self.target, self.dep, self.builder.clean_mode and self or self._target_args, self.builder.disp_mode )
  249. if code == 1 then -- this means "mark target as 'not executed'"
  250. keep_flag = false
  251. code = 0
  252. end
  253. end
  254. if code ~= 0 then
  255. print( utils.col_red( "[builder] Error building target" ) )
  256. if self.builder.disp_mode ~= 'all' and type( cmd ) == "string" then
  257. print( utils.col_red( "[builder] Last executed command was: " ) )
  258. print( cmd )
  259. end
  260. os.exit( 1 )
  261. end
  262. end
  263. -- Execute the post-build function if needed
  264. if self._post_build_function then self._post_build_function( self, docmd ) end
  265. -- Marked target as "already ran" so it won't run again
  266. self.builder.runlist[ self.target ] = true
  267. return docmd and keep_flag
  268. end
  269. -- Return the actual target name (taking into account phony targets)
  270. _target.target_name = function( self )
  271. return get_target_name( self.target )
  272. end
  273. -- Restrict cleaning this target
  274. _target.prevent_clean = function( self, flag )
  275. self.dont_clean = flag
  276. end
  277. -- Object type
  278. _target.__type = function()
  279. return "_target"
  280. end
  281. _target.execute = function( self, cmd )
  282. local code
  283. if utils.is_windows() and #cmd > 8190 and self.can_substitute_cmdline then
  284. -- Avoid cmd's maximum command line length limitation
  285. local t = cmd:find( " " )
  286. f = io.open( "tmpcmdline", "w" )
  287. local rest = cmd:sub( t + 1 )
  288. f:write( ( rest:gsub( "\\", "/" ) ) )
  289. f:close()
  290. cmd = cmd:sub( 1, t - 1 ) .. " @tmpcmdline"
  291. end
  292. local code = os.execute( cmd )
  293. os.remove( "tmpcmdline" )
  294. return code
  295. end
  296. _target.set_substitute_cmdline = function( self, flag )
  297. self.can_substitute_cmdline = flag
  298. end
  299. -------------------------------------------------------------------------------
  300. -- Builder public interface
  301. builder = { KEEP_DIR = 0, BUILD_DIR_LINEARIZED = 1 }
  302. ---------------------------------------
  303. -- Initialization and option handling
  304. -- Create a new builder object with the output in 'build_dir' and with the
  305. -- specified compile, dependencies and link command
  306. builder.new = function( build_dir )
  307. self = {}
  308. setmetatable( self, { __index = builder } )
  309. self.build_dir = build_dir or ".build"
  310. self.exe_extension = utils.is_windows() and "exe" or ""
  311. self.clean_mode = false
  312. self.opts = utils.options_handler()
  313. self.args = {}
  314. self.user_args = {}
  315. self.build_mode = self.KEEP_DIR
  316. self.targets = {}
  317. self.targetargs = {}
  318. self._tlist = {}
  319. self.runlist = {}
  320. self.disp_mode = 'all'
  321. self.cmdline_macros = {}
  322. self.c_targets = {}
  323. self.preprocess_mode = false
  324. self.asm_mode = false
  325. return self
  326. end
  327. -- Helper: create the build output directory
  328. builder._create_build_dir = function( self )
  329. if self.build_dir_created then return end
  330. if self.build_mode ~= self.KEEP_DIR then
  331. -- Create builds directory if needed
  332. local mode = lfs.attributes( self.build_dir, "mode" )
  333. if not mode or mode ~= "directory" then
  334. if not utils.full_mkdir( self.build_dir ) then
  335. print( "[builder] Unable to create directory " .. self.build_dir )
  336. os.exit( 1 )
  337. end
  338. end
  339. end
  340. self.build_dir_created = true
  341. end
  342. -- Add an options to the builder
  343. builder.add_option = function( self, name, help, default, data )
  344. self.opts:add_option( name, help, default, data )
  345. end
  346. -- Initialize builder from the given command line
  347. builder.init = function( self, args )
  348. -- Add the default options
  349. local opts = self.opts
  350. opts:add_option( "build_mode", 'choose location of the object files', self.KEEP_DIR,
  351. { keep_dir = self.KEEP_DIR, build_dir_linearized = self.BUILD_DIR_LINEARIZED } )
  352. opts:add_option( "build_dir", 'choose build directory', self.build_dir )
  353. opts:add_option( "disp_mode", 'set builder display mode', 'summary', { 'all', 'summary', 'minimal' } )
  354. -- Apply default values to all options
  355. for i = 1, opts:get_num_opts() do
  356. local o = opts:get_option( i )
  357. self.args[ o.name:upper() ] = o.default
  358. end
  359. -- Read and interpret command line
  360. for i = 1, #args do
  361. local a = args[ i ]
  362. if a:upper() == "-C" then -- clean option (-c)
  363. self.clean_mode = true
  364. elseif a:upper() == '-H' then -- help option (-h)
  365. self:_show_help()
  366. os.exit( 1 )
  367. elseif a:upper() == "-E" then -- preprocess
  368. self.preprocess_mode = true
  369. elseif a:upper() == "-S" then -- generate assembler
  370. self.asm_mode = true
  371. elseif a:find( '-D' ) == 1 and #a > 2 then -- this is a macro definition that will be auomatically added to the compiler flags
  372. table.insert( self.cmdline_macros, a:sub( 3 ) )
  373. elseif a:find( '=' ) then -- builder argument (key=value)
  374. local k, v = opts:handle_arg( a )
  375. if not k then
  376. self:_show_help()
  377. os.exit( 1 )
  378. end
  379. self.args[ k:upper() ] = v
  380. self.user_args[ k:upper() ] = true
  381. else -- this must be the target name / target arguments
  382. if self.targetname == nil then
  383. self.targetname = a
  384. else
  385. table.insert( self.targetargs, a )
  386. end
  387. end
  388. end
  389. -- Read back the default options
  390. self.build_mode = self.args.BUILD_MODE
  391. self.build_dir = self.args.BUILD_DIR
  392. self.disp_mode = self.args.DISP_MODE
  393. end
  394. -- Return the value of the option with the given name
  395. builder.get_option = function( self, optname )
  396. return self.args[ optname:upper() ]
  397. end
  398. -- Returns true if the given option was specified by the user on the command line, false otherwise
  399. builder.is_user_option = function( self, optname )
  400. return self.user_args[ optname:upper() ]
  401. end
  402. -- Show builder help
  403. builder._show_help = function( self )
  404. print( "[builder] Valid options:" )
  405. print( " -h: help (this text)" )
  406. print( " -c: clean target" )
  407. print( " -E: generate preprocessed output for single file targets" )
  408. print( " -S: generate assembler output for single file targets" )
  409. self.opts:show_help()
  410. end
  411. ---------------------------------------
  412. -- Builder configuration
  413. -- Set the compile command
  414. builder.set_compile_cmd = function( self, cmd )
  415. self.comp_cmd = cmd
  416. end
  417. -- Set the link command
  418. builder.set_link_cmd = function( self, cmd )
  419. self.link_cmd = cmd
  420. end
  421. -- Set the assembler command
  422. builder.set_asm_cmd = function( self, cmd )
  423. self._asm_cmd = cmd
  424. end
  425. -- Set (actually force) the object file extension
  426. builder.set_object_extension = function( self, ext )
  427. self.obj_extension = ext
  428. end
  429. -- Set (actually force) the executable file extension
  430. builder.set_exe_extension = function( self, ext )
  431. self.exe_extension = ext
  432. end
  433. -- Set the clean mode
  434. builder.set_clean_mode = function( self, isclean )
  435. self.clean_mode = isclean
  436. end
  437. -- Sets the build mode
  438. builder.set_build_mode = function( self, mode )
  439. self.build_mode = mode
  440. end
  441. -- Set the build directory
  442. builder.set_build_dir = function( self, dir )
  443. if self.build_dir_created then
  444. print "[builder] Error: build directory already created"
  445. os.exit( 1 )
  446. end
  447. self.build_dir = dir
  448. self:_create_build_dir()
  449. end
  450. -- Return the current build directory
  451. builder.get_build_dir = function( self )
  452. return self.build_dir
  453. end
  454. -- Return the target arguments
  455. builder.get_target_args = function( self )
  456. return self.targetargs
  457. end
  458. -- Set a specific dependency generation command for the assembler
  459. -- Pass 'false' to skip dependency generation for assembler files
  460. builder.set_asm_dep_cmd = function( self, asm_dep_cmd )
  461. self.asm_dep_cmd = asm_dep_cmd
  462. end
  463. -- Set a specific dependency generation command for the compiler
  464. -- Pass 'false' to skip dependency generation for C files
  465. builder.set_c_dep_cmd = function( self, c_dep_cmd )
  466. self.c_dep_cmd = c_dep_cmd
  467. end
  468. -- Save the builder configuration for a given component to a string
  469. builder._config_to_string = function( self, what )
  470. local ctable = {}
  471. local state_fields
  472. if what == 'comp' then
  473. state_fields = { 'comp_cmd', '_asm_cmd', 'c_dep_cmd', 'asm_dep_cmd', 'obj_extension' }
  474. elseif what == 'link' then
  475. state_fields = { 'link_cmd' }
  476. else
  477. print( sf( "Invalid argument '%s' to _config_to_string", what ) )
  478. os.exit( 1 )
  479. end
  480. utils.foreach( state_fields, function( k, v ) ctable[ v ] = self[ v ] end )
  481. return table.tostring( ctable )
  482. end
  483. -- Check the configuration of the given component against the previous one
  484. -- Return true if the configuration has changed
  485. builder._compare_config = function( self, what )
  486. local res = false
  487. local crtstate = self:_config_to_string( what )
  488. if not self.clean_mode then
  489. local fconf = io.open( self.build_dir .. utils.dir_sep .. ".builddata." .. what, "rb" )
  490. if fconf then
  491. local oldstate = fconf:read( "*a" )
  492. fconf:close()
  493. if oldstate:lower() ~= crtstate:lower() then res = true end
  494. end
  495. end
  496. -- Write state to build dir
  497. fconf = io.open( self.build_dir .. utils.dir_sep .. ".builddata." .. what, "wb" )
  498. if fconf then
  499. fconf:write( self:_config_to_string( what ) )
  500. fconf:close()
  501. end
  502. return res
  503. end
  504. -- Sets the way commands are displayed
  505. builder.set_disp_mode = function( self, mode )
  506. mode = mode:lower()
  507. if mode ~= 'all' and mode ~= 'summary' and mode ~= "minimal" then
  508. print( sf( "[builder] Invalid display mode '%s'", mode ) )
  509. os.exit( 1 )
  510. end
  511. self.disp_mode = mode
  512. end
  513. ---------------------------------------
  514. -- Command line builders
  515. -- Internal helper
  516. builder._generic_cmd = function( self, args )
  517. local compcmd = args.compiler or "gcc"
  518. compcmd = compcmd .. " "
  519. local flags = type( args.flags ) == 'table' and table_to_string( utils.linearize_array( args.flags ) ) or args.flags
  520. local defines = type( args.defines ) == 'table' and table_to_string( utils.linearize_array( args.defines ) ) or args.defines
  521. local includes = type( args.includes ) == 'table' and table_to_string( utils.linearize_array( args.includes ) ) or args.includes
  522. local comptype = table_to_string( args.comptype ) or "-c"
  523. compcmd = compcmd .. utils.prepend_string( defines, "-D" )
  524. compcmd = compcmd .. utils.prepend_string( includes, "-I" )
  525. return compcmd .. flags .. " " .. comptype .. " -o $(TARGET) $(FIRST)"
  526. end
  527. -- Return a compile command based on the specified args
  528. builder.compile_cmd = function( self, args )
  529. args.defines = { args.defines, self.cmdline_macros }
  530. if self.preprocess_mode then
  531. args.comptype = "-E"
  532. elseif self.asm_mode then
  533. args.comptype = "-S"
  534. else
  535. args.comptype = "-c"
  536. end
  537. return self:_generic_cmd( args )
  538. end
  539. -- Return an assembler command based on the specified args
  540. builder.asm_cmd = function( self, args )
  541. args.defines = { args.defines, self.cmdline_macros }
  542. args.compiler = args.assembler
  543. args.comptype = self.preprocess_mode and "-E" or "-c"
  544. return self:_generic_cmd( args )
  545. end
  546. -- Return a link command based on the specified args
  547. builder.link_cmd = function( self, args )
  548. local flags = type( args.flags ) == 'table' and table_to_string( utils.linearize_array( args.flags ) ) or args.flags
  549. local libraries = type( args.libraries ) == 'table' and table_to_string( utils.linearize_array( args.libraries ) ) or args.libraries
  550. local linkcmd = args.linker or "gcc"
  551. linkcmd = linkcmd .. " " .. flags .. " -o $(TARGET) $(DEPENDS)"
  552. linkcmd = linkcmd .. " " .. utils.prepend_string( libraries, "-l" )
  553. return linkcmd
  554. end
  555. ---------------------------------------
  556. -- Target handling
  557. -- Create a return a new C to object target
  558. builder.c_target = function( self, target, deps, comp_cmd )
  559. return _target.new( target, deps, comp_cmd or self.comp_cmd, self, 'comp' )
  560. end
  561. -- Create a return a new ASM to object target
  562. builder.asm_target = function( self, target, deps, asm_cmd )
  563. return _target.new( target, deps, asm_cmd or self._asm_cmd, self, 'asm' )
  564. end
  565. -- Return the name of a dependency file name corresponding to a C source
  566. builder.get_dep_filename = function( self, srcname )
  567. return utils.replace_extension( self.build_dir .. utils.dir_sep .. linearize_fname( srcname ), "d" )
  568. end
  569. -- Create a return a new C dependency target
  570. builder.dep_target = function( self, dep, depdeps, dep_cmd )
  571. local depname = self:get_dep_filename( dep )
  572. return _target.new( depname, depdeps, dep_cmd, self, 'dep' )
  573. end
  574. -- Create and return a new link target
  575. builder.link_target = function( self, out, dep, link_cmd )
  576. local path, ext = utils.split_ext( out )
  577. if not ext and self.exe_extension and #self.exe_extension > 0 then
  578. out = out .. self.exe_extension
  579. end
  580. local t = _target.new( out, dep, link_cmd or self.link_cmd, self, 'link' )
  581. if self:_compare_config( 'link' ) then t:force_rebuild( true ) end
  582. t:set_substitute_cmdline( true )
  583. return t
  584. end
  585. -- Create and return a new generic target
  586. builder.target = function( self, dest_target, deps, cmd )
  587. return _target.new( dest_target, deps, cmd, self )
  588. end
  589. -- Register a target (called from _target.new)
  590. builder.register_target = function( self, name, obj )
  591. self._tlist[ name:gsub( "\\", "/" ) ] = obj
  592. end
  593. -- Returns a registered target (nil if not found)
  594. builder.get_registered_target = function( self, name )
  595. return self._tlist[ name:gsub( "\\", "/" ) ]
  596. end
  597. ---------------------------------------
  598. -- Actual building functions
  599. -- Return the object name corresponding to a source file name
  600. builder.obj_name = function( self, name, ext )
  601. local r = ext or self.obj_extension
  602. if not r then
  603. r = utils.is_windows() and "obj" or "o"
  604. end
  605. local objname = utils.replace_extension( name, r )
  606. -- KEEP_DIR: object file in the same directory as source file
  607. -- BUILD_DIR_LINEARIZED: object file in the build directory, linearized filename
  608. if self.build_mode == self.KEEP_DIR then
  609. return objname
  610. elseif self.build_mode == self.BUILD_DIR_LINEARIZED then
  611. return self.build_dir .. utils.dir_sep .. linearize_fname( objname )
  612. end
  613. end
  614. -- Read and interpret dependencies for each file specified in "ftable"
  615. -- "ftable" is either a space-separated string with all the source files or an array
  616. builder.read_depends = function( self, ftable )
  617. if type( ftable ) == 'string' then ftable = utils.string_to_table( ftable ) end
  618. -- Read dependency data
  619. local dtable = {}
  620. for i = 1, #ftable do
  621. local f = io.open( self:get_dep_filename( ftable[ i ] ), "rb" )
  622. local lines = ftable[ i ]
  623. if f then
  624. lines = f:read( "*a" )
  625. f:close()
  626. lines = lines:gsub( "\n", " " ):gsub( "\\%s+", " " ):gsub( "%s+", " " ):gsub( "^.-: (.*)", "%1" )
  627. end
  628. dtable[ ftable[ i ] ] = lines
  629. end
  630. return dtable
  631. end
  632. -- Create and return compile targets for the given sources
  633. builder.create_compile_targets = function( self, ftable, res )
  634. if type( ftable ) == 'string' then ftable = utils.string_to_table( ftable ) end
  635. res = res or {}
  636. ccmd, oname = "-c", "o"
  637. if self.preprocess_mode then
  638. ccmd, oname = '-E', "pre"
  639. elseif self.asm_mode then
  640. ccmd, oname = '-S', 's'
  641. end
  642. -- Build dependencies for all targets
  643. for i = 1, #ftable do
  644. local isasm = ftable[ i ]:find( "%.c$" ) == nil
  645. -- Skip assembler targets if 'asm_dep_cmd' is set to 'false'
  646. -- Skip C targets if 'c_dep_cmd' is set to 'false'
  647. local skip = isasm and self.asm_dep_cmd == false
  648. skip = skip or ( not isasm and self.c_dep_cmd == false )
  649. local deps = self:get_dep_filename( ftable[ i ] )
  650. local target
  651. if not isasm then
  652. local depcmd = skip and self.comp_cmd or ( self.c_dep_cmd or self.comp_cmd:gsub( ccmd .. " ", sf( ccmd .. " -MD -MF %s ", deps ) ) )
  653. target = self:c_target( self:obj_name( ftable[ i ], oname ), { self:get_registered_target( deps ) or ftable[ i ] }, depcmd )
  654. else
  655. local depcmd = skip and self._asm_cmd or ( self.asm_dep_cmd or self._asm_cmd:gsub( ccmd .. " ", sf( ccmd .. " -MD -MF %s ", deps ) ) )
  656. target = self:asm_target( self:obj_name( ftable[ i ], oname ), { self:get_registered_target( deps ) or ftable[ i ] }, depcmd )
  657. end
  658. -- Pre build step: replace dependencies with the ones from the compiler generated dependency file
  659. local dprint = function( ... ) if self.disp_mode ~= "minimal" then print( ... ) end end
  660. if not skip then
  661. target:set_pre_build_function( function( t, _ )
  662. if not self.clean_mode then
  663. local fres = self:read_depends( ftable[ i ] )
  664. local fdeps = fres[ ftable[ i ] ]
  665. if #fdeps:gsub( "%s+", "" ) == 0 then fdeps = ftable[ i ] end
  666. t:set_dependencies( fdeps )
  667. else
  668. if self.disp_mode ~= "minimal" then io.write( sf( "[builder] Removing %s ... ", deps ) ) end
  669. if os.remove( deps ) then dprint "done." else dprint "failed!" end
  670. end
  671. end )
  672. end
  673. target.srcname = ftable[ i ]
  674. -- TODO: check clean mode?
  675. if not isasm then self.c_targets[ #self.c_targets + 1 ] = target end
  676. table.insert( res, target )
  677. end
  678. return res
  679. end
  680. -- Add a target to the list of builder targets
  681. builder.add_target = function( self, target, help, alias )
  682. self.targets[ target.target ] = { target = target, help = help }
  683. alias = alias or {}
  684. for _, v in ipairs( alias ) do
  685. self.targets[ v ] = { target = target, help = help }
  686. end
  687. return target
  688. end
  689. -- Make a target the default one
  690. builder.default = function( self, target )
  691. self.deftarget = target.target
  692. self.targets.default = { target = target, help = "default target" }
  693. end
  694. -- Build everything
  695. builder.build = function( self, target )
  696. local t = self.targetname or self.deftarget
  697. if not t then
  698. print( utils.col_red( "[builder] Error: build target not specified" ) )
  699. os.exit( 1 )
  700. end
  701. local trg
  702. -- Look for single targets (C source files)
  703. for _, ct in pairs( self.c_targets ) do
  704. if ct.srcname == t then
  705. trg = ct
  706. break
  707. end
  708. end
  709. if not trg then
  710. if not self.targets[ t ] then
  711. print( sf( "[builder] Error: target '%s' not found", t ) )
  712. print( "Available targets: " )
  713. print( " all source files" )
  714. for k, v in pairs( self.targets ) do
  715. if not is_phony( k ) then
  716. print( sf( " %s - %s", k, v.help or "(no help available)" ) )
  717. end
  718. end
  719. if self.deftarget and not is_phony( self.deftarget ) then
  720. print( sf( "Default target is '%s'", self.deftarget ) )
  721. end
  722. os.exit( 1 )
  723. else
  724. if self.preprocess_mode or self.asm_mode then
  725. print( "[builder] Error: preprocess (-E) or asm (-S) works only with single file targets." )
  726. os.exit( 1 )
  727. end
  728. trg = self.targets[ t ].target
  729. end
  730. end
  731. self:_create_build_dir()
  732. -- At this point check if we have a change in the state that would require a rebuild
  733. if self:_compare_config( 'comp' ) then
  734. print( utils.col_yellow( "[builder] Forcing rebuild due to configuration change." ) )
  735. self.global_force_rebuild = true
  736. else
  737. self.global_force_rebuild = false
  738. end
  739. -- Do the actual build
  740. local res = trg:build()
  741. if not res then print( utils.col_yellow( sf( '[builder] %s: up to date', t ) ) ) end
  742. if self.clean_mode then
  743. os.remove( self.build_dir .. utils.dir_sep .. ".builddata.comp" )
  744. os.remove( self.build_dir .. utils.dir_sep .. ".builddata.link" )
  745. end
  746. print( utils.col_yellow( "[builder] Done building target." ) )
  747. return res
  748. end
  749. -- Create dependencies, create object files, link final object
  750. builder.make_exe_target = function( self, target, file_list )
  751. local odeps = self:create_compile_targets( file_list )
  752. local exetarget = self:link_target( target, odeps )
  753. self:default( self:add_target( exetarget ) )
  754. return exetarget
  755. end
  756. -------------------------------------------------------------------------------
  757. -- Other exported functions
  758. function new_builder( build_dir )
  759. return builder.new( build_dir )
  760. end