forked from nodemcu/nodemcu-firmware
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathbuild.lua
848 lines (760 loc) · 27.8 KB
/
build.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
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
-- 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