forked from elua/elua
-
Notifications
You must be signed in to change notification settings - Fork 0
/
build_elua.lua
executable file
·476 lines (419 loc) · 18.7 KB
/
build_elua.lua
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
#! /usr/bin/env lua
--[[
build_elua.lua: A build script for eLua written in Lua.
The command line syntax is the same as for the old scons/SConstruct system.
See http://www.eluaproject.net/en_building.html
The only required option is the target board or CPU. e.g.:
lua build_elua.lua board=MIZAR32
This script requires some well-known Lua libraries to run.
To install them on Ubuntu/Debian, go (as root):
apt-get install luarocks
luarocks install luafilesystem
luarocks install lpack
luarocks install md5
--]]
local args = { ... }
local b = require "utils.build"
local mkfs = require "utils.mkfs"
local bconf = require "config.config"
local board_base_dir = "boards"
local bd = require "build_data"
builder = b.new_builder()
utils = b.utils
sf = string.format
-------------------------------------------------------------------------------
-- Build configuration 'shortcuts'
cdefs, cflags, includes, lflags, asflags, libs = {}, {}, {}, {}, {}, {}
-- "Normalize" a name to make it a suitable C macro name
function cnorm( name )
name = name:gsub( "[%-%s]*", '' )
return name:upper()
end
-- Add a macro defition
function addm( data )
table.insert( cdefs, data )
end
-- Add an include directory
function addi( data )
table.insert( includes, data )
end
-- Add a compiler flag
function addcf( data )
table.insert( cflags, data )
end
-- Delete a compiler flag
function delcf( data )
cflags = utils.linearize_array( cflags )
for _, v in pairs( data ) do
local i = utils.array_element_index( cflags, v )
if i then table.remove( cflags, i ) end
end
end
-- Add a linker flag
function addlf( data )
table.insert( lflags, data )
end
-- Add an assembler flag
function addaf( data )
table.insert( asflags, data )
end
-- Add a library
function addlib( data )
table.insert( libs, data )
end
-------------------------------------------------------------------------------
-- Return the full path of a board configuration file
local function get_conf_file_path( bname )
local known_board_name = utils.concat_path( { board_base_dir, "known", bname .. ".lua" } )
local custom_board_name = utils.concat_path( { board_base_dir, "custom", bname .. ".lua" } )
if utils.is_file( custom_board_name ) then return custom_board_name end
if utils.is_file( known_board_name ) then return known_board_name end
assert( sf( "board configuration file for board '%s' not found!", bname ) )
end
-- Automatically build the list of available boards by scanning the board_base_dir directory
local board_flist = utils.linearize_array( utils.string_to_table( utils.get_files( board_base_dir, "%.lua$" ) ) )
local board_list = {}
for k, v in pairs( board_flist ) do
local temp = utils.replace_extension( v, '' )
temp = utils.string_to_table( temp, utils.dir_sep )
temp = temp[ #temp ] -- now 'temp' contains the actual name of the board
if not utils.array_element_index( board_list, temp ) then board_list[ #board_list + 1 ] = temp end
end
-- Check a single command line option against the corresponding value in the build configuration
local function check_cmdline_vs_conf( argname, comp, bd )
if bd[ argname ] then
if comp[ argname ] == "auto" then
comp[ argname ] = bd[ argname ]
elseif bd[ argname ] ~= comp[ argname ] then
if builder:is_user_option( argname ) then
print( utils.col_yellow( sf( "[CONFIG] WARNING: changing '%s' from '%s' to '%s' as specified in the command line",
argname, tostring( bd[ argname ] ), tostring( comp[ argname ] ) ) ) )
else
comp[ argname ] = bd[ argname ]
end
end
end
end
builder:add_option( 'target', 'build "regular" float lua, 32 bit integer-only "lualong" or 64-bit integer only lua "lualonglong"', 'lua', { 'lua', 'lualong', 'lualonglong' } )
builder:add_option( 'allocator', 'select memory allocator', 'auto', { 'newlib', 'multiple', 'simple', 'auto' } )
builder:add_option( 'board', 'selects board for target (cpu will be inferred)', nil, board_list )
builder:add_option( 'toolchain', 'specifies toolchain to use (auto=search for usable toolchain)', 'auto', { bd.get_all_toolchains(), 'auto' } )
builder:add_option( 'optram', 'enables Lua Tiny RAM enhancements', true )
builder:add_option( 'boot', 'boot mode, standard will boot to shell, luarpc boots to an rpc server', 'standard', { 'standard' , 'luarpc' } )
builder:add_option( 'romfs', 'ROMFS compilation mode', 'verbatim', { 'verbatim' , 'compress', 'compile' } )
builder:add_option( 'cpumode', 'ARM CPU compilation mode (only affects certain ARM targets)', nil, { 'arm', 'thumb' } )
builder:add_option( 'bootloader', 'Build for bootloader usage (AVR32 only)', 'none', { 'none', 'emblod' } )
builder:add_option( "output_dir", "choose executable directory", "." )
builder:add_option( "romfs_dir", 'choose ROMFS directory', 'romfs' )
builder:add_option( "board_config_file", "choose board configuration file", "" )
builder:add_option( "skip_conf", "skip board configuration step, use pre-generated header file directly", false )
builder:add_option( "config_only", "execute only the configurator, then exit", false )
builder:init( args )
builder:set_build_mode( builder.BUILD_DIR_LINEARIZED )
-- Build the 'comp' target which will 'redirect' all the requests
-- for its fields to builder:get_option
comp = {}
setmetatable( comp, { __index = function( t, key ) return builder:get_option( key ) end } )
local function dprint( ... )
if comp.disp_mode ~= "minimal" then
print( ... )
end
end
if not comp.board then
print "You must specify the board"
os.exit( -1 )
end
-- Interpret the board definition file
local bfopt, bfname = builder:get_option( "board_config_file" )
if #bfopt > 0 then
if not utils.is_file( bfopt ) then
print( utils.col_red( sf( "[CONFIG] Error: board configuration file '%s' not found.", bfopt ) ) )
os.exit( -1 )
end
bfname = bfopt
else
bfname = get_conf_file_path( comp.board )
if not bfname then
print( utils.col_red( sf( "[CONFIG] Error: board configuration file for board '%s' not found in '%s'.", comp.board, board_base_dir ) ) )
os.exit( -1 )
end
end
dprint( utils.col_blue( "[CONFIG] Found board description file at " .. bfname ) )
local bdata, err = bconf.compile_board( bfname, comp.board )
if not bdata then
print( utils.col_red( "[CONFIG] Error compiling board description file: " .. err ) )
return
end
-- Check if the file has changed. If not, do not rewrite it. This keeps the compilation time sane.
local bhname = utils.concat_path( { board_base_dir, "headers", "board_" .. comp.board:lower() .. ".h" } )
if builder:get_option( "skip_conf" ) then
dprint( utils.col_blue( "[CONFIG] skipping generation of configuration file" ) )
else
if ( utils.get_hash_of_string( bdata.header ) ~= utils.get_hash_of_file( bhname ) or not utils.is_file( bhname ) ) then
-- Save the header file
local f = assert( io.open( bhname, "wb" ) )
f:write( bdata.header )
f:close()
dprint( utils.col_blue( "[CONFIG] Generated board header file at " .. bhname ) )
else
dprint( utils.col_blue( "[CONFIG] Board header file is unchanged." ) )
end
end
if comp.config_only then return end
-- Define the correct CPU header for inclusion in the platform_conf.h file
addm( 'ELUA_CPU_HEADER="\\"cpu_' .. bdata.cpu:lower() .. '.h\\""' )
-- Define the correct board header for inclusion in the platform_conf.h file
addm( 'ELUA_BOARD_HEADER="\\"board_' .. comp.board:lower() .. '.h\\""' )
-- Make available the board directory for the generated header files
addi( utils.concat_path{ board_base_dir, "headers" } )
-- Force compilation flags if needed
if bdata.build then
utils.foreach( { 'target', 'allocator', 'optram', 'boot', 'romfs', 'cpumode', 'bootloader' }, function( k, v )
check_cmdline_vs_conf( v, comp, bdata.build )
end )
end
-- Automatically set the allocator to 'multiple' if needed
if bdata.multi_alloc and comp.allocator == "newlib" then
io.write( utils.col_yellow( "[CONFIG] WARNING: your board has non-contigous RAM areas, but you specified an allocator ('newlib') that can't handle this configuration." ) )
print( utils.col_yellow( "Rebuild with another allocator ('multiple' or 'simple')" ) )
end
if comp.allocator == "auto" then comp.allocator = bdata.multi_alloc and "multiple" or "newlib" end
comp.cpu = bdata.cpu:upper()
if not comp.optram then
print( utils.col_yellow( "[CONFIG] WARNING: you have disabled Lua Tiny RAM (LTR). You might experience compilation issues. Also, some modules might not work correctly." ) )
end
platform = bd.get_platform_of_cpu( comp.cpu )
if not platform then
print( "Unable to find platform (this shouldn't happen, check the build script for errors)" )
os.exit( -1 )
end
-- Check the toolchain
local usable_chains = bd.get_toolchains_of_platform( platform )
if comp.toolchain ~= 'auto' then
if utils.array_element_index( usable_chains, comp.toolchain ) == nil then
print( sf( "Invalid toolchain '%s' for CPU '%s'", comp.toolchain, comp.cpu ) )
print( sf( "List of accepted toolchains (for %s): %s", comp.cpu, table.concat( usable_chains, "," ) ) )
os.exit( -1 )
end
toolset = bd.get_toolchain_data( comp.toolchain )
comp.CC = toolset.compile
comp.AS = toolset.compile
else
-- If 'auto' try to match a working toolchain with target
-- Try to execute all compilers, exit when one found
local chain
for i = 1, #usable_chains do
local c = usable_chains[ i ]
local t = bd.get_toolchain_data( c )
local res = utils.check_command( t.compile .. " " .. t.version )
if res == 0 then chain = c break end
end
if chain then
comp.toolchain = chain
toolset = bd.get_toolchain_data( chain )
comp.CC = toolset.compile
comp.AS = comp.CC
else
print "Unable to find an usable toolchain in your path."
print( sf( "List of accepted toolchains (for %s): %s", comp.cpu, table.concat( usable_chains, "," ) ) )
os.exit( -1 )
end
end
-- Build the compilation command now
local fscompcmd = ''
if comp.romfs == 'compile' then
if comp.target == 'lualonglong' then
print "Cross-compilation is not yet supported for 64-bit integer-only Lua (lualonglong)."
os.exit( -1 )
end
local suffix = ''
if utils.is_windows() then
suffix = '.exe'
end
-- First check for luac.cross in the current directory
if not utils.is_file( "luac.cross" .. suffix ) then
print "The eLua cross compiler was not found."
print "Build it by running 'lua cross-lua.lua'"
os.exit( -1 )
end
local cmdpath = { lfs.currentdir(), sf( 'luac.cross%s -ccn %s -cce %s -o %%s -s %%s', suffix, toolset[ "cross_" .. comp.target:lower() ], toolset.cross_cpumode:lower() ) }
fscompcmd = table.concat( cmdpath, utils.dir_sep )
elseif comp.romfs == 'compress' then
fscompcmd = 'lua luasrcdiet.lua --quiet --maximum --opt-comments --opt-whitespace --opt-emptylines --opt-eols --opt-strings --opt-numbers --opt-locals -o %s %s'
end
-- Determine build version
if utils.check_command('git describe --always') == 0 then
addm( "USE_GIT_REVISION" )
elua_vers = utils.exec_capture('git describe --always')
-- If purely hexadecimal (no tag reference) prepend 'dev-'
if string.find(elua_vers, "^[+-]?%x+$") then
elua_vers = 'dev-' .. elua_vers
end
local sver = utils.gen_header_string( 'git_version', { elua_version = elua_vers, elua_str_version = ("\"" .. elua_vers .. "\"" ) } )
if utils.get_hash_of_string( sver ) ~= utils.get_hash_of_file( utils.concat_path{ 'inc', 'git_version.h' } ) then
utils.gen_header_file( 'git_version', { elua_version = elua_vers, elua_str_version = ("\"" .. elua_vers .. "\"" ) } )
end
else
print "WARNING: unable to determine version from repository"
elua_vers = "unknown"
end
-- Create the output directory if it is not created yet
local outd = builder:get_option( "output_dir" )
if not utils.is_dir( outd ) then
if not utils.full_mkdir( outd ) then
print( "[builder] Unable to create directory " .. outd )
os.exit( 1 )
end
end
-- Output file
output = outd .. utils.dir_sep .. 'elua_' .. comp.target .. '_' .. comp.board:lower()
builder:set_build_dir( builder:get_build_dir() .. utils.dir_sep .. comp.board:lower() )
-- User report
dprint ""
dprint "*********************************"
dprint "Compiling eLua ..."
dprint( "CPU: ", comp.cpu )
dprint( "Board: ", comp.board )
dprint( "Platform: ", platform )
dprint( "Allocator: ", comp.allocator )
dprint( "Boot Mode: ", comp.boot )
dprint( "Target: ", comp.target )
dprint( "Toolchain: ", comp.toolchain )
dprint( "ROMFS mode: ", comp.romfs )
dprint( "Version: ", elua_vers )
dprint "*********************************"
dprint ""
-- Build list of source files, include directories, macro definitions
addm( "ELUA_CPU=" .. comp.cpu:upper() )
addm( "ELUA_BOARD=" .. comp.board:upper() )
addm( "ELUA_PLATFORM=" .. platform:upper() )
addm( "__BUFSIZ__=128" )
-- Also make the above into direct defines (to use in conditional C code)
addm( "ELUA_CPU_" .. cnorm( comp.cpu ) )
addm( "ELUA_BOARD_" .. cnorm( comp.board ) )
addm( "ELUA_PLATFORM_" .. cnorm( platform ) )
if comp.allocator == 'multiple' then
addm( "USE_MULTIPLE_ALLOCATOR" )
elseif comp.allocator == 'simple' then
addm( "USE_SIMPLE_ALLOCATOR" )
end
if comp.boot == 'luarpc' then addm( "ELUA_BOOT_RPC" ) end
if comp.target == 'lualong' or comp.target == 'lualonglong' then addm( "LUA_NUMBER_INTEGRAL" ) end
if comp.target == 'lualonglong' then addm( "LUA_INTEGRAL_LONGLONG" ) end
if comp.target ~= 'lualong' and comp.target ~= "lualonglong" then addm( "LUA_PACK_VALUE" ) end
if bd.get_endianness_of_platform( platform ) == "big" then addm( "ELUA_ENDIAN_BIG" ) else addm( "ELUA_ENDIAN_LITTLE" ) end
-- Special macro definitions for the SIM target
if platform == 'sim' then addm( { "ELUA_SIMULATOR", "ELUA_SIM_" .. cnorm( comp.cpu ) } ) end
-- Lua source files and include path
exclude_patterns = { "^src/platform", "^src/uip", "^src/serial", "^src/luarpc_desktop_serial.c", "^src/linenoise_posix.c", "^src/lua/print.c", "^src/lua/luac.c" }
local source_files = utils.get_files( "src", function( fname )
fname = fname:gsub( "\\", "/" )
local include = fname:find( ".*%.c$" )
if include then
utils.foreach( exclude_patterns, function( k, v ) if fname:match( v ) then include = false end end )
end
return include
end )
-- Add uIP files manually because not all of them are included in the build ([TODO] why?)
local uip_files = " " .. utils.prepend_path( "uip_arp.c uip.c uiplib.c dhcpc.c psock.c resolv.c uip-neighbor.c", "src/uip" )
addi{ { 'inc', 'inc/newlib', 'inc/remotefs', 'src/platform', 'src/lua' }, { 'src/modules', 'src/platform/' .. platform, 'src/platform/' .. platform .. '/cpus' }, "src/uip", "src/fatfs" }
addm( "LUA_OPTIMIZE_MEMORY=" .. ( comp.optram and "2" or "0" ) )
addcf( { '-Os','-fomit-frame-pointer' } )
-- Toolset data (filled by each platform in part)
tools = {}
specific_files = ''
-- We get platform-specific data by executing the platform script
dofile( sf( "src/platform/%s/conf.lua", platform ) )
-- Complete file list
source_files = source_files .. uip_files .. specific_files
-------------------------------------------------------------------------------
-- Create compiler/linker/assembler command lines and build
-- ROM file system builder
romfs_exclude_patterns = { '%.DS_Store', '%.gitignore' }
function match_pattern_list( item, list )
for k, v in pairs( list ) do
if item:find(v) then return true end
end
end
local function make_romfs( target, deps )
print "Building ROM file system ..."
local romdir = builder:get_option( "romfs_dir" )
local flist = {}
flist = utils.string_to_table( utils.get_files( romdir, function( fname ) return not match_pattern_list( fname, romfs_exclude_patterns ) end ) )
flist = utils.linearize_array( flist )
for k, v in pairs( flist ) do
flist[ k ] = v:gsub( romdir .. utils.dir_sep, "" )
end
if not mkfs.mkfs( romdir, "romfiles", flist, comp.romfs, fscompcmd ) then return -1 end
if utils.is_file( "inc/romfiles.h" ) then
-- Read both the old and the new file
local oldfile = io.open( "inc/romfiles.h", "rb" )
assert( oldfile )
local newfile = io.open( "romfiles.h", "rb" )
assert( newfile )
local olddata, newdata = oldfile:read( "*a" ), newfile:read( "*a" )
oldfile:close()
newfile:close()
-- If content is similar return '1' to builder to indicate that the target didn't really
-- produce a change even though it ran
if olddata == newdata then
os.remove( "romfiles.h" )
return 1
end
os.remove( "inc/romfiles.h" )
end
os.rename( "romfiles.h", "inc/romfiles.h" )
return 0
end
-- Generic 'prog' action function
local function genprog( target, deps )
local outname = deps[ 1 ]:target_name()
local outtype = target:find( "%.hex$" ) and "ihex" or "binary"
print( sf( "Generating binary image %s...", target ) )
os.execute( sf( "%s %s", toolset.size, outname ) )
os.execute( sf( "%s -O %s %s %s", toolset.bin, outtype, outname, target ) )
return 0
end
-- Generic 'size' action function
local function sizefunc( target, deps )
local outname = deps[ 1 ]:target_name()
os.execute( sf( "%s %s", toolset.size, outname ) )
return 0
end
-- Command lines for the tools (compiler, linker, assembler)
compcmd = compcmd or builder:compile_cmd{ flags = cflags, defines = cdefs, includes = includes, compiler = toolset.compile }
linkcmd = linkcmd or builder:link_cmd{ flags = lflags, libraries = libs, linker = toolset.compile }
ascmd = ascmd or builder:asm_cmd{ flags = asflags, defines = cdefs, includes = includes, assembler = toolset.asm }
builder:set_exe_extension( ".elf" )
builder:set_compile_cmd( compcmd )
builder:set_link_cmd( linkcmd )
builder:set_asm_cmd( ascmd )
-- Create the ROMFS target
local romfs_target = builder:target( "#phony:romfs", nil, make_romfs )
romfs_target:force_rebuild( true )
-- Create executable targets
odeps = builder:create_compile_targets( source_files )
exetarget = builder:link_target( output, { romfs_target, odeps } )
-- This is also the default target
builder:default( builder:add_target( exetarget, 'build eLua executable' ) )
-- Create 'prog' target(s)
local ptargets = {}
local progfunc = tools[ platform ].progfunc or genprog
utils.foreach( tools[ platform ].prog_flist, function( _, t )
local target = builder:target( t, { exetarget }, progfunc )
table.insert( ptargets, target )
end )
if #ptargets > 0 then
progtarget = builder:target( "#phony:prog", ptargets )
builder:add_target( progtarget, "build eLua firmware image", { "prog" } )
end
-- Create generic 'size' target
local size_target = builder:target( "#phony:size", { exetarget }, sizefunc )
size_target:force_rebuild( true )
builder:add_target( size_target, "shows the size of the eLua firmware", { "size" } )
-- If the backend needs to do more processing before the build starts, do it now
if tools[ platform ].pre_build then
tools[ platform ].pre_build()
end
-- Finally build everything
builder:build()