Unverified Commit 27a83feb authored by Terry Ellison's avatar Terry Ellison Committed by GitHub
Browse files

Merge pull request #2301 from TerryE/dev-LFS

LFS evaluation version -- second release
parents 2cc195d5 a3c2f48f
File mode changed from 100755 to 100644
......@@ -172,6 +172,40 @@ none
#### Returns
flash ID (number)
## node.flashindex()
Returns the function reference for a function in the LFS (Lua Flash Store).
#### Syntax
`node.flashindex(modulename)`
#### Parameters
`modulename` The name of the module to be loaded. If this is `nil` or invalid then an info list is returned
#### Returns
- In the case where the LFS in not loaded, `node.flashindex` evaluates to `nil`, followed by the flash and mapped base addresss of the LFS
- If the LFS is loaded and the function is called with the name of a valid module in the LFS, then the function is returned in the same way the `load()` and the other Lua load functions do.
- Otherwise an extended info list is returned: the Unix time of the LFS build, the flash and mapped base addresses of the LFS and its current length, and an array of the valid module names in the LFS.
#### Example
The `node.flashindex()` is a low level API call that is normally wrapped using standard Lua code to present a simpler application API. See the module `_init.lua` in the `lua_examples/lfs` directory for an example of how to do this.
## node.flashreload()
Reload the LFS (Lua Flash Store) with the flash image provided. Flash images are generated on the host machine using the `luac.cross`commnad.
#### Syntax
`node.flashreload(imageName)`
#### Parameters
`imageName` The of name of a image file in the filesystem to be loaded into the LFS.
#### Returns
If the LFS image has the incorrect signature or size, then `false` is returned.
In the case of the `imagename` being a valid LFS image, this is then loaded into flash. The ESP is then immediately rebooted so control is not returned to the calling application.
## node.flashsize()
Returns the flash chip size in bytes. On 4MB modules like ESP-12 the return value is 4194304 = 4096KB.
......@@ -221,7 +255,7 @@ system heap size left in bytes (number)
## node.info()
Returns NodeMCU version, chipid, flashid, flash size, flash mode, flash speed.
Returns NodeMCU version, chipid, flashid, flash size, flash mode, flash speed, and Lua File Store (LFS) usage statics.
#### Syntax
`node.info()`
......
......@@ -233,22 +233,25 @@ SECTIONS
/* Link-time arrays containing the defs for the included modules */
. = ALIGN(4);
lua_libs = ABSOLUTE(.);
lua_libs_base = ABSOLUTE(.);
/* Allow either empty define or defined-to-1 to include the module */
KEEP(*(.lua_libs))
LONG(0) LONG(0) /* Null-terminate the array */
lua_rotable = ABSOLUTE(.);
lua_rotable_base = ABSOLUTE(.);
KEEP(*(.lua_rotable))
LONG(0) LONG(0) /* Null-terminate the array */
/* SDK doesn't use libc functions, and are therefore safe to put in flash */
/* SDK doesn't use libc functions, and are therefore safe to put in flash */
*/libc.a:*.o(.text* .literal*)
/* end libc functions */
/* Reserved areas, flash page aligned and last */
. = ALIGN(4096);
KEEP(*(.irom.reserved .irom.reserved.*))
_irom0_text_end = ABSOLUTE(.);
_flash_used_end = ABSOLUTE(.);
} >irom0_0_seg :irom0_0_phdr =0xffffffff
}
/* get ROM code address */
......
--
-- File: _init.lua
--[[
This is a template for the LFS equivalent of the SPIFFS init.lua.
It is a good idea to such an _init.lua module to your LFS and do most of the LFS
module related initialisaion in this. This example uses standard Lua features to
simplify the LFS API.
The first section adds a 'LFS' table to _G and uses the __index metamethod to
resolve functions in the LFS, so you can execute the main function of module
'fred' by executing LFS.fred(params), etc. It also implements some standard
readonly properties:
LFS._time The Unix Timestamp when the luac.cross was executed. This can be
used as a version identifier.
LFS._config This returns a table of useful configuration parameters, hence
print (("0x%6x"):format(LFS._config.lfs_base))
gives you the parameter to use in the luac.cross -a option.
LFS._list This returns a table of the LFS modules, hence
print(table.concat(LFS._list),'\n')
gives you a single column listing of all modules in the LFS.
---------------------------------------------------------------------------------]]
local index = node.flashindex
local lfs_t = {
__index = function(_, name)
local fn_ut, ba, ma, size, modules = index(name)
if not ba then
return fn_ut
elseif name == '_time' then
return fn_ut
elseif name == '_config' then
local fs_ma, fs_size = file.fscfg()
return {lfs_base = ba, lfs_mapped = ma, lfs_size = size,
fs_mapped = fs_ma, fs_size = fs_size}
elseif name == '_list' then
return modules
else
return nil
end
end,
__newindex = function(_, name, value)
error("LFS is readonly. Invalid write to LFS." .. name, 2)
end,
}
local G=getfenv()
G.LFS = setmetatable(lfs_t,lfs_t)
--[[-------------------------------------------------------------------------------
The second section adds the LFS to the require searchlist, so that you can
require a Lua module 'jean' in the LFS by simply doing require "jean". However
note that this is at the search entry following the FS searcher, so if you also
have jean.lc or jean.lua in SPIFFS, then this SPIFFS version will get loaded into
RAM instead of using. (Useful, for development).
Note that if you want LFS to take a higher priority than SPIFFS, the use the [2]
slot for loaders. If you want to reverse these in your init.lua or interactively
for debugging, then use
do local pl = package.loaders; pl[2],pl[4] = pl[4],pl[2]; end
---------------------------------------------------------------------------------]]
package.loaders[4] = function(module) -- loader_flash
local fn, ba = index(module)
return ba and "Module not in LFS" or fn
end
--[[-------------------------------------------------------------------------------
You can add any other initialisation here, for example a couple of the globals
are never used, so setting them to nil saves a couple of global entries
---------------------------------------------------------------------------------]]
G.module = nil -- disable Lua 5.0 style modules to save RAM
package.seeall = nil
--
-- File: LFS_dummy_strings.lua
--[[
luac.cross -f generates a ROM string table which is part of the compiled LFS
image. This table includes all strings referenced in the loaded modules.
If you want to preload other string constants, then one way to achieve this is
to include a dummy module in the LFS that references the strings that you want
to load. You never need to call this module; it's inclusion in the LFS image is
enough to add the strings to the ROM table. Your application can use any strings
in the ROM table without incuring any RAM or Lua Garbage Collector (LGC)
overhead.
The local preload example is a useful starting point. However, if you call the
following code in your application during testing, then this will provide a
listing of the current RAM string table.
do
local a=debug.getstrings'RAM'
for i =1, #a do a[i] = ('%q'):format(a[i]) end
print ('local preload='..table.concat(a,','))
end
This will exclude any strings already in the ROM table, so the output is the list
of putative strings that you should consider adding to LFS ROM table.
---------------------------------------------------------------------------------]]
local preload = "?.lc;?.lua", "/\n;\n?\n!\n-", "@init.lua", "_G", "_LOADED",
"_LOADLIB", "__add", "__call", "__concat", "__div", "__eq", "__gc", "__index",
"__le", "__len", "__lt", "__mod", "__mode", "__mul", "__newindex", "__pow",
"__sub", "__tostring", "__unm", "collectgarbage", "cpath", "debug", "file",
"file.obj", "file.vol", "flash", "getstrings", "index", "ipairs", "list", "loaded",
"loader", "loaders", "loadlib", "module", "net.tcpserver", "net.tcpsocket",
"net.udpsocket", "newproxy", "package", "pairs", "path", "preload", "reload",
"require", "seeall", "wdclr", "not enough memory", "sjson.decoder","sjson.encoder",
"tmr.timer"
-- First time image boot to discover the confuration
--
-- If you want to use absolute address LFS load or SPIFFS imaging, then boot the
-- image for the first time bare, that is without either LFS or SPIFFS preloaded
-- then enter the following commands interactively through the UART:
--
do
local _,ma,fa=node.flashindex()
for n,v in pairs{LFS_MAPPED=ma, LFS_BASE=fa, SPIFFS_BASE=sa} do
print(('export %s=""0x%x"'):format(n, v)
end
end
--
-- This will print out 3 hex constants: the absolute address used in the
-- 'luac.cross -a' options and the flash adresses of the LFS and SPIFFS.
--
--[[ So you would need these commands to image your ESP module:
USB=/dev/ttyUSB0 # or whatever the device of your USB is
NODEMCU=~/nodemcu # The root of your NodeMCU file hierarchy
SRC=$NODEMCU/local/lua # your source directory for your LFS Lua files.
BIN=$NODEMCU/bin
ESPTOOL=$NODEMCU/tools/esptool.py
$ESPTOOL --port $USB erase_flash # Do this is you are having load funnies
$ESPTOOL --port $USB --baud 460800 write_flash -fm dio 0x00000 \
$BIN/0x00000.bin 0x10000 $BIN/0x10000.bin
#
# Now restart your module and use whatever your intective tool is to do the above
# cmds, so if this outputs 0x4027b000, -0x7b000, 0x100000 then you can do
#
$NODEMCU/luac.cross -a 0x4027b000 -o $BIN/0x7b000-flash.img $SRC/*.lua
$ESPTOOL --port $USB --baud 460800 write_flash -fm dio 0x7b000 \
$BIN/0x7b000-flash.img
# and if you've setup a SPIFFS then
$ESPTOOL --port $USB --baud 460800 write_flash -fm dio 0x100000 \
$BIN/0x100000-0x10000.img
# and now you are good to go
]]
-----------------------------------------------------------------------------------
--
-- File: init.lua
--
-- With the previous example you still need an init.lua to bootstrap the _init
-- module in LFS. Here is an example. It's a good idea either to use a timer
-- delay or a GPIO pin during development, so that you as developer can break into
-- the boot sequence if there is a problem with the _init bootstrap that is causing
-- a panic loop. Here is one example of how you might do this. You have a second
-- to inject tmr.stop(0) into UART0. Extend this dealy if your reactions can't
-- meet this.
--
-- You also want to do autoload the LFS, for example by adding the following:
--
if node.flashindex() == nil then
node.flashreload('flash.img')
end
tmr.alarm(0, 1000, tmr.ALARM_SINGLE,
function()
local fi=node.flashindex; return pcall(fi and fi'_init')
end)
......@@ -24,13 +24,16 @@ pages:
- Home: 'en/index.md'
- Building the firmware: 'en/build.md'
- Flashing the firmware: 'en/flash.md'
- Internal filesystem notes: 'en/spiffs.md'
- Filesystem on SD card: 'en/sdcard.md'
- Uploading code: 'en/upload.md'
- FAQs:
- Lua Developer FAQ: 'en/lua-developer-faq.md'
- Extension Developer FAQ: 'en/extn-developer-faq.md'
- Hardware FAQ: 'en/hardware-faq.md'
- Whitepapers:
- Filesystem on SD card: 'en/sdcard.md'
- Internal filesystem notes: 'en/spiffs.md'
- Lua Compact Debug(LCD): 'en/lfs.md'
- Lua Flash Store(LFS): 'en/lcd.md'
- Support: 'en/support.md'
- Modules:
- 'adc': 'en/modules/adc.md'
......
......@@ -3,9 +3,10 @@
#
FSSOURCE ?= ../local/fs/
LUASOURCE ?= ../local/lua/
FLASHSIZE ?= 4mb 32mb 8mb
FLASH_SW = -S
SUBDIRS =
HOSTCC ?= gcc
OBJDUMP = $(or $(shell which objdump),xtensa-lx106-elf-objdump)
......@@ -16,11 +17,24 @@ OBJDUMP = $(or $(shell which objdump),xtensa-lx106-elf-objdump)
SPIFFSFILES ?= $(patsubst $(FSSOURCE)%,%,$(shell find $(FSSOURCE) -name '*' '!' -name .gitignore ))
#################################################################
# Get the filesize of /bin/0x10000.bin
# Get the filesize of /bin/0x10000.bin and SPIFFS sizing
#
FLASH_USED_END = $$((0x`$(OBJDUMP) -t ../app/.output/eagle/debug/image/eagle.app.v6.out |grep _flash_used_end |cut -f1 -d" "` - 0x40200000))
FLASH_FS_SIZE := $(shell $(CC) -E -dM - <../app/include/user_config.h | grep SPIFFS_MAX_FILESYSTEM_SIZE| cut -d ' ' -f 3)
ifneq ($(strip $(FLASH_FS_SIZE)),)
FLASHSIZE = $(shell printf "0x%x" $(FLASH_FS_SIZE))
FLASH_SW = -c
endif
FLASH_FS_LOC := $(shell $(CC) -E -dM - <../app/include/user_config.h | grep SPIFFS_FIXED_LOCATION| cut -d ' ' -f 3)
ifeq ($(strip $(FLASH_FS_LOC)),)
FLASH_FS_LOC := $(shell printf "0x%x" $$((0x$(shell $(OBJDUMP) -t ../app/.output/eagle/debug/image/eagle.app.v6.out |grep " _flash_used_end" |cut -f1 -d" ") - 0x40200000)))
else
FLASH_FS_LOC := $(shell printf "0x%x" $(FLASH_FS_LOC))
endif
LFSSOURCES := $(wildcard $(LUASOURCE)/*.lua)
#############################################################
# Rules base
......@@ -29,6 +43,16 @@ FLASH_USED_END = $$((0x`$(OBJDUMP) -t ../app/.output/eagle/debug/image/eagle.app
all: spiffsscript
.PHONY: TEST
TEST:
@echo $(FLASHSIZE)
@echo $(FLASH_FS_SIZE)
@echo $(FLASH_FS_LOC)
@echo $(FLASH_USED_END)
spiffsimg/spiffsimg:
.PHONY: spiffsimg
.PHONY: spiffsimg/spiffsimg
......@@ -37,15 +61,23 @@ spiffsimg: spiffsimg/spiffsimg
@echo Built spiffsimg in spiffsimg/spiffsimg
spiffsimg/spiffsimg:
@$(MAKE) -C spiffsimg CC=$(HOSTCC)
@$(MAKE) -C spiffsimg
spiffsscript: remove-image spiffsimg/spiffsimg
spiffsscript: remove-image LFSimage spiffsimg/spiffsimg
rm -f ./spiffsimg/spiffs.lst
echo "" >> ./spiffsimg/spiffs.lst
@echo "" >> ./spiffsimg/spiffs.lst
@$(foreach f, $(SPIFFSFILES), echo "import $(FSSOURCE)$(f) $(f)" >> ./spiffsimg/spiffs.lst ;)
@$(foreach sz, $(FLASHSIZE), spiffsimg/spiffsimg -U $(FLASH_USED_END) -o ../bin/spiffs-$(sz).dat -f ../bin/0x%x-$(sz).bin -S $(sz) -r ./spiffsimg/spiffs.lst -d; )
$(foreach sz, $(FLASHSIZE), spiffsimg/spiffsimg -f ../bin/0x%x-$(sz).img $(FLASH_SW) $(sz) -U $(FLASH_FS_LOC) -r ./spiffsimg/spiffs.lst -d; )
@$(foreach sz, $(FLASHSIZE), if [ -r ../bin/spiffs-$(sz).dat ]; then echo Built $$(cat ../bin/spiffs-$(sz).dat)-$(sz).bin; fi; )
ifneq ($(LFSSOURCES),)
LFSimage: $(LFSSOURCES)
../luac.cross -f -o $(FSSOURCE)flash.img $(LFSSOURCES)
else
LFSimage:
rm -f $(FSSOURCE)flash.img
endif
remove-image:
$(foreach sz, $(FLASHSIZE), if [ -r ../bin/spiffs-$(sz).dat ]; then rm -f ../bin/$$(cat ../bin/spiffs-$(sz).dat)-$(sz).bin; fi; )
rm -f ../bin/spiffs*.dat
......
-- 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
local args = { ... }
local b = require "tools.build"
local builder = b.new_builder( ".build/cross-lua" )
local utils = b.utils
local sf = string.format
if not (_VERSION == "Lua 5.1" and pcall(require,"lfs")) then
print [[
cross_lua.lua must be run within Lua 5.1 and it requires the Lua Filesystem to be installed.
On most *nix distrubitions youwill find a packages lua-5.1 and lua-filesystem, or
alternalively you can install lua-rocks and use the Rocks package manager to install lfs.
]]
os.exit(1)
end
builder:init( args )
builder:set_build_mode( builder.BUILD_DIR_LINEARIZED )
local output = 'luac.cross'
local cdefs = '-DLUA_CROSS_COMPILER -Ddbg_printf=printf'
-- Lua source files and include path
local lua_files = [[
lapi.c lauxlib.c lbaselib.c lcode.c ldblib.c ldebug.c ldo.c ldump.c
lfunc.c lgc.c llex.c lmathlib.c lmem.c loadlib.c lobject.c lopcodes.c
lparser.c lrotable.c lstate.c lstring.c lstrlib.c ltable.c ltablib.c
ltm.c lundump.c lvm.c lzio.c
luac_cross/luac.c luac_cross/loslib.c luac_cross/print.c
../modules/linit.c
../libc/c_stdlib.c
]]
lua_files = lua_files:gsub( "\n" , "" )
local lua_full_files = utils.prepend_path( lua_files, "app/lua" )
local local_include = "-Iapp/include -Iinclude -Iapp/lua"
-- Compiler/linker options
builder:set_compile_cmd( sf( "gcc -O2 %s -Wall %s -c $(FIRST) -o $(TARGET)", local_include, cdefs ) )
builder:set_link_cmd( "gcc -o $(TARGET) $(DEPENDS) -lm" )
-- Build everything
builder:make_exe_target( output, lua_full_files )
builder:build()
CC =gcc
SRCS=\
main.c \
../../app/spiffs/spiffs_cache.c ../../app/spiffs/spiffs_check.c ../../app/spiffs/spiffs_gc.c ../../app/spiffs/spiffs_hydrogen.c ../../app/spiffs/spiffs_nucleus.c
......
-- Generic utility functions
module( ..., package.seeall )
local lfs = require "lfs"
local sf = string.format
-- Taken from Lake
dir_sep = package.config:sub( 1, 1 )
is_os_windows = dir_sep == '\\'
-- Converts a string with items separated by 'sep' into a table
string_to_table = function( s, sep )
if type( s ) ~= "string" then return end
sep = sep or ' '
if s:sub( -1, -1 ) ~= sep then s = s .. sep end
s = s:gsub( sf( "^%s*", sep ), "" )
local t = {}
local fmt = sf( "(.-)%s+", sep )
for w in s:gmatch( fmt ) do table.insert( t, w ) end
return t
end
-- Split a file name into 'path part' and 'extension part'
split_ext = function( s )
local pos
for i = #s, 1, -1 do
if s:sub( i, i ) == "." then
pos = i
break
end
end
if not pos or s:find( dir_sep, pos + 1 ) then return s end
return s:sub( 1, pos - 1 ), s:sub( pos )
end
-- Replace the extension of a given file name
replace_extension = function( s, newext )
local p, e = split_ext( s )
if e then
if newext and #newext > 0 then
s = p .. "." .. newext
else
s = p
end
end
return s
end
-- Return 'true' if building from Windows, false otherwise
is_windows = function()
return is_os_windows
end
-- Prepend each component of a 'pat'-separated string with 'prefix'
prepend_string = function( s, prefix, pat )
if not s or #s == 0 then return "" end
pat = pat or ' '
local res = ''
local st = string_to_table( s, pat )
foreach( st, function( k, v ) res = res .. prefix .. v .. " " end )
return res
end
-- Like above, but consider 'prefix' a path
prepend_path = function( s, prefix, pat )
return prepend_string( s, prefix .. dir_sep, pat )
end
-- full mkdir: create all the paths needed for a multipath
full_mkdir = function( path )
local ptables = 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
concat_path = function( paths )
return table.concat( paths, dir_sep )
end
-- Return true if the given array contains the given element, false otherwise
array_element_index = function( arr, element )
for i = 1, #arr do
if arr[ i ] == element then return i end
end
end
-- Linearize an array with (possibly) embedded arrays into a simple array
_linearize_array = function( arr, res, filter )
if type( arr ) ~= "table" then return end
for i = 1, #arr do
local e = arr[ i ]
if type( e ) == 'table' and filter( e ) then
_linearize_array( e, res, filter )
else
table.insert( res, e )
end
end
end
linearize_array = function( arr, filter )
local res = {}
filter = filter or function( v ) return true end
_linearize_array( arr, res, filter )
return res
end
-- Return an array with the keys of a table
table_keys = function( t )
local keys = {}
foreach( t, function( k, v ) table.insert( keys, k ) end )
return keys
end
-- Return an array with the values of a table
table_values = function( t )
local vals = {}
foreach( t, function( k, v ) table.insert( vals, v ) end )
return vals
end
-- Returns true if 'path' is a regular file, false otherwise
is_file = function( path )
return lfs.attributes( path, "mode" ) == "file"
end
-- Returns true if 'path' is a directory, false otherwise
is_dir = function( path )
return lfs.attributes( path, "mode" ) == "directory"
end
-- Return a list of files in the given directory matching a given mask
get_files = function( path, mask, norec, level )
local t = ''
level = level or 0
for f in lfs.dir( path ) do
local fname = path .. dir_sep .. f
if lfs.attributes( fname, "mode" ) == "file" then
local include
if type( mask ) == "string" then
include = fname:find( mask )
else
include = mask( fname )
end
if include then t = t .. ' ' .. fname end
elseif lfs.attributes( fname, "mode" ) == "directory" and not fname:find( "%.+$" ) and not norec then
t = t .. " " .. get_files( fname, mask, norec, level + 1 )
end
end
return level > 0 and t or t:gsub( "^%s+", "" )
end
-- Check if the given command can be executed properly
check_command = function( cmd )
local res = os.execute( cmd .. " > .build.temp 2>&1" )
os.remove( ".build.temp" )
return res
end
-- Execute a command and capture output
-- From: http://stackoverflow.com/a/326715/105950
exec_capture = function( cmd, raw )
local f = assert(io.popen(cmd, 'r'))
local s = assert(f:read('*a'))
f:close()
if raw then return s end
s = string.gsub(s, '^%s+', '')
s = string.gsub(s, '%s+$', '')
s = string.gsub(s, '[\n\r]+', ' ')
return s
end
-- Execute the given command for each value in a table
foreach = function ( t, cmd )
if type( t ) ~= "table" then return end
for k, v in pairs( t ) do cmd( k, v ) end
end
-- Generate header with the given #defines, return result as string
gen_header_string = function( name, defines )
local s = "// eLua " .. name:lower() .. " definition\n\n"
s = s .. "#ifndef __" .. name:upper() .. "_H__\n"
s = s .. "#define __" .. name:upper() .. "_H__\n\n"
for key,value in pairs(defines) do
s = s .. string.format("#define %-25s%-19s\n",key:upper(),value)
end
s = s .. "\n#endif\n"
return s
end
-- Generate header with the given #defines, save result to file
gen_header_file = function( name, defines )
local hname = concat_path{ "inc", name:lower() .. ".h" }
local h = assert( io.open( hname, "w" ) )
h:write( gen_header_string( name, defines ) )
h:close()
end
-- Remove the given elements from an array
remove_array_elements = function( arr, del )
del = istable( del ) and del or { del }
foreach( del, function( k, v )
local pos = array_element_index( arr, v )
if pos then table.remove( arr, pos ) end
end )
end
-- Remove a directory recusively
-- USE WITH CARE!! Doesn't do much checks :)
rmdir_rec = function ( dirname )
if lfs.attributes( dirname, "mode" ) ~= "directory" then return end
for f in lfs.dir( dirname ) do
local ename = string.format( "%s/%s", dirname, f )
local attrs = lfs.attributes( ename )
if attrs.mode == 'directory' and f ~= '.' and f ~= '..' then
rmdir_rec( ename )
elseif attrs.mode == 'file' or attrs.mode == 'named pipe' or attrs.mode == 'link' then
os.remove( ename )
end
end
lfs.rmdir( dirname )
end
-- Concatenates the second table into the first one
concat_tables = function( dst, src )
foreach( src, function( k, v ) dst[ k ] = v end )
end
-------------------------------------------------------------------------------
-- Color-related funtions
-- Currently disabled when running in Windows
-- (they can be enabled by setting WIN_ANSI_TERM)
local dcoltable = { 'black', 'red', 'green', 'yellow', 'blue', 'magenta', 'cyan', 'white' }
local coltable = {}
foreach( dcoltable, function( k, v ) coltable[ v ] = k - 1 end )
local _col_builder = function( col )
local _col_maker = function( s )
if is_os_windows and not os.getenv( "WIN_ANSI_TERM" ) then
return s
else
return( sf( "\027[%d;1m%s\027[m", coltable[ col ] + 30, s ) )
end
end
return _col_maker
end
col_funcs = {}
foreach( coltable, function( k, v )
local fname = "col_" .. k
_G[ fname ] = _col_builder( k )
col_funcs[ k ] = _G[ fname ]
end )
-------------------------------------------------------------------------------
-- Option handling
local options = {}
options.new = function()
local self = {}
self.options = {}
setmetatable( self, { __index = options } )
return self
end
-- Argument validator: boolean value
options._bool_validator = function( v )
if v == '0' or v:upper() == 'FALSE' then
return false
elseif v == '1' or v:upper() == 'TRUE' then
return true
end
end
-- Argument validator: choice value
options._choice_validator = function( v, allowed )
for i = 1, #allowed do
if v:upper() == allowed[ i ]:upper() then return allowed[ i ] end
end
end
-- Argument validator: choice map (argument value maps to something)
options._choice_map_validator = function( v, allowed )
for k, value in pairs( allowed ) do
if v:upper() == k:upper() then return value end
end
end
-- Argument validator: string value (no validation)
options._string_validator = function( v )
return v
end
-- Argument printer: boolean value
options._bool_printer = function( o )
return "true|false", o.default and "true" or "false"
end
-- Argument printer: choice value
options._choice_printer = function( o )
local clist, opts = '', o.data
for i = 1, #opts do
clist = clist .. ( i ~= 1 and "|" or "" ) .. opts[ i ]
end
return clist, o.default
end
-- Argument printer: choice map printer
options._choice_map_printer = function( o )
local clist, opts, def = '', o.data
local i = 1
for k, v in pairs( opts ) do
clist = clist .. ( i ~= 1 and "|" or "" ) .. k
if o.default == v then def = k end
i = i + 1
end
return clist, def
end
-- Argument printer: string printer
options._string_printer = function( o )
return nil, o.default
end
-- Add an option of the specified type
options._add_option = function( self, optname, opttype, help, default, data )
local validators =
{
string = options._string_validator, choice = options._choice_validator,
boolean = options._bool_validator, choice_map = options._choice_map_validator
}
local printers =
{
string = options._string_printer, choice = options._choice_printer,
boolean = options._bool_printer, choice_map = options._choice_map_printer
}
if not validators[ opttype ] then
print( sf( "[builder] Invalid option type '%s'", opttype ) )
os.exit( 1 )
end
table.insert( self.options, { name = optname, help = help, validator = validators[ opttype ], printer = printers[ opttype ], data = data, default = default } )
end
-- Find an option with the given name
options._find_option = function( self, optname )
for i = 1, #self.options do
local o = self.options[ i ]
if o.name:upper() == optname:upper() then return self.options[ i ] end
end
end
-- 'add option' helper (automatically detects option type)
options.add_option = function( self, name, help, default, data )
local otype
if type( default ) == 'boolean' then
otype = 'boolean'
elseif data and type( data ) == 'table' and #data == 0 then
otype = 'choice_map'
elseif data and type( data ) == 'table' then
otype = 'choice'
data = linearize_array( data )
elseif type( default ) == 'string' then
otype = 'string'
else
print( sf( "Error: cannot detect option type for '%s'", name ) )
os.exit( 1 )
end
self:_add_option( name, otype, help, default, data )
end
options.get_num_opts = function( self )
return #self.options
end
options.get_option = function( self, i )
return self.options[ i ]
end
-- Handle an option of type 'key=value'
-- Returns both the key and the value or nil for error
options.handle_arg = function( self, a )
local si, ei, k, v = a:find( "([^=]+)=(.*)$" )
if not k or not v then
print( sf( "Error: invalid syntax in '%s'", a ) )
return
end
local opt = self:_find_option( k )
if not opt then
print( sf( "Error: invalid option '%s'", k ) )
return
end
local optv = opt.validator( v, opt.data )
if optv == nil then
print( sf( "Error: invalid value '%s' for option '%s'", v, k ) )
return
end
return k, optv
end
-- Show help for all the registered options
options.show_help = function( self )
for i = 1, #self.options do
local o = self.options[ i ]
print( sf( "\n %s: %s", o.name, o.help ) )
local values, default = o.printer( o )
if values then
print( sf( " Possible values: %s", values ) )
end
print( sf( " Default value: %s", default or "none (changes at runtime)" ) )
end
end
-- Create a new option handler
function options_handler()
return options.new()
end
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment