forked from Zomojo/compiletools
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathcake
executable file
·1273 lines (1011 loc) · 42.9 KB
/
cake
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
#!/usr/bin/env python
from __future__ import print_function
import pickle
import sys
import commands
import os
import re
import pdb
if sys.version_info < (2,6):
from sets import Set
set = Set
if sys.version_info < (2,6):
import md5 as cake_hasher
else:
import hashlib as cake_hasher
BINDIR="bin/"
OBJDIR=""
verbose = False
debug = False
class OrderedSet:
"""A set that preserves the order of insertion"""
def __init__(self, init = ()):
self.ordered = []
self.unordered = {}
for s in init:
self.insert(s)
def insert(self, e):
if e in self.unordered:
return
self.ordered.append(e)
self.unordered[e] = True
def __repr__(self):
return repr(self.ordered)
def __contains__(self, e):
return self.unordered.__contains__(e)
def __len__(self):
return self.ordered.__len__()
def __iter__(self):
return self.ordered.__iter__()
class UserException (Exception):
def __init__(self, text):
Exception.__init__(self, text)
def to_bool(value):
"""
Tries to convert a wide variety of values to a boolean
Raises an exception for unrecognised values
"""
if str(value).lower() in ("yes","y","true","t","1","on"):
return True
if str(value).lower() in ("no","n","false","f","0","off"):
return False
raise Exception("Don't know how to convert " + str(value) + " to boolean.")
def environ(variable, default):
if default is None:
if not variable in os.environ:
raise UserException("Couldn't find required environment variable " + variable)
return os.environ[variable]
else:
if not variable in os.environ:
return default
else:
return os.environ[variable]
def parse_etc(config_file):
"""parses /etc/cake as if it was part of the environment.
os.environ has higher precedence
"""
if not os.path.exists(config_file):
raise UserException("Trying to parse config file. Could not find " + config_file)
f = open(config_file)
lines = f.readlines()
f.close()
for l in lines:
if l.startswith("#"):
continue
l = l.strip()
if len(l) == 0:
continue
key = l[0:l.index("=")].strip()
value = l[l.index("=") + 1:].strip()
for k in os.environ:
value = value.replace('"', "")
value = value.replace("$" + k, os.environ[k])
value = value.replace("${" + k + "}", os.environ[k])
if not key in os.environ:
os.environ[key] = str(value)
usage_text = """
Usage: cake [compilation args] filename.cpp [app args]
cake generates and runs C and C++ executables with almost no configuration. To build a C or C++ program, type "cake filename.c" or "cake filename.cpp".
Cake uses the header includes to determine what other implementation (c,cpp) files are also required to be built and linked against.
Cake also recognises that developers need to build different variants of the same executable. A variant is defined to be a compiler and optimisation combination.
Examples of variants are gcc46_release and clang_debug.
Source annotations:
Embed these magic comments in your hpp and cpp files to give cake instructions on compilation and link flags.
//#CXXFLAGS=<flags> Appends the given options to the compile step.
//#LINKFLAGS=<flags> Appends the given options to the link step
//#GCC44_CXXFLAGS=<flags> Appends the given options to the compile step when building with gcc 4.4.
//#GCC44_LINKFLAGS=<flags> Appends the given options to the link step when building with gcc 4.4
If no variant specific annotations are found, then the global variants are also
searched. This allows default behaviour to be specified, while allowing
for a particular variant as well.
Environment:
Environment variables can also be set, from lowest to highest priority, in /etc/cake.conf, ~/.cake.conf or directly in the shell.
CAKE_DEFAULT_VARIANT Sets the default variant to use if --variant=<some variant> is not specified on the command line
CAKE_<variant>_ID Sets the prefix to the embedded source annotations and predefined build macro.
CAKE_<variant>_CPP Sets the C preprocessor command.
CAKE_<variant>_CC Sets the C compiler command.
CAKE_<variant>_CXX Sets the C++ compiler command
CAKE_<variant>_LINKER Sets the linker command.
CAKE_<variant>_CPPFLAGS Sets the preprocessor flags for all c and cpp files in the build.
CAKE_<variant>_CFLAGS Sets the compilation flags for all c files in the build.
CAKE_<variant>_CXXFLAGS Sets the compilation flags for all cpp files in the build.
CAKE_<variant>_LINKFLAGS Sets the flags used while linking.
CAKE_<variant>_TESTPREFIX Sets the execution prefix used while running unit tests.
CAKE_<variant>_POSTPREFIX Sets the execution prefix used while running post-build commands.
CAKE_BINDIR Sets the directory where all binary files will be created.
CAKE_OBJDIR Sets the directory where all object files will be created.
CAKE_PROJECT_VERSION_CMD Sets the command to execute that will return the version number of the project being built. cake then sets a macro equal to this version.
CAKE_PARALLEL Sets the number of CPUs to use in parallel for a build. Defaults to all cpus.
Options:
--help Shows this message.
--quiet Doesn't output progress messages.
--verbose Outputs the result of build commands (doesn't run make with -s)
--cake-debug Output extra cake specific info.
--config Specify the config file to use.
--bindir Specifies the directory to contain binary executable outputs. Defaults to 'bin'.
--objdir Specifies the directory to contain object intermediate files. Defaults to 'bin/obj'.
--generate Only runs the makefile generation step, does not build.
--build Builds the given targets (default).
--file-list Print list of referenced files.
--output=<filename> Overrides the output filename.
--variant=<vvv> Reads the CAKE_<vvv>_CC, CAKE_<vvv>_CXXFLAGS and CAKE_<vvv>_LINKFLAGS
environment variables to determine the build flags.
Examples of variants are debug, release, gcc44_debug, gcc46_release.
--static-library Build a static library rather than executable. This is an alias for --LINKER="ar -src"
--dynamic-library Build a dynamic library rather than executable. This is an alias for --append-LINKFLAGS="-shared"
-j|--jobs=<number> Number of CPUs to use in parallel in the build, defaults to all
--ID=<id> Sets the prefix to the embedded source annotations, and a predefined macro CAKE_${ID}
--CPP=<preprocessor> Sets the C preprocessor command.
--CC=<compiler> Sets the C compiler command.
--CXX=<compiler> Sets the C++ compiler command.
--LINKER=<linker> Sets the linker command.
--CPPFLAGS=<flags> Sets the preprocessor flags for all c and cpp files in the build.
--CFLAGS=<flags> Sets the compilation flags for all c files in the build.
--CXXFLAGS=<flags> Sets the compilation flags for all cpp files in the build.
--LINKFLAGS=<flags> Sets the flags used while linking.
--TESTPREFIX=<cmd> Runs tests with the given prefix, eg. "valgrind --quiet --error-exitcode=1"
--POSTPREFIX=<cmd> Runs post execution commands with the given prefix, eg. "timeout 60"
--append-CPPFLAGS=... Appends the given text to the CPPFLAGS already set. Useful for adding search paths etc.
--append-CFLAGS=... Appends the given text to the CFLAGS already set. Useful for adding search paths etc.
--append-CXXFLAGS=... Appends the given text to the CXXFLAGS already set. Useful for adding search paths etc.
--append-LINKFLAGS=.. Appends the given text to the LINKFLAGS already set. Use for example with `wx-config --libs`
--bindir=... Overrides the directory where binaries are produced. 'bin/' by default.
--project-version-cmd=... Sets the command to execute that will return the version number of the project being built.
--include-git-root Walk up directory path to find .git directory. If found, add path as an include path.
This is enabled by default.
--no-git-root Disable the git root include.
--include-git-parent If the git root exists then add the parent path as an include path.
Useful for combining code in multiple repositories.
--no-git-parent Disable the git root parent include (Default)
--begintests Starts a test block. The cpp files following this declaration will
generate executables which are then run.
--endtests Ends a test block.
--beginpost Starts a post execution block. The commands given after this will be
run verbatim after each build. Useful for running integration tests,
or generating tarballs, uploading to a website etc.
--endpost Ends a post execution block.
Examples:
This command-line generates bin/prime-factoriser and bin/frobnicator in release mode.
It also generates several tests into the bin directory and runs them. If they are
all successful, integration_test.sh is run.
cake apps/prime-factoriser.cpp apps/frobnicator.cpp --begintests tests/*.cpp --endtests --beginpost ./integration_test.sh --variant=release
To build a static library of the get_numbers.cpp file in the example tests
cake --static-library tests/get_numbers.cpp
To build a dynamic library of the get_numbers.cpp file in the example tests
cake --dynamic-library tests/get_numbers.cpp
"""
def usage(msg = ""):
if len(msg) > 0:
print(msg, file=sys.stderr)
print("", file=sys.stderr)
print(usage_text.strip() + "\n")
sys.exit(1)
def printCakeVariables():
print(" ID : " + CAKE_ID)
print(" VARIANT : " + Variant)
print(" CPP : " + CPP)
print(" CC : " + CC)
print(" CXX : " + CXX)
print(" LINKER : " + LINKER)
print(" CPPFLAGS : " + CPPFLAGS)
print(" CFLAGS : " + CFLAGS)
print(" CXXFLAGS : " + CXXFLAGS)
print(" LINKFLAGS : " + LINKFLAGS)
print(" TESTPREFIX: " + TESTPREFIX)
print(" POSTPREFIX: " + POSTPREFIX)
print(" BINDIR : " + BINDIR)
print(" OBJDIR : " + OBJDIR)
print(" PARALLEL : " + PARALLEL)
print(" PREPROCESS: " + str(PREPROCESS))
print(" PROJECT_VERSION_CMD : " + PROJECT_VERSION_CMD)
print("\n")
def extractOptions(text, option_prefix) :
"""Find all options matching beginning with option_prefix from the
text, to be called in iterative context. yield's (originfile,
optionname, optionvalue)
"""
idx = 0
while idx < len(text) :
prefix_idx = text.find(option_prefix, idx)
if prefix_idx == -1 :
return
# get the file this option comes from
origin_idx = text.rfind("\n# ", 0, prefix_idx)
if origin_idx == -1 :
origin_idx = 0 # first line of file, won't have preceding \n
else :
origin_idx += 1
origin_end = text.find("\n", origin_idx)
origin_str = text[origin_idx:origin_end]
# origin line looks like # 1 "/path/to/source.c", get just that path
fn_idx = origin_str.find('"')
fn_end = origin_str.rfind('"')
origin_fn = origin_str[fn_idx+1:fn_end]
option_end = text.find("\n", prefix_idx)
if option_end == -1 :
option_end = len(text)
option_str = text[prefix_idx+len(option_prefix):option_end]
a = option_str.split("=", 1)
if len(a) > 1 :
yield (origin_fn, a[0], a[1])
idx = option_end
def accumulateOptions(text, option_prefix, options) :
"""Call extractOptions searching for any option with option_prefix,
for each one found if that option is in options, accumulate
the value of that option into a list and put it into a dictionary
that is subsequently returned.
"""
all_options = {}
for origin_fn, option_name, option_value in extractOptions(text, option_prefix) :
if option_name in options :
arr = all_options.get(option_name, [])
arr.append( (origin_fn, option_value) )
all_options[option_name] = arr
return all_options
def joinPaths(origin_file, file_path) :
"""Given a pathname to a file, origin_file, and the pathname file_path,
join them such that we take the directory portion of origin_file and
add to that a normalized file_path, eliding any . or .. found.
"""
if file_path[0] == '/' :
return file_path
dir_parts = origin_file.split('/')[0:-1] # remove the filename from the end
file_parts = file_path.split('/')
for i in file_parts :
if i == '.' :
pass
elif i == '..' :
dir_parts.pop()
else :
dir_parts.append(i)
return '/'.join(dir_parts)
realpath_cache = {}
def realpath(x):
global realpath_cache
#print "BEGIN ",x
if not x in realpath_cache:
realpath_cache[x] = os.path.realpath(x)
return realpath_cache[x]
def munge(to_munge):
if isinstance(to_munge, dict):
if len(to_munge) == 1:
return OBJDIR + "@@".join([realpath(x) for x in to_munge]).replace("/", "@")
else:
return OBJDIR + cake_hasher.md5(str([realpath(x) for x in to_munge])).hexdigest()
else:
return OBJDIR + realpath(to_munge).replace("/", "@")
def force_get_dependencies_for(deps_file, source_file, quiet, verbose):
"""Recalculates the dependencies and caches them for a given source file"""
if not quiet:
print("... " + source_file + " (dependencies)")
cmd = CPP + CPPFLAGS + " -DCAKE_DEPS -MM -MF " + deps_file + ".tmp " + source_file
if verbose:
print(cmd)
status, output = commands.getstatusoutput(cmd)
if status != 0:
raise UserException(cmd + "\n" + output)
f = open(deps_file + ".tmp")
text = f.read()
f.close()
os.unlink(deps_file + ".tmp")
files = text.split(":")[1]
files = files.replace("\\", " ").replace("\t"," ").replace("\n", " ")
files = [x for x in files.split(" ") if len(x) > 0]
files = list(set([realpath(x) for x in files]))
files.sort()
headers = [realpath(h) for h in files if h.endswith(".hpp") or h.endswith(".h")]
sources = [realpath(h) for h in files if h.endswith(".cpp") or h.endswith(".c")]
# determine cflags, cxxflags and linkflags
cflags = {}
cxxflags = {}
linkflags = OrderedSet()
option_prefix = "//#"
explicit_c = CAKE_ID + "_CFLAGS"
explicit_cxx = CAKE_ID + "_CXXFLAGS"
explicit_link = CAKE_ID + "_LINKFLAGS"
explicit_source = CAKE_ID + "_SOURCE"
explicit_glob_c = "CFLAGS"
explicit_glob_cxx = "CXXFLAGS"
explicit_glob_link = "LINKFLAGS"
explicit_glob_source = "SOURCE"
path = os.path.split(h)[0]
text = ""
if PREPROCESS:
# Preprocess but leave comments
i_file = deps_file.replace(".deps", ".i")
cmd = CPP + CPPFLAGS + " -C -E -o " + i_file + " " + source_file
if verbose:
print(cmd)
status, output = status, output = commands.getstatusoutput(cmd)
if status != 0:
raise UserException(cmd + "\n" + output)
with open(i_file) as f:
text=f.read()
os.remove(i_file) # TODO. Cache the i_file to avoid recreating
else:
# reading and handling as one string is slightly faster then
# handling a list of strings.
# Only read first 2k for speed
for h in headers + [source_file]:
with open(h) as f:
text+=f.read(2048)
option_set = set([
explicit_c,
explicit_cxx,
explicit_link,
explicit_source,
explicit_glob_c,
explicit_glob_cxx,
explicit_glob_link,
explicit_glob_source
])
found = False
all_options = accumulateOptions(text, option_prefix, option_set)
# first check for variant specific flags
if len(CAKE_ID) > 0:
for result_origin, result in all_options.get(explicit_c, []) :
if debug:
print("explicit " + explicit_c + " = '" + result + "' for " + source_file)
result = result.replace("${path}", path)
cflags[result] = True
found = True
for result_origin, result in all_options.get(explicit_cxx, []) :
if debug:
print("explicit " + explicit_cxx + " = '" + result + "' for " + source_file)
result = result.replace("${path}", path)
cxxflags[result] = True
found = True
for result_origin, result in all_options.get(explicit_link, []) :
if debug:
print("explicit " + explicit_link + " = '" + result + "' for " + source_file)
linkflags.insert(result.replace("${path}", path))
found = True
for result_origin, result in all_options.get(explicit_source, []) :
result_path = joinPaths(result_origin, result)
if debug:
print("explicit " + explicit_source + " = '" + result_path + "' for " + source_file)
sources.append(result_path)
found = True
# if none, then check globals
if not found:
for result_origin, result in all_options.get(explicit_glob_c, []) :
if debug:
print("explicit " + explicit_glob_c + " = '" + result + "' for " + source_file)
result = result.replace("${path}", path)
cflags[result] = True
for result_origin, result in all_options.get(explicit_glob_cxx, []) :
if debug:
print("explicit " + explicit_glob_cxx + " = '" + result + "' for " + source_file)
result = result.replace("${path}", path)
cxxflags[result] = True
for result_origin, result in all_options.get(explicit_glob_link, []) :
if debug:
print("explicit " + explicit_glob_link + " = '" + result + "' for " + source_file)
linkflags.insert(result.replace("${path}", path))
for result_origin, result in all_options.get(explicit_glob_source, []) :
result_path = joinPaths(result_origin, result)
if debug:
print("explicit " + explicit_glob_source + " = '" + result_path + "' for " + source_file)
sources.append(result_path)
# cache
f = open(deps_file, "w")
pickle.dump((headers, sources, cflags, cxxflags, linkflags), f)
f.close()
if deps_file in stat_cache:
del stat_cache[deps_file]
if deps_file in realpath_cache:
del realpath_cache[deps_file]
return headers, sources, cflags, cxxflags, linkflags
stat_cache = {}
def stat(f):
if not f in stat_cache:
try:
stat_cache[f] = os.stat(f)
except OSError:
stat_cache[f] = None
return stat_cache[f]
dependency_cache = {}
def get_dependencies_for(source_file, quiet, verbose):
"""Converts a gcc make command into a set of headers and source dependencies"""
#pdb.set_trace()
global dependency_cache
if source_file in dependency_cache:
return dependency_cache[source_file]
deps_file = munge(source_file) + ".deps"
# try and reuse the existing if possible
deps_stat = stat(deps_file)
if deps_stat:
deps_mtime = deps_stat.st_mtime
all_good = True
try:
f = open(deps_file)
headers, sources, cflags, cxxflags, linkflags = pickle.load(f)
f.close()
except:
all_good = False
if all_good:
for s in headers + [source_file]:
try:
if stat(s).st_mtime > deps_mtime:
all_good = False
break
except: # missing file counts as a miss
all_good = False
break
if all_good:
result = headers, sources, cflags, cxxflags, linkflags
dependency_cache[source_file] = result
return result
# failed, regenerate dependencies
result = force_get_dependencies_for(deps_file, source_file, quiet, verbose)
dependency_cache[source_file] = result
return result
def insert_dependencies(sources, ignored, new_file, linkflags, cause, quiet, verbose, file_list):
"""Given a set of sources already being compiled, inserts the new file."""
#pdb.set_trace()
if not new_file.startswith("/"):
raise Exception("The new_file being examined needs to have a full path")
if new_file in sources:
return
if new_file in ignored:
return
if stat(new_file) is None:
ignored.append(new_file)
return
# recursive step
new_headers, new_sources, newcflags, newcxxflags, newlinkflags = get_dependencies_for(new_file, quiet, verbose)
sources[realpath(new_file)] = (newcflags, newcxxflags, cause, new_headers)
file_list.insert(new_file)
# merge in link options
for l in newlinkflags:
linkflags.insert(l)
copy = cause[:]
copy.append(new_file)
for h in new_headers:
insert_dependencies(sources, ignored, os.path.splitext(h)[0] + ".cpp", linkflags, copy, quiet, verbose, file_list)
insert_dependencies(sources, ignored, os.path.splitext(h)[0] + ".c", linkflags, copy, quiet, verbose, file_list)
for s in new_sources:
insert_dependencies(sources, ignored, s, linkflags, copy, quiet, verbose, file_list)
def try_set_variant(variant,static_library):
global Variant, CAKE_ID, CPP, CC, CXX, LINKER, CPPFLAGS, CFLAGS, CXXFLAGS, LINKFLAGS, TESTPREFIX, POSTPREFIX
Variant = "CAKE_" + variant.upper()
CAKE_ID = environ(Variant + "_ID", "")
CPP = environ(Variant + "_CPP", None)
CC = environ(Variant + "_CC", None)
CXX = environ(Variant + "_CXX", None)
if static_library:
LINKER = "ar -src"
else:
LINKER = environ(Variant + "_LINKER", None)
CPPFLAGS = environ(Variant + "_CPPFLAGS", None)
CFLAGS = environ(Variant + "_CFLAGS", None)
CXXFLAGS = environ(Variant + "_CXXFLAGS", None)
LINKFLAGS = environ(Variant + "_LINKFLAGS", None)
TESTPREFIX = environ(Variant + "_TESTPREFIX", None)
POSTPREFIX = environ(Variant + "_POSTPREFIX", None)
def lazily_write(filename, newtext):
oldtext = ""
try:
f = open(filename)
oldtext = f.read()
f.close()
except:
pass
if newtext != oldtext:
f = open(filename, "w")
if filename in stat_cache:
del stat_cache[filename]
if filename in realpath_cache:
del realpath_cache[filename]
f.write(newtext)
f.close()
ignore_option_mash = [ '-fprofile-generate', '-fprofile-use' ]
def objectname(source, entry):
"""
Calculate a hash that identifies when a source file and compile options are constant.
Then use the source filename and the hash as the name for the object file.
The reasoning is that we want to avoid recompiling an object file if the source file and the compile options are the same
but we must recompile if _either_ the source file or the compile options change.
The ignore_option_mash list contains the options that we can safely ignore from the hash.
"""
cflags, cxxflags, cause, headers = entry
mash_name = "".join(cflags) + " " + CFLAGS + " " + "".join(cxxflags) + " " + CXXFLAGS + " "
if source.endswith(".c"):
mash_name += CC
else:
mash_name += CXX
mash_name = re.sub(r'CAKE_PROJECT_VERSION=\\".*?\\"', "", mash_name)
mash_name += str(PREPROCESS)
o = mash_name.split();
o.sort()
mash_inc = ""
for s in o:
if not s in ignore_option_mash:
mash_inc += s
else:
mash_inc += 'ignore'
h = cake_hasher.md5( mash_inc ).hexdigest()
return munge(source) + "-" + str(len(str(mash_inc))) + "-" + h + ".o"
def generate_rules(source, output_name, generate_test, makefilename, quiet, verbose, static_library, file_list):
"""
Generates a set of make rules for the given source.
If generate_test is true, also generates a test run rule.
"""
global Variant
rules = {}
sources = {}
ignored = []
linkflags = OrderedSet()
cause = []
source = realpath(source)
file_list.insert( source )
insert_dependencies(sources, ignored, source, linkflags, cause, quiet, verbose, file_list)
# compile rule for each object
for s in sources:
obj = objectname(s, sources[s])
cflags, cxxflags, cause, headers = sources[s]
for h in headers:
file_list.insert( h )
definition = []
definition.append(obj + " : " + " ".join(headers + [s]))
if not quiet:
definition.append("\t" + "@echo ... " + s)
if s.endswith(".c"):
definition.append("\t" + CC + " " + CFLAGS + " " + " ".join(cflags) + " -c " + " " + s + " " " -o " + obj)
else:
definition.append("\t" + CXX + " " + CXXFLAGS + " " + " ".join(cxxflags) + " -c " + " " + s + " " " -o " + obj)
rules[obj] = "\n".join(definition)
# link rule
definition = []
tmp_output_name = OBJDIR + Variant + "/" + os.path.split(output_name)[-1]
definition.append( tmp_output_name + " : " + " ".join([objectname(s, sources[s]) for s in sources]) + " " + makefilename)
linker_line = "\t" + LINKER + " "
if not static_library:
linker_line += "-o "
linker_line += tmp_output_name + " " + " " .join([objectname(s, sources[s]) for s in sources]) + " "
if not static_library:
linker_line += LINKFLAGS + " " + " ".join(linkflags)
definition.append( linker_line )
definition.append( "\n.PHONY : " + output_name )
definition.append( "\n" + output_name + " : " + tmp_output_name )
if not quiet:
definition.append("\t" + "@echo ... " + output_name)
definition.append( "\trm -f " + output_name )
definition.append( "\tcp " + tmp_output_name + " " + output_name )
rules[output_name] = "\n".join(definition)
if generate_test:
definition = []
test = munge(output_name) + ".result"
definition.append( test + " : " + tmp_output_name )
if not quiet:
definition.append("\t" + "@echo ... test " + output_name)
t = ""
if TESTPREFIX != "":
t = TESTPREFIX + " "
definition.append( "\t" + "rm -f " + test + " && " + t + tmp_output_name + " && touch " + test)
rules[test] = "\n".join(definition)
return rules
def render_makefile(makefilename, rules):
"""Renders a set of rules as a makefile"""
rules_as_list = [rules[r] for r in rules]
rules_as_list.sort()
objects = [r for r in rules]
objects.sort()
# top-level build rule
text = []
text.append("all : " + " ".join(objects))
text.append("")
for rule in rules_as_list:
text.append(rule)
text.append("")
text = "\n".join(text)
lazily_write(makefilename, text)
def cpus():
global PARALLEL
if len(PARALLEL):
num_procs = PARALLEL
else:
f = open("/proc/cpuinfo")
t = [x for x in f.readlines() if x.startswith("processor")]
f.close()
if 0 == len(t):
num_procs = 1
else:
num_procs = len(t)
return str(num_procs)
def do_generate(source_to_output, tests, post_steps, quiet, verbose, static_library, file_list):
"""Generates all needed makefiles"""
global Variant
all_rules = {}
for source in source_to_output:
makefilename = munge(source) + "." + Variant + ".Makefile"
rules = generate_rules(source, source_to_output[source], source_to_output[source] in tests, makefilename, quiet, verbose, static_library, file_list)
all_rules.update(rules)
render_makefile(makefilename, rules)
combined_filename = munge(source_to_output) + "." + Variant + ".combined.Makefile"
all_previous = [r for r in all_rules]
previous = all_previous
post_with_space = POSTPREFIX.strip()
if len(post_with_space) > 0:
post_with_space = POSTPREFIX + " "
for s in post_steps:
passed = OBJDIR + cake_hasher.md5(s).hexdigest() + ".passed"
rule = passed + " : " + " ".join(previous + [s]) + "\n"
if not quiet:
rule += "\t" + "echo ... post " + post_with_space + s
rule += "\trm -f " + passed + " && " + post_with_space + s + " && touch " + passed
all_rules[passed] = rule
previous = all_previous + [s]
render_makefile(combined_filename, all_rules)
return combined_filename
def do_build(makefilename, verbose):
cmd="make -r " + {False:"-s ",True:""}[verbose] + "-f " + makefilename + " -j" + cpus()
if verbose:
print(cmd)
result = os.system(cmd)
if result != 0:
print()
print("ERROR: Build failed.")
sys.exit(1)
elif verbose:
print()
print("Build successful.")
def do_run(output, args):
os.execvp(output, [output] + args)
def find_git_root():
p = os.path.abspath(".")
while (p != "/"):
if (os.path.exists( p + "/.git" )):
return p
p = os.path.dirname(p)
return;
def main(config_file):
global CAKE_DEFAULT_COMPILER_PREFIX, CAKE_ID, CPP, CC, CXX, LINKER
global CPPFLAGS, CFLAGS, CXXFLAGS, LINKFLAGS
global TESTPREFIX, POSTPREFIX, BINDIR, OBJDIR, PROJECT_VERSION_CMD
global PARALLEL
global verbose, debug
global Variant
if len(sys.argv) < 2:
usage()
# parse arguments
args = sys.argv[1:]
nextOutput = None
generate = True
build = True
file_list = False
quiet = False
static_library = False
dynamic_library=False
to_build = {}
inTests = False
inPost = False
tests = []
post_steps = []
include_git_root = True
include_git_parent = False
git_root = None
# Initialise the variables to the debug default
try_set_variant(Variant,static_library)
# set verbose and check for help
# copy list so we can remove from the original and still iterate
for a in list(args):
if a == "--verbose":
verbose = True
args.remove(a)
elif a == "--cake-debug":
debug = True
args.remove(a)
elif a == "--help":
usage()
return
elif a == "--version":
# This reports on the current version of cake, not the version of the project being built by cake.
# This relies on replacing the CAKE_PROJECT_VERSION_MACRO with an actual version number at packaging time
# Don't confuse this with PROJECT_VERSION which is the version of the project that cake is building.
print("CAKE_PROJECT_VERSION_MACRO")
return
# deal with variant next
# to set the base set of flags for the other options to apply to
for a in list(args):
if a.startswith("--variant="):
variant = a[a.index("=")+1:]
if variant.upper() in ["DEBUG","RELEASE","COVERAGE"]:
variant = CAKE_DEFAULT_COMPILER_PREFIX + "_" + variant
try_set_variant(variant,static_library)
args.remove(a)
continue
# now check for linkflag sensitive options
for a in list(args):
if a == "--static-library":
static_library = True
LINKER = "ar -src"
args.remove(a)
elif a == "--dynamic-library":
dynamic_library = True
LINKFLAGS += " -shared"
args.remove(a)
iter_args = iter(args)
for a in iter_args:
if a.startswith("--config="):
config_file = a[a.index("=")+1:]
continue;
if a.startswith("--ID="):
CAKE_ID = a[a.index("=")+1:]
continue
if a.startswith("--CPP="):
CPP = a[a.index("=")+1:]
continue
if a.startswith("--CC="):
CC = a[a.index("=")+1:]
continue
if a.startswith("--CXX="):
CXX = a[a.index("=")+1:]
continue
if a.startswith("--LINKER="):
LINKER = a[a.index("=")+1:]
continue
if a.startswith("--CPPFLAGS="):
CPPFLAGS = " " + a[a.index("=")+1:]
continue
if a.startswith("--append-CPPFLAGS="):
CPPFLAGS += " " + a[a.index("=")+1:]
continue
if a.startswith("--CFLAGS="):
CFLAGS = " " + a[a.index("=")+1:]
continue
if a.startswith("--append-CFLAGS="):
CFLAGS += " " + a[a.index("=")+1:]
continue
if a.startswith("--CXXFLAGS="):
CXXFLAGS = " " + a[a.index("=")+1:]
continue
if a.startswith("--append-CXXFLAGS="):
CXXFLAGS += " " + a[a.index("=")+1:]
continue
if a.startswith("--LINKFLAGS="):
LINKFLAGS = a[a.index("=")+1:]
continue
if a.startswith("--append-LINKFLAGS="):
LINKFLAGS += " " + a[a.index("=")+1:]
continue
if a.startswith("--TESTPREFIX="):
TESTPREFIX = a[a.index("=")+1:]
continue
if a.startswith("--POSTPREFIX="):
POSTPREFIX = a[a.index("=")+1:]
continue
if a.startswith("--bindir="):
BINDIR = a[a.index("=")+1:]
if not BINDIR.endswith("/"):
BINDIR = BINDIR + "/"
continue
if a.startswith("--jobs="):
PARALLEL = a[a.index("=")+1:]
continue
if a.startswith("-j"):
PARALLEL = next(iter_args)
continue
if a.startswith("--objdir="):