1#!/usr/bin/env python2 2# 3# Process Duktape option metadata and produce various useful outputs: 4# 5# - duk_config.h with specific or autodetected platform, compiler, and 6# architecture; forced options; sanity checks; etc 7# - option documentation for Duktape 1.x feature options (DUK_OPT_xxx) 8# - option documentation for Duktape 1.x/2.x config options (DUK_USE_xxx) 9# 10# Genconfig tries to build all outputs based on modular metadata, so that 11# managing a large number of config options (which is hard to avoid given 12# the wide range of targets Duktape supports) remains maintainable. 13# 14# Genconfig does *not* try to support all exotic platforms out there. 15# Instead, the goal is to allow the metadata to be extended, or to provide 16# a reasonable starting point for manual duk_config.h tweaking. 17# 18# For Duktape 1.3 release the main goal was to autogenerate a Duktape 1.2 19# compatible "autodetect" header from legacy snippets, with other outputs 20# being experimental. For Duktape 1.4 duk_config.h is always created from 21# modular sources. 22# 23 24import os 25import sys 26import re 27import json 28import yaml 29import optparse 30import tarfile 31import tempfile 32import atexit 33import shutil 34try: 35 from StringIO import StringIO 36except ImportError: 37 from io import StringIO 38 39# 40# Globals holding scanned metadata, helper snippets, etc 41# 42 43# Metadata to scan from config files. 44use_defs = None 45use_defs_list = None 46opt_defs = None 47opt_defs_list = None 48use_tags = None 49use_tags_list = None 50tags_meta = None 51required_use_meta_keys = [ 52 'define', 53 'introduced', 54 'default', 55 'tags', 56 'description' 57] 58allowed_use_meta_keys = [ 59 'define', 60 'feature_enables', 61 'feature_disables', 62 'feature_snippet', 63 'feature_no_default', 64 'related_feature_defines', 65 'introduced', 66 'deprecated', 67 'removed', 68 'unused', 69 'requires', 70 'conflicts', 71 'related', 72 'default', 73 'tags', 74 'description', 75] 76required_opt_meta_keys = [ 77 'define', 78 'introduced', 79 'tags', 80 'description' 81] 82allowed_opt_meta_keys = [ 83 'define', 84 'introduced', 85 'deprecated', 86 'removed', 87 'unused', 88 'requires', 89 'conflicts', 90 'related', 91 'tags', 92 'description' 93] 94 95# Preferred tag order for option documentation. 96doc_tag_order = [ 97 'portability', 98 'memory', 99 'lowmemory', 100 'ecmascript', 101 'execution', 102 'debugger', 103 'debug', 104 'development' 105] 106 107# Preferred tag order for generated C header files. 108header_tag_order = doc_tag_order 109 110# Helper headers snippets. 111helper_snippets = None 112 113# Assume these provides come from outside. 114assumed_provides = { 115 'DUK_SINGLE_FILE': True, # compiling Duktape from a single source file (duktape.c) version 116 'DUK_COMPILING_DUKTAPE': True, # compiling Duktape (not user application) 117 'DUK_CONFIG_H_INCLUDED': True, # artifact, include guard 118} 119 120# Platform files must provide at least these (additional checks 121# in validate_platform_file()). Fill-ins provide missing optionals. 122platform_required_provides = [ 123 'DUK_USE_OS_STRING' # must be #define'd 124] 125 126# Architecture files must provide at least these (additional checks 127# in validate_architecture_file()). Fill-ins provide missing optionals. 128architecture_required_provides = [ 129 'DUK_USE_ARCH_STRING' 130] 131 132# Compiler files must provide at least these (additional checks 133# in validate_compiler_file()). Fill-ins provide missing optionals. 134compiler_required_provides = [ 135 # Compilers need a lot of defines; missing defines are automatically 136 # filled in with defaults (which are mostly compiler independent), so 137 # the requires define list is not very large. 138 139 'DUK_USE_COMPILER_STRING', # must be #define'd 140 'DUK_USE_BRANCH_HINTS', # may be #undef'd, as long as provided 141 'DUK_USE_VARIADIC_MACROS', # may be #undef'd, as long as provided 142 'DUK_USE_UNION_INITIALIZERS' # may be #undef'd, as long as provided 143] 144 145# 146# Miscellaneous helpers 147# 148 149def get_auto_delete_tempdir(): 150 tmpdir = tempfile.mkdtemp(suffix='-genconfig') 151 def _f(dirname): 152 #print('Deleting temporary directory: %r' % dirname) 153 if os.path.isdir(dirname) and '-genconfig' in dirname: 154 shutil.rmtree(dirname) 155 atexit.register(_f, tmpdir) 156 return tmpdir 157 158def strip_comments_from_lines(lines): 159 # Not exact but close enough. Doesn't handle string literals etc, 160 # but these are not a concrete issue for scanning preprocessor 161 # #define references. 162 # 163 # Comment contents are stripped of any DUK_ prefixed text to avoid 164 # incorrect requires/provides detection. Other comment text is kept; 165 # in particular a "/* redefine */" comment must remain intact here. 166 # (The 'redefine' hack is not actively needed now.) 167 # 168 # Avoid Python 2.6 vs. Python 2.7 argument differences. 169 170 def censor(x): 171 return re.sub(re.compile('DUK_\w+', re.MULTILINE), 'xxx', x.group(0)) 172 173 tmp = '\n'.join(lines) 174 tmp = re.sub(re.compile('/\*.*?\*/', re.MULTILINE | re.DOTALL), censor, tmp) 175 tmp = re.sub(re.compile('//.*?$', re.MULTILINE), censor, tmp) 176 return tmp.split('\n') 177 178# Header snippet representation: lines, provides defines, requires defines. 179re_line_provides = re.compile(r'^#(?:define|undef)\s+(\w+).*$') 180re_line_requires = re.compile(r'(DUK_[A-Z0-9_]+)') # uppercase only, don't match DUK_USE_xxx for example 181class Snippet: 182 lines = None # lines of text and/or snippets 183 provides = None # map from define to 'True' for now 184 requires = None # map from define to 'True' for now 185 186 def __init__(self, lines, provides=None, requires=None, autoscan_requires=True, autoscan_provides=True): 187 self.lines = [] 188 if not isinstance(lines, list): 189 raise Exception('Snippet constructor must be a list (not e.g. a string): %s' % repr(lines)) 190 for line in lines: 191 if isinstance(line, str): 192 self.lines.append(line) 193 elif isinstance(line, unicode): 194 self.lines.append(line.encode('utf-8')) 195 else: 196 raise Exception('invalid line: %r' % line) 197 self.provides = {} 198 if provides is not None: 199 for k in provides.keys(): 200 self.provides[k] = True 201 self.requires = {} 202 if requires is not None: 203 for k in requires.keys(): 204 self.requires[k] = True 205 206 stripped_lines = strip_comments_from_lines(lines) 207 # for line in stripped_lines: print(line) 208 209 for line in stripped_lines: 210 # Careful with order, snippet may self-reference its own 211 # defines in which case there's no outward dependency. 212 # (This is not 100% because the order of require/provide 213 # matters and this is not handled now.) 214 # 215 # Also, some snippets may #undef/#define another define but 216 # they don't "provide" the define as such. Such redefinitions 217 # are marked "/* redefine */" in the snippets. They're best 218 # avoided (and not currently needed in Duktape 1.4.0). 219 220 if autoscan_provides: 221 m = re_line_provides.match(line) 222 if m is not None and '/* redefine */' not in line and \ 223 len(m.group(1)) > 0 and m.group(1)[-1] != '_': 224 # Don't allow e.g. DUK_USE_ which results from matching DUK_USE_xxx 225 #print('PROVIDES: %r' % m.group(1)) 226 self.provides[m.group(1)] = True 227 if autoscan_requires: 228 matches = re.findall(re_line_requires, line) 229 for m in matches: 230 if len(m) > 0 and m[-1] == '_': 231 # Don't allow e.g. DUK_USE_ which results from matching DUK_USE_xxx 232 pass 233 elif m[:7] == 'DUK_OPT': 234 # DUK_OPT_xxx always come from outside 235 pass 236 elif m[:7] == 'DUK_USE': 237 # DUK_USE_xxx are internal and they should not be 'requirements' 238 pass 239 elif self.provides.has_key(m): 240 # Snippet provides it's own require; omit 241 pass 242 else: 243 #print('REQUIRES: %r' % m) 244 self.requires[m] = True 245 246 def fromFile(cls, filename): 247 lines = [] 248 with open(filename, 'rb') as f: 249 for line in f: 250 if line[-1] == '\n': 251 line = line[:-1] 252 if line[:8] == '#snippet': 253 m = re.match(r'#snippet\s+"(.*?)"', line) 254 # XXX: better plumbing for lookup path 255 sub_fn = os.path.normpath(os.path.join(filename, '..', '..', 'header-snippets', m.group(1))) 256 #print('#snippet ' + sub_fn) 257 sn = Snippet.fromFile(sub_fn) 258 lines += sn.lines 259 else: 260 lines.append(line) 261 return Snippet(lines, autoscan_requires=True, autoscan_provides=True) 262 fromFile = classmethod(fromFile) 263 264 def merge(cls, snippets): 265 ret = Snippet([], [], []) 266 for s in snippets: 267 ret.lines += s.lines 268 for k in s.provides.keys(): 269 ret.provides[k] = True 270 for k in s.requires.keys(): 271 ret.requires[k] = True 272 return ret 273 merge = classmethod(merge) 274 275# Helper for building a text file from individual lines, injected files, etc. 276# Inserted values are converted to Snippets so that their provides/requires 277# information can be tracked. When non-C outputs are created, these will be 278# bogus but ignored. 279class FileBuilder: 280 vals = None # snippet list 281 base_dir = None 282 use_cpp_warning = False 283 284 def __init__(self, base_dir=None, use_cpp_warning=False): 285 self.vals = [] 286 self.base_dir = base_dir 287 self.use_cpp_warning = use_cpp_warning 288 289 def line(self, line): 290 self.vals.append(Snippet([ line ])) 291 292 def lines(self, lines): 293 if len(lines) > 0 and lines[-1] == '\n': 294 lines = lines[:-1] # strip last newline to avoid empty line 295 self.vals.append(Snippet(lines.split('\n'))) 296 297 def empty(self): 298 self.vals.append(Snippet([ '' ])) 299 300 def rst_heading(self, title, char, doubled=False): 301 tmp = [] 302 if doubled: 303 tmp.append(char * len(title)) 304 tmp.append(title) 305 tmp.append(char * len(title)) 306 self.vals.append(Snippet(tmp)) 307 308 def snippet_relative(self, fn): 309 sn = Snippet.fromFile(os.path.join(self.base_dir, fn)) 310 self.vals.append(sn) 311 return sn 312 313 def snippet_absolute(self, fn): 314 sn = Snippet.fromFile(fn) 315 self.vals.append(sn) 316 return sn 317 318 def cpp_error(self, msg): 319 # XXX: assume no newlines etc 320 self.vals.append(Snippet([ '#error %s' % msg ])) 321 322 def cpp_warning(self, msg): 323 # XXX: assume no newlines etc 324 # XXX: support compiler specific warning mechanisms 325 if self.use_cpp_warning: 326 # C preprocessor '#warning' is often supported 327 self.vals.append(Snippet([ '#warning %s' % msg ])) 328 else: 329 self.vals.append(Snippet([ '/* WARNING: %s */' % msg ])) 330 331 def cpp_warning_or_error(self, msg, is_error=True): 332 if is_error: 333 self.cpp_error(msg) 334 else: 335 self.cpp_warning(msg) 336 337 def chdr_comment_line(self, msg): 338 self.vals.append(Snippet([ '/* %s */' % msg ])) 339 340 def chdr_block_heading(self, msg): 341 lines = [] 342 lines.append('') 343 lines.append('/*') 344 lines.append(' * ' + msg) 345 lines.append(' */') 346 lines.append('') 347 self.vals.append(Snippet(lines)) 348 349 def join(self): 350 tmp = [] 351 for line in self.vals: 352 if not isinstance(line, object): 353 raise Exception('self.vals must be all snippets') 354 for x in line.lines: # x is a Snippet 355 tmp.append(x) 356 return '\n'.join(tmp) 357 358 def fill_dependencies_for_snippets(self, idx_deps): 359 fill_dependencies_for_snippets(self.vals, idx_deps) 360 361# Insert missing define dependencies into index 'idx_deps' repeatedly 362# until no unsatisfied dependencies exist. This is used to pull in 363# the required DUK_F_xxx helper defines without pulling them all in. 364# The resolution mechanism also ensures dependencies are pulled in the 365# correct order, i.e. DUK_F_xxx helpers may depend on each other (as 366# long as there are no circular dependencies). 367# 368# XXX: this can be simplified a lot 369def fill_dependencies_for_snippets(snippets, idx_deps): 370 # graph[A] = [ B, ... ] <-> B, ... provide something A requires. 371 graph = {} 372 snlist = [] 373 resolved = [] # for printing only 374 375 def add(sn): 376 if sn in snlist: 377 return # already present 378 snlist.append(sn) 379 380 to_add = [] 381 382 for k in sn.requires.keys(): 383 if assumed_provides.has_key(k): 384 continue 385 386 found = False 387 for sn2 in snlist: 388 if sn2.provides.has_key(k): 389 if not graph.has_key(sn): 390 graph[sn] = [] 391 graph[sn].append(sn2) 392 found = True # at least one other node provides 'k' 393 394 if not found: 395 #print('Resolving %r' % k) 396 resolved.append(k) 397 398 # Find a header snippet which provides the missing define. 399 # Some DUK_F_xxx files provide multiple defines, so we don't 400 # necessarily know the snippet filename here. 401 402 sn_req = None 403 for sn2 in helper_snippets: 404 if sn2.provides.has_key(k): 405 sn_req = sn2 406 break 407 if sn_req is None: 408 print(repr(sn.lines)) 409 raise Exception('cannot resolve missing require: %r' % k) 410 411 # Snippet may have further unresolved provides; add recursively 412 to_add.append(sn_req) 413 414 if not graph.has_key(sn): 415 graph[sn] = [] 416 graph[sn].append(sn_req) 417 418 for sn in to_add: 419 add(sn) 420 421 # Add original snippets. This fills in the required nodes 422 # recursively. 423 for sn in snippets: 424 add(sn) 425 426 # Figure out fill-ins by looking for snippets not in original 427 # list and without any unserialized dependent nodes. 428 handled = {} 429 for sn in snippets: 430 handled[sn] = True 431 keepgoing = True 432 while keepgoing: 433 keepgoing = False 434 for sn in snlist: 435 if handled.has_key(sn): 436 continue 437 438 success = True 439 for dep in graph.get(sn, []): 440 if not handled.has_key(dep): 441 success = False 442 if success: 443 snippets.insert(idx_deps, sn) 444 idx_deps += 1 445 snippets.insert(idx_deps, Snippet([ '' ])) 446 idx_deps += 1 447 handled[sn] = True 448 keepgoing = True 449 break 450 451 # XXX: detect and handle loops cleanly 452 for sn in snlist: 453 if handled.has_key(sn): 454 continue 455 print('UNHANDLED KEY') 456 print('PROVIDES: %r' % sn.provides) 457 print('REQUIRES: %r' % sn.requires) 458 print('\n'.join(sn.lines)) 459 460# print(repr(graph)) 461# print(repr(snlist)) 462# print('Resolved helper defines: %r' % resolved) 463 print('Resolved %d helper defines' % len(resolved)) 464 465def serialize_snippet_list(snippets): 466 ret = [] 467 468 emitted_provides = {} 469 for k in assumed_provides.keys(): 470 emitted_provides[k] = True 471 472 for sn in snippets: 473 ret += sn.lines 474 for k in sn.provides.keys(): 475 emitted_provides[k] = True 476 for k in sn.requires.keys(): 477 if not emitted_provides.has_key(k): 478 # XXX: conditional warning, happens in some normal cases 479 #print('WARNING: define %r required, not provided so far' % k) 480 pass 481 482 return '\n'.join(ret) 483 484def remove_duplicate_newlines(x): 485 ret = [] 486 empty = False 487 for line in x.split('\n'): 488 if line == '': 489 if empty: 490 pass 491 else: 492 ret.append(line) 493 empty = True 494 else: 495 empty = False 496 ret.append(line) 497 return '\n'.join(ret) 498 499def scan_use_defs(dirname): 500 global use_defs, use_defs_list 501 use_defs = {} 502 use_defs_list = [] 503 504 for fn in os.listdir(dirname): 505 root, ext = os.path.splitext(fn) 506 if not root.startswith('DUK_USE_') or ext != '.yaml': 507 continue 508 with open(os.path.join(dirname, fn), 'rb') as f: 509 doc = yaml.load(f) 510 if doc.get('example', False): 511 continue 512 if doc.get('unimplemented', False): 513 print('WARNING: unimplemented: %s' % fn) 514 continue 515 dockeys = doc.keys() 516 for k in dockeys: 517 if not k in allowed_use_meta_keys: 518 print('WARNING: unknown key %s in metadata file %s' % (k, fn)) 519 for k in required_use_meta_keys: 520 if not k in dockeys: 521 print('WARNING: missing key %s in metadata file %s' % (k, fn)) 522 523 use_defs[doc['define']] = doc 524 525 keys = use_defs.keys() 526 keys.sort() 527 for k in keys: 528 use_defs_list.append(use_defs[k]) 529 530def scan_opt_defs(dirname): 531 global opt_defs, opt_defs_list 532 opt_defs = {} 533 opt_defs_list = [] 534 535 for fn in os.listdir(dirname): 536 root, ext = os.path.splitext(fn) 537 if not root.startswith('DUK_OPT_') or ext != '.yaml': 538 continue 539 with open(os.path.join(dirname, fn), 'rb') as f: 540 doc = yaml.load(f) 541 if doc.get('example', False): 542 continue 543 if doc.get('unimplemented', False): 544 print('WARNING: unimplemented: %s' % fn) 545 continue 546 dockeys = doc.keys() 547 for k in dockeys: 548 if not k in allowed_opt_meta_keys: 549 print('WARNING: unknown key %s in metadata file %s' % (k, fn)) 550 for k in required_opt_meta_keys: 551 if not k in dockeys: 552 print('WARNING: missing key %s in metadata file %s' % (k, fn)) 553 554 opt_defs[doc['define']] = doc 555 556 keys = opt_defs.keys() 557 keys.sort() 558 for k in keys: 559 opt_defs_list.append(opt_defs[k]) 560 561def scan_use_tags(): 562 global use_tags, use_tags_list 563 use_tags = {} 564 565 for doc in use_defs_list: 566 for tag in doc.get('tags', []): 567 use_tags[tag] = True 568 569 use_tags_list = use_tags.keys() 570 use_tags_list.sort() 571 572def scan_tags_meta(filename): 573 global tags_meta 574 575 with open(filename, 'rb') as f: 576 tags_meta = yaml.load(f) 577 578def scan_helper_snippets(dirname): # DUK_F_xxx snippets 579 global helper_snippets 580 helper_snippets = [] 581 582 for fn in os.listdir(dirname): 583 if (fn[0:6] != 'DUK_F_'): 584 continue 585 #print('Autoscanning snippet: %s' % fn) 586 helper_snippets.append(Snippet.fromFile(os.path.join(dirname, fn))) 587 588def get_opt_defs(removed=True, deprecated=True, unused=True): 589 ret = [] 590 for doc in opt_defs_list: 591 # XXX: aware of target version 592 if removed == False and doc.get('removed', None) is not None: 593 continue 594 if deprecated == False and doc.get('deprecated', None) is not None: 595 continue 596 if unused == False and doc.get('unused', False) == True: 597 continue 598 ret.append(doc) 599 return ret 600 601def get_use_defs(removed=True, deprecated=True, unused=True): 602 ret = [] 603 for doc in use_defs_list: 604 # XXX: aware of target version 605 if removed == False and doc.get('removed', None) is not None: 606 continue 607 if deprecated == False and doc.get('deprecated', None) is not None: 608 continue 609 if unused == False and doc.get('unused', False) == True: 610 continue 611 ret.append(doc) 612 return ret 613 614def validate_platform_file(filename): 615 sn = Snippet.fromFile(filename) 616 617 for req in platform_required_provides: 618 if req not in sn.provides: 619 raise Exception('Platform %s is missing %s' % (filename, req)) 620 621 # DUK_SETJMP, DUK_LONGJMP, DUK_JMPBUF_TYPE are optional, fill-in 622 # provides if none defined. 623 624def validate_architecture_file(filename): 625 sn = Snippet.fromFile(filename) 626 627 for req in architecture_required_provides: 628 if req not in sn.provides: 629 raise Exception('Architecture %s is missing %s' % (filename, req)) 630 631 # Byte order and alignment defines are allowed to be missing, 632 # a fill-in will handle them. This is necessary because for 633 # some architecture byte order and/or alignment may vary between 634 # targets and may be software configurable. 635 636 # XXX: require automatic detection to be signaled? 637 # e.g. define DUK_USE_ALIGN_BY -1 638 # define DUK_USE_BYTE_ORDER -1 639 640def validate_compiler_file(filename): 641 sn = Snippet.fromFile(filename) 642 643 for req in compiler_required_provides: 644 if req not in sn.provides: 645 raise Exception('Compiler %s is missing %s' % (filename, req)) 646 647def get_tag_title(tag): 648 meta = tags_meta.get(tag, None) 649 if meta is None: 650 return tag 651 else: 652 return meta.get('title', tag) 653 654def get_tag_description(tag): 655 meta = tags_meta.get(tag, None) 656 if meta is None: 657 return None 658 else: 659 return meta.get('description', None) 660 661def get_tag_list_with_preferred_order(preferred): 662 tags = [] 663 664 # Preferred tags first 665 for tag in preferred: 666 if tag not in tags: 667 tags.append(tag) 668 669 # Remaining tags in alphabetic order 670 for tag in use_tags_list: 671 if tag not in tags: 672 tags.append(tag) 673 674 #print('Effective tag order: %r' % tags) 675 return tags 676 677def rst_format(text): 678 # XXX: placeholder, need to decide on markup conventions for YAML files 679 ret = [] 680 for para in text.split('\n'): 681 if para == '': 682 continue 683 ret.append(para) 684 return '\n\n'.join(ret) 685 686def cint_encode(x): 687 if not isinstance(x, (int, long)): 688 raise Exception('invalid input: %r' % x) 689 690 # XXX: unsigned constants? 691 if x > 0x7fffffff or x < -0x80000000: 692 return '%dLL' % x 693 elif x > 0x7fff or x < -0x8000: 694 return '%dL' % x 695 else: 696 return '%d' % x 697 698def cstr_encode(x): 699 if isinstance(x, unicode): 700 x = x.encode('utf-8') 701 if not isinstance(x, str): 702 raise Exception('invalid input: %r' % x) 703 704 res = '"' 705 term = False 706 has_terms = False 707 for c in x: 708 if term: 709 # Avoid ambiguous hex escapes 710 res += '" "' 711 term = False 712 has_terms = True 713 o = ord(c) 714 if o < 0x20 or o > 0x7e or c in '"\\': 715 res += '\\x%02x' % o 716 term = True 717 else: 718 res += c 719 res += '"' 720 721 if has_terms: 722 res = '(' + res + ')' 723 724 return res 725 726# 727# Autogeneration of option documentation 728# 729 730# Shared helper to generate DUK_OPT_xxx and DUK_USE_xxx documentation. 731# XXX: unfinished placeholder 732def generate_option_documentation(opts, opt_list=None, rst_title=None, include_default=False): 733 ret = FileBuilder(use_cpp_warning=opts.use_cpp_warning) 734 735 tags = get_tag_list_with_preferred_order(doc_tag_order) 736 737 title = rst_title 738 ret.rst_heading(title, '=', doubled=True) 739 740 handled = {} 741 742 for tag in tags: 743 first = True 744 745 for doc in opt_list: 746 if tag != doc['tags'][0]: # sort under primary tag 747 continue 748 dname = doc['define'] 749 desc = doc.get('description', None) 750 751 if handled.has_key(dname): 752 raise Exception('define handled twice, should not happen: %r' % dname) 753 handled[dname] = True 754 755 if first: # emit tag heading only if there are subsections 756 ret.empty() 757 ret.rst_heading(get_tag_title(tag), '=') 758 759 tag_desc = get_tag_description(tag) 760 if tag_desc is not None: 761 ret.empty() 762 ret.line(rst_format(tag_desc)) 763 first = False 764 765 ret.empty() 766 ret.rst_heading(dname, '-') 767 768 if desc is not None: 769 ret.empty() 770 ret.line(rst_format(desc)) 771 772 if include_default: 773 ret.empty() 774 ret.line('Default: ``' + str(doc['default']) + '``') # XXX: rst or other format 775 776 for doc in opt_list: 777 dname = doc['define'] 778 if not handled.has_key(dname): 779 raise Exception('unhandled define (maybe missing from tags list?): %r' % dname) 780 781 ret.empty() 782 return ret.join() 783 784def generate_feature_option_documentation(opts): 785 defs = get_opt_defs() 786 return generate_option_documentation(opts, opt_list=defs, rst_title='Duktape feature options', include_default=False) 787 788def generate_config_option_documentation(opts): 789 defs = get_use_defs() 790 return generate_option_documentation(opts, opt_list=defs, rst_title='Duktape config options', include_default=True) 791 792# 793# Helpers for duk_config.h generation 794# 795 796def get_forced_options(opts): 797 # Forced options, last occurrence wins (allows a base config file to be 798 # overridden by a more specific one). 799 forced_opts = {} 800 for val in opts.force_options_yaml: 801 doc = yaml.load(StringIO(val)) 802 for k in doc.keys(): 803 if use_defs.has_key(k): 804 pass # key is known 805 else: 806 print('WARNING: option override key %s not defined in metadata, ignoring' % k) 807 forced_opts[k] = doc[k] # shallow copy 808 809 if len(forced_opts.keys()) > 0: 810 print('Overrides: %s' % json.dumps(forced_opts)) 811 812 return forced_opts 813 814# Emit a default #define / #undef for an option based on 815# a config option metadata node (parsed YAML doc). 816def emit_default_from_config_meta(ret, doc, forced_opts, undef_done): 817 defname = doc['define'] 818 defval = forced_opts.get(defname, doc['default']) 819 820 # NOTE: careful with Python equality, e.g. "0 == False" is true. 821 if isinstance(defval, bool) and defval == True: 822 ret.line('#define ' + defname) 823 elif isinstance(defval, bool) and defval == False: 824 if not undef_done: 825 ret.line('#undef ' + defname) 826 else: 827 # Default value is false, and caller has emitted 828 # an unconditional #undef, so don't emit a duplicate 829 pass 830 elif isinstance(defval, (int, long)): 831 # integer value 832 ret.line('#define ' + defname + ' ' + cint_encode(defval)) 833 elif isinstance(defval, (str, unicode)): 834 # verbatim value 835 ret.line('#define ' + defname + ' ' + defval) 836 elif isinstance(defval, dict): 837 if defval.has_key('verbatim'): 838 # verbatim text for the entire line 839 ret.line(defval['verbatim']) 840 elif defval.has_key('string'): 841 # C string value 842 ret.line('#define ' + defname + ' ' + cstr_encode(defval['string'])) 843 else: 844 raise Exception('unsupported value for option %s: %r' % (defname, defval)) 845 else: 846 raise Exception('unsupported value for option %s: %r' % (defname, defval)) 847 848# Add a header snippet for detecting presence of DUK_OPT_xxx feature 849# options which will be removed in Duktape 2.x. 850def add_legacy_feature_option_checks(opts, ret): 851 ret.chdr_block_heading('Checks for legacy feature options (DUK_OPT_xxx)') 852 ret.empty() 853 854 defs = [] 855 for doc in get_opt_defs(): 856 if doc['define'] not in defs: 857 defs.append(doc['define']) 858 for doc in get_opt_defs(): 859 for dname in doc.get('related_feature_defines', []): 860 if dname not in defs: 861 defs.append(dname) 862 defs.sort() 863 864 for optname in defs: 865 suggested = [] 866 for doc in get_use_defs(): 867 if optname in doc.get('related_feature_defines', []): 868 suggested.append(doc['define']) 869 ret.line('#if defined(%s)' % optname) 870 if len(suggested) > 0: 871 ret.cpp_warning_or_error('unsupported legacy feature option %s used, consider options: %s' % (optname, ', '.join(suggested)), opts.sanity_strict) 872 else: 873 ret.cpp_warning_or_error('unsupported legacy feature option %s used' % optname, opts.sanity_strict) 874 ret.line('#endif') 875 876 ret.empty() 877 878# Add a header snippet for checking consistency of DUK_USE_xxx config 879# options, e.g. inconsistent options, invalid option values. 880def add_config_option_checks(opts, ret): 881 ret.chdr_block_heading('Checks for config option consistency (DUK_USE_xxx)') 882 ret.empty() 883 884 defs = [] 885 for doc in get_use_defs(): 886 if doc['define'] not in defs: 887 defs.append(doc['define']) 888 defs.sort() 889 890 for optname in defs: 891 doc = use_defs[optname] 892 dname = doc['define'] 893 894 # XXX: more checks 895 896 if doc.get('removed', None) is not None: 897 ret.line('#if defined(%s)' % dname) 898 ret.cpp_warning_or_error('unsupported config option used (option has been removed): %s' % dname, opts.sanity_strict) 899 ret.line('#endif') 900 elif doc.get('deprecated', None) is not None: 901 ret.line('#if defined(%s)' % dname) 902 ret.cpp_warning_or_error('unsupported config option used (option has been deprecated): %s' % dname, opts.sanity_strict) 903 ret.line('#endif') 904 905 for req in doc.get('requires', []): 906 ret.line('#if defined(%s) && !defined(%s)' % (dname, req)) 907 ret.cpp_warning_or_error('config option %s requires option %s (which is missing)' % (dname, req), opts.sanity_strict) 908 ret.line('#endif') 909 910 for req in doc.get('conflicts', []): 911 ret.line('#if defined(%s) && defined(%s)' % (dname, req)) 912 ret.cpp_warning_or_error('config option %s conflicts with option %s (which is also defined)' % (dname, req), opts.sanity_strict) 913 ret.line('#endif') 914 915 ret.empty() 916 ret.snippet_relative('cpp_exception_sanity.h.in') 917 ret.empty() 918 919# Add a header snippet for providing a __OVERRIDE_DEFINES__ section. 920def add_override_defines_section(opts, ret): 921 ret.empty() 922 ret.line('/*') 923 ret.line(' * You may add overriding #define/#undef directives below for') 924 ret.line(' * customization. You of course cannot un-#include or un-typedef') 925 ret.line(' * anything; these require direct changes above.') 926 ret.line(' */') 927 ret.empty() 928 ret.line('/* __OVERRIDE_DEFINES__ */') 929 ret.empty() 930 931# Add automatic DUK_OPT_XXX and DUK_OPT_NO_XXX handling for backwards 932# compatibility with Duktape 1.2 and before. 933def add_feature_option_handling(opts, ret, forced_opts, already_provided_keys): 934 ret.chdr_block_heading('Feature option handling') 935 936 for doc in get_use_defs(removed=False, deprecated=False, unused=False): 937 # If a related feature option exists, it can be used to force 938 # enable/disable the target feature. If neither feature option 939 # (DUK_OPT_xxx or DUK_OPT_NO_xxx) is given, revert to default. 940 941 config_define = doc['define'] 942 943 feature_define = None 944 feature_no_define = None 945 inverted = False 946 if doc.has_key('feature_enables'): 947 feature_define = doc['feature_enables'] 948 elif doc.has_key('feature_disables'): 949 feature_define = doc['feature_disables'] 950 inverted = True 951 else: 952 pass 953 954 if feature_define is not None: 955 feature_no_define = 'DUK_OPT_NO_' + feature_define[8:] 956 ret.line('#if defined(%s)' % feature_define) 957 if inverted: 958 ret.line('#undef %s' % config_define) 959 else: 960 ret.line('#define %s' % config_define) 961 ret.line('#elif defined(%s)' % feature_no_define) 962 if inverted: 963 ret.line('#define %s' % config_define) 964 else: 965 ret.line('#undef %s' % config_define) 966 ret.line('#else') 967 undef_done = False 968 969 # For some options like DUK_OPT_PACKED_TVAL the default comes 970 # from platform definition. 971 if doc.get('feature_no_default', False): 972 print('Skip default for option %s' % config_define) 973 ret.line('/* Already provided above */') 974 elif already_provided_keys.has_key(config_define): 975 # This is a fallback in case config option metadata is wrong. 976 print('Skip default for option %s (already provided but not flagged in metadata!)' % config_define) 977 ret.line('/* Already provided above */') 978 else: 979 emit_default_from_config_meta(ret, doc, forced_opts, undef_done) 980 ret.line('#endif') 981 elif doc.has_key('feature_snippet'): 982 ret.lines(doc['feature_snippet']) 983 else: 984 pass 985 986 ret.empty() 987 988 ret.empty() 989 990# Development time helper: add DUK_ACTIVE which provides a runtime C string 991# indicating what DUK_USE_xxx config options are active at run time. This 992# is useful in genconfig development so that one can e.g. diff the active 993# run time options of two headers. This is intended just for genconfig 994# development and is not available in normal headers. 995def add_duk_active_defines_macro(ret): 996 ret.chdr_block_heading('DUK_ACTIVE_DEFINES macro (development only)') 997 998 idx = 0 999 for doc in get_use_defs(): 1000 defname = doc['define'] 1001 1002 ret.line('#if defined(%s)' % defname) 1003 ret.line('#define DUK_ACTIVE_DEF%d " %s"' % (idx, defname)) 1004 ret.line('#else') 1005 ret.line('#define DUK_ACTIVE_DEF%d ""' % idx) 1006 ret.line('#endif') 1007 1008 idx += 1 1009 1010 tmp = [] 1011 for i in xrange(idx): 1012 tmp.append('DUK_ACTIVE_DEF%d' % i) 1013 1014 ret.line('#define DUK_ACTIVE_DEFINES ("Active: ["' + ' '.join(tmp) + ' " ]")') 1015 1016# 1017# duk_config.h generation 1018# 1019 1020# Generate a duk_config.h where platform, architecture, and compiler are 1021# all either autodetected or specified by user. 1022# 1023# Autodetection is based on a configured list of supported platforms, 1024# architectures, and compilers. For example, platforms.yaml defines the 1025# supported platforms and provides a helper define (DUK_F_xxx) to use for 1026# detecting that platform, and names the header snippet to provide the 1027# platform-specific definitions. Necessary dependencies (DUK_F_xxx) are 1028# automatically pulled in. 1029# 1030# Automatic "fill ins" are used for mandatory platform, architecture, and 1031# compiler defines which have a reasonable portable default. This reduces 1032# e.g. compiler-specific define count because there are a lot compiler 1033# macros which have a good default. 1034def generate_duk_config_header(opts, meta_dir): 1035 ret = FileBuilder(base_dir=os.path.join(meta_dir, 'header-snippets'), \ 1036 use_cpp_warning=opts.use_cpp_warning) 1037 1038 forced_opts = get_forced_options(opts) 1039 1040 platforms = None 1041 with open(os.path.join(meta_dir, 'platforms.yaml'), 'rb') as f: 1042 platforms = yaml.load(f) 1043 architectures = None 1044 with open(os.path.join(meta_dir, 'architectures.yaml'), 'rb') as f: 1045 architectures = yaml.load(f) 1046 compilers = None 1047 with open(os.path.join(meta_dir, 'compilers.yaml'), 'rb') as f: 1048 compilers = yaml.load(f) 1049 1050 # XXX: indicate feature option support, sanity checks enabled, etc 1051 # in general summary of options, perhaps genconfig command line? 1052 1053 ret.line('/*') 1054 ret.line(' * duk_config.h configuration header generated by genconfig.py.') 1055 ret.line(' *') 1056 ret.line(' * Git commit: %s' % opts.git_commit or 'n/a') 1057 ret.line(' * Git describe: %s' % opts.git_describe or 'n/a') 1058 ret.line(' * Git branch: %s' % opts.git_branch or 'n/a') 1059 ret.line(' *') 1060 if opts.platform is not None: 1061 ret.line(' * Platform: ' + opts.platform) 1062 else: 1063 ret.line(' * Supported platforms:') 1064 for platf in platforms['autodetect']: 1065 ret.line(' * - %s' % platf.get('name', platf.get('check'))) 1066 ret.line(' *') 1067 if opts.architecture is not None: 1068 ret.line(' * Architecture: ' + opts.architecture) 1069 else: 1070 ret.line(' * Supported architectures:') 1071 for arch in architectures['autodetect']: 1072 ret.line(' * - %s' % arch.get('name', arch.get('check'))) 1073 ret.line(' *') 1074 if opts.compiler is not None: 1075 ret.line(' * Compiler: ' + opts.compiler) 1076 else: 1077 ret.line(' * Supported compilers:') 1078 for comp in compilers['autodetect']: 1079 ret.line(' * - %s' % comp.get('name', comp.get('check'))) 1080 ret.line(' *') 1081 ret.line(' */') 1082 ret.empty() 1083 ret.line('#if !defined(DUK_CONFIG_H_INCLUDED)') 1084 ret.line('#define DUK_CONFIG_H_INCLUDED') 1085 ret.empty() 1086 1087 ret.chdr_block_heading('Intermediate helper defines') 1088 1089 # DLL build affects visibility attributes on Windows but unfortunately 1090 # cannot be detected automatically from preprocessor defines or such. 1091 # DLL build status is hidden behind DUK_F_DLL_BUILD and there are two 1092 # ways for that to be set: 1093 # 1094 # - Duktape 1.3 backwards compatible DUK_OPT_DLL_BUILD 1095 # - Genconfig --dll option 1096 ret.chdr_comment_line('DLL build detection') 1097 ret.line('#if defined(DUK_OPT_DLL_BUILD)') 1098 ret.line('#define DUK_F_DLL_BUILD') 1099 ret.line('#elif defined(DUK_OPT_NO_DLL_BUILD)') 1100 ret.line('#undef DUK_F_DLL_BUILD') 1101 ret.line('#else') 1102 if opts.dll: 1103 ret.line('/* configured for DLL build */') 1104 ret.line('#define DUK_F_DLL_BUILD') 1105 else: 1106 ret.line('/* not configured for DLL build */') 1107 ret.line('#undef DUK_F_DLL_BUILD') 1108 ret.line('#endif') 1109 ret.empty() 1110 1111 idx_deps = len(ret.vals) # position where to emit DUK_F_xxx dependencies 1112 1113 # Feature selection, system include, Date provider 1114 # Most #include statements are here 1115 1116 if opts.platform is not None: 1117 ret.chdr_block_heading('Platform: ' + opts.platform) 1118 1119 ret.snippet_relative('platform_cppextras.h.in') 1120 ret.empty() 1121 1122 # XXX: better to lookup platforms metadata 1123 include = 'platform_%s.h.in' % opts.platform 1124 abs_fn = os.path.join(meta_dir, 'platforms', include) 1125 validate_platform_file(abs_fn) 1126 ret.snippet_absolute(abs_fn) 1127 else: 1128 ret.chdr_block_heading('Platform autodetection') 1129 1130 ret.snippet_relative('platform_cppextras.h.in') 1131 ret.empty() 1132 1133 for idx, platf in enumerate(platforms['autodetect']): 1134 check = platf.get('check', None) 1135 include = platf['include'] 1136 abs_fn = os.path.join(meta_dir, 'platforms', include) 1137 1138 validate_platform_file(abs_fn) 1139 1140 if idx == 0: 1141 ret.line('#if defined(%s)' % check) 1142 else: 1143 if check is None: 1144 ret.line('#else') 1145 else: 1146 ret.line('#elif defined(%s)' % check) 1147 ret.line('/* --- %s --- */' % platf.get('name', '???')) 1148 ret.snippet_absolute(abs_fn) 1149 ret.line('#endif /* autodetect platform */') 1150 1151 ret.empty() 1152 ret.snippet_relative('platform_sharedincludes.h.in') 1153 ret.empty() 1154 1155 byteorder_provided_by_all = True # byteorder provided by all architecture files 1156 alignment_provided_by_all = True # alignment provided by all architecture files 1157 packedtval_provided_by_all = True # packed tval provided by all architecture files 1158 1159 if opts.architecture is not None: 1160 ret.chdr_block_heading('Architecture: ' + opts.architecture) 1161 1162 # XXX: better to lookup architectures metadata 1163 include = 'architecture_%s.h.in' % opts.architecture 1164 abs_fn = os.path.join(meta_dir, 'architectures', include) 1165 validate_architecture_file(abs_fn) 1166 sn = ret.snippet_absolute(abs_fn) 1167 if not sn.provides.get('DUK_USE_BYTEORDER', False): 1168 byteorder_provided_by_all = False 1169 if not sn.provides.get('DUK_USE_ALIGN_BY', False): 1170 alignment_provided_by_all = False 1171 if sn.provides.get('DUK_USE_PACKED_TVAL', False): 1172 ret.line('#define DUK_F_PACKED_TVAL_PROVIDED') # signal to fillin 1173 else: 1174 packedtval_provided_by_all = False 1175 else: 1176 ret.chdr_block_heading('Architecture autodetection') 1177 1178 for idx, arch in enumerate(architectures['autodetect']): 1179 check = arch.get('check', None) 1180 include = arch['include'] 1181 abs_fn = os.path.join(meta_dir, 'architectures', include) 1182 1183 validate_architecture_file(abs_fn) 1184 1185 if idx == 0: 1186 ret.line('#if defined(%s)' % check) 1187 else: 1188 if check is None: 1189 ret.line('#else') 1190 else: 1191 ret.line('#elif defined(%s)' % check) 1192 ret.line('/* --- %s --- */' % arch.get('name', '???')) 1193 sn = ret.snippet_absolute(abs_fn) 1194 if not sn.provides.get('DUK_USE_BYTEORDER', False): 1195 byteorder_provided_by_all = False 1196 if not sn.provides.get('DUK_USE_ALIGN_BY', False): 1197 alignment_provided_by_all = False 1198 if sn.provides.get('DUK_USE_PACKED_TVAL', False): 1199 ret.line('#define DUK_F_PACKED_TVAL_PROVIDED') # signal to fillin 1200 else: 1201 packedtval_provided_by_all = False 1202 ret.line('#endif /* autodetect architecture */') 1203 1204 ret.empty() 1205 1206 if opts.compiler is not None: 1207 ret.chdr_block_heading('Compiler: ' + opts.compiler) 1208 1209 # XXX: better to lookup compilers metadata 1210 include = 'compiler_%s.h.in' % opts.compiler 1211 abs_fn = os.path.join(meta_dir, 'compilers', include) 1212 validate_compiler_file(abs_fn) 1213 sn = ret.snippet_absolute(abs_fn) 1214 else: 1215 ret.chdr_block_heading('Compiler autodetection') 1216 1217 for idx, comp in enumerate(compilers['autodetect']): 1218 check = comp.get('check', None) 1219 include = comp['include'] 1220 abs_fn = os.path.join(meta_dir, 'compilers', include) 1221 1222 validate_compiler_file(abs_fn) 1223 1224 if idx == 0: 1225 ret.line('#if defined(%s)' % check) 1226 else: 1227 if check is None: 1228 ret.line('#else') 1229 else: 1230 ret.line('#elif defined(%s)' % check) 1231 ret.line('/* --- %s --- */' % comp.get('name', '???')) 1232 sn = ret.snippet_absolute(abs_fn) 1233 ret.line('#endif /* autodetect compiler */') 1234 1235 ret.empty() 1236 1237 # DUK_F_UCLIBC is special because __UCLIBC__ is provided by an #include 1238 # file, so the check must happen after platform includes. It'd be nice 1239 # for this to be automatic (e.g. DUK_F_UCLIBC.h.in could indicate the 1240 # dependency somehow). 1241 1242 ret.snippet_absolute(os.path.join(meta_dir, 'helper-snippets', 'DUK_F_UCLIBC.h.in')) 1243 ret.empty() 1244 1245 # XXX: platform/compiler could provide types; if so, need some signaling 1246 # defines like DUK_F_TYPEDEFS_DEFINED 1247 1248 # Number types 1249 if opts.c99_types_only: 1250 ret.snippet_relative('types1.h.in') 1251 ret.line('/* C99 types assumed */') 1252 ret.snippet_relative('types_c99.h.in') 1253 ret.empty() 1254 else: 1255 ret.snippet_relative('types1.h.in') 1256 ret.line('#if defined(DUK_F_HAVE_INTTYPES)') 1257 ret.line('/* C99 or compatible */') 1258 ret.empty() 1259 ret.snippet_relative('types_c99.h.in') 1260 ret.empty() 1261 ret.line('#else /* C99 types */') 1262 ret.empty() 1263 ret.snippet_relative('types_legacy.h.in') 1264 ret.empty() 1265 ret.line('#endif /* C99 types */') 1266 ret.empty() 1267 ret.snippet_relative('types2.h.in') 1268 ret.empty() 1269 ret.snippet_relative('64bitops.h.in') 1270 ret.empty() 1271 1272 # Platform, architecture, compiler fillins. These are after all 1273 # detection so that e.g. DUK_SPRINTF() can be provided by platform 1274 # or compiler before trying a fill-in. 1275 1276 ret.chdr_block_heading('Fill-ins for platform, architecture, and compiler') 1277 1278 ret.snippet_relative('platform_fillins.h.in') 1279 ret.empty() 1280 ret.snippet_relative('architecture_fillins.h.in') 1281 if not byteorder_provided_by_all: 1282 ret.empty() 1283 ret.snippet_relative('byteorder_fillin.h.in') 1284 if not alignment_provided_by_all: 1285 ret.empty() 1286 ret.snippet_relative('alignment_fillin.h.in') 1287 ret.empty() 1288 ret.snippet_relative('compiler_fillins.h.in') 1289 ret.empty() 1290 ret.snippet_relative('inline_workaround.h.in') 1291 ret.empty() 1292 if not packedtval_provided_by_all: 1293 ret.empty() 1294 ret.snippet_relative('packed_tval_fillin.h.in') 1295 1296 # Object layout 1297 ret.snippet_relative('object_layout.h.in') 1298 ret.empty() 1299 1300 # Detect and reject 'fast math' 1301 ret.snippet_relative('reject_fast_math.h.in') 1302 ret.empty() 1303 1304 # Automatic DUK_OPT_xxx feature option handling 1305 if opts.support_feature_options: 1306 print('Autogenerating feature option (DUK_OPT_xxx) support') 1307 tmp = Snippet(ret.join().split('\n')) 1308 add_feature_option_handling(opts, ret, forced_opts, tmp.provides) 1309 1310 # Emit forced options. If a corresponding option is already defined 1311 # by a snippet above, #undef it first. 1312 1313 tmp = Snippet(ret.join().split('\n')) 1314 first_forced = True 1315 for doc in get_use_defs(removed=not opts.omit_removed_config_options, 1316 deprecated=not opts.omit_deprecated_config_options, 1317 unused=not opts.omit_unused_config_options): 1318 defname = doc['define'] 1319 1320 if not forced_opts.has_key(defname): 1321 continue 1322 1323 if not doc.has_key('default'): 1324 raise Exception('config option %s is missing default value' % defname) 1325 1326 if first_forced: 1327 ret.chdr_block_heading('Forced options') 1328 first_forced = False 1329 1330 undef_done = False 1331 if tmp.provides.has_key(defname): 1332 ret.line('#undef ' + defname) 1333 undef_done = True 1334 1335 emit_default_from_config_meta(ret, doc, forced_opts, undef_done) 1336 1337 ret.empty() 1338 1339 # If manually-edited snippets don't #define or #undef a certain 1340 # config option, emit a default value here. This is useful to 1341 # fill-in for new config options not covered by manual snippets 1342 # (which is intentional). 1343 1344 tmp = Snippet(ret.join().split('\n')) 1345 need = {} 1346 for doc in get_use_defs(removed=False): 1347 need[doc['define']] = True 1348 for k in tmp.provides.keys(): 1349 if need.has_key(k): 1350 del need[k] 1351 need_keys = sorted(need.keys()) 1352 1353 if len(need_keys) > 0: 1354 ret.chdr_block_heading('Autogenerated defaults') 1355 1356 for k in need_keys: 1357 #print('config option %s not covered by manual snippets, emitting default automatically' % k) 1358 emit_default_from_config_meta(ret, use_defs[k], {}, False) 1359 1360 ret.empty() 1361 1362 ret.snippet_relative('custom_header.h.in') 1363 ret.empty() 1364 1365 if len(opts.fixup_header_lines) > 0: 1366 ret.chdr_block_heading('Fixups') 1367 for line in opts.fixup_header_lines: 1368 ret.line(line) 1369 ret.empty() 1370 1371 add_override_defines_section(opts, ret) 1372 1373 # Date provider snippet is after custom header and overrides, so that 1374 # the user may define e.g. DUK_USE_DATE_NOW_GETTIMEOFDAY in their 1375 # custom header. 1376 ret.snippet_relative('date_provider.h.in') 1377 ret.empty() 1378 1379 ret.fill_dependencies_for_snippets(idx_deps) 1380 1381 if opts.emit_legacy_feature_check: 1382 add_legacy_feature_option_checks(opts, ret) 1383 if opts.emit_config_sanity_check: 1384 add_config_option_checks(opts, ret) 1385 if opts.add_active_defines_macro: 1386 add_duk_active_defines_macro(ret) 1387 1388 # Derived defines (DUK_USE_INTEGER_LE, etc) from DUK_USE_BYTEORDER. 1389 # Duktape internals currently rely on the derived defines. This is 1390 # after sanity checks because the derived defines are marked removed. 1391 ret.snippet_relative('byteorder_derived.h.in') 1392 ret.empty() 1393 1394 ret.line('#endif /* DUK_CONFIG_H_INCLUDED */') 1395 ret.empty() # for trailing newline 1396 return remove_duplicate_newlines(ret.join()) 1397 1398# 1399# Main 1400# 1401 1402def main(): 1403 # Forced options from multiple sources are gathered into a shared list 1404 # so that the override order remains the same as on the command line. 1405 force_options_yaml = [] 1406 def add_force_option_yaml(option, opt, value, parser): 1407 # XXX: check that YAML parses 1408 force_options_yaml.append(value) 1409 def add_force_option_file(option, opt, value, parser): 1410 # XXX: check that YAML parses 1411 with open(value, 'rb') as f: 1412 force_options_yaml.append(f.read()) 1413 def add_force_option_define(option, opt, value, parser): 1414 tmp = value.split('=') 1415 if len(tmp) == 1: 1416 doc = { tmp[0]: True } 1417 elif len(tmp) == 2: 1418 doc = { tmp[0]: tmp[1] } 1419 else: 1420 raise Exception('invalid option value: %r' % value) 1421 force_options_yaml.append(yaml.safe_dump(doc)) 1422 def add_force_option_undefine(option, opt, value, parser): 1423 tmp = value.split('=') 1424 if len(tmp) == 1: 1425 doc = { tmp[0]: False } 1426 else: 1427 raise Exception('invalid option value: %r' % value) 1428 force_options_yaml.append(yaml.safe_dump(doc)) 1429 1430 fixup_header_lines = [] 1431 def add_fixup_header_line(option, opt, value, parser): 1432 fixup_header_lines.append(value) 1433 def add_fixup_header_file(option, opt, value, parser): 1434 with open(value, 'rb') as f: 1435 for line in f: 1436 if line[-1] == '\n': 1437 line = line[:-1] 1438 fixup_header_lines.append(line) 1439 1440 commands = [ 1441 'duk-config-header', 1442 'feature-documentation', 1443 'config-documentation' 1444 ] 1445 parser = optparse.OptionParser( 1446 usage='Usage: %prog [options] COMMAND', 1447 description='Generate a duk_config.h or config option documentation based on config metadata.', 1448 epilog='COMMAND can be one of: ' + ', '.join(commands) + '.' 1449 ) 1450 1451 parser.add_option('--metadata', dest='metadata', default=None, help='metadata directory or metadata tar.gz file') 1452 parser.add_option('--output', dest='output', default=None, help='output filename for C header or RST documentation file') 1453 parser.add_option('--platform', dest='platform', default=None, help='platform (for "barebones-header" command)') 1454 parser.add_option('--compiler', dest='compiler', default=None, help='compiler (for "barebones-header" command)') 1455 parser.add_option('--architecture', dest='architecture', default=None, help='architecture (for "barebones-header" command)') 1456 parser.add_option('--c99-types-only', dest='c99_types_only', action='store_true', default=False, help='assume C99 types, no legacy type detection') 1457 parser.add_option('--dll', dest='dll', action='store_true', default=False, help='dll build of Duktape, affects symbol visibility macros especially on Windows') 1458 parser.add_option('--support-feature-options', dest='support_feature_options', action='store_true', default=False, help='support DUK_OPT_xxx feature options in duk_config.h') 1459 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)') 1460 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_OPT_xxx)') 1461 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') 1462 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') 1463 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') 1464 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') 1465 parser.add_option('--define', type='string', 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"') 1466 parser.add_option('-D', type='string', 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"') 1467 parser.add_option('--undefine', type='string', 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"') 1468 parser.add_option('-U', type='string', 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"') 1469 parser.add_option('--option-yaml', type='string', 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")') 1470 parser.add_option('--option-file', type='string', dest='force_options_yaml', action='callback', callback=add_force_option_file, default=force_options_yaml, help='YAML file(s) providing config option overrides') 1471 parser.add_option('--fixup-file', type='string', 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') 1472 parser.add_option('--fixup-line', type='string', 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")') 1473 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') 1474 parser.add_option('--use-cpp-warning', dest='use_cpp_warning', action='store_true', default=False, help='emit a (non-portable) #warning when appropriate') 1475 parser.add_option('--git-commit', dest='git_commit', default=None, help='git commit hash to be included in header comments') 1476 parser.add_option('--git-describe', dest='git_describe', default=None, help='git describe string to be included in header comments') 1477 parser.add_option('--git-branch', dest='git_branch', default=None, help='git branch string to be included in header comments') 1478 (opts, args) = parser.parse_args() 1479 1480 meta_dir = opts.metadata 1481 if opts.metadata is None: 1482 if os.path.isfile(os.path.join('.', 'genconfig_metadata.tar.gz')): 1483 opts.metadata = 'genconfig_metadata.tar.gz' 1484 elif os.path.isdir(os.path.join('.', 'config-options')): 1485 opts.metadata = '.' 1486 1487 if opts.metadata is not None and os.path.isdir(opts.metadata): 1488 meta_dir = opts.metadata 1489 metadata_src_text = 'Using metadata directory: %r' % meta_dir 1490 elif opts.metadata is not None and os.path.isfile(opts.metadata) and tarfile.is_tarfile(opts.metadata): 1491 meta_dir = get_auto_delete_tempdir() 1492 tar = tarfile.open(name=opts.metadata, mode='r:*') 1493 tar.extractall(path=meta_dir) 1494 metadata_src_text = 'Using metadata tar file %r, unpacked to directory: %r' % (opts.metadata, meta_dir) 1495 else: 1496 raise Exception('metadata source must be a directory or a tar.gz file') 1497 1498 scan_helper_snippets(os.path.join(meta_dir, 'helper-snippets')) 1499 scan_use_defs(os.path.join(meta_dir, 'config-options')) 1500 scan_opt_defs(os.path.join(meta_dir, 'feature-options')) 1501 scan_use_tags() 1502 scan_tags_meta(os.path.join(meta_dir, 'tags.yaml')) 1503 print('%s, scanned %d DUK_OPT_xxx, %d DUK_USE_XXX, %d helper snippets' % \ 1504 (metadata_src_text, len(opt_defs.keys()), len(use_defs.keys()), len(helper_snippets))) 1505 #print('Tags: %r' % use_tags_list) 1506 1507 if len(args) == 0: 1508 raise Exception('missing command') 1509 cmd = args[0] 1510 1511 # Compatibility with Duktape 1.3 1512 if cmd == 'autodetect-header': 1513 cmd = 'duk-config-header' 1514 if cmd == 'barebones-header': 1515 cmd = 'duk-config-header' 1516 1517 if cmd == 'duk-config-header': 1518 # Generate a duk_config.h header with platform, compiler, and 1519 # architecture either autodetected (default) or specified by 1520 # user. Support for autogenerated DUK_OPT_xxx flags is also 1521 # selected by user. 1522 result = generate_duk_config_header(opts, meta_dir) 1523 with open(opts.output, 'wb') as f: 1524 f.write(result) 1525 elif cmd == 'feature-documentation': 1526 result = generate_feature_option_documentation(opts) 1527 with open(opts.output, 'wb') as f: 1528 f.write(result) 1529 elif cmd == 'config-documentation': 1530 result = generate_config_option_documentation(opts) 1531 with open(opts.output, 'wb') as f: 1532 f.write(result) 1533 else: 1534 raise Exception('invalid command: %r' % cmd) 1535 1536if __name__ == '__main__': 1537 main() 1538