-
Notifications
You must be signed in to change notification settings - Fork 0
/
rulebase.default
2374 lines (1973 loc) · 68.9 KB
/
rulebase.default
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
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
# vim:set syntax=tcl:
# vi:se noet sw=4 ts=4:
#
# These are the built-in rules
#
# They can be replaced if necessary by creating rulebase.spec
# at the top level of your project.
# @synopsis:
#
# rulebase.default is the default tmake rulebase...
#
# The following environment variables are used if set:
#
## CC - C compiler
## CXX - C++ compiler
## ...
use getopt glob
proc rulebase-invoke {cmd} {
switch -exact -- $cmd {
prolog {
tailcall rulebase-prolog
}
epilog {
tailcall rulebase-epilog
}
init {
tailcall rulebase-init
}
default {
dputs m "Rulebase does not implement rulebase-invoke $cmd"
}
}
}
# ==================================================================
# Default variable settings and rules
# ==================================================================
proc rulebase-init {} {
# If settings are loaded via Load, these should all be ignored
# Thus we use define?
define? CCACHE ""
define? CC gcc
define? CXX g++
define? AR ar
define? RANLIB ranlib
define? STRIP strip
define? STRIPFLAGS ""
define? STRIPLIBFLAGS ""
define? ARFLAGS cr
define? CPPFLAGS ""
define? CFLAGS ""
define? CXXFLAGS ""
define? LDFLAGS ""
if {[iswin]} {
define? EXEEXT .exe
} else {
define? EXEEXT ""
}
define? SH_CFLAGS ""
define? SH_LDFLAGS ""
define? SH_LINKFLAGS ""
define? SH_SOEXT .so
define? SHOBJ_CFLAGS ""
define? SHOBJ_LDFLAGS ""
define? LD_LIBRARY_PATH ""
define? CC_FOR_BUILD gcc
define? HOST_CFLAGS ""
if {[iswin]} {
define? HOST_EXEEXT .exe
} else {
define? HOST_EXEEXT ""
}
define? LEX flex
define? LFLAGS ""
define? YACC bison
define? YFLAGS -y
define? LOCAL_LIBS ""
define? DESTDIR ""
define? OBJCFLAGS ""
define? INCPATHS ""
# Default header pattern for C and C++ dynamic dependencies
define? CHDRPATTERN {^[\t ]*#[\t ]*include[\t ]*[<\"]([^\">]*)[\">]}
# Value to check for change (e.g. from environment) to force Autosetup configure to rerun
define? CONFIGURE_ENV ""
# These are set by the project via CFlags, ObjectCFlags, LinkFlags, etc.
define C_FLAGS ""
define CXX_FLAGS ""
define LD_FLAGS ""
define PUBLISH publish
define PROJLIBS ""
define SYSLIBS ""
define HOST_SYSLIBS ""
define PRECOMPILED_HEADER ""
# All globals variables for rulebase.default are kept in the $::tmakedrb array
set ::tmakedrb {
testruncount 0
testpasscount 0
testid 0
setuptestenv 0
objmap {}
objrules {}
}
}
# ==================================================================
# Help Overview
# ==================================================================
rule-overview {
This is rulebase.default, the default tmake rulebase. Note the following conventions:
File specifications for Install and Publish rules support the following forms:
- Regular filenames
- Renaming rules of the form: target=source
- Glob patterns (e.g. *.png)
For rules that set variables/flags, those definitions apply to rules later in the file.
For example, when setting C_FLAGS with CFlags, those flags are used by subsequent
Object and Executable rules, but not earlier rules.
Note: Scope can be used to limit the lifetime of definitions.
The following global phony targets are created:
all: Builds the 'all' targets from all subdirectories
test: Runs the 'test' targets from all subdirectories
clean: Runs the 'clean' targets from all subdirectories
distclean: Runs the 'distclean' targets from all subdirectories
install: Runs all install rules
uninstall: Runs all uninstall rules
Note that these global targets can be accessed from within a subdirectory
by using a / prefix. e.g. /all
The following per-directory phony targets are created:
all: Builds executables and shared objects for the current directory
(or libraries if there are no executables or shared objects)
test: Runs all tests for the current directory
clean: Removes all build products, including Clean targets, for the current directory
distclean: Runs 'clean', and removes DistClean targets for the current directory,
and removes all orphan objects
install: A convenience alias for /install
uninstall: A convenience alias for /uninstall
Note that by default, Lib is an alias for ArchiveLib. This can be changed with:
alias Lib SharedLib
Normally 'install' targets are built on-demand, but the following can be set
to build the install dependencies as part of the 'all' target in order to avoid
build during install when installing as root.
define INSTALLDEPTARGET all
}
# ==================================================================
# BUILT-IN Object Rules
# ==================================================================
proc is-duplicate-obj-rule {obj src vars} {
global tmakedrb
# Build the info for the rule
set info [list $src]
foreach var $vars {
lappend info $var [get-define $var]
}
if {[dict exists $tmakedrb objrules $obj]} {
# Is it identical?
dputs m "Ignoring identical duplicate object rule for $obj at [find-source-location]"
set oldinfo [dict get $tmakedrb objrules $obj]
if {$oldinfo ne $info} {
build-error [warning-location "Error: Duplicate Object rule for $obj with different settings"]
}
return 1
}
dict set tmakedrb objrules $obj $info
return 0
}
# @ObjectRule.<ext> object source
#
# Rule for creating 'object' from source file 'source' with the
# extentions 'ext'.
#
# The appropriate ObjectRule.<ext> is invoked from Object
# (and hence Objects, Executable and Lib) based on the extension.
#
# Note that 'object' and 'source' are fully qualified names, not local names.
rule ObjectRule.c {obj src} {
ObjectRule.c object source
Generates an object file from a C source file
Bound variables: CC, C_FLAGS (from CFlags, ObjectCFlags), INCPATHS (from IncludePaths)
Used variables: CPPFLAGS, CFLAGS, CCACHE, CHDRPATTERN (for dynamic dependency generation)
PRECOMPILED_HEADER (see PrecompiledHeader)
} {
if {![is-duplicate-obj-rule $obj $src {CC C_FLAGS INCPATHS}]} {
# Capture the current value of CC, C_FLAGS and INCPATHS
target $obj -inputs $src -msg {note Cc $targetname} -getvars CC C_FLAGS INCPATHS -do {
run $CCACHE $CC $C_FLAGS $CFLAGS $CPPFLAGS -c $inputs -o $target
} -dyndep {
header-scan-regexp-recursive $INCPATHS "" $CHDRPATTERN
} -depends [get-define PRECOMPILED_HEADER]
}
return $obj
}
rule ObjectRule.cpp {obj src} {
ObjectRule.cpp object source
(Also aliases ObjectRule.cc, ObjectRule.cxx, ObjectRule.C)
Generates an object file from a C++ source file
Bound variables: CXX, CXX_FLAGS (from C++Flags), INCPATHS (from IncludePaths)
Used variables: CPPFLAGS, CXXFLAGS, CCACHE, CHDRPATTERN (for dynamic dependency generation),
PRECOMPILED_HEADER (see PrecompiledHeader)
} {
if {![is-duplicate-obj-rule $obj $src {CXX CXX_FLAGS INCPATHS}]} {
# Capture the current value of CXX, CXX_FLAGS and INCPATHS
target $obj -inputs $src -msg {note C++ $targetname} -getvars CXX CXX_FLAGS INCPATHS -do {
run $CCACHE $CXX $CXX_FLAGS $CXXFLAGS $CPPFLAGS -c $inputs -o $target
} -dyndep {
header-scan-regexp-recursive $INCPATHS "" $CHDRPATTERN
} -depends [get-define PRECOMPILED_HEADER]
}
return $obj
}
# Why haven't we settled on a single extension?
alias ObjectRule.cc ObjectRule.cpp
alias ObjectRule.cxx ObjectRule.cpp
alias ObjectRule.C ObjectRule.cpp
rule Lex {args} {
Lex [--prefix=.lex] source.l
Generates .c and .h from a lexer source file
Note that $LEX must support --outfile and --header-file
Returns the (local) generated files.
Bound variables: LEX, LFLAGS
} {
show-this-rule
getopt {--prefix: src} args
if {![exists prefix]} {
set prefix .lex
}
set src [make-local $src]
set base [file rootname $src]
set targets [list $base$prefix.c $base$prefix.h]
# Build the output files with lex/flex
target $targets -inputs $src -msg {note Lex $targetname} -getvars LEX LFLAGS -do {
run $LEX $LFLAGS --outfile=[lindex $target 0] --header-file=[lindex $target 1] $inputs
}
add-clean [make-local clean] $targets
add-clean [make-local distclean] $targets
make-unlocal {*}$targets
}
rule ObjectRule.l {obj src} {
ObjectRule.l object source
Uses the Lex rule to create an object from lexer source.
Note that the object name is ignored, and is instead derived from the source.
} {
show-this-rule
lassign [Lex [make-unlocal $src]] csrc hsrc
Object [change-ext .o $csrc] $csrc
}
rule Yacc {args} {
Yacc [--prefix=.tab] source.y
Generates an object file from a yacc source file
Note that $YACC must support --output and --defines
Returns the (local) generated files.
Bound variables: YACC, YFLAGS
} {
show-this-rule
getopt {--prefix: src} args
if {![exists prefix]} {
set prefix .tab
}
set src [make-local $src]
set base [file rootname $src]
set targets [list $base$prefix.c $base$prefix.h]
# Build the output files with yacc/bison
target $targets -inputs $src -msg {note Yacc $targetname} -getvars YACC YFLAGS -do {
run $YACC $YFLAGS --output=[lindex $target 0] --defines=[lindex $target 1] $inputs
}
add-clean [make-local clean] $targets
add-clean [make-local distclean] $targets
make-unlocal {*}$targets
}
rule ObjectRule.y {obj src} {
ObjectRule.y object source
Uses the Yacc rule to create an object from yacc source.
Note that the object name is ignored, and is instead derived from the source.
} {
show-this-rule
lassign [Yacc [make-unlocal $src]] csrc hsrc
Object [change-ext .o $csrc] $csrc
}
# ==================================================================
# PROLOG/EPILOG HOOKS
# ==================================================================
proc rulebase-prolog {} {
global tmakedrb
show-this-rule [local-dir]
# Local phony targets build from the current directory down
if {[local-prefix] ne ""} {
set parentprefix [make-prefix [file dirname [local-dir]]]
foreach t {all clean distclean test} {
set localtarget [make-local $t]
target $localtarget -phony
target $parentprefix$t -phony -depends $localtarget
}
Phony install install
Phony uninstall uninstall
clean-target [make-local clean] Clean
clean-target [make-local distclean] DistClean
}
# Capture these variables
foreach i {INCPATHS C_FLAGS CXX_FLAGS} {
dict set tmakedrb $i [get-define $i]
}
# This can be removed with IncludePaths --reset
IncludePaths .
}
proc rulebase-epilog {} {
show-this-rule [local-dir]
# Does subdir/all have any dependencies? If not, make it depend on subdir/libs (if that rule exists)
set t [get-target-rule [make-local all]]
if {[llength $t(depends)] == 0} {
if {[is-rule? [make-local libs]]} {
Depends all [make-local libs]
}
}
# Ensure that even directories with no targets are removed
Clean __dummy__
}
# ==================================================================
# UTILTY PROCS
# ==================================================================
# @target-alias target targetexe
#
# On some systems, executables are identified with an extension (e.g. .exe)
# For cross-platform compatibility, rules omit this extension and an alias is used
# to link to the actual filename.
#
# This command checks the rule name (e.g. abc) and the executable name (e.g. abc.exe)
# and if they are different, returns an appropriate target alias for the executable
# (e.g. -alias abc). Otherwise an empty list is returned.
#
proc target-alias {target targetexe} {
if {$target ne $targetexe} {
return [list -alias [make-local $target]]
}
return ""
}
# @publish-path dir
#
# Returns the path to the given 'publish' subdirectory,
# e.g. [publish-path include] returns something like objdir/.publish/include
#
proc publish-path {dir} {
file-build [file-join [get-define PUBLISH] $dir]
}
# @publish-dir dir
#
# Returns the path to the given 'publish' subdirectory relative to the build directory
# e.g. [publish-dir include] returns something like .publish/include
#
proc publish-dir {dir} {
file-join [get-define PUBLISH] $dir
}
# @publish-prefix dir
#
# Like publish-dir, but returns the value suitable to be used as a prefix
#
proc publish-prefix {dir} {
make-prefix [publish-dir $dir]
}
# @publish-one-file dest src
#
# Creates a rule to publish $src as $dest (build relative name) by creating a hard link.
# e.g. publish-one-file include/header.h header.h
#
proc publish-one-file {args dest src} {
#dputs m "publish-one-file dest=$dest, src=$src, realdest=[publish-dir $dest]"
hard-link [publish-dir $dest] $src -msg {note Publish $targetname} {*}$args
}
# @publish-lib libname alias
#
# Creates a rule to publish an archive library, $libname, to the $PUBLISH/lib directory.
# $alias is the alias name of the library, e.g. <lib>blah
#
proc publish-lib {libname alias} {
hard-link [publish-prefix lib]$libname [make-local $libname] -msg {note Publish $targetname} -alias $alias
}
# @make-shlib-symlinks first alias...
#
# Create symlinks such that each of the given aliase is a symlink to $first.
# All paths/names should be in the same directory
# The alias targets must not exist.
#
proc make-shlib-symlinks {first args} {
foreach alias $args {
file-link -sym $alias [file tail $first]
}
}
# @publish-shared-lib names source alias
#
# Creates a rule to publish a shared library, $source, with the given names to the
# $PUBLISH/lib directory.
# This includes creating appropriate symlinks.
# $alias is the alias name of the library, e.g. <lib>blah
#
proc publish-shared-lib {names source alias} {
set published [prefix [publish-prefix lib] {*}$names]
target $published -inputs $source -msg {note Publish [lindex $targetname 0]} -do {
file delete {*}$target
file-link [lindex $target 0] $inputs
make-shlib-symlinks {*}$target
} -vars libname [lindex $names 0] -alias $alias
add-clean [make-local clean] $published
add-clean [make-local distclean] $published
}
# @install-shared-lib ?--striplib? dest names source flags
#
# Creates a rule to install a shared library, $source, to install directory $dest
# This includes creating appropriate symlinks.
# $flags are passed to install-file and should either be empty, or "striplib".
proc install-shared-lib {args} {
getopt {--striplib dest names source} args
set installed [prefix [make-prefix [get-define DESTDIR]$dest] $names]
set installopts {}
lappend installopts --bin
if {$striplib} {
lappend installopts "--strip=[get-define STRIP] [get-define STRIPLIBFLAGS]"
}
target $installed -nocache -rootok -inputs $source -msg {note Install $libname} -do {
file delete {*}$target
install-file {*}$installopts [lindex $target 0] $inputs
make-shlib-symlinks {*}$target
} -vars installopts $installopts libname [file-join $dest [lindex $names 0]]
target install -depends {*}$installed
add-clean uninstall $installed
}
# @make-local-or-abs filename
#
# If $filename is an absolute path, returns $filename
# Otherwise returns [make-local $filename]
proc make-local-or-abs {filename} {
if {[string match /* $filename]} {
return $filename
}
return [make-local $filename]
}
# @expand-filespec dir filelist keepdir
#
# Implements target renaming/globbing for Publish and Install
#
# $dir is the target directory
# $filelist is the file specification
# $keepdir is a boolean which indicates whether to keep the source dir in the target name
#
# The file specification may include:
# - Regular filenames
# - Renaming rules of the form: target=src
# - Glob patterns
#
# Returns a list of the form: target1 source1 target2 source2 ...
proc expand-filespec {dir filelist keepdir} {
set result {}
foreach spec $filelist {
if {$spec eq ""} {
continue
}
if {[string match *=* $spec]} {
lassign [split $spec =] target src
#dputs m "file renaming: dir=$dir, target=$target, src=$src => target=[file-join $dir $target] src=[make-local $src]"
lappend result [file-join $dir $target] [make-local-or-abs $src]
continue
}
if {[string match {*[{}*?]*} $spec]} {
set srcs [Glob --all $spec]
} else {
set srcs $spec
}
foreach src $srcs {
if {$keepdir} {
#dputs m "keepdir: dir=$dir, src=$src => target=[file-join $dir $src] src=[make-local-or-abs $src]"
lappend result [file-join $dir $src] [make-local-or-abs $src]
} else {
#dputs m "no keepdir: dir=$dir, src=$src => target=[file-join $dir [file tail $src]] src=[make-local-or-abs $src]"
lappend result [file-join $dir [file tail $src]] [make-local-or-abs $src]
}
}
}
#puts "expand-filespec: $filelist => $result"
return $result
}
# @expand-objects objs
#
# Expands a list of objects which may contain library aliases of the form <lib>$basename
# Each library alias is expanded into the list of objects for that library.
# (Note that SharedLib and ArchiveLib create an object list when creating libraries)
# Any non-aliases are left unchanged.
#
# Returns the expanded object list.
proc expand-objects {objs} {
set result {}
foreach obj $objs {
if {[string match <lib>* $obj]} {
if {![dict exists $::tmakedrb(objmap) $obj]} {
build-error [warning-location "Error: Unable to find objects for unknown lib $obj"]
return -code break
}
# XXX: How to guarantee that $SH_CFLAGS are set on these objects?
# (Either directly via ObjectCFlags or with a top-level CFlags)
#
lappend result {*}[file-build-list [dict get $::tmakedrb(objmap) $obj]]
} else {
lappend result $obj
}
}
return $result
}
# @load-config-file filename
#
# Reads a configuration file of the form:
#
## CONFIG_BOOL_VALUE=y (or m)
## CONFIG_STR_VALUE="string value"
## CONFIG_INT_VALUE=123
#
# Each matching value is loaded as a corresponding variable definition
# where if the value is "y", it is changed to "1", and if the value
# is a quoted string, the quotes are removed.
proc load-config-file {filename} {
set n 0
foreach line [split [readfile $filename] \n] {
incr n
set line [string trim $line]
if {$line eq "" || [string match "#*" $line]} {
continue
}
if {[regexp {^([^=:]*):?=(.*)} $line -> name value]} {
set value [string trim $value]
if {$value eq "y"} {
set value 1
} elseif {[string match {"*"} $value]} {
set value [lindex $value 0]
}
dputs c "define $name $value"
# Add source info here for tracking purposes
define $name [info-source $value $filename $n]
} else {
user-notice purple "$filename:$n: Warning: Failed to parse $line"
}
}
if {$n == 0} {
user-notice purple "Warning: $filename contains no configuration"
}
}
# @setup-test-env
#
# Prepares the environment for running tests.
# Given the (absolute path of) $PUBLISH/lib and the name of the LD_LIBRARY_PATH variable,
# appends the publish lib $LD_LIBRARY_PATH in the environment to allow published shared libraries to be loaded
# Also sets TMAKE_LIBPATH to the same value for script helpers on Mac OS X
# since DYLD_LIBRARY_PATH can't be passed to scripts
# (See https://forums.developer.apple.com/message/31148)
proc setup-test-env {publishlib ldlibpath} {
setenv TMAKE_LIBPATH $publishlib
if {$ldlibpath ne ""} {
set path [getenv $ldlibpath ""]
append-with-space path $publishlib $::tcl_platform(pathSeparator)
setenv $ldlibpath $path
}
}
# @get-test-id
#
# Returns a unique test id of the form 'test#nnn'
# Used to help identify Test targets since they don't have
# a natural target name.
proc get-test-id {} {
return test#[incr ::tmakedrb(testid)]
}
# @run-test-command testcommand
#
# This is a helper for running test commands.
# It is used by the Test rule.
# The environment should first be set up with setup-test-env
# Then SRCDIR is exported to the environment
# stdout, stderr and errok are checked from the caller to determine
# how output/errors are handled/checked
proc run-test-command {testcommand} {
upvar stdout stdout
upvar stderr stderr
upvar errok errok
upvar SRCDIR SRCDIR
set cmdline $testcommand
set outfile {}
foreach i {stdout stderr} redir {> 2>} {
if {[exists $i]} {
set outfile($i) [file tempfile]
lappend testcommand ${redir}$outfile($i)
}
}
if {![exists stdout]} {
lappend testcommand >@stdout
}
# Get this value from the calling scope and set in the environment
setenv SRCDIR $SRCDIR
incr ::tmakedrb(testruncount)
set rc [catch {
vputs [string trim $testcommand]
exec {*}$testcommand
} msg opts]
if {[exists errok]} {
set rc 0
}
if {$rc == 0} {
# Succeeded. Does the output match?
foreach {i gotfile} $outfile {
set expfile [set $i]
set got [readfile $gotfile]
set exp [readfile $expfile]
if {$got ne $exp} {
if {$rc == 0} {
set rc 1
pputs \n\t$cmdline\n
set msg "\nError: Output does not match"
}
vputs "diff -u $expfile $gotfile"
catch {
exec diff -u $expfile $gotfile >@stdout
}
}
}
}
# Delete output
foreach file [dict values $outfile] {
file delete -force $file
}
if {$rc == 0} {
incr ::tmakedrb(testpasscount)
return 0
}
build-error $msg
return -code break
}
# @hard-link ?--copy? dest source ?target-args...?
#
# Creates a rule to link $dest to $source (or copy if --copy is specified)
# Additional arguments may be passed and are added to the target rule.
#
proc hard-link {args} {
#show-this-rule
getopt {dest source --copy args} args
# XXX: If the platform doesn't support hard links,
# consider falling back to soft links and then to file copy
if {$copy || ![exists -command file-link]} {
target $dest -inputs $source -do {
file delete $target
file copy $inputs $target
} {*}$args
} else {
target $dest -inputs $source -do {
file delete $target
file-link $target $inputs
} {*}$args
}
# Note that we can't use Clean here because $dest is already
# a local name
add-clean [make-local clean] $dest
add-clean [make-local distclean] $dest
}
# @sym-link dest source ?target-args...?
#
# Creates a rule to link $dest to $source
# Additional arguments may be passed and are added to the target rule.
#
proc sym-link {args} {
#show-this-rule
getopt {dest linktarget args} args
target $dest -symlink -vars linktarget $linktarget -do {
file delete $target
file-link -sym $target $linktarget
} {*}$args
# Note that we can't use Clean here because $dest is already
# a local name
add-clean [make-local clean] $dest
add-clean [make-local distclean] $dest
}
# @install-file ?--strip=$STRIPCMD? ?--bin? target source
#
# Helper for installing files
# Copies the file from $source to $target, then performs zero or more actions
# depending upon the flags (which is a list)
#
#- --bin - make the target executable with chmodx-file
#- --strip=$STRIPCMD - If specified, strips the target with the given strip command
#
proc install-file {args} {
getopt {--strip: --bin target source} args
vputs "Copy $source $target"
# Delete the target first in case it is in use
# XXX Should we instead copy to a tmp file and force rename?
file delete $target
file copy $source $target
if {$bin} {
chmodx-file $target
}
if {[exists strip]} {
vputs "Strip $target"
exec {*}$strip $target
}
}
# @chmodx-file target
#
# Makes the given file executable with chmod +x
proc chmodx-file {target} {
vputs "Chmod $target"
exec chmod +x $target
}
# @remove-empty-directories filename-list
#
# Considers the set of directories containing each filename in the list.
# Removes any of those directories that are empty.
proc remove-empty-directories {files} {
set dirs {}
foreach file $files {
while {1} {
set dirname [file dirname $file]
if {$dirname eq $file || $dirname eq "."} {
break
}
dict set dirs [file dirname $file] 1
set file $dirname
}
}
if {[dict size $dirs]} {
# Sort in reverse order so that abc/def/ghi is removed before abc/def
#dputs m "file delete {*}[lsort -decreasing [dict keys $dirs]]"
foreach dir [lsort -decreasing [dict keys $dirs]] {
catch { file delete $dir }
}
}
}
# @clean-files filename-list ?msg?
#
# Helper to clean files (and any empty directories)
#
# Removes the list of files, and then any directories that contain those
# files which have become empty.
#
# If $msg is provided, it is printed via 'note', otherwise a default
# message is printed.
proc clean-files {files {msg {}}} {
if {[llength $files]} {
vputs "rm $files"
if {$msg ne ""} {
note "Clean [llength $files] $msg"
}
file delete {*}$files
# Also need to remove any empty directories
remove-empty-directories $files
}
}
# @clean-target type msg ?args...?
#
# Create the phony clean target of the given type and message.
# The created target invokes clean-files
#
# Additional files can be added to the target with 'add-clean'
#
# If 'args' are specified, these are added to the target. e.g. -rootok
#
proc clean-target {type msg args} {
target $type -phony -nofail -vars cleanfiles {} -msg "note $msg [local-dir]" {*}$args -do {
clean-files $cleanfiles
}
}
# @add-clean type filename-list
#
# Adds the given files to the list to be removed for the given type (target)
#
proc add-clean {type filelist} {
# This could be done with Depends or target, but it is much
# faster to simply append to the rule variable directly
target-add-var $type cleanfiles [file-build-list $filelist]
}
# @add-src-clean type filename-list
#
# Like add-clean, except $filelist are in the source directory rather than the build directory
#
proc add-src-clean {type filelist} {
target-add-var $type cleanfiles $filelist
}
# @find-project-bin binfile ?-chdir 0|1? ?-optional 0|1?
#
# Searches for the given binary file as follows:
#- If $binfile is a known target, the result is the target
#- If $binfile is of the form <bin>name, the result is the published binary
#- If the file exists in the local directory, the result is the file
#- Otherwise, if optional=1, the file is assumed to be an external binary
#- Otherwise an error is raised.
#
# The result is a list of two values: {dependency filename}
#
# The first value is a build dependency for the file. (Empty for an external binary)
# The second value is the path to the file suitable for executing it, taking
# into account the setting of 'chdir'.
proc find-project-bin {bin args} {
set opts [list -chdir 0 -optional 0 {*}$args]
set localbin [make-local $bin]
if {[regexp {^<bin>(.*)} $bin -> basename]} {
dputxs l {[find-source-location]: find-project-bin: $bin is a publish target}
set publishbin [publish-prefix bin]$basename
if {$opts(-chdir)} {
# We will be running in [file-build [local-dir]],
# while the executable to run is in [file-build $PUBLISH/bin]
# So calculate a relative path
set curdir [file-build [local-dir]]
set builddir [publish-path bin]
# It can't hurt to create the $PUBLISH/bin here and it makes relative-path always work
file-mkdir -rootskip $builddir
set reldir [relative-path $builddir $curdir]
set result [list $bin $reldir/$basename]
} else {
set result [list $bin [file-build $publishbin]]
}
} elseif {[is-target? $localbin]} {
dputxs l {[find-source-location]: find-project-bin: $localbin is a local target}
if {$opts(-chdir)} {
set result [list $localbin ./$bin]
} else {
# Note: Use [build-dir] here rather than [file-build] to retain ./
# if building in-tree
set result [list $localbin [build-dir]/$localbin]
}
} elseif {[file exists $localbin]} {
dputxs l {[find-source-location]: find-project-bin: $localbin exists as source}
if {$opts(-chdir)} {
# Need to adjust the interp to account for chdir
set result [list $localbin [file-src-relative $localbin]]
} else {
set result [list $localbin $localbin]
}
} elseif {$opts(-optional)} {
dputxs l {[find-source-location]: find-project-bin: $bin is assumed to be external}
set result [list "" $bin]
} else {
# Not found
build-error [warning-location "$bin is neither a local nor a published (<bin>) target"]
set result [list "" ""]
}
dputxs l {[find-source-location]: find-project-bin returning $result}
return $result
}
add-deferred-action {
# Need to add $PUBLISH/include to the include paths
IncludePaths --build [publish-dir include]
}
# Don't allow the same rule to be specified twice
# Avoids accidental merging of, e.g., Executable rules
proc check-dup-rule {rule} {
if {[is-rule? $rule]} {
set info [get-rule $rule]
parse-error "A rule for $rule already exists at $info(source)"
}
}
# ==================================================================
# HIGH LEVEL RULES
# ==================================================================
rule Executable {args} {
Executable --test --chdir --nobuild --publish --no|strip --nofork --install=<dir> target sources...
Builds an executable from one or more source files. For each source, the appropriate ObjectRule is used
to generate the object file, and these files are linked (along with any libraries) to create the executable.
Bound variables: CCLD (from Linker), LD_FLAGS (from LinkFlags), PROJLIBS and PROJDEPS (from UseLibs)
LOCAL_LIBS (from ArchiveLib), SYSLIBS (from UseSystemLibs)
Used variables: LDFLAGS
The following options are supported:
--test Marks the executable as a test (see Test)
--nofork With --test, uses Test --nofork
--chdir With --test, uses Test --chdir
--nobuild Don't make this executable dependent on phony 'all' (which is otherwise the default)
--publish Publishes the executable (see PublishBin)
--install=<dir> Install the executable to the given directory (see InstallFile)
--strip[=full|dynamic|none] Sets the strip type on installation. --strip means full. omitted means none. (see InstallFile)
} {
add-default-opts args
show-this-rule
set strip none
getopt {--test --chdir --nobuild --nofork --publish --strip:full --install: target args} args
set localtarget [make-local $target]
set targetexe $target[get-define EXEEXT]
set localtargetexe $localtarget[get-define EXEEXT]
# Flatten and glob inputs
set objects [Objects [Glob --all [join $args]]]
define? CCLD [get-define CC]