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