123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848 |
- -- eLua build system
- module( ..., package.seeall )
- local lfs = require "lfs"
- local sf = string.format
- utils = require "tools.utils"
- -------------------------------------------------------------------------------
- -- Various helpers
- -- Return the time of the last modification of the file
- local function get_ftime( path )
- local t = lfs.attributes( path, 'modification' )
- return t or -1
- end
- -- Check if a given target name is phony
- local function is_phony( target )
- return target:find( "#phony" ) == 1
- end
- -- Return a string with $(key) replaced with 'value'
- local function expand_key( s, key, value )
- if not value then return s end
- local fmt = sf( "%%$%%(%s%%)", key )
- return ( s:gsub( fmt, value ) )
- end
- -- Return a target name considering phony targets
- local function get_target_name( s )
- if not is_phony( s ) then return s end
- end
- -- 'Liniarize' a file name by replacing its path separators indicators with '_'
- local function linearize_fname( s )
- return ( s:gsub( "[\\/]", "__" ) )
- end
- -- Helper: transform a table into a string if needed
- local function table_to_string( t )
- if not t then return nil end
- if type( t ) == "table" then t = table.concat( t, " " ) end
- return t
- end
- -- Helper: return the extended type of an object (takes into account __type)
- local function exttype( o )
- local t = type( o )
- if t == "table" and o.__type then t = o:__type() end
- return t
- end
- ---------------------------------------
- -- Table utils
- -- (from http://lua-users.org/wiki/TableUtils)
- function table.val_to_str( v )
- if "string" == type( v ) then
- v = string.gsub( v, "\n", "\\n" )
- if string.match( string.gsub(v,"[^'\"]",""), '^"+$' ) then
- return "'" .. v .. "'"
- end
- return '"' .. string.gsub(v,'"', '\\"' ) .. '"'
- else
- return "table" == type( v ) and table.tostring( v ) or tostring( v )
- end
- end
- function table.key_to_str ( k )
- if "string" == type( k ) and string.match( k, "^[_%a][_%a%d]*$" ) then
- return k
- else
- return "[" .. table.val_to_str( k ) .. "]"
- end
- end
- function table.tostring( tbl )
- local result, done = {}, {}
- for k, v in ipairs( tbl ) do
- table.insert( result, table.val_to_str( v ) )
- done[ k ] = true
- end
- for k, v in pairs( tbl ) do
- if not done[ k ] then
- table.insert( result,
- table.key_to_str( k ) .. "=" .. table.val_to_str( v ) )
- end
- end
- return "{" .. table.concat( result, "," ) .. "}"
- end
- -------------------------------------------------------------------------------
- -- Dummy 'builder': simply checks the date of a file
- local _fbuilder = {}
- _fbuilder.new = function( target, dep )
- local self = {}
- setmetatable( self, { __index = _fbuilder } )
- self.target = target
- self.dep = dep
- return self
- end
- _fbuilder.build = function( self )
- -- Doesn't build anything but returns 'true' if the dependency is newer than
- -- the target
- if is_phony( self.target ) then
- return true
- else
- return get_ftime( self.dep ) > get_ftime( self.target )
- end
- end
- _fbuilder.target_name = function( self )
- return get_target_name( self.dep )
- end
- -- Object type
- _fbuilder.__type = function()
- return "_fbuilder"
- end
- -------------------------------------------------------------------------------
- -- Target object
- local _target = {}
- _target.new = function( target, dep, command, builder, ttype )
- local self = {}
- setmetatable( self, { __index = _target } )
- self.target = target
- self.command = command
- self.builder = builder
- builder:register_target( target, self )
- self:set_dependencies( dep )
- self.dep = self:_build_dependencies( self.origdep )
- self.dont_clean = false
- self.can_substitute_cmdline = false
- self._force_rebuild = #self.dep == 0
- builder.runlist[ target ] = false
- self:set_type( ttype )
- return self
- end
- -- Set dependencies as a string; actual dependencies are computed by _build_dependencies
- -- (below) when 'build' is called
- _target.set_dependencies = function( self, dep )
- self.origdep = dep
- end
- -- Set the target type
- -- This is only for displaying actions
- _target.set_type = function( self, ttype )
- local atable = { comp = { "[COMPILE]", 'blue' } , dep = { "[DEPENDS]", 'magenta' }, link = { "[LINK]", 'yellow' }, asm = { "[ASM]", 'white' } }
- local tdata = atable[ ttype ]
- if not tdata then
- self.dispstr = is_phony( self.target ) and "[PHONY]" or "[TARGET]"
- self.dispcol = 'green'
- else
- self.dispstr = tdata[ 1 ]
- self.dispcol = tdata[ 2 ]
- end
- end
- -- Set dependencies
- -- This uses a proxy table and returns string deps dynamically according
- -- to the targets currently registered in the builder
- _target._build_dependencies = function( self, dep )
- -- Step 1: start with an array
- if type( dep ) == "string" then dep = utils.string_to_table( dep ) end
- -- Step 2: linearize "dep" array keeping targets
- local filter = function( e )
- local t = exttype( e )
- return t ~= "_ftarget" and t ~= "_target"
- end
- dep = utils.linearize_array( dep, filter )
- -- Step 3: strings are turned into _fbuilder objects if not found as targets;
- -- otherwise the corresponding target object is used
- for i = 1, #dep do
- if type( dep[ i ] ) == 'string' then
- local t = self.builder:get_registered_target( dep[ i ] )
- dep[ i ] = t or _fbuilder.new( self.target, dep[ i ] )
- end
- end
- return dep
- end
- -- Set pre-build function
- _target.set_pre_build_function = function( self, f )
- self._pre_build_function = f
- end
- -- Set post-build function
- _target.set_post_build_function = function( self, f )
- self._post_build_function = f
- end
- -- Force rebuild
- _target.force_rebuild = function( self, flag )
- self._force_rebuild = flag
- end
- -- Set additional arguments to send to the builder function if it is a callable
- _target.set_target_args = function( self, args )
- self._target_args = args
- end
- -- Function to execute in clean mode
- _target._cleaner = function( target, deps, tobj, disp_mode )
- -- Clean the main target if it is not a phony target
- local dprint = function( ... )
- if disp_mode ~= "minimal" then print( ... ) end
- end
- if not is_phony( target ) then
- if tobj.dont_clean then
- dprint( sf( "[builder] Target '%s' will not be deleted", target ) )
- return 0
- end
- if disp_mode ~= "minimal" then io.write( sf( "[builder] Removing %s ... ", target ) ) end
- if os.remove( target ) then dprint "done." else dprint "failed!" end
- end
- return 0
- end
- -- Build the given target
- _target.build = function( self )
- if self.builder.runlist[ self.target ] then return end
- local docmd = self:target_name() and lfs.attributes( self:target_name(), "mode" ) ~= "file"
- docmd = docmd or self.builder.global_force_rebuild
- local initdocmd = docmd
- self.dep = self:_build_dependencies( self.origdep )
- local depends, dep, previnit = '', self.dep, self.origdep
- -- Iterate through all dependencies, execute each one in turn
- local deprunner = function()
- for i = 1, #dep do
- local res = dep[ i ]:build()
- docmd = docmd or res
- local t = dep[ i ]:target_name()
- if exttype( dep[ i ] ) == "_target" and t and not is_phony( self.target ) then
- docmd = docmd or get_ftime( t ) > get_ftime( self.target )
- end
- if t then depends = depends .. t .. " " end
- end
- end
- deprunner()
- -- Execute the preb-build function if needed
- if self._pre_build_function then self._pre_build_function( self, docmd ) end
- -- If the dependencies changed as a result of running the pre-build function
- -- run through them again
- if previnit ~= self.origdep then
- self.dep = self:_build_dependencies( self.origdep )
- depends, dep, docmd = '', self.dep, initdocmd
- deprunner()
- end
- -- If at least one dependency is new rebuild the target
- docmd = docmd or self._force_rebuild or self.builder.clean_mode
- local keep_flag = true
- if docmd and self.command then
- if self.builder.disp_mode ~= 'all' and self.builder.disp_mode ~= "minimal" and not self.builder.clean_mode then
- io.write( utils.col_funcs[ self.dispcol ]( self.dispstr ) .. " " )
- end
- local cmd, code = self.command
- if self.builder.clean_mode then cmd = _target._cleaner end
- if type( cmd ) == 'string' then
- cmd = expand_key( cmd, "TARGET", self.target )
- cmd = expand_key( cmd, "DEPENDS", depends )
- cmd = expand_key( cmd, "FIRST", dep[ 1 ]:target_name() )
- if self.builder.disp_mode == 'all' then
- print( cmd )
- elseif self.builder.disp_mode ~= "minimal" then
- print( self.target )
- end
- code = self:execute( cmd )
- else
- if not self.builder.clean_mode and self.builder.disp_mode ~= "all" and self.builder.disp_mode ~= "minimal" then
- print( self.target )
- end
- code = cmd( self.target, self.dep, self.builder.clean_mode and self or self._target_args, self.builder.disp_mode )
- if code == 1 then -- this means "mark target as 'not executed'"
- keep_flag = false
- code = 0
- end
- end
- if code ~= 0 then
- print( utils.col_red( "[builder] Error building target" ) )
- if self.builder.disp_mode ~= 'all' and type( cmd ) == "string" then
- print( utils.col_red( "[builder] Last executed command was: " ) )
- print( cmd )
- end
- os.exit( 1 )
- end
- end
- -- Execute the post-build function if needed
- if self._post_build_function then self._post_build_function( self, docmd ) end
- -- Marked target as "already ran" so it won't run again
- self.builder.runlist[ self.target ] = true
- return docmd and keep_flag
- end
- -- Return the actual target name (taking into account phony targets)
- _target.target_name = function( self )
- return get_target_name( self.target )
- end
- -- Restrict cleaning this target
- _target.prevent_clean = function( self, flag )
- self.dont_clean = flag
- end
- -- Object type
- _target.__type = function()
- return "_target"
- end
- _target.execute = function( self, cmd )
- local code
- if utils.is_windows() and #cmd > 8190 and self.can_substitute_cmdline then
- -- Avoid cmd's maximum command line length limitation
- local t = cmd:find( " " )
- f = io.open( "tmpcmdline", "w" )
- local rest = cmd:sub( t + 1 )
- f:write( ( rest:gsub( "\\", "/" ) ) )
- f:close()
- cmd = cmd:sub( 1, t - 1 ) .. " @tmpcmdline"
- end
- local code = os.execute( cmd )
- os.remove( "tmpcmdline" )
- return code
- end
- _target.set_substitute_cmdline = function( self, flag )
- self.can_substitute_cmdline = flag
- end
- -------------------------------------------------------------------------------
- -- Builder public interface
- builder = { KEEP_DIR = 0, BUILD_DIR_LINEARIZED = 1 }
- ---------------------------------------
- -- Initialization and option handling
- -- Create a new builder object with the output in 'build_dir' and with the
- -- specified compile, dependencies and link command
- builder.new = function( build_dir )
- self = {}
- setmetatable( self, { __index = builder } )
- self.build_dir = build_dir or ".build"
- self.exe_extension = utils.is_windows() and "exe" or ""
- self.clean_mode = false
- self.opts = utils.options_handler()
- self.args = {}
- self.user_args = {}
- self.build_mode = self.KEEP_DIR
- self.targets = {}
- self.targetargs = {}
- self._tlist = {}
- self.runlist = {}
- self.disp_mode = 'all'
- self.cmdline_macros = {}
- self.c_targets = {}
- self.preprocess_mode = false
- self.asm_mode = false
- return self
- end
- -- Helper: create the build output directory
- builder._create_build_dir = function( self )
- if self.build_dir_created then return end
- if self.build_mode ~= self.KEEP_DIR then
- -- Create builds directory if needed
- local mode = lfs.attributes( self.build_dir, "mode" )
- if not mode or mode ~= "directory" then
- if not utils.full_mkdir( self.build_dir ) then
- print( "[builder] Unable to create directory " .. self.build_dir )
- os.exit( 1 )
- end
- end
- end
- self.build_dir_created = true
- end
- -- Add an options to the builder
- builder.add_option = function( self, name, help, default, data )
- self.opts:add_option( name, help, default, data )
- end
- -- Initialize builder from the given command line
- builder.init = function( self, args )
- -- Add the default options
- local opts = self.opts
- opts:add_option( "build_mode", 'choose location of the object files', self.KEEP_DIR,
- { keep_dir = self.KEEP_DIR, build_dir_linearized = self.BUILD_DIR_LINEARIZED } )
- opts:add_option( "build_dir", 'choose build directory', self.build_dir )
- opts:add_option( "disp_mode", 'set builder display mode', 'summary', { 'all', 'summary', 'minimal' } )
- -- Apply default values to all options
- for i = 1, opts:get_num_opts() do
- local o = opts:get_option( i )
- self.args[ o.name:upper() ] = o.default
- end
- -- Read and interpret command line
- for i = 1, #args do
- local a = args[ i ]
- if a:upper() == "-C" then -- clean option (-c)
- self.clean_mode = true
- elseif a:upper() == '-H' then -- help option (-h)
- self:_show_help()
- os.exit( 1 )
- elseif a:upper() == "-E" then -- preprocess
- self.preprocess_mode = true
- elseif a:upper() == "-S" then -- generate assembler
- self.asm_mode = true
- elseif a:find( '-D' ) == 1 and #a > 2 then -- this is a macro definition that will be auomatically added to the compiler flags
- table.insert( self.cmdline_macros, a:sub( 3 ) )
- elseif a:find( '=' ) then -- builder argument (key=value)
- local k, v = opts:handle_arg( a )
- if not k then
- self:_show_help()
- os.exit( 1 )
- end
- self.args[ k:upper() ] = v
- self.user_args[ k:upper() ] = true
- else -- this must be the target name / target arguments
- if self.targetname == nil then
- self.targetname = a
- else
- table.insert( self.targetargs, a )
- end
- end
- end
- -- Read back the default options
- self.build_mode = self.args.BUILD_MODE
- self.build_dir = self.args.BUILD_DIR
- self.disp_mode = self.args.DISP_MODE
- end
- -- Return the value of the option with the given name
- builder.get_option = function( self, optname )
- return self.args[ optname:upper() ]
- end
- -- Returns true if the given option was specified by the user on the command line, false otherwise
- builder.is_user_option = function( self, optname )
- return self.user_args[ optname:upper() ]
- end
- -- Show builder help
- builder._show_help = function( self )
- print( "[builder] Valid options:" )
- print( " -h: help (this text)" )
- print( " -c: clean target" )
- print( " -E: generate preprocessed output for single file targets" )
- print( " -S: generate assembler output for single file targets" )
- self.opts:show_help()
- end
- ---------------------------------------
- -- Builder configuration
- -- Set the compile command
- builder.set_compile_cmd = function( self, cmd )
- self.comp_cmd = cmd
- end
- -- Set the link command
- builder.set_link_cmd = function( self, cmd )
- self.link_cmd = cmd
- end
- -- Set the assembler command
- builder.set_asm_cmd = function( self, cmd )
- self._asm_cmd = cmd
- end
- -- Set (actually force) the object file extension
- builder.set_object_extension = function( self, ext )
- self.obj_extension = ext
- end
- -- Set (actually force) the executable file extension
- builder.set_exe_extension = function( self, ext )
- self.exe_extension = ext
- end
- -- Set the clean mode
- builder.set_clean_mode = function( self, isclean )
- self.clean_mode = isclean
- end
- -- Sets the build mode
- builder.set_build_mode = function( self, mode )
- self.build_mode = mode
- end
- -- Set the build directory
- builder.set_build_dir = function( self, dir )
- if self.build_dir_created then
- print "[builder] Error: build directory already created"
- os.exit( 1 )
- end
- self.build_dir = dir
- self:_create_build_dir()
- end
- -- Return the current build directory
- builder.get_build_dir = function( self )
- return self.build_dir
- end
- -- Return the target arguments
- builder.get_target_args = function( self )
- return self.targetargs
- end
- -- Set a specific dependency generation command for the assembler
- -- Pass 'false' to skip dependency generation for assembler files
- builder.set_asm_dep_cmd = function( self, asm_dep_cmd )
- self.asm_dep_cmd = asm_dep_cmd
- end
- -- Set a specific dependency generation command for the compiler
- -- Pass 'false' to skip dependency generation for C files
- builder.set_c_dep_cmd = function( self, c_dep_cmd )
- self.c_dep_cmd = c_dep_cmd
- end
- -- Save the builder configuration for a given component to a string
- builder._config_to_string = function( self, what )
- local ctable = {}
- local state_fields
- if what == 'comp' then
- state_fields = { 'comp_cmd', '_asm_cmd', 'c_dep_cmd', 'asm_dep_cmd', 'obj_extension' }
- elseif what == 'link' then
- state_fields = { 'link_cmd' }
- else
- print( sf( "Invalid argument '%s' to _config_to_string", what ) )
- os.exit( 1 )
- end
- utils.foreach( state_fields, function( k, v ) ctable[ v ] = self[ v ] end )
- return table.tostring( ctable )
- end
- -- Check the configuration of the given component against the previous one
- -- Return true if the configuration has changed
- builder._compare_config = function( self, what )
- local res = false
- local crtstate = self:_config_to_string( what )
- if not self.clean_mode then
- local fconf = io.open( self.build_dir .. utils.dir_sep .. ".builddata." .. what, "rb" )
- if fconf then
- local oldstate = fconf:read( "*a" )
- fconf:close()
- if oldstate:lower() ~= crtstate:lower() then res = true end
- end
- end
- -- Write state to build dir
- fconf = io.open( self.build_dir .. utils.dir_sep .. ".builddata." .. what, "wb" )
- if fconf then
- fconf:write( self:_config_to_string( what ) )
- fconf:close()
- end
- return res
- end
- -- Sets the way commands are displayed
- builder.set_disp_mode = function( self, mode )
- mode = mode:lower()
- if mode ~= 'all' and mode ~= 'summary' and mode ~= "minimal" then
- print( sf( "[builder] Invalid display mode '%s'", mode ) )
- os.exit( 1 )
- end
- self.disp_mode = mode
- end
- ---------------------------------------
- -- Command line builders
- -- Internal helper
- builder._generic_cmd = function( self, args )
- local compcmd = args.compiler or "gcc"
- compcmd = compcmd .. " "
- local flags = type( args.flags ) == 'table' and table_to_string( utils.linearize_array( args.flags ) ) or args.flags
- local defines = type( args.defines ) == 'table' and table_to_string( utils.linearize_array( args.defines ) ) or args.defines
- local includes = type( args.includes ) == 'table' and table_to_string( utils.linearize_array( args.includes ) ) or args.includes
- local comptype = table_to_string( args.comptype ) or "-c"
- compcmd = compcmd .. utils.prepend_string( defines, "-D" )
- compcmd = compcmd .. utils.prepend_string( includes, "-I" )
- return compcmd .. flags .. " " .. comptype .. " -o $(TARGET) $(FIRST)"
- end
- -- Return a compile command based on the specified args
- builder.compile_cmd = function( self, args )
- args.defines = { args.defines, self.cmdline_macros }
- if self.preprocess_mode then
- args.comptype = "-E"
- elseif self.asm_mode then
- args.comptype = "-S"
- else
- args.comptype = "-c"
- end
- return self:_generic_cmd( args )
- end
- -- Return an assembler command based on the specified args
- builder.asm_cmd = function( self, args )
- args.defines = { args.defines, self.cmdline_macros }
- args.compiler = args.assembler
- args.comptype = self.preprocess_mode and "-E" or "-c"
- return self:_generic_cmd( args )
- end
- -- Return a link command based on the specified args
- builder.link_cmd = function( self, args )
- local flags = type( args.flags ) == 'table' and table_to_string( utils.linearize_array( args.flags ) ) or args.flags
- local libraries = type( args.libraries ) == 'table' and table_to_string( utils.linearize_array( args.libraries ) ) or args.libraries
- local linkcmd = args.linker or "gcc"
- linkcmd = linkcmd .. " " .. flags .. " -o $(TARGET) $(DEPENDS)"
- linkcmd = linkcmd .. " " .. utils.prepend_string( libraries, "-l" )
- return linkcmd
- end
- ---------------------------------------
- -- Target handling
- -- Create a return a new C to object target
- builder.c_target = function( self, target, deps, comp_cmd )
- return _target.new( target, deps, comp_cmd or self.comp_cmd, self, 'comp' )
- end
- -- Create a return a new ASM to object target
- builder.asm_target = function( self, target, deps, asm_cmd )
- return _target.new( target, deps, asm_cmd or self._asm_cmd, self, 'asm' )
- end
- -- Return the name of a dependency file name corresponding to a C source
- builder.get_dep_filename = function( self, srcname )
- return utils.replace_extension( self.build_dir .. utils.dir_sep .. linearize_fname( srcname ), "d" )
- end
- -- Create a return a new C dependency target
- builder.dep_target = function( self, dep, depdeps, dep_cmd )
- local depname = self:get_dep_filename( dep )
- return _target.new( depname, depdeps, dep_cmd, self, 'dep' )
- end
- -- Create and return a new link target
- builder.link_target = function( self, out, dep, link_cmd )
- local path, ext = utils.split_ext( out )
- if not ext and self.exe_extension and #self.exe_extension > 0 then
- out = out .. self.exe_extension
- end
- local t = _target.new( out, dep, link_cmd or self.link_cmd, self, 'link' )
- if self:_compare_config( 'link' ) then t:force_rebuild( true ) end
- t:set_substitute_cmdline( true )
- return t
- end
- -- Create and return a new generic target
- builder.target = function( self, dest_target, deps, cmd )
- return _target.new( dest_target, deps, cmd, self )
- end
- -- Register a target (called from _target.new)
- builder.register_target = function( self, name, obj )
- self._tlist[ name:gsub( "\\", "/" ) ] = obj
- end
- -- Returns a registered target (nil if not found)
- builder.get_registered_target = function( self, name )
- return self._tlist[ name:gsub( "\\", "/" ) ]
- end
- ---------------------------------------
- -- Actual building functions
- -- Return the object name corresponding to a source file name
- builder.obj_name = function( self, name, ext )
- local r = ext or self.obj_extension
- if not r then
- r = utils.is_windows() and "obj" or "o"
- end
- local objname = utils.replace_extension( name, r )
- -- KEEP_DIR: object file in the same directory as source file
- -- BUILD_DIR_LINEARIZED: object file in the build directory, linearized filename
- if self.build_mode == self.KEEP_DIR then
- return objname
- elseif self.build_mode == self.BUILD_DIR_LINEARIZED then
- return self.build_dir .. utils.dir_sep .. linearize_fname( objname )
- end
- end
- -- Read and interpret dependencies for each file specified in "ftable"
- -- "ftable" is either a space-separated string with all the source files or an array
- builder.read_depends = function( self, ftable )
- if type( ftable ) == 'string' then ftable = utils.string_to_table( ftable ) end
- -- Read dependency data
- local dtable = {}
- for i = 1, #ftable do
- local f = io.open( self:get_dep_filename( ftable[ i ] ), "rb" )
- local lines = ftable[ i ]
- if f then
- lines = f:read( "*a" )
- f:close()
- lines = lines:gsub( "\n", " " ):gsub( "\\%s+", " " ):gsub( "%s+", " " ):gsub( "^.-: (.*)", "%1" )
- end
- dtable[ ftable[ i ] ] = lines
- end
- return dtable
- end
- -- Create and return compile targets for the given sources
- builder.create_compile_targets = function( self, ftable, res )
- if type( ftable ) == 'string' then ftable = utils.string_to_table( ftable ) end
- res = res or {}
- ccmd, oname = "-c", "o"
- if self.preprocess_mode then
- ccmd, oname = '-E', "pre"
- elseif self.asm_mode then
- ccmd, oname = '-S', 's'
- end
- -- Build dependencies for all targets
- for i = 1, #ftable do
- local isasm = ftable[ i ]:find( "%.c$" ) == nil
- -- Skip assembler targets if 'asm_dep_cmd' is set to 'false'
- -- Skip C targets if 'c_dep_cmd' is set to 'false'
- local skip = isasm and self.asm_dep_cmd == false
- skip = skip or ( not isasm and self.c_dep_cmd == false )
- local deps = self:get_dep_filename( ftable[ i ] )
- local target
- if not isasm then
- local depcmd = skip and self.comp_cmd or ( self.c_dep_cmd or self.comp_cmd:gsub( ccmd .. " ", sf( ccmd .. " -MD -MF %s ", deps ) ) )
- target = self:c_target( self:obj_name( ftable[ i ], oname ), { self:get_registered_target( deps ) or ftable[ i ] }, depcmd )
- else
- local depcmd = skip and self._asm_cmd or ( self.asm_dep_cmd or self._asm_cmd:gsub( ccmd .. " ", sf( ccmd .. " -MD -MF %s ", deps ) ) )
- target = self:asm_target( self:obj_name( ftable[ i ], oname ), { self:get_registered_target( deps ) or ftable[ i ] }, depcmd )
- end
- -- Pre build step: replace dependencies with the ones from the compiler generated dependency file
- local dprint = function( ... ) if self.disp_mode ~= "minimal" then print( ... ) end end
- if not skip then
- target:set_pre_build_function( function( t, _ )
- if not self.clean_mode then
- local fres = self:read_depends( ftable[ i ] )
- local fdeps = fres[ ftable[ i ] ]
- if #fdeps:gsub( "%s+", "" ) == 0 then fdeps = ftable[ i ] end
- t:set_dependencies( fdeps )
- else
- if self.disp_mode ~= "minimal" then io.write( sf( "[builder] Removing %s ... ", deps ) ) end
- if os.remove( deps ) then dprint "done." else dprint "failed!" end
- end
- end )
- end
- target.srcname = ftable[ i ]
- -- TODO: check clean mode?
- if not isasm then self.c_targets[ #self.c_targets + 1 ] = target end
- table.insert( res, target )
- end
- return res
- end
- -- Add a target to the list of builder targets
- builder.add_target = function( self, target, help, alias )
- self.targets[ target.target ] = { target = target, help = help }
- alias = alias or {}
- for _, v in ipairs( alias ) do
- self.targets[ v ] = { target = target, help = help }
- end
- return target
- end
- -- Make a target the default one
- builder.default = function( self, target )
- self.deftarget = target.target
- self.targets.default = { target = target, help = "default target" }
- end
- -- Build everything
- builder.build = function( self, target )
- local t = self.targetname or self.deftarget
- if not t then
- print( utils.col_red( "[builder] Error: build target not specified" ) )
- os.exit( 1 )
- end
- local trg
- -- Look for single targets (C source files)
- for _, ct in pairs( self.c_targets ) do
- if ct.srcname == t then
- trg = ct
- break
- end
- end
- if not trg then
- if not self.targets[ t ] then
- print( sf( "[builder] Error: target '%s' not found", t ) )
- print( "Available targets: " )
- print( " all source files" )
- for k, v in pairs( self.targets ) do
- if not is_phony( k ) then
- print( sf( " %s - %s", k, v.help or "(no help available)" ) )
- end
- end
- if self.deftarget and not is_phony( self.deftarget ) then
- print( sf( "Default target is '%s'", self.deftarget ) )
- end
- os.exit( 1 )
- else
- if self.preprocess_mode or self.asm_mode then
- print( "[builder] Error: preprocess (-E) or asm (-S) works only with single file targets." )
- os.exit( 1 )
- end
- trg = self.targets[ t ].target
- end
- end
- self:_create_build_dir()
- -- At this point check if we have a change in the state that would require a rebuild
- if self:_compare_config( 'comp' ) then
- print( utils.col_yellow( "[builder] Forcing rebuild due to configuration change." ) )
- self.global_force_rebuild = true
- else
- self.global_force_rebuild = false
- end
- -- Do the actual build
- local res = trg:build()
- if not res then print( utils.col_yellow( sf( '[builder] %s: up to date', t ) ) ) end
- if self.clean_mode then
- os.remove( self.build_dir .. utils.dir_sep .. ".builddata.comp" )
- os.remove( self.build_dir .. utils.dir_sep .. ".builddata.link" )
- end
- print( utils.col_yellow( "[builder] Done building target." ) )
- return res
- end
- -- Create dependencies, create object files, link final object
- builder.make_exe_target = function( self, target, file_list )
- local odeps = self:create_compile_targets( file_list )
- local exetarget = self:link_target( target, odeps )
- self:default( self:add_target( exetarget ) )
- return exetarget
- end
- -------------------------------------------------------------------------------
- -- Other exported functions
- function new_builder( build_dir )
- return builder.new( build_dir )
- end
|