mirror of
https://github.com/elua/elua.git
synced 2025-01-08 20:56:17 +08:00
First step towards a Lua based build system. NOT Lake based, just a set of functions that I wrote (utils/build.lua). Does automatic dependency checking (something Lake doesn't do very
well), per-project build directories and other nice stuff. So far used only for mux and rfs_server; 'lua mux.lua' or 'lua rfs_server.lua' to test (add "-c" to clean). Only needs lfs.
This commit is contained in:
parent
3f8e89498e
commit
2d0dbd0ed3
36
mux.lua
Normal file
36
mux.lua
Normal file
@ -0,0 +1,36 @@
|
||||
local args = { ... }
|
||||
local b = require "utils.build"
|
||||
local builder = b.new_builder( ".build/mux" )
|
||||
local utils = b.utils
|
||||
builder:init( args )
|
||||
builder:set_build_mode( builder.BUILD_DIR_LINEARIZED )
|
||||
|
||||
local flist = "main.c"
|
||||
local rfs_flist = "main.c server.c log.c deskutils.c"
|
||||
local cdefs = "RFS_UDP_TRANSPORT RFS_INSIDE_MUX_MODE"
|
||||
local socklib
|
||||
if utils.is_windows() then
|
||||
cdefs = cdefs .. " WIN32_BUILD"
|
||||
rfs_flist = rfs_flist .. " os_io_win32.c serial_win32.c net_win32.c"
|
||||
exeprefix = "exe"
|
||||
socklib = "ws2_32"
|
||||
else
|
||||
rfs_flist = rfs_flist .. " os_io_posix.c serial_posix.c net_posix.c"
|
||||
exeprefix = ""
|
||||
end
|
||||
|
||||
local full_files = utils.prepend_path( flist, "mux_src" ) .. utils.prepend_path( rfs_flist, "rfs_server_src" ) .. "src/remotefs/remotefs.c src/eluarpc.c"
|
||||
local local_include = "mux_src rfs_server_src inc inc/remotefs"
|
||||
local compcmd = builder:compile_cmd{ flags = "-m32 -O0 -Wall -g", defines = cdefs, includes = local_include }
|
||||
local depcmd = builder:dep_cmd{ flags = "-m32 -O0 -Wall -g", defines = cdefs, includes = local_include }
|
||||
local linkcmd = builder:link_cmd{ flags = "-m32", libraries = socklib }
|
||||
builder:set_dep_cmd( depcmd )
|
||||
builder:set_compile_cmd( compcmd )
|
||||
builder:set_link_cmd( linkcmd )
|
||||
builder:set_exe_extension( exeprefix )
|
||||
|
||||
-- Build everyting
|
||||
builder:make_depends( full_files )
|
||||
local odeps = builder:create_compile_targets( full_files )
|
||||
builder:build_c_target( "mux", odeps )
|
||||
|
43
rfs_server.lua
Normal file
43
rfs_server.lua
Normal file
@ -0,0 +1,43 @@
|
||||
local args = { ... }
|
||||
local b = require "utils.build"
|
||||
local builder = b.new_builder( ".build/rfs_server" )
|
||||
local utils = b.utils
|
||||
builder:init( args )
|
||||
builder:set_build_mode( builder.BUILD_DIR_LINEARIZED )
|
||||
|
||||
sim = builder:get_arg( 'sim' )
|
||||
sim = sim and tonumber( sim ) or 0
|
||||
|
||||
local flist, socklib
|
||||
local cdefs = "RFS_STANDALONE_MODE"
|
||||
local mainname = sim == 0 and 'main.c' or 'main_sim.c'
|
||||
local exeprefix = ""
|
||||
if utils.is_windows() then
|
||||
if sim == 1 then
|
||||
print "SIM target not supported under Windows"
|
||||
os.exit( 1 )
|
||||
end
|
||||
flist = "main.c server.c os_io_win32.c log.c net_win32.c serial_win32.c deskutils.c"
|
||||
cdefs = cdefs .. " WIN32_BUILD"
|
||||
exeprefix = "exe"
|
||||
socklib = 'ws2_32'
|
||||
else
|
||||
flist = mainname .. " server.c os_io_posix.c log.c net_posix.c serial_posix.c deskutils.c"
|
||||
end
|
||||
|
||||
local output = sim == 0 and 'rfs_server' or 'rfs_sim_server'
|
||||
local local_include = "rfs_server_src inc/remotefs inc"
|
||||
local full_files = utils.prepend_path( flist, 'rfs_server_src' ) .. " src/remotefs/remotefs.c src/eluarpc.c"
|
||||
local compcmd = builder:compile_cmd{ flags = "-m32 -O0 -Wall -g", defines = cdefs, includes = local_include }
|
||||
local depcmd = builder:dep_cmd{ flags = "-m32 -O0 -Wall -g", defines = cdefs, includes = local_include }
|
||||
local linkcmd = builder:link_cmd{ flags = "-m32", libraries = socklib }
|
||||
builder:set_dep_cmd( depcmd )
|
||||
builder:set_compile_cmd( compcmd )
|
||||
builder:set_link_cmd( linkcmd )
|
||||
builder:set_exe_extension( exeprefix )
|
||||
|
||||
-- Build everything
|
||||
builder:make_depends( full_files )
|
||||
local odeps = builder:create_compile_targets( full_files )
|
||||
builder:build_c_target( "rfs_server", odeps )
|
||||
|
475
utils/build.lua
Normal file
475
utils/build.lua
Normal file
@ -0,0 +1,475 @@
|
||||
-- eLua build system
|
||||
module( ..., package.seeall )
|
||||
|
||||
local lfs = require "lfs"
|
||||
local sf = string.format
|
||||
|
||||
-- Taken from Lake
|
||||
local dir_sep = package.config:sub( 1, 1 )
|
||||
local is_windows = dir_sep == '\\'
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
-- 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 )
|
||||
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 )
|
||||
local fmt = "%" .. dir_sep
|
||||
return ( s:gsub( fmt, "__" ) )
|
||||
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
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
-- Public utilities
|
||||
|
||||
utils = { dir_sep = dir_sep }
|
||||
|
||||
-- Converts a string with items separated by 'sep' into a table
|
||||
utils.string_to_table = function( s, sep )
|
||||
sep = sep or ' '
|
||||
if s:sub( -1, -1 ) ~= sep then s = s .. sep end
|
||||
local t = {}
|
||||
local fmt = sf( "(.-)%s+", sep )
|
||||
for w in s:gmatch( fmt ) do table.insert( t, w ) end
|
||||
return t
|
||||
end
|
||||
|
||||
-- Replace the extension of a give file name
|
||||
utils.replace_extension = function( s, newext )
|
||||
local pos
|
||||
for i = #s, 1, -1 do
|
||||
if s:sub( i, i ) == "." then
|
||||
pos = i
|
||||
break
|
||||
end
|
||||
end
|
||||
if pos then s = s:sub( 1, pos ) .. newext end
|
||||
return s
|
||||
end
|
||||
|
||||
-- Return 'true' if building from Windows, false otherwise
|
||||
utils.is_windows = function()
|
||||
return is_windows
|
||||
end
|
||||
|
||||
-- Prepend each component of a 'pat'-separated string with 'prefix'
|
||||
utils.prepend_string = function( s, prefix, pat )
|
||||
if not s then return "" end
|
||||
pat = pat or ' '
|
||||
local res = ''
|
||||
local st = utils.string_to_table( s, pat )
|
||||
for i = 1, #st do
|
||||
res = res .. prefix .. st[ i ] .. " "
|
||||
end
|
||||
return res
|
||||
end
|
||||
|
||||
-- Like above but consider 'prefix' a path
|
||||
utils.prepend_path = function( s, prefix, pat )
|
||||
return utils.prepend_string( s, prefix .. dir_sep, pat )
|
||||
end
|
||||
|
||||
-- full mkdir: create all the paths needed for a multipath
|
||||
utils.full_mkdir = function( path )
|
||||
local ptables = utils.string_to_table( path, dir_sep )
|
||||
local p, res = ''
|
||||
for i = 1, #ptables do
|
||||
p = ( i ~= 1 and p .. dir_sep or p ) .. ptables[ i ]
|
||||
res = lfs.mkdir( p )
|
||||
end
|
||||
return res
|
||||
end
|
||||
|
||||
-- Concatenate the given paths to form a complete path
|
||||
utils.concat_path = function( paths )
|
||||
return table.concat( paths, dir_sep )
|
||||
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
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
-- Target object
|
||||
|
||||
local _target = {}
|
||||
|
||||
_target.new = function( target, dep, command )
|
||||
local self = {}
|
||||
setmetatable( self, { __index = _target } )
|
||||
self.target = target
|
||||
self.command = command
|
||||
-- Transform 'dep' into a list of objects that support the 'build' method
|
||||
if type( dep ) == 'string' then
|
||||
dep = utils.string_to_table( dep )
|
||||
elseif type( dep ) ~= 'table' then
|
||||
dep = {}
|
||||
end
|
||||
-- Iterate through 'dep' transforming file names in _fbuilder targets
|
||||
for i = 1, #dep do
|
||||
if type( dep[ i ] ) == "string" then
|
||||
dep[ i ] = _fbuilder.new( target, dep[ i ] )
|
||||
end
|
||||
end
|
||||
self.dep = dep
|
||||
self._force_rebuild = #dep == 0
|
||||
return self
|
||||
end
|
||||
|
||||
-- Force rebuild
|
||||
_target.force_rebuild = function( self, flag )
|
||||
self._force_rebuild = flag
|
||||
end
|
||||
|
||||
-- Build the given target
|
||||
_target.build = function( self )
|
||||
local docmd = self:target_name() and lfs.attributes( self:target_name(), "mode" ) ~= "file"
|
||||
local depends = ''
|
||||
local dep = self.dep
|
||||
-- Iterate through all dependencies, execute each one in turn
|
||||
for i = 1, #dep do
|
||||
local res = dep[ i ]:build()
|
||||
docmd = docmd or res
|
||||
local t = dep[ i ]:target_name()
|
||||
if t then depends = depends .. t .. " " end
|
||||
end
|
||||
docmd = docmd or self._force_rebuild
|
||||
-- If at least one dependency is new rebuild the target
|
||||
if docmd and self.command then
|
||||
local cmd = self.command
|
||||
local code
|
||||
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() )
|
||||
print( cmd )
|
||||
code = os.execute( cmd )
|
||||
else
|
||||
code = cmd( self.target, self.dep )
|
||||
end
|
||||
if code ~= 0 then return nil, "Error building target " .. self.target end
|
||||
end
|
||||
return docmd
|
||||
end
|
||||
|
||||
_target.target_name = function( self )
|
||||
return get_target_name( self.target )
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
-- Builder public interface
|
||||
|
||||
builder = { KEEP_DIR = 0, BUILD_DIR = 1, BUILD_DIR_LINEARIZED = 2 }
|
||||
|
||||
-- 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, comp_cmd, dep_cmd, link_cmd )
|
||||
self = {}
|
||||
setmetatable( self, { __index = builder } )
|
||||
self.build_dir = build_dir or ".build"
|
||||
self.comp_cmd = comp_cmd
|
||||
self.dep_cmd = dep_cmd
|
||||
self.link_cmd = link_cmd
|
||||
self.exe_extension = utils.is_windows() and "exe" or ""
|
||||
-- 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( "Unable to create directory " .. self.build_dir )
|
||||
os.exit( 1 )
|
||||
end
|
||||
end
|
||||
self.clean_mode = false
|
||||
self.build_mode = self.KEEP_DIR
|
||||
return self
|
||||
end
|
||||
|
||||
-- Initialize builder from the given command line
|
||||
builder.init = function( self, args )
|
||||
self.args = args
|
||||
-- Look for '-c' first
|
||||
for i = 1, #args do
|
||||
local a = args[ i ]:upper()
|
||||
if a == "-C" then
|
||||
self.clean_mode = true
|
||||
end
|
||||
end
|
||||
-- Then look for 'build_mode' spec
|
||||
local v = self:get_arg( 'build_mode', 'KEEP_DIR' ):upper()
|
||||
self.build_mode = self[ v ] or self.KEEP_DIR
|
||||
end
|
||||
|
||||
-- Get the given argument or the default value if not found
|
||||
-- Arguments are given in the form 'arg=value'
|
||||
builder.get_arg = function( self, name, default )
|
||||
local args = self.args
|
||||
name = name:upper()
|
||||
for i = 1, #args do
|
||||
local arg = args[ i ]
|
||||
local si, ei, k, v = arg:find( "([^=]+)=(.*)$" )
|
||||
if si and k:upper() == name then return v end
|
||||
end
|
||||
return default
|
||||
end
|
||||
|
||||
-- Set the compile command
|
||||
builder.set_compile_cmd = function( self, cmd )
|
||||
self.comp_cmd = cmd
|
||||
end
|
||||
|
||||
-- Set the dependencies command
|
||||
builder.set_dep_cmd = function( self, cmd )
|
||||
self.dep_cmd = cmd
|
||||
end
|
||||
|
||||
-- Set the link command
|
||||
builder.set_link_cmd = function( self, cmd )
|
||||
self.link_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
|
||||
|
||||
-- Is the builder in clean mode?
|
||||
builder.is_cleaning = function( self )
|
||||
return self.clean_mode
|
||||
end
|
||||
|
||||
-- Sets the build mode
|
||||
builder.set_build_mode = function( self, mode )
|
||||
self.build_mode = mode
|
||||
end
|
||||
|
||||
-- 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 )
|
||||
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 .. 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 or self.dep_cmd )
|
||||
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
|
||||
return _target.new( out, dep, link_cmd or self.link_cmd )
|
||||
end
|
||||
|
||||
-- Create and return a new generic target
|
||||
builder.target = function( self, dest_target, deps, cmd )
|
||||
return _target.new( dest_target, deps, cmd )
|
||||
end
|
||||
|
||||
-- Internal helper
|
||||
builder._generic_cmd = function( self, args )
|
||||
local compcmd = args.compiler or "gcc"
|
||||
compcmd = compcmd .. " "
|
||||
local flags = table_to_string( args.flags )
|
||||
local defines = table_to_string( args.defines )
|
||||
local includes = table_to_string( 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 )
|
||||
return self:_generic_cmd( args )
|
||||
end
|
||||
|
||||
-- Return a dependency generation command based on the specified args
|
||||
builder.dep_cmd = function( self, args )
|
||||
args.comptype = "-E -MM"
|
||||
return self:_generic_cmd( args )
|
||||
end
|
||||
|
||||
-- Return a link command based on the specified args
|
||||
builder.link_cmd = function( self, args )
|
||||
local flags = table_to_string( args.flags )
|
||||
local libraries = table_to_string( args.libraries )
|
||||
local linkcmd = args.linker or "gcc"
|
||||
linkcmd = linkcmd .. " " .. flags .. " -o $(TARGET) $(DEPENDS)"
|
||||
linkcmd = linkcmd .. utils.prepend_string( libraries, "-l" )
|
||||
return linkcmd
|
||||
end
|
||||
|
||||
-- Return the object name corresponding to a source file name
|
||||
builder.obj_name = function( self, name )
|
||||
local r = 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 .. dir_sep .. linearize_fname( objname )
|
||||
else
|
||||
local si, ei, path, fname = objname:find( "(.+)/(.-)$" )
|
||||
if not si then fname = objname end
|
||||
return self.build_dir .. dir_sep .. fname
|
||||
end
|
||||
end
|
||||
|
||||
-- Helper: remove the target (used in clean mode)
|
||||
builder._clean = function( target, deps )
|
||||
print( "Removing " .. target )
|
||||
os.remove( target )
|
||||
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
|
||||
|
||||
-- Build and interpret dependencies for the given source files
|
||||
-- "flable" is either a space-separated string with all the source files or an array
|
||||
builder.make_depends = function( self, ftable )
|
||||
if type( ftable ) == 'string' then ftable = utils.string_to_table( ftable )end
|
||||
|
||||
-- Start with initial dependency data (this might be nil when generated initially)
|
||||
local initdep = self:read_depends( ftable )
|
||||
|
||||
-- Build dependencies for all targets
|
||||
local deptargets = {}
|
||||
for i = 1, #ftable do
|
||||
local target = self:dep_target( ftable[ i ], initdep[ ftable[ i ] ], self.clean_mode and builder._clean or nil )
|
||||
target:force_rebuild( self.clean_mode )
|
||||
table.insert( deptargets, target )
|
||||
end
|
||||
local t = builder:target( "#phony.deps", deptargets )
|
||||
t:build()
|
||||
if self.clean_mode then return end
|
||||
|
||||
-- Read dependency data
|
||||
self.dtable = self:read_depends( ftable )
|
||||
end
|
||||
|
||||
-- Create compile targets for the given sources
|
||||
builder.create_compile_targets = function( self, ftable, res )
|
||||
if not self.dtable then
|
||||
if not self.clean_mode then
|
||||
print "Error: call make_depends before compile_targets"
|
||||
os.exit( 1 )
|
||||
end
|
||||
end
|
||||
res = res or {}
|
||||
if type( ftable ) == 'string' then
|
||||
ftable = utils.string_to_table( ftable )
|
||||
end
|
||||
|
||||
-- Build dependencies for all targets
|
||||
for i = 1, #ftable do
|
||||
local target = self:c_target( self:obj_name( ftable[ i ] ), self.dtable and self.dtable[ ftable[ i ] ], self.clean_mode and builder._clean or nil )
|
||||
target:force_rebuild( self.clean_mode )
|
||||
table.insert( res, target )
|
||||
end
|
||||
|
||||
return res
|
||||
end
|
||||
|
||||
-- Build the specified target starting from the given deps
|
||||
-- This links in regular mode and cleans in clean mode
|
||||
builder.build_c_target = function( self, out, odeps )
|
||||
local t = self:link_target( out, odeps, self.clean_mode and builder._clean or nil )
|
||||
t:force_rebuild( self.clean_mode )
|
||||
t:build()
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
-- Other exported functions
|
||||
|
||||
function new_builder( build_dir, comp_cmd, dep_cmd, link_cmd )
|
||||
return builder.new( build_dir, comp_cmd, dep_cmd, link_cmd )
|
||||
end
|
||||
|
Loading…
x
Reference in New Issue
Block a user