Skip to content
GitLab
Menu
Projects
Groups
Snippets
Loading...
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
Menu
Open sidebar
ruanhaishen
Nodemcu Firmware
Commits
307323d1
Commit
307323d1
authored
Sep 01, 2015
by
TerryE
Browse files
Add luac.cross capability to nodeMCU
parent
a08626d8
Changes
43
Show whitespace changes
Inline
Side-by-side
tools/build.lua
0 → 100644
View file @
307323d1
-- 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
tools/cross-lua.lua
0 → 100644
View file @
307323d1
local
args
=
{
...
}
local
b
=
require
"tools.build"
local
builder
=
b
.
new_builder
(
".build/cross-lua"
)
local
utils
=
b
.
utils
local
sf
=
string.format
builder
:
init
(
args
)
builder
:
set_build_mode
(
builder
.
BUILD_DIR_LINEARIZED
)
local
output
=
'luac.cross'
local
cdefs
=
'-DLUA_CROSS_COMPILER -O2'
-- 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
]]
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
()
tools/utils.lua
0 → 100644
View file @
307323d1
-- 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
Prev
1
2
3
Next
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment