#!/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 ' %d LL ' % x
elif x > 0x7fff or x < - 0x8000 :
return ' %d L ' % 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 ( )