mirror of
https://github.com/elua/elua.git
synced 2025-01-25 01:02:54 +08:00
The separate dependency generation step from the Lua build system was not needed, now the dependencies are generated at the same time as the object files.
767 lines
25 KiB
Lua
767 lines
25 KiB
Lua
-- eLua build system
|
|
|
|
module( ..., package.seeall )
|
|
|
|
local lfs = require "lfs"
|
|
local sf = string.format
|
|
utils = require "utils.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._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 )
|
|
-- Clean the main target if it is not a phony target
|
|
if not is_phony( target ) then
|
|
if tobj.dont_clean then
|
|
print( sf( "[builder] Target '%s' will not be deleted", target ) )
|
|
return 0
|
|
end
|
|
io.write( sf( "[builder] Removing %s ... ", target ) )
|
|
if os.remove( target ) then print "done." else print "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 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 )
|
|
else
|
|
print( self.target )
|
|
end
|
|
code = os.execute( cmd )
|
|
else
|
|
if not self.builder.clean_mode and self.builder.disp_mode ~= "all" then
|
|
print( self.target )
|
|
end
|
|
code = cmd( self.target, self.dep, self.builder.clean_mode and self or self._target_args )
|
|
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
|
|
|
|
-------------------------------------------------------------------------------
|
|
-- Builder public interface
|
|
|
|
builder = { KEEP_DIR = 0, BUILD_DIR = 1, BUILD_DIR_LINEARIZED = 2 }
|
|
|
|
---------------------------------------
|
|
-- 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.build_mode = self.KEEP_DIR
|
|
self.targets = {}
|
|
self.targetargs = {}
|
|
self._tlist = {}
|
|
self.runlist = {}
|
|
self.disp_mode = 'all'
|
|
self.cmdline_macros = {}
|
|
return self
|
|
end
|
|
|
|
-- Helper: create the build output directory
|
|
builder._create_outdir = function( self )
|
|
if self.output_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.output_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 = self.BUILD_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' } )
|
|
-- 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: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
|
|
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
|
|
|
|
-- Show builder help
|
|
builder._show_help = function( self )
|
|
print( "[builder] Valid options:" )
|
|
print( " -h: help (this text)" )
|
|
print( " -c: clean target" )
|
|
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 output directory
|
|
builder.set_output_dir = function( self, dir )
|
|
if self.output_dir_created then
|
|
print "[ builder] Error: output directory already created"
|
|
os.exit( 1 )
|
|
end
|
|
self.build_dir = dir
|
|
self:_create_outdir()
|
|
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' 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 }
|
|
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
|
|
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 )
|
|
if not out:find( "%." ) 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
|
|
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: object file in the build directory
|
|
-- 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 )
|
|
else
|
|
local si, ei, path, fname = objname:find( "(.+)/(.-)$" )
|
|
if not si then fname = objname end
|
|
return self.build_dir .. utils.dir_sep .. fname
|
|
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 {}
|
|
-- 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( "-c ", sf( "-c -MD -MF %s ", deps ) ) )
|
|
target = self:c_target( self:obj_name( ftable[ i ] ), { 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( "-c ", sf( "-c -MD -MF %s ", deps ) ) )
|
|
target = self:asm_target( self:obj_name( ftable[ i ] ), { self:get_registered_target( deps ) or ftable[ i ] }, depcmd )
|
|
end
|
|
-- Pre build step: replace dependencies with the ones from the compiler generated dependency file
|
|
if not skip then
|
|
target:set_pre_build_function( function( t, _ )
|
|
if not self.clean_mode then
|
|
local fres = self:read_depends( ftable[ i ] )
|
|
t:set_dependencies( fres[ ftable[ i ] ] or ftable[ i ] )
|
|
end
|
|
end )
|
|
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( "[builder] Error: build target not specified" )
|
|
os.exit( 1 )
|
|
end
|
|
if not self.targets[ t ] then
|
|
print( sf( "[builder] Error: target '%s' not found", t ) )
|
|
print( "Available targets: " )
|
|
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 )
|
|
end
|
|
self:_create_outdir()
|
|
-- At this point check if we have a change in the state that would require a rebuild
|
|
if self:_compare_config( 'comp' ) then
|
|
print "[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 = self.targets[ t ].target:build()
|
|
if not res then print( 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 "[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
|
|
|