blob: 9c996481ac8d23e5e9dc3653ff0afc9aeae9be5b [file] [log] [blame]
#!/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 1.x feature options (DUK_OPT_xxx)
# - option documentation for Duktape 1.x/2.x 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.
#
# For Duktape 1.3 release the main goal was to autogenerate a Duktape 1.2
# compatible "autodetect" header from legacy snippets, with other outputs
# being experimental. For Duktape 1.4 duk_config.h is always created from
# modular sources.
#
import os
import sys
import re
import json
import yaml
import optparse
import tarfile
import tempfile
import atexit
import shutil
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',
'feature_enables',
'feature_disables',
'feature_snippet',
'feature_no_default',
'related_feature_defines',
'introduced',
'deprecated',
'removed',
'unused',
'requires',
'conflicts',
'related',
'default',
'tags',
'description',
]
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):
#print('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: print(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
#print('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':
# 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:
#print('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)))
#print('#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:
#print('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:
print(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
print('UNHANDLED KEY')
print('PROVIDES: %r' % sn.provides)
print('REQUIRES: %r' % sn.requires)
print('\n'.join(sn.lines))
# print(repr(graph))
# print(repr(snlist))
# print('Resolved helper defines: %r' % resolved)
print('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
#print('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):
print('WARNING: unimplemented: %s' % fn)
continue
dockeys = doc.keys()
for k in dockeys:
if not k in allowed_use_meta_keys:
print('WARNING: unknown key %s in metadata file %s' % (k, fn))
for k in required_use_meta_keys:
if not k in dockeys:
print('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):
print('WARNING: unimplemented: %s' % fn)
continue
dockeys = doc.keys()
for k in dockeys:
if not k in allowed_opt_meta_keys:
print('WARNING: unknown key %s in metadata file %s' % (k, fn))
for k in required_opt_meta_keys:
if not k in dockeys:
print('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
#print('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)
#print('Effective tag order: %r' % tags)
return tags
def rst_format(text):
# XXX: placeholder, need to decide on markup conventions for YAML files
ret = []
for para in text.split('\n'):
if para == '':
continue
ret.append(para)
return '\n\n'.join(ret)
def cint_encode(x):
if not isinstance(x, (int, long)):
raise Exception('invalid input: %r' % x)
# XXX: unsigned constants?
if x > 0x7fffffff or x < -0x80000000:
return '%dLL' % x
elif x > 0x7fff or x < -0x8000:
return '%dL' % x
else:
return '%d' % x
def cstr_encode(x):
if isinstance(x, unicode):
x = x.encode('utf-8')
if not isinstance(x, str):
raise Exception('invalid input: %r' % x)
res = '"'
term = False
has_terms = False
for c in x:
if term:
# Avoid ambiguous hex escapes
res += '" "'
term = False
has_terms = True
o = ord(c)
if o < 0x20 or o > 0x7e or c in '"\\':
res += '\\x%02x' % o
term = True
else:
res += c
res += '"'
if has_terms:
res = '(' + res + ')'
return res
#
# Autogeneration of option documentation
#
# Shared helper to generate DUK_OPT_xxx and 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_feature_option_documentation(opts):
defs = get_opt_defs()
return generate_option_documentation(opts, opt_list=defs, rst_title='Duktape feature options', include_default=False)
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:
print('WARNING: option override key %s not defined in metadata, ignoring' % k)
forced_opts[k] = doc[k] # shallow copy
if len(forced_opts.keys()) > 0:
print('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):
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)
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
elif isinstance(defval, (int, long)):
# integer value
ret.line('#define ' + defname + ' ' + cint_encode(defval))
elif isinstance(defval, (str, unicode)):
# verbatim value
ret.line('#define ' + defname + ' ' + defval)
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))
else:
raise Exception('unsupported value for option %s: %r' % (defname, defval))
# Add a header snippet for detecting presence of DUK_OPT_xxx feature
# options which will be removed in Duktape 2.x.
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'])
for doc in get_opt_defs():
for dname in doc.get('related_feature_defines', []):
if dname not in defs:
defs.append(dname)
defs.sort()
for optname in defs:
suggested = []
for doc in get_use_defs():
if optname in doc.get('related_feature_defines', []):
suggested.append(doc['define'])
ret.line('#if defined(%s)' % optname)
if len(suggested) > 0:
ret.cpp_warning_or_error('unsupported legacy feature option %s used, consider options: %s' % (optname, ', '.join(suggested)), opts.sanity_strict)
else:
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 automatic DUK_OPT_XXX and DUK_OPT_NO_XXX handling for backwards
# compatibility with Duktape 1.2 and before.
def add_feature_option_handling(opts, ret, forced_opts, already_provided_keys):
ret.chdr_block_heading('Feature option handling')
for doc in get_use_defs(removed=False, deprecated=False, unused=False):
# If a related feature option exists, it can be used to force
# enable/disable the target feature. If neither feature option
# (DUK_OPT_xxx or DUK_OPT_NO_xxx) is given, revert to default.
config_define = doc['define']
feature_define = None
feature_no_define = None
inverted = False
if doc.has_key('feature_enables'):
feature_define = doc['feature_enables']
elif doc.has_key('feature_disables'):
feature_define = doc['feature_disables']
inverted = True
else:
pass
if feature_define is not None:
feature_no_define = 'DUK_OPT_NO_' + feature_define[8:]
ret.line('#if defined(%s)' % feature_define)
if inverted:
ret.line('#undef %s' % config_define)
else:
ret.line('#define %s' % config_define)
ret.line('#elif defined(%s)' % feature_no_define)
if inverted:
ret.line('#define %s' % config_define)
else:
ret.line('#undef %s' % config_define)
ret.line('#else')
undef_done = False
# For some options like DUK_OPT_PACKED_TVAL the default comes
# from platform definition.
if doc.get('feature_no_default', False):
print('Skip default for option %s' % config_define)
ret.line('/* Already provided above */')
elif already_provided_keys.has_key(config_define):
# This is a fallback in case config option metadata is wrong.
print('Skip default for option %s (already provided but not flagged in metadata!)' % config_define)
ret.line('/* Already provided above */')
else:
emit_default_from_config_meta(ret, doc, forced_opts, undef_done)
ret.line('#endif')
elif doc.has_key('feature_snippet'):
ret.lines(doc['feature_snippet'])
else:
pass
ret.empty()
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)
forced_opts = get_forced_options(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
# ways for that to be set:
#
# - Duktape 1.3 backwards compatible DUK_OPT_DLL_BUILD
# - Genconfig --dll option
ret.chdr_comment_line('DLL build detection')
ret.line('#if defined(DUK_OPT_DLL_BUILD)')
ret.line('#define DUK_F_DLL_BUILD')
ret.line('#elif defined(DUK_OPT_NO_DLL_BUILD)')
ret.line('#undef DUK_F_DLL_BUILD')
ret.line('#else')
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.line('#endif')
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()
# Automatic DUK_OPT_xxx feature option handling
if opts.support_feature_options:
print('Autogenerating feature option (DUK_OPT_xxx) support')
tmp = Snippet(ret.join().split('\n'))
add_feature_option_handling(opts, ret, forced_opts, tmp.provides)
# 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)
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:
#print('config option %s not covered by manual snippets, emitting default automatically' % k)
emit_default_from_config_meta(ret, use_defs[k], {}, False)
ret.empty()
ret.snippet_relative('custom_header.h.in')
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)
# 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())
#
# Main
#
def main():
# 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)
commands = [
'duk-config-header',
'feature-documentation',
'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) + '.'
)
parser.add_option('--metadata', dest='metadata', default=None, help='metadata directory or metadata tar.gz file')
parser.add_option('--output', dest='output', default=None, help='output filename for C header or RST documentation file')
parser.add_option('--platform', dest='platform', default=None, help='platform (for "barebones-header" command)')
parser.add_option('--compiler', dest='compiler', default=None, help='compiler (for "barebones-header" command)')
parser.add_option('--architecture', dest='architecture', default=None, help='architecture (for "barebones-header" command)')
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='support DUK_OPT_xxx feature options in duk_config.h')
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_OPT_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', 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', 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', 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', 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', 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', 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', 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', 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')
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')
(opts, args) = parser.parse_args()
meta_dir = opts.metadata
if opts.metadata is None:
if os.path.isfile(os.path.join('.', 'genconfig_metadata.tar.gz')):
opts.metadata = 'genconfig_metadata.tar.gz'
elif os.path.isdir(os.path.join('.', 'config-options')):
opts.metadata = '.'
if opts.metadata is not None and os.path.isdir(opts.metadata):
meta_dir = opts.metadata
metadata_src_text = 'Using metadata directory: %r' % meta_dir
elif opts.metadata is not None and os.path.isfile(opts.metadata) and tarfile.is_tarfile(opts.metadata):
meta_dir = get_auto_delete_tempdir()
tar = tarfile.open(name=opts.metadata, mode='r:*')
tar.extractall(path=meta_dir)
metadata_src_text = 'Using metadata tar file %r, unpacked to directory: %r' % (opts.metadata, meta_dir)
else:
raise Exception('metadata source must be a directory or a tar.gz file')
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'))
print('%s, scanned %d DUK_OPT_xxx, %d DUK_USE_XXX, %d helper snippets' % \
(metadata_src_text, len(opt_defs.keys()), len(use_defs.keys()), len(helper_snippets)))
#print('Tags: %r' % use_tags_list)
if len(args) == 0:
raise Exception('missing command')
cmd = args[0]
# Compatibility with Duktape 1.3
if cmd == 'autodetect-header':
cmd = 'duk-config-header'
if cmd == 'barebones-header':
cmd = 'duk-config-header'
if cmd == 'duk-config-header':
# Generate a duk_config.h header with platform, compiler, and
# architecture either autodetected (default) or specified by
# user. Support for autogenerated DUK_OPT_xxx flags is also
# selected by user.
result = generate_duk_config_header(opts, meta_dir)
with open(opts.output, 'wb') as f:
f.write(result)
elif cmd == 'feature-documentation':
result = generate_feature_option_documentation(opts)
with open(opts.output, 'wb') as f:
f.write(result)
elif cmd == 'config-documentation':
result = generate_config_option_documentation(opts)
with open(opts.output, 'wb') as f:
f.write(result)
else:
raise Exception('invalid command: %r' % cmd)
if __name__ == '__main__':
main()