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 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 1151 1152 1153 1154 1155 1156 1157 1158 1159 1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190 1191 1192 1193 1194 1195 1196 1197 1198 1199 1200 1201 1202 1203 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213 1214 1215 1216 1217 1218 1219 1220 1221 1222 1223 1224 1225 1226 1227 1228 1229 1230 1231 1232 1233 1234 1235 1236 1237 1238 1239 1240 1241 1242 1243 1244 1245 1246 1247 1248 1249 1250 1251 1252 1253 1254 1255 1256 1257 1258 1259 1260 1261 1262 1263 1264 1265 1266 1267 1268 1269 1270 1271 1272 1273 1274 1275 1276 1277 1278 1279 1280 1281 1282 1283 1284 1285 1286 1287 1288 1289 1290 1291 1292 1293 1294 1295 1296 1297 1298 1299 1300 1301 1302 1303 1304 1305 1306 1307 1308 1309 1310 1311 1312 1313 1314 1315 1316 1317 1318 1319 1320 1321 1322 1323 1324 1325 1326 1327 1328 1329 1330 1331 1332 1333 1334 1335 1336 1337 1338 1339 1340 1341 1342 1343 1344 1345 1346 1347 1348 1349 1350 1351 1352 1353 1354 1355 1356 1357 1358 1359 1360 1361 1362 1363 1364 1365 1366 1367 1368 1369 1370 1371 1372 1373 1374 1375 1376 1377 1378 1379 1380 1381 1382 1383 1384 1385 1386 1387 1388 1389 1390 1391 1392 1393 1394 1395 1396 1397 1398 1399 1400 1401 1402 1403 1404 1405 1406 1407 1408 1409 1410 1411 1412 1413 1414 1415 1416 1417 1418 1419 1420 1421 1422 1423 1424 1425 1426 1427 1428 1429 1430 1431 1432 1433 1434 1435 1436 1437 1438 1439 1440 1441 1442 1443 1444 1445 1446 1447 1448 1449 1450 1451 1452 1453 1454 1455 1456 1457 1458 1459 1460 1461 1462 1463 1464 1465 1466 1467 1468 1469 1470 1471 1472 1473 1474 1475 1476 1477 1478 1479 1480 1481 1482 1483 1484 1485 1486 1487 1488 1489 1490 1491 1492 1493 1494 1495 1496 1497 1498 1499 1500 1501 1502 1503 1504 1505 1506 1507 1508 1509 1510 1511 1512 1513 1514 1515 1516 1517 1518 1519 1520 1521 1522 1523 1524 1525 1526 1527 1528 1529 1530 1531 1532 1533 1534 1535 1536 1537 1538 1539 1540
|
#!/usr/bin/env python2
#
# Process Duktape option metadata and produce various useful outputs:
#
# - duk_config.h with specific or autodetected platform, compiler, and
# architecture, forced options, sanity checks, etc
# - option documentation for Duktape config options (DUK_USE_xxx)
#
# Genconfig tries to build all outputs based on modular metadata, so that
# managing a large number of config options (which is hard to avoid given
# the wide range of targets Duktape supports) remains maintainable.
#
# Genconfig does *not* try to support all exotic platforms out there.
# Instead, the goal is to allow the metadata to be extended, or to provide
# a reasonable starting point for manual duk_config.h tweaking.
#
import logging
import sys
logging.basicConfig(level=logging.INFO, stream=sys.stdout, format='%(name)-21s %(levelname)-7s %(message)s')
logger = logging.getLogger('genconfig.py')
logger.setLevel(logging.INFO)
import os
import re
import json
import yaml
import optparse
import tarfile
import tempfile
import atexit
import shutil
import logging
try:
from StringIO import StringIO
except ImportError:
from io import StringIO
#
# Globals holding scanned metadata, helper snippets, etc
#
# Metadata to scan from config files.
use_defs = None
use_defs_list = None
opt_defs = None
opt_defs_list = None
use_tags = None
use_tags_list = None
tags_meta = None
required_use_meta_keys = [
'define',
'introduced',
'default',
'tags',
'description'
]
allowed_use_meta_keys = [
'define',
'introduced',
'deprecated',
'removed',
'unused',
'requires',
'conflicts',
'related',
'default',
'tags',
'description',
'warn_if_missing'
]
required_opt_meta_keys = [
'define',
'introduced',
'tags',
'description'
]
allowed_opt_meta_keys = [
'define',
'introduced',
'deprecated',
'removed',
'unused',
'requires',
'conflicts',
'related',
'tags',
'description'
]
# Preferred tag order for option documentation.
doc_tag_order = [
'portability',
'memory',
'lowmemory',
'ecmascript',
'execution',
'debugger',
'debug',
'development'
]
# Preferred tag order for generated C header files.
header_tag_order = doc_tag_order
# Helper headers snippets.
helper_snippets = None
# Assume these provides come from outside.
assumed_provides = {
'DUK_SINGLE_FILE': True, # compiling Duktape from a single source file (duktape.c) version
'DUK_COMPILING_DUKTAPE': True, # compiling Duktape (not user application)
'DUK_CONFIG_H_INCLUDED': True, # artifact, include guard
}
# Platform files must provide at least these (additional checks
# in validate_platform_file()). Fill-ins provide missing optionals.
platform_required_provides = [
'DUK_USE_OS_STRING' # must be #define'd
]
# Architecture files must provide at least these (additional checks
# in validate_architecture_file()). Fill-ins provide missing optionals.
architecture_required_provides = [
'DUK_USE_ARCH_STRING'
]
# Compiler files must provide at least these (additional checks
# in validate_compiler_file()). Fill-ins provide missing optionals.
compiler_required_provides = [
# Compilers need a lot of defines; missing defines are automatically
# filled in with defaults (which are mostly compiler independent), so
# the requires define list is not very large.
'DUK_USE_COMPILER_STRING', # must be #define'd
'DUK_USE_BRANCH_HINTS', # may be #undef'd, as long as provided
'DUK_USE_VARIADIC_MACROS', # may be #undef'd, as long as provided
'DUK_USE_UNION_INITIALIZERS' # may be #undef'd, as long as provided
]
#
# Miscellaneous helpers
#
def get_auto_delete_tempdir():
tmpdir = tempfile.mkdtemp(suffix='-genconfig')
def _f(dirname):
logger.debug('Deleting temporary directory: %r' % dirname)
if os.path.isdir(dirname) and '-genconfig' in dirname:
shutil.rmtree(dirname)
atexit.register(_f, tmpdir)
return tmpdir
def strip_comments_from_lines(lines):
# Not exact but close enough. Doesn't handle string literals etc,
# but these are not a concrete issue for scanning preprocessor
# #define references.
#
# Comment contents are stripped of any DUK_ prefixed text to avoid
# incorrect requires/provides detection. Other comment text is kept;
# in particular a "/* redefine */" comment must remain intact here.
# (The 'redefine' hack is not actively needed now.)
#
# Avoid Python 2.6 vs. Python 2.7 argument differences.
def censor(x):
return re.sub(re.compile('DUK_\w+', re.MULTILINE), 'xxx', x.group(0))
tmp = '\n'.join(lines)
tmp = re.sub(re.compile('/\*.*?\*/', re.MULTILINE | re.DOTALL), censor, tmp)
tmp = re.sub(re.compile('//.*?$', re.MULTILINE), censor, tmp)
return tmp.split('\n')
# Header snippet representation: lines, provides defines, requires defines.
re_line_provides = re.compile(r'^#(?:define|undef)\s+(\w+).*$')
re_line_requires = re.compile(r'(DUK_[A-Z0-9_]+)') # uppercase only, don't match DUK_USE_xxx for example
class Snippet:
lines = None # lines of text and/or snippets
provides = None # map from define to 'True' for now
requires = None # map from define to 'True' for now
def __init__(self, lines, provides=None, requires=None, autoscan_requires=True, autoscan_provides=True):
self.lines = []
if not isinstance(lines, list):
raise Exception('Snippet constructor must be a list (not e.g. a string): %s' % repr(lines))
for line in lines:
if isinstance(line, str):
self.lines.append(line)
elif isinstance(line, unicode):
self.lines.append(line.encode('utf-8'))
else:
raise Exception('invalid line: %r' % line)
self.provides = {}
if provides is not None:
for k in provides.keys():
self.provides[k] = True
self.requires = {}
if requires is not None:
for k in requires.keys():
self.requires[k] = True
stripped_lines = strip_comments_from_lines(lines)
#for line in stripped_lines:
# logger.debug(line)
for line in stripped_lines:
# Careful with order, snippet may self-reference its own
# defines in which case there's no outward dependency.
# (This is not 100% because the order of require/provide
# matters and this is not handled now.)
#
# Also, some snippets may #undef/#define another define but
# they don't "provide" the define as such. Such redefinitions
# are marked "/* redefine */" in the snippets. They're best
# avoided (and not currently needed in Duktape 1.4.0).
if autoscan_provides:
m = re_line_provides.match(line)
if m is not None and '/* redefine */' not in line and \
len(m.group(1)) > 0 and m.group(1)[-1] != '_':
# Don't allow e.g. DUK_USE_ which results from matching DUK_USE_xxx
#logger.debug('PROVIDES: %r' % m.group(1))
self.provides[m.group(1)] = True
if autoscan_requires:
matches = re.findall(re_line_requires, line)
for m in matches:
if len(m) > 0 and m[-1] == '_':
# Don't allow e.g. DUK_USE_ which results from matching DUK_USE_xxx
pass
elif m[:7] == 'DUK_OPT':
#logger.warning('Encountered DUK_OPT_xxx in a header snippet: %s' % repr(line))
# DUK_OPT_xxx always come from outside
pass
elif m[:7] == 'DUK_USE':
# DUK_USE_xxx are internal and they should not be 'requirements'
pass
elif self.provides.has_key(m):
# Snippet provides it's own require; omit
pass
else:
#logger.debug('REQUIRES: %r' % m)
self.requires[m] = True
def fromFile(cls, filename):
lines = []
with open(filename, 'rb') as f:
for line in f:
if line[-1] == '\n':
line = line[:-1]
if line[:8] == '#snippet':
m = re.match(r'#snippet\s+"(.*?)"', line)
# XXX: better plumbing for lookup path
sub_fn = os.path.normpath(os.path.join(filename, '..', '..', 'header-snippets', m.group(1)))
logger.debug('#snippet ' + sub_fn)
sn = Snippet.fromFile(sub_fn)
lines += sn.lines
else:
lines.append(line)
return Snippet(lines, autoscan_requires=True, autoscan_provides=True)
fromFile = classmethod(fromFile)
def merge(cls, snippets):
ret = Snippet([], [], [])
for s in snippets:
ret.lines += s.lines
for k in s.provides.keys():
ret.provides[k] = True
for k in s.requires.keys():
ret.requires[k] = True
return ret
merge = classmethod(merge)
# Helper for building a text file from individual lines, injected files, etc.
# Inserted values are converted to Snippets so that their provides/requires
# information can be tracked. When non-C outputs are created, these will be
# bogus but ignored.
class FileBuilder:
vals = None # snippet list
base_dir = None
use_cpp_warning = False
def __init__(self, base_dir=None, use_cpp_warning=False):
self.vals = []
self.base_dir = base_dir
self.use_cpp_warning = use_cpp_warning
def line(self, line):
self.vals.append(Snippet([ line ]))
def lines(self, lines):
if len(lines) > 0 and lines[-1] == '\n':
lines = lines[:-1] # strip last newline to avoid empty line
self.vals.append(Snippet(lines.split('\n')))
def empty(self):
self.vals.append(Snippet([ '' ]))
def rst_heading(self, title, char, doubled=False):
tmp = []
if doubled:
tmp.append(char * len(title))
tmp.append(title)
tmp.append(char * len(title))
self.vals.append(Snippet(tmp))
def snippet_relative(self, fn):
sn = Snippet.fromFile(os.path.join(self.base_dir, fn))
self.vals.append(sn)
return sn
def snippet_absolute(self, fn):
sn = Snippet.fromFile(fn)
self.vals.append(sn)
return sn
def cpp_error(self, msg):
# XXX: assume no newlines etc
self.vals.append(Snippet([ '#error %s' % msg ]))
def cpp_warning(self, msg):
# XXX: assume no newlines etc
# XXX: support compiler specific warning mechanisms
if self.use_cpp_warning:
# C preprocessor '#warning' is often supported
self.vals.append(Snippet([ '#warning %s' % msg ]))
else:
self.vals.append(Snippet([ '/* WARNING: %s */' % msg ]))
def cpp_warning_or_error(self, msg, is_error=True):
if is_error:
self.cpp_error(msg)
else:
self.cpp_warning(msg)
def chdr_comment_line(self, msg):
self.vals.append(Snippet([ '/* %s */' % msg ]))
def chdr_block_heading(self, msg):
lines = []
lines.append('')
lines.append('/*')
lines.append(' * ' + msg)
lines.append(' */')
lines.append('')
self.vals.append(Snippet(lines))
def join(self):
tmp = []
for line in self.vals:
if not isinstance(line, object):
raise Exception('self.vals must be all snippets')
for x in line.lines: # x is a Snippet
tmp.append(x)
return '\n'.join(tmp)
def fill_dependencies_for_snippets(self, idx_deps):
fill_dependencies_for_snippets(self.vals, idx_deps)
# Insert missing define dependencies into index 'idx_deps' repeatedly
# until no unsatisfied dependencies exist. This is used to pull in
# the required DUK_F_xxx helper defines without pulling them all in.
# The resolution mechanism also ensures dependencies are pulled in the
# correct order, i.e. DUK_F_xxx helpers may depend on each other (as
# long as there are no circular dependencies).
#
# XXX: this can be simplified a lot
def fill_dependencies_for_snippets(snippets, idx_deps):
# graph[A] = [ B, ... ] <-> B, ... provide something A requires.
graph = {}
snlist = []
resolved = [] # for printing only
def add(sn):
if sn in snlist:
return # already present
snlist.append(sn)
to_add = []
for k in sn.requires.keys():
if assumed_provides.has_key(k):
continue
found = False
for sn2 in snlist:
if sn2.provides.has_key(k):
if not graph.has_key(sn):
graph[sn] = []
graph[sn].append(sn2)
found = True # at least one other node provides 'k'
if not found:
logger.debug('Resolving %r' % k)
resolved.append(k)
# Find a header snippet which provides the missing define.
# Some DUK_F_xxx files provide multiple defines, so we don't
# necessarily know the snippet filename here.
sn_req = None
for sn2 in helper_snippets:
if sn2.provides.has_key(k):
sn_req = sn2
break
if sn_req is None:
logger.debug(repr(sn.lines))
raise Exception('cannot resolve missing require: %r' % k)
# Snippet may have further unresolved provides; add recursively
to_add.append(sn_req)
if not graph.has_key(sn):
graph[sn] = []
graph[sn].append(sn_req)
for sn in to_add:
add(sn)
# Add original snippets. This fills in the required nodes
# recursively.
for sn in snippets:
add(sn)
# Figure out fill-ins by looking for snippets not in original
# list and without any unserialized dependent nodes.
handled = {}
for sn in snippets:
handled[sn] = True
keepgoing = True
while keepgoing:
keepgoing = False
for sn in snlist:
if handled.has_key(sn):
continue
success = True
for dep in graph.get(sn, []):
if not handled.has_key(dep):
success = False
if success:
snippets.insert(idx_deps, sn)
idx_deps += 1
snippets.insert(idx_deps, Snippet([ '' ]))
idx_deps += 1
handled[sn] = True
keepgoing = True
break
# XXX: detect and handle loops cleanly
for sn in snlist:
if handled.has_key(sn):
continue
logger.debug('UNHANDLED KEY')
logger.debug('PROVIDES: %r' % sn.provides)
logger.debug('REQUIRES: %r' % sn.requires)
logger.debug('\n'.join(sn.lines))
#logger.debug(repr(graph))
#logger.debug(repr(snlist))
logger.debug('Resolved helper defines: %r' % resolved)
logger.debug('Resolved %d helper defines' % len(resolved))
def serialize_snippet_list(snippets):
ret = []
emitted_provides = {}
for k in assumed_provides.keys():
emitted_provides[k] = True
for sn in snippets:
ret += sn.lines
for k in sn.provides.keys():
emitted_provides[k] = True
for k in sn.requires.keys():
if not emitted_provides.has_key(k):
# XXX: conditional warning, happens in some normal cases
logger.warning('define %r required, not provided so far' % k)
pass
return '\n'.join(ret)
def remove_duplicate_newlines(x):
ret = []
empty = False
for line in x.split('\n'):
if line == '':
if empty:
pass
else:
ret.append(line)
empty = True
else:
empty = False
ret.append(line)
return '\n'.join(ret)
def scan_use_defs(dirname):
global use_defs, use_defs_list
use_defs = {}
use_defs_list = []
for fn in os.listdir(dirname):
root, ext = os.path.splitext(fn)
if not root.startswith('DUK_USE_') or ext != '.yaml':
continue
with open(os.path.join(dirname, fn), 'rb') as f:
doc = yaml.load(f)
if doc.get('example', False):
continue
if doc.get('unimplemented', False):
logger.warning('unimplemented: %s' % fn)
continue
dockeys = doc.keys()
for k in dockeys:
if not k in allowed_use_meta_keys:
logger.warning('unknown key %s in metadata file %s' % (k, fn))
for k in required_use_meta_keys:
if not k in dockeys:
logger.warning('missing key %s in metadata file %s' % (k, fn))
use_defs[doc['define']] = doc
keys = use_defs.keys()
keys.sort()
for k in keys:
use_defs_list.append(use_defs[k])
def scan_opt_defs(dirname):
global opt_defs, opt_defs_list
opt_defs = {}
opt_defs_list = []
for fn in os.listdir(dirname):
root, ext = os.path.splitext(fn)
if not root.startswith('DUK_OPT_') or ext != '.yaml':
continue
with open(os.path.join(dirname, fn), 'rb') as f:
doc = yaml.load(f)
if doc.get('example', False):
continue
if doc.get('unimplemented', False):
logger.warning('unimplemented: %s' % fn)
continue
dockeys = doc.keys()
for k in dockeys:
if not k in allowed_opt_meta_keys:
logger.warning('unknown key %s in metadata file %s' % (k, fn))
for k in required_opt_meta_keys:
if not k in dockeys:
logger.warning('missing key %s in metadata file %s' % (k, fn))
opt_defs[doc['define']] = doc
keys = opt_defs.keys()
keys.sort()
for k in keys:
opt_defs_list.append(opt_defs[k])
def scan_use_tags():
global use_tags, use_tags_list
use_tags = {}
for doc in use_defs_list:
for tag in doc.get('tags', []):
use_tags[tag] = True
use_tags_list = use_tags.keys()
use_tags_list.sort()
def scan_tags_meta(filename):
global tags_meta
with open(filename, 'rb') as f:
tags_meta = yaml.load(f)
def scan_helper_snippets(dirname): # DUK_F_xxx snippets
global helper_snippets
helper_snippets = []
for fn in os.listdir(dirname):
if (fn[0:6] != 'DUK_F_'):
continue
logger.debug('Autoscanning snippet: %s' % fn)
helper_snippets.append(Snippet.fromFile(os.path.join(dirname, fn)))
def get_opt_defs(removed=True, deprecated=True, unused=True):
ret = []
for doc in opt_defs_list:
# XXX: aware of target version
if removed == False and doc.get('removed', None) is not None:
continue
if deprecated == False and doc.get('deprecated', None) is not None:
continue
if unused == False and doc.get('unused', False) == True:
continue
ret.append(doc)
return ret
def get_use_defs(removed=True, deprecated=True, unused=True):
ret = []
for doc in use_defs_list:
# XXX: aware of target version
if removed == False and doc.get('removed', None) is not None:
continue
if deprecated == False and doc.get('deprecated', None) is not None:
continue
if unused == False and doc.get('unused', False) == True:
continue
ret.append(doc)
return ret
def validate_platform_file(filename):
sn = Snippet.fromFile(filename)
for req in platform_required_provides:
if req not in sn.provides:
raise Exception('Platform %s is missing %s' % (filename, req))
# DUK_SETJMP, DUK_LONGJMP, DUK_JMPBUF_TYPE are optional, fill-in
# provides if none defined.
def validate_architecture_file(filename):
sn = Snippet.fromFile(filename)
for req in architecture_required_provides:
if req not in sn.provides:
raise Exception('Architecture %s is missing %s' % (filename, req))
# Byte order and alignment defines are allowed to be missing,
# a fill-in will handle them. This is necessary because for
# some architecture byte order and/or alignment may vary between
# targets and may be software configurable.
# XXX: require automatic detection to be signaled?
# e.g. define DUK_USE_ALIGN_BY -1
# define DUK_USE_BYTE_ORDER -1
def validate_compiler_file(filename):
sn = Snippet.fromFile(filename)
for req in compiler_required_provides:
if req not in sn.provides:
raise Exception('Compiler %s is missing %s' % (filename, req))
def get_tag_title(tag):
meta = tags_meta.get(tag, None)
if meta is None:
return tag
else:
return meta.get('title', tag)
def get_tag_description(tag):
meta = tags_meta.get(tag, None)
if meta is None:
return None
else:
return meta.get('description', None)
def get_tag_list_with_preferred_order(preferred):
tags = []
# Preferred tags first
for tag in preferred:
if tag not in tags:
tags.append(tag)
# Remaining tags in alphabetic order
for tag in use_tags_list:
if tag not in tags:
tags.append(tag)
logger.debug('Effective tag order: %r' % tags)
return tags
def rst_format(text):
# XXX: placeholder, need to decide on markup conventions for YAML files
ret = []
for para in text.split('\n'):
if para == '':
continue
ret.append(para)
return '\n\n'.join(ret)
def cint_encode(x):
if not isinstance(x, (int, long)):
raise Exception('invalid input: %r' % x)
# XXX: unsigned constants?
if x > 0x7fffffff or x < -0x80000000:
return '%dLL' % x
elif x > 0x7fff or x < -0x8000:
return '%dL' % x
else:
return '%d' % x
def cstr_encode(x):
if isinstance(x, unicode):
x = x.encode('utf-8')
if not isinstance(x, str):
raise Exception('invalid input: %r' % x)
res = '"'
term = False
has_terms = False
for c in x:
if term:
# Avoid ambiguous hex escapes
res += '" "'
term = False
has_terms = True
o = ord(c)
if o < 0x20 or o > 0x7e or c in '"\\':
res += '\\x%02x' % o
term = True
else:
res += c
res += '"'
if has_terms:
res = '(' + res + ')'
return res
#
# Autogeneration of option documentation
#
# Shared helper to generate DUK_USE_xxx documentation.
# XXX: unfinished placeholder
def generate_option_documentation(opts, opt_list=None, rst_title=None, include_default=False):
ret = FileBuilder(use_cpp_warning=opts.use_cpp_warning)
tags = get_tag_list_with_preferred_order(doc_tag_order)
title = rst_title
ret.rst_heading(title, '=', doubled=True)
handled = {}
for tag in tags:
first = True
for doc in opt_list:
if tag != doc['tags'][0]: # sort under primary tag
continue
dname = doc['define']
desc = doc.get('description', None)
if handled.has_key(dname):
raise Exception('define handled twice, should not happen: %r' % dname)
handled[dname] = True
if first: # emit tag heading only if there are subsections
ret.empty()
ret.rst_heading(get_tag_title(tag), '=')
tag_desc = get_tag_description(tag)
if tag_desc is not None:
ret.empty()
ret.line(rst_format(tag_desc))
first = False
ret.empty()
ret.rst_heading(dname, '-')
if desc is not None:
ret.empty()
ret.line(rst_format(desc))
if include_default:
ret.empty()
ret.line('Default: ``' + str(doc['default']) + '``') # XXX: rst or other format
for doc in opt_list:
dname = doc['define']
if not handled.has_key(dname):
raise Exception('unhandled define (maybe missing from tags list?): %r' % dname)
ret.empty()
return ret.join()
def generate_config_option_documentation(opts):
defs = get_use_defs()
return generate_option_documentation(opts, opt_list=defs, rst_title='Duktape config options', include_default=True)
#
# Helpers for duk_config.h generation
#
def get_forced_options(opts):
# Forced options, last occurrence wins (allows a base config file to be
# overridden by a more specific one).
forced_opts = {}
for val in opts.force_options_yaml:
doc = yaml.load(StringIO(val))
for k in doc.keys():
if use_defs.has_key(k):
pass # key is known
else:
logger.warning('option override key %s not defined in metadata, ignoring' % k)
forced_opts[k] = doc[k] # shallow copy
if len(forced_opts.keys()) > 0:
logger.debug('Overrides: %s' % json.dumps(forced_opts))
return forced_opts
# Emit a default #define / #undef for an option based on
# a config option metadata node (parsed YAML doc).
def emit_default_from_config_meta(ret, doc, forced_opts, undef_done, active_opts):
defname = doc['define']
defval = forced_opts.get(defname, doc['default'])
# NOTE: careful with Python equality, e.g. "0 == False" is true.
if isinstance(defval, bool) and defval == True:
ret.line('#define ' + defname)
active_opts[defname] = True
elif isinstance(defval, bool) and defval == False:
if not undef_done:
ret.line('#undef ' + defname)
else:
# Default value is false, and caller has emitted
# an unconditional #undef, so don't emit a duplicate
pass
active_opts[defname] = False
elif isinstance(defval, (int, long)):
# integer value
ret.line('#define ' + defname + ' ' + cint_encode(defval))
active_opts[defname] = True
elif isinstance(defval, (str, unicode)):
# verbatim value
ret.line('#define ' + defname + ' ' + defval)
active_opts[defname] = True
elif isinstance(defval, dict):
if defval.has_key('verbatim'):
# verbatim text for the entire line
ret.line(defval['verbatim'])
elif defval.has_key('string'):
# C string value
ret.line('#define ' + defname + ' ' + cstr_encode(defval['string']))
else:
raise Exception('unsupported value for option %s: %r' % (defname, defval))
active_opts[defname] = True
else:
raise Exception('unsupported value for option %s: %r' % (defname, defval))
# Add a header snippet for detecting presence of DUK_OPT_xxx feature
# options and warning/erroring if application defines them. Useful for
# Duktape 2.x migration.
def add_legacy_feature_option_checks(opts, ret):
ret.chdr_block_heading('Checks for legacy feature options (DUK_OPT_xxx)')
ret.empty()
defs = []
for doc in get_opt_defs():
if doc['define'] not in defs:
defs.append(doc['define'])
defs.sort()
for optname in defs:
ret.line('#if defined(%s)' % optname)
ret.cpp_warning_or_error('unsupported legacy feature option %s used' % optname, opts.sanity_strict)
ret.line('#endif')
ret.empty()
# Add a header snippet for checking consistency of DUK_USE_xxx config
# options, e.g. inconsistent options, invalid option values.
def add_config_option_checks(opts, ret):
ret.chdr_block_heading('Checks for config option consistency (DUK_USE_xxx)')
ret.empty()
defs = []
for doc in get_use_defs():
if doc['define'] not in defs:
defs.append(doc['define'])
defs.sort()
for optname in defs:
doc = use_defs[optname]
dname = doc['define']
# XXX: more checks
if doc.get('removed', None) is not None:
ret.line('#if defined(%s)' % dname)
ret.cpp_warning_or_error('unsupported config option used (option has been removed): %s' % dname, opts.sanity_strict)
ret.line('#endif')
elif doc.get('deprecated', None) is not None:
ret.line('#if defined(%s)' % dname)
ret.cpp_warning_or_error('unsupported config option used (option has been deprecated): %s' % dname, opts.sanity_strict)
ret.line('#endif')
for req in doc.get('requires', []):
ret.line('#if defined(%s) && !defined(%s)' % (dname, req))
ret.cpp_warning_or_error('config option %s requires option %s (which is missing)' % (dname, req), opts.sanity_strict)
ret.line('#endif')
for req in doc.get('conflicts', []):
ret.line('#if defined(%s) && defined(%s)' % (dname, req))
ret.cpp_warning_or_error('config option %s conflicts with option %s (which is also defined)' % (dname, req), opts.sanity_strict)
ret.line('#endif')
ret.empty()
ret.snippet_relative('cpp_exception_sanity.h.in')
ret.empty()
# Add a header snippet for providing a __OVERRIDE_DEFINES__ section.
def add_override_defines_section(opts, ret):
ret.empty()
ret.line('/*')
ret.line(' * You may add overriding #define/#undef directives below for')
ret.line(' * customization. You of course cannot un-#include or un-typedef')
ret.line(' * anything; these require direct changes above.')
ret.line(' */')
ret.empty()
ret.line('/* __OVERRIDE_DEFINES__ */')
ret.empty()
# Add a header snippet for conditional C/C++ include files.
def add_conditional_includes_section(opts, ret):
ret.empty()
ret.line('/*')
ret.line(' * Conditional includes')
ret.line(' */')
ret.empty()
ret.snippet_relative('platform_conditionalincludes.h.in')
ret.empty()
# Development time helper: add DUK_ACTIVE which provides a runtime C string
# indicating what DUK_USE_xxx config options are active at run time. This
# is useful in genconfig development so that one can e.g. diff the active
# run time options of two headers. This is intended just for genconfig
# development and is not available in normal headers.
def add_duk_active_defines_macro(ret):
ret.chdr_block_heading('DUK_ACTIVE_DEFINES macro (development only)')
idx = 0
for doc in get_use_defs():
defname = doc['define']
ret.line('#if defined(%s)' % defname)
ret.line('#define DUK_ACTIVE_DEF%d " %s"' % (idx, defname))
ret.line('#else')
ret.line('#define DUK_ACTIVE_DEF%d ""' % idx)
ret.line('#endif')
idx += 1
tmp = []
for i in xrange(idx):
tmp.append('DUK_ACTIVE_DEF%d' % i)
ret.line('#define DUK_ACTIVE_DEFINES ("Active: ["' + ' '.join(tmp) + ' " ]")')
#
# duk_config.h generation
#
# Generate a duk_config.h where platform, architecture, and compiler are
# all either autodetected or specified by user.
#
# Autodetection is based on a configured list of supported platforms,
# architectures, and compilers. For example, platforms.yaml defines the
# supported platforms and provides a helper define (DUK_F_xxx) to use for
# detecting that platform, and names the header snippet to provide the
# platform-specific definitions. Necessary dependencies (DUK_F_xxx) are
# automatically pulled in.
#
# Automatic "fill ins" are used for mandatory platform, architecture, and
# compiler defines which have a reasonable portable default. This reduces
# e.g. compiler-specific define count because there are a lot compiler
# macros which have a good default.
def generate_duk_config_header(opts, meta_dir):
ret = FileBuilder(base_dir=os.path.join(meta_dir, 'header-snippets'), \
use_cpp_warning=opts.use_cpp_warning)
# Parse forced options. Warn about missing forced options when it is
# strongly recommended that the option is provided.
forced_opts = get_forced_options(opts)
for doc in use_defs_list:
if doc.get('warn_if_missing', False) and not forced_opts.has_key(doc['define']):
# Awkward handling for DUK_USE_CPP_EXCEPTIONS + DUK_USE_FATAL_HANDLER.
if doc['define'] == 'DUK_USE_FATAL_HANDLER' and forced_opts.has_key('DUK_USE_CPP_EXCEPTIONS'):
pass # DUK_USE_FATAL_HANDLER not critical with DUK_USE_CPP_EXCEPTIONS
else:
logger.warning('Recommended config option ' + doc['define'] + ' not provided')
# Gather a map of "active options" for genbuiltins.py. This is used to
# implement proper optional built-ins, e.g. if a certain config option
# (like DUK_USE_ES6_PROXY) is disabled, the corresponding objects and
# properties are dropped entirely. The mechanism is not perfect: it won't
# detect fixup changes for example.
active_opts = {}
platforms = None
with open(os.path.join(meta_dir, 'platforms.yaml'), 'rb') as f:
platforms = yaml.load(f)
architectures = None
with open(os.path.join(meta_dir, 'architectures.yaml'), 'rb') as f:
architectures = yaml.load(f)
compilers = None
with open(os.path.join(meta_dir, 'compilers.yaml'), 'rb') as f:
compilers = yaml.load(f)
# XXX: indicate feature option support, sanity checks enabled, etc
# in general summary of options, perhaps genconfig command line?
ret.line('/*')
ret.line(' * duk_config.h configuration header generated by genconfig.py.')
ret.line(' *')
ret.line(' * Git commit: %s' % opts.git_commit or 'n/a')
ret.line(' * Git describe: %s' % opts.git_describe or 'n/a')
ret.line(' * Git branch: %s' % opts.git_branch or 'n/a')
ret.line(' *')
if opts.platform is not None:
ret.line(' * Platform: ' + opts.platform)
else:
ret.line(' * Supported platforms:')
for platf in platforms['autodetect']:
ret.line(' * - %s' % platf.get('name', platf.get('check')))
ret.line(' *')
if opts.architecture is not None:
ret.line(' * Architecture: ' + opts.architecture)
else:
ret.line(' * Supported architectures:')
for arch in architectures['autodetect']:
ret.line(' * - %s' % arch.get('name', arch.get('check')))
ret.line(' *')
if opts.compiler is not None:
ret.line(' * Compiler: ' + opts.compiler)
else:
ret.line(' * Supported compilers:')
for comp in compilers['autodetect']:
ret.line(' * - %s' % comp.get('name', comp.get('check')))
ret.line(' *')
ret.line(' */')
ret.empty()
ret.line('#if !defined(DUK_CONFIG_H_INCLUDED)')
ret.line('#define DUK_CONFIG_H_INCLUDED')
ret.empty()
ret.chdr_block_heading('Intermediate helper defines')
# DLL build affects visibility attributes on Windows but unfortunately
# cannot be detected automatically from preprocessor defines or such.
# DLL build status is hidden behind DUK_F_DLL_BUILD. and there are two
ret.chdr_comment_line('DLL build detection')
if opts.dll:
ret.line('/* configured for DLL build */')
ret.line('#define DUK_F_DLL_BUILD')
else:
ret.line('/* not configured for DLL build */')
ret.line('#undef DUK_F_DLL_BUILD')
ret.empty()
idx_deps = len(ret.vals) # position where to emit DUK_F_xxx dependencies
# Feature selection, system include, Date provider
# Most #include statements are here
if opts.platform is not None:
ret.chdr_block_heading('Platform: ' + opts.platform)
ret.snippet_relative('platform_cppextras.h.in')
ret.empty()
# XXX: better to lookup platforms metadata
include = 'platform_%s.h.in' % opts.platform
abs_fn = os.path.join(meta_dir, 'platforms', include)
validate_platform_file(abs_fn)
ret.snippet_absolute(abs_fn)
else:
ret.chdr_block_heading('Platform autodetection')
ret.snippet_relative('platform_cppextras.h.in')
ret.empty()
for idx, platf in enumerate(platforms['autodetect']):
check = platf.get('check', None)
include = platf['include']
abs_fn = os.path.join(meta_dir, 'platforms', include)
validate_platform_file(abs_fn)
if idx == 0:
ret.line('#if defined(%s)' % check)
else:
if check is None:
ret.line('#else')
else:
ret.line('#elif defined(%s)' % check)
ret.line('/* --- %s --- */' % platf.get('name', '???'))
ret.snippet_absolute(abs_fn)
ret.line('#endif /* autodetect platform */')
ret.empty()
ret.snippet_relative('platform_sharedincludes.h.in')
ret.empty()
byteorder_provided_by_all = True # byteorder provided by all architecture files
alignment_provided_by_all = True # alignment provided by all architecture files
packedtval_provided_by_all = True # packed tval provided by all architecture files
if opts.architecture is not None:
ret.chdr_block_heading('Architecture: ' + opts.architecture)
# XXX: better to lookup architectures metadata
include = 'architecture_%s.h.in' % opts.architecture
abs_fn = os.path.join(meta_dir, 'architectures', include)
validate_architecture_file(abs_fn)
sn = ret.snippet_absolute(abs_fn)
if not sn.provides.get('DUK_USE_BYTEORDER', False):
byteorder_provided_by_all = False
if not sn.provides.get('DUK_USE_ALIGN_BY', False):
alignment_provided_by_all = False
if sn.provides.get('DUK_USE_PACKED_TVAL', False):
ret.line('#define DUK_F_PACKED_TVAL_PROVIDED') # signal to fillin
else:
packedtval_provided_by_all = False
else:
ret.chdr_block_heading('Architecture autodetection')
for idx, arch in enumerate(architectures['autodetect']):
check = arch.get('check', None)
include = arch['include']
abs_fn = os.path.join(meta_dir, 'architectures', include)
validate_architecture_file(abs_fn)
if idx == 0:
ret.line('#if defined(%s)' % check)
else:
if check is None:
ret.line('#else')
else:
ret.line('#elif defined(%s)' % check)
ret.line('/* --- %s --- */' % arch.get('name', '???'))
sn = ret.snippet_absolute(abs_fn)
if not sn.provides.get('DUK_USE_BYTEORDER', False):
byteorder_provided_by_all = False
if not sn.provides.get('DUK_USE_ALIGN_BY', False):
alignment_provided_by_all = False
if sn.provides.get('DUK_USE_PACKED_TVAL', False):
ret.line('#define DUK_F_PACKED_TVAL_PROVIDED') # signal to fillin
else:
packedtval_provided_by_all = False
ret.line('#endif /* autodetect architecture */')
ret.empty()
if opts.compiler is not None:
ret.chdr_block_heading('Compiler: ' + opts.compiler)
# XXX: better to lookup compilers metadata
include = 'compiler_%s.h.in' % opts.compiler
abs_fn = os.path.join(meta_dir, 'compilers', include)
validate_compiler_file(abs_fn)
sn = ret.snippet_absolute(abs_fn)
else:
ret.chdr_block_heading('Compiler autodetection')
for idx, comp in enumerate(compilers['autodetect']):
check = comp.get('check', None)
include = comp['include']
abs_fn = os.path.join(meta_dir, 'compilers', include)
validate_compiler_file(abs_fn)
if idx == 0:
ret.line('#if defined(%s)' % check)
else:
if check is None:
ret.line('#else')
else:
ret.line('#elif defined(%s)' % check)
ret.line('/* --- %s --- */' % comp.get('name', '???'))
sn = ret.snippet_absolute(abs_fn)
ret.line('#endif /* autodetect compiler */')
ret.empty()
# DUK_F_UCLIBC is special because __UCLIBC__ is provided by an #include
# file, so the check must happen after platform includes. It'd be nice
# for this to be automatic (e.g. DUK_F_UCLIBC.h.in could indicate the
# dependency somehow).
ret.snippet_absolute(os.path.join(meta_dir, 'helper-snippets', 'DUK_F_UCLIBC.h.in'))
ret.empty()
# XXX: platform/compiler could provide types; if so, need some signaling
# defines like DUK_F_TYPEDEFS_DEFINED
# Number types
if opts.c99_types_only:
ret.snippet_relative('types1.h.in')
ret.line('/* C99 types assumed */')
ret.snippet_relative('types_c99.h.in')
ret.empty()
else:
ret.snippet_relative('types1.h.in')
ret.line('#if defined(DUK_F_HAVE_INTTYPES)')
ret.line('/* C99 or compatible */')
ret.empty()
ret.snippet_relative('types_c99.h.in')
ret.empty()
ret.line('#else /* C99 types */')
ret.empty()
ret.snippet_relative('types_legacy.h.in')
ret.empty()
ret.line('#endif /* C99 types */')
ret.empty()
ret.snippet_relative('types2.h.in')
ret.empty()
ret.snippet_relative('64bitops.h.in')
ret.empty()
# Platform, architecture, compiler fillins. These are after all
# detection so that e.g. DUK_SPRINTF() can be provided by platform
# or compiler before trying a fill-in.
ret.chdr_block_heading('Fill-ins for platform, architecture, and compiler')
ret.snippet_relative('platform_fillins.h.in')
ret.empty()
ret.snippet_relative('architecture_fillins.h.in')
if not byteorder_provided_by_all:
ret.empty()
ret.snippet_relative('byteorder_fillin.h.in')
if not alignment_provided_by_all:
ret.empty()
ret.snippet_relative('alignment_fillin.h.in')
ret.empty()
ret.snippet_relative('compiler_fillins.h.in')
ret.empty()
ret.snippet_relative('inline_workaround.h.in')
ret.empty()
if not packedtval_provided_by_all:
ret.empty()
ret.snippet_relative('packed_tval_fillin.h.in')
# Object layout
ret.snippet_relative('object_layout.h.in')
ret.empty()
# Detect and reject 'fast math'
ret.snippet_relative('reject_fast_math.h.in')
ret.empty()
# Emit forced options. If a corresponding option is already defined
# by a snippet above, #undef it first.
tmp = Snippet(ret.join().split('\n'))
first_forced = True
for doc in get_use_defs(removed=not opts.omit_removed_config_options,
deprecated=not opts.omit_deprecated_config_options,
unused=not opts.omit_unused_config_options):
defname = doc['define']
if not forced_opts.has_key(defname):
continue
if not doc.has_key('default'):
raise Exception('config option %s is missing default value' % defname)
if first_forced:
ret.chdr_block_heading('Forced options')
first_forced = False
undef_done = False
if tmp.provides.has_key(defname):
ret.line('#undef ' + defname)
undef_done = True
emit_default_from_config_meta(ret, doc, forced_opts, undef_done, active_opts)
ret.empty()
# If manually-edited snippets don't #define or #undef a certain
# config option, emit a default value here. This is useful to
# fill-in for new config options not covered by manual snippets
# (which is intentional).
tmp = Snippet(ret.join().split('\n'))
need = {}
for doc in get_use_defs(removed=False):
need[doc['define']] = True
for k in tmp.provides.keys():
if need.has_key(k):
del need[k]
need_keys = sorted(need.keys())
if len(need_keys) > 0:
ret.chdr_block_heading('Autogenerated defaults')
for k in need_keys:
logger.debug('config option %s not covered by manual snippets, emitting default automatically' % k)
emit_default_from_config_meta(ret, use_defs[k], {}, False, active_opts)
ret.empty()
if len(opts.fixup_header_lines) > 0:
ret.chdr_block_heading('Fixups')
for line in opts.fixup_header_lines:
ret.line(line)
ret.empty()
add_override_defines_section(opts, ret)
# Some headers are only included if final DUK_USE_xxx option settings
# indicate they're needed, for example C++ <exception>.
add_conditional_includes_section(opts, ret)
# Date provider snippet is after custom header and overrides, so that
# the user may define e.g. DUK_USE_DATE_NOW_GETTIMEOFDAY in their
# custom header.
ret.snippet_relative('date_provider.h.in')
ret.empty()
ret.fill_dependencies_for_snippets(idx_deps)
if opts.emit_legacy_feature_check:
add_legacy_feature_option_checks(opts, ret)
if opts.emit_config_sanity_check:
add_config_option_checks(opts, ret)
if opts.add_active_defines_macro:
add_duk_active_defines_macro(ret)
# Derived defines (DUK_USE_INTEGER_LE, etc) from DUK_USE_BYTEORDER.
# Duktape internals currently rely on the derived defines. This is
# after sanity checks because the derived defines are marked removed.
ret.snippet_relative('byteorder_derived.h.in')
ret.empty()
ret.line('#endif /* DUK_CONFIG_H_INCLUDED */')
ret.empty() # for trailing newline
return remove_duplicate_newlines(ret.join()), active_opts
#
# Misc
#
# Validate DUK_USE_xxx config options found in source code against known
# config metadata. Also warn about non-removed config options that are
# not found in the source.
def validate_config_options_in_source(fn):
with open(fn, 'rb') as f:
doc = json.loads(f.read())
defs_used = {}
for opt in doc.get('used_duk_use_options'):
defs_used[opt] = True
if opt == 'DUK_USE_xxx' or opt == 'DUK_USE_XXX':
continue # allow common placeholders
meta = use_defs.get(opt)
if meta is None:
raise Exception('unknown config option in source code: %r' % opt)
if meta.get('removed', None) is not None:
#logger.info('removed config option in source code: %r' % opt)
#raise Exception('removed config option in source code: %r' % opt)
pass
for meta in use_defs_list:
if not defs_used.has_key(meta['define']):
if not meta.has_key('removed'):
logger.debug('config option %r not found in source code' % meta['define'])
#
# Main
#
def add_genconfig_optparse_options(parser, direct=False):
# Forced options from multiple sources are gathered into a shared list
# so that the override order remains the same as on the command line.
force_options_yaml = []
def add_force_option_yaml(option, opt, value, parser):
# XXX: check that YAML parses
force_options_yaml.append(value)
def add_force_option_file(option, opt, value, parser):
# XXX: check that YAML parses
with open(value, 'rb') as f:
force_options_yaml.append(f.read())
def add_force_option_define(option, opt, value, parser):
tmp = value.split('=')
if len(tmp) == 1:
doc = { tmp[0]: True }
elif len(tmp) == 2:
doc = { tmp[0]: tmp[1] }
else:
raise Exception('invalid option value: %r' % value)
force_options_yaml.append(yaml.safe_dump(doc))
def add_force_option_undefine(option, opt, value, parser):
tmp = value.split('=')
if len(tmp) == 1:
doc = { tmp[0]: False }
else:
raise Exception('invalid option value: %r' % value)
force_options_yaml.append(yaml.safe_dump(doc))
fixup_header_lines = []
def add_fixup_header_line(option, opt, value, parser):
fixup_header_lines.append(value)
def add_fixup_header_file(option, opt, value, parser):
with open(value, 'rb') as f:
for line in f:
if line[-1] == '\n':
line = line[:-1]
fixup_header_lines.append(line)
if direct:
parser.add_option('--metadata', dest='config_metadata', default=None, help='metadata directory')
parser.add_option('--output', dest='output', default=None, help='output filename for C header or RST documentation file')
parser.add_option('--output-active-options', dest='output_active_options', default=None, help='output JSON file with active config options information')
else:
# Different option name when called through configure.py,
# also no --output option.
parser.add_option('--config-metadata', dest='config_metadata', default=None, help='metadata directory (defaulted based on configure.py script path)')
parser.add_option('--platform', dest='platform', default=None, help='platform (default is autodetect)')
parser.add_option('--compiler', dest='compiler', default=None, help='compiler (default is autodetect)')
parser.add_option('--architecture', dest='architecture', default=None, help='architecture (default is autodetec)')
parser.add_option('--c99-types-only', dest='c99_types_only', action='store_true', default=False, help='assume C99 types, no legacy type detection')
parser.add_option('--dll', dest='dll', action='store_true', default=False, help='dll build of Duktape, affects symbol visibility macros especially on Windows')
parser.add_option('--support-feature-options', dest='support_feature_options', action='store_true', default=False, help=optparse.SUPPRESS_HELP)
parser.add_option('--emit-legacy-feature-check', dest='emit_legacy_feature_check', action='store_true', default=False, help='emit preprocessor checks to reject legacy feature options (DUK_OPT_xxx)')
parser.add_option('--emit-config-sanity-check', dest='emit_config_sanity_check', action='store_true', default=False, help='emit preprocessor checks for config option consistency (DUK_USE_xxx)')
parser.add_option('--omit-removed-config-options', dest='omit_removed_config_options', action='store_true', default=False, help='omit removed config options from generated headers')
parser.add_option('--omit-deprecated-config-options', dest='omit_deprecated_config_options', action='store_true', default=False, help='omit deprecated config options from generated headers')
parser.add_option('--omit-unused-config-options', dest='omit_unused_config_options', action='store_true', default=False, help='omit unused config options from generated headers')
parser.add_option('--add-active-defines-macro', dest='add_active_defines_macro', action='store_true', default=False, help='add DUK_ACTIVE_DEFINES macro, for development only')
parser.add_option('--define', type='string', metavar='OPTION', dest='force_options_yaml', action='callback', callback=add_force_option_define, default=force_options_yaml, help='force #define option using a C compiler like syntax, e.g. "--define DUK_USE_DEEP_C_STACK" or "--define DUK_USE_TRACEBACK_DEPTH=10"')
parser.add_option('-D', type='string', metavar='OPTION', dest='force_options_yaml', action='callback', callback=add_force_option_define, default=force_options_yaml, help='synonym for --define, e.g. "-DDUK_USE_DEEP_C_STACK" or "-DDUK_USE_TRACEBACK_DEPTH=10"')
parser.add_option('--undefine', type='string', metavar='OPTION', dest='force_options_yaml', action='callback', callback=add_force_option_undefine, default=force_options_yaml, help='force #undef option using a C compiler like syntax, e.g. "--undefine DUK_USE_DEEP_C_STACK"')
parser.add_option('-U', type='string', metavar='OPTION', dest='force_options_yaml', action='callback', callback=add_force_option_undefine, default=force_options_yaml, help='synonym for --undefine, e.g. "-UDUK_USE_DEEP_C_STACK"')
parser.add_option('--option-yaml', type='string', metavar='YAML', dest='force_options_yaml', action='callback', callback=add_force_option_yaml, default=force_options_yaml, help='force option(s) using inline YAML (e.g. --option-yaml "DUK_USE_DEEP_C_STACK: true")')
parser.add_option('--option-file', type='string', metavar='FILENAME', dest='force_options_yaml', action='callback', callback=add_force_option_file, default=force_options_yaml, help='YAML file(s) providing config option overrides')
parser.add_option('--fixup-file', type='string', metavar='FILENAME', dest='fixup_header_lines', action='callback', callback=add_fixup_header_file, default=fixup_header_lines, help='C header snippet file(s) to be appended to generated header, useful for manual option fixups')
parser.add_option('--fixup-line', type='string', metavar='LINE', dest='fixup_header_lines', action='callback', callback=add_fixup_header_line, default=fixup_header_lines, help='C header fixup line to be appended to generated header (e.g. --fixup-line "#define DUK_USE_FASTINT")')
parser.add_option('--sanity-warning', dest='sanity_strict', action='store_false', default=True, help='emit a warning instead of #error for option sanity check issues')
parser.add_option('--use-cpp-warning', dest='use_cpp_warning', action='store_true', default=False, help='emit a (non-portable) #warning when appropriate')
if direct:
parser.add_option('--used-stridx-metadata', dest='used_stridx_metadata', default=None, help='metadata for used stridx, bidx, DUK_USE_xxx')
parser.add_option('--git-commit', dest='git_commit', default=None, help='git commit hash to be included in header comments')
parser.add_option('--git-describe', dest='git_describe', default=None, help='git describe string to be included in header comments')
parser.add_option('--git-branch', dest='git_branch', default=None, help='git branch string to be included in header comments')
parser.add_option('--quiet', dest='quiet', action='store_true', default=False, help='Suppress info messages (show warnings)')
parser.add_option('--verbose', dest='verbose', action='store_true', default=False, help='Show verbose debug messages')
def parse_options():
commands = [
'duk-config-header',
'config-documentation'
]
parser = optparse.OptionParser(
usage='Usage: %prog [options] COMMAND',
description='Generate a duk_config.h or config option documentation based on config metadata.',
epilog='COMMAND can be one of: ' + ', '.join(commands) + '.'
)
add_genconfig_optparse_options(parser, direct=True)
(opts, args) = parser.parse_args()
return opts, args
def genconfig(opts, args):
# Log level.
if opts.quiet:
logger.setLevel(logging.WARNING)
elif opts.verbose:
logger.setLevel(logging.DEBUG)
if opts.support_feature_options:
raise Exception('--support-feature-options and support for DUK_OPT_xxx feature options are obsolete, use DUK_USE_xxx config options instead')
meta_dir = opts.config_metadata
if opts.config_metadata is None:
if os.path.isdir(os.path.join('.', 'config-options')):
opts.config_metadata = '.'
if opts.config_metadata is not None and os.path.isdir(opts.config_metadata):
meta_dir = opts.config_metadata
metadata_src_text = 'Using metadata directory: %r' % meta_dir
else:
raise Exception('metadata argument must be a directory (tar.gz no longer supported)')
scan_helper_snippets(os.path.join(meta_dir, 'helper-snippets'))
scan_use_defs(os.path.join(meta_dir, 'config-options'))
scan_opt_defs(os.path.join(meta_dir, 'feature-options'))
scan_use_tags()
scan_tags_meta(os.path.join(meta_dir, 'tags.yaml'))
logger.debug('%s, scanned%d DUK_USE_XXX, %d helper snippets' % \
(metadata_src_text, len(use_defs.keys()), len(helper_snippets)))
logger.debug('Tags: %r' % use_tags_list)
if opts.used_stridx_metadata is not None:
validate_config_options_in_source(opts.used_stridx_metadata)
if len(args) == 0:
raise Exception('missing command')
cmd = args[0]
if cmd == 'duk-config-header':
# Generate a duk_config.h header with platform, compiler, and
# architecture either autodetected (default) or specified by
# user.
desc = [
'platform=' + ('any', opts.platform)[opts.platform is not None],
'architecture=' + ('any', opts.architecture)[opts.architecture is not None],
'compiler=' + ('any', opts.compiler)[opts.compiler is not None]
]
if opts.dll:
desc.append('dll mode')
logger.info('Creating duk_config.h: ' + ', '.join(desc))
result, active_opts = generate_duk_config_header(opts, meta_dir)
with open(opts.output, 'wb') as f:
f.write(result)
logger.debug('Wrote duk_config.h to ' + str(opts.output))
if opts.output_active_options is not None:
with open(opts.output_active_options, 'wb') as f:
f.write(json.dumps(active_opts, indent=4))
logger.debug('Wrote active options JSON metadata to ' + str(opts.output_active_options))
elif cmd == 'feature-documentation':
raise Exception('The feature-documentation command has been removed along with DUK_OPT_xxx feature option support')
elif cmd == 'config-documentation':
logger.info('Creating config option documentation')
result = generate_config_option_documentation(opts)
with open(opts.output, 'wb') as f:
f.write(result)
logger.debug('Wrote config option documentation to ' + str(opts.output))
else:
raise Exception('invalid command: %r' % cmd)
def main():
opts, args = parse_options()
genconfig(opts, args)
if __name__ == '__main__':
main()
|