]>
Commit | Line | Data |
---|---|---|
7c673cae FG |
1 | #!/usr/bin/python |
2 | # | |
3 | # Process Duktape option metadata and produce various useful outputs: | |
4 | # | |
5 | # - duk_config.h matching Duktape 1.x feature option model (DUK_OPT_xxx) | |
6 | # - duk_config.h for a selected platform, compiler, forced options, 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 | # NOTE: For Duktape 1.3 release the main goal is to autogenerate a Duktape | |
19 | # 1.2 compatible "autodetect" header from snippets. Other outputs are still | |
20 | # experimental. | |
21 | # | |
22 | ||
23 | import os | |
24 | import sys | |
25 | import re | |
26 | import json | |
27 | import yaml | |
28 | import optparse | |
29 | import tarfile | |
30 | import tempfile | |
31 | import atexit | |
32 | import shutil | |
33 | import StringIO | |
34 | ||
35 | # | |
36 | # Globals holding scanned metadata, helper snippets, etc | |
37 | # | |
38 | ||
39 | # Metadata to scan from config files. | |
40 | use_defs = None | |
41 | use_defs_list = None | |
42 | opt_defs = None | |
43 | opt_defs_list = None | |
44 | use_tags = None | |
45 | use_tags_list = None | |
46 | tags_meta = None | |
47 | required_use_meta_keys = [ | |
48 | 'define', | |
49 | 'introduced', | |
50 | 'default', | |
51 | 'tags', | |
52 | 'description' | |
53 | ] | |
54 | allowed_use_meta_keys = [ | |
55 | 'define', | |
56 | 'feature_enables', | |
57 | 'feature_disables', | |
58 | 'feature_snippet', | |
59 | 'related_feature_defines', | |
60 | 'introduced', | |
61 | 'deprecated', | |
62 | 'removed', | |
63 | 'unused', | |
64 | 'requires', | |
65 | 'conflicts', | |
66 | 'related', | |
67 | 'default', | |
68 | 'tags', | |
69 | 'description', | |
70 | ] | |
71 | required_opt_meta_keys = [ | |
72 | 'define', | |
73 | 'introduced', | |
74 | 'tags', | |
75 | 'description' | |
76 | ] | |
77 | allowed_opt_meta_keys = [ | |
78 | 'define', | |
79 | 'introduced', | |
80 | 'deprecated', | |
81 | 'removed', | |
82 | 'unused', | |
83 | 'requires', | |
84 | 'conflicts', | |
85 | 'related', | |
86 | 'tags', | |
87 | 'description' | |
88 | ] | |
89 | ||
90 | # Preferred tag order for option documentation. | |
91 | doc_tag_order = [ | |
92 | 'portability', | |
93 | 'memory', | |
94 | 'lowmemory', | |
95 | 'ecmascript', | |
96 | 'execution', | |
97 | 'debugger', | |
98 | 'debug', | |
99 | 'development' | |
100 | ] | |
101 | ||
102 | # Preferred tag order for generated C header files. | |
103 | header_tag_order = doc_tag_order | |
104 | ||
105 | # Helper headers snippets. | |
106 | helper_snippets = None | |
107 | ||
108 | # Assume these provides come from outside. | |
109 | assumed_provides = { | |
110 | 'DUK_SINGLE_FILE': True, # compiling Duktape from a single source file (duktape.c) version | |
111 | 'DUK_COMPILING_DUKTAPE': True, # compiling Duktape (not user application) | |
112 | 'DUK_CONFIG_H_INCLUDED': True, # artifact, include guard | |
113 | } | |
114 | ||
115 | # Platform files must provide at least these (additional checks | |
116 | # in validate_platform_file()). | |
117 | platform_required_provides = [ | |
118 | 'DUK_USE_OS_STRING', | |
119 | 'DUK_SETJMP', 'DUK_LONGJMP', | |
120 | ] | |
121 | ||
122 | # Architecture files must provide at least these (additional checks | |
123 | # in validate_architecture_file()). | |
124 | architecture_required_provides = [ | |
125 | 'DUK_USE_ARCH_STRING', | |
126 | 'DUK_USE_ALIGN_BY', 'DUK_USE_UNALIGNED_ACCESSES_POSSIBLE', 'DUK_USE_HASHBYTES_UNALIGNED_U32_ACCESS', | |
127 | 'DUK_USE_PACKED_TVAL', 'DUK_USE_PACKED_TVAL_POSSIBLE' | |
128 | ] | |
129 | ||
130 | # Compiler files must provide at least these (additional checks | |
131 | # in validate_compiler_file()). | |
132 | compiler_required_provides = [ | |
133 | # XXX: incomplete, maybe a generic fill-in for missing stuff because | |
134 | # there's quite a lot of required compiler defines. | |
135 | ||
136 | 'DUK_USE_COMPILER_STRING', | |
137 | ||
138 | 'DUK_EXTERNAL_DECL', 'DUK_EXTERNAL', | |
139 | 'DUK_INTERNAL_DECL', 'DUK_INTERNAL', | |
140 | 'DUK_LOCAL_DECL', 'DUK_LOCAL', | |
141 | ||
142 | 'DUK_FILE_MACRO', 'DUK_LINE_MACRO', 'DUK_FUNC_MACRO' | |
143 | ] | |
144 | ||
145 | # | |
146 | # Miscellaneous helpers | |
147 | # | |
148 | ||
149 | def 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 | ||
158 | def 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 | # | |
167 | # Avoid Python 2.6 vs. Python 2.7 argument differences. | |
168 | ||
169 | def censor(x): | |
170 | return re.sub(re.compile('DUK_\w+', re.MULTILINE), 'xxx', x.group(0)) | |
171 | ||
172 | tmp = '\n'.join(lines) | |
173 | tmp = re.sub(re.compile('/\*.*?\*/', re.MULTILINE | re.DOTALL), censor, tmp) | |
174 | tmp = re.sub(re.compile('//.*?$', re.MULTILINE), censor, tmp) | |
175 | return tmp.split('\n') | |
176 | ||
177 | # Header snippet representation: lines, provides defines, requires defines. | |
178 | re_line_provides = re.compile(r'^#(?:define|undef)\s+(\w+).*$') | |
179 | re_line_requires = re.compile(r'(DUK_[A-Z0-9_]+)') # uppercase only, don't match DUK_USE_xxx for example | |
180 | class Snippet: | |
181 | lines = None # lines of text and/or snippets | |
182 | provides = None # map from define to 'True' for now | |
183 | requires = None # map from define to 'True' for now | |
184 | ||
185 | def __init__(self, lines, provides=None, requires=None, autoscan_requires=True, autoscan_provides=True): | |
186 | self.lines = [] | |
187 | if not isinstance(lines, list): | |
188 | raise Exception('Snippet constructor must be a list (not e.g. a string): %s' % repr(lines)) | |
189 | for line in lines: | |
190 | if isinstance(line, str): | |
191 | self.lines.append(line) | |
192 | elif isinstance(line, unicode): | |
193 | self.lines.append(line.encode('utf-8')) | |
194 | else: | |
195 | raise Exception('invalid line: %r' % line) | |
196 | self.provides = {} | |
197 | if provides is not None: | |
198 | for k in provides.keys(): | |
199 | self.provides[k] = True | |
200 | self.requires = {} | |
201 | if requires is not None: | |
202 | for k in requires.keys(): | |
203 | self.requires[k] = True | |
204 | ||
205 | stripped_lines = strip_comments_from_lines(lines) | |
206 | # for line in stripped_lines: print(line) | |
207 | ||
208 | for line in stripped_lines: | |
209 | # Careful with order, snippet may self-reference its own | |
210 | # defines in which case there's no outward dependency. | |
211 | # (This is not 100% because the order of require/provide | |
212 | # matters and this is not handled now.) | |
213 | # | |
214 | # Also, some snippets may #undef/#define another define but | |
215 | # they don't "provide" the define as such. For example, | |
216 | # DUK_F_CLANG.h.in undefines DUK_F_GCC defines if clang is | |
217 | # detected: DUK_F_CLANG.h.in is considered to require | |
218 | # DUK_F_GCC but doesn't provide it. Such redefinitions are | |
219 | # marked "/* redefine */" in the snippets. They're best | |
220 | # avoided, of course. | |
221 | ||
222 | if autoscan_provides: | |
223 | m = re_line_provides.match(line) | |
224 | if m is not None and '/* redefine */' not in line and \ | |
225 | len(m.group(1)) > 0 and m.group(1)[-1] != '_': | |
226 | # Don't allow e.g. DUK_USE_ which results from matching DUK_USE_xxx | |
227 | #print('PROVIDES: %r' % m.group(1)) | |
228 | self.provides[m.group(1)] = True | |
229 | if autoscan_requires: | |
230 | matches = re.findall(re_line_requires, line) | |
231 | for m in matches: | |
232 | if len(m) > 0 and m[-1] == '_': | |
233 | # Don't allow e.g. DUK_USE_ which results from matching DUK_USE_xxx | |
234 | pass | |
235 | elif m[:7] == 'DUK_OPT': | |
236 | # DUK_OPT_xxx always come from outside | |
237 | pass | |
238 | elif m[:7] == 'DUK_USE': | |
239 | # DUK_USE_xxx are internal and they should not be 'requirements' | |
240 | pass | |
241 | elif self.provides.has_key(m): | |
242 | # Snippet provides it's own require; omit | |
243 | pass | |
244 | else: | |
245 | #print('REQUIRES: %r' % m) | |
246 | self.requires[m] = True | |
247 | ||
248 | def fromFile(cls, filename): | |
249 | lines = [] | |
250 | with open(filename, 'rb') as f: | |
251 | for line in f: | |
252 | if line[-1] == '\n': | |
253 | line = line[:-1] | |
254 | lines.append(line) | |
255 | return Snippet(lines, autoscan_requires=True, autoscan_provides=True) | |
256 | fromFile = classmethod(fromFile) | |
257 | ||
258 | def merge(cls, snippets): | |
259 | ret = Snippet([], [], []) | |
260 | for s in snippets: | |
261 | ret.lines += s.lines | |
262 | for k in s.provides.keys(): | |
263 | ret.provides[k] = True | |
264 | for k in s.requires.keys(): | |
265 | ret.requires[k] = True | |
266 | return ret | |
267 | merge = classmethod(merge) | |
268 | ||
269 | # Helper for building a text file from individual lines, injected files, etc. | |
270 | # Inserted values are converted to Snippets so that their provides/requires | |
271 | # information can be tracked. When non-C outputs are created, these will be | |
272 | # bogus but ignored. | |
273 | class FileBuilder: | |
274 | vals = None # snippet list | |
275 | base_dir = None | |
276 | use_cpp_warning = False | |
277 | ||
278 | def __init__(self, base_dir=None, use_cpp_warning=False): | |
279 | self.vals = [] | |
280 | self.base_dir = base_dir | |
281 | self.use_cpp_warning = use_cpp_warning | |
282 | ||
283 | def line(self, line): | |
284 | self.vals.append(Snippet([ line ])) | |
285 | ||
286 | def lines(self, lines): | |
287 | if len(lines) > 0 and lines[-1] == '\n': | |
288 | lines = lines[:-1] # strip last newline to avoid empty line | |
289 | self.vals.append(Snippet(lines.split('\n'))) | |
290 | ||
291 | def empty(self): | |
292 | self.vals.append(Snippet([ '' ])) | |
293 | ||
294 | def rst_heading(self, title, char, doubled=False): | |
295 | tmp = [] | |
296 | if doubled: | |
297 | tmp.append(char * len(title)) | |
298 | tmp.append(title) | |
299 | tmp.append(char * len(title)) | |
300 | self.vals.append(Snippet(tmp)) | |
301 | ||
302 | def snippet_relative(self, fn): | |
303 | sn = Snippet.fromFile(os.path.join(self.base_dir, fn)) | |
304 | self.vals.append(sn) | |
305 | ||
306 | def snippet_absolute(fn): | |
307 | sn = Snippet.fromFile(fn) | |
308 | self.vals.append(sn) | |
309 | ||
310 | def cpp_error(self, msg): | |
311 | # XXX: assume no newlines etc | |
312 | self.vals.append(Snippet([ '#error %s' % msg ])) | |
313 | ||
314 | def cpp_warning(self, msg): | |
315 | # XXX: assume no newlines etc | |
316 | # XXX: support compiler specific warning mechanisms | |
317 | if self.use_cpp_warning: | |
318 | # C preprocessor '#warning' is often supported | |
319 | self.vals.append(Snippet([ '#warning %s' % msg ])) | |
320 | else: | |
321 | self.vals.append(Snippet([ '/* WARNING: %s */' % msg ])) | |
322 | ||
323 | def cpp_warning_or_error(self, msg, is_error=True): | |
324 | if is_error: | |
325 | self.cpp_error(msg) | |
326 | else: | |
327 | self.cpp_warning(msg) | |
328 | ||
329 | def chdr_block_heading(self, msg): | |
330 | lines = [] | |
331 | lines.append('') | |
332 | lines.append('/*') | |
333 | lines.append(' * ' + msg) | |
334 | lines.append(' */') | |
335 | lines.append('') | |
336 | self.vals.append(Snippet(lines)) | |
337 | ||
338 | def join(self): | |
339 | tmp = [] | |
340 | for line in self.vals: | |
341 | if not isinstance(line, object): | |
342 | raise Exception('self.vals must be all snippets') | |
343 | for x in line.lines: # x is a Snippet | |
344 | tmp.append(x) | |
345 | return '\n'.join(tmp) | |
346 | ||
347 | def fill_dependencies_for_snippets(self, idx_deps): | |
348 | fill_dependencies_for_snippets(self.vals, idx_deps) | |
349 | ||
350 | # Insert missing define dependencies into index 'idx_deps' repeatedly | |
351 | # until no unsatisfied dependencies exist. This is used to pull in | |
352 | # the required DUK_F_xxx helper defines without pulling them all in. | |
353 | # The resolution mechanism also ensures dependencies are pulled in the | |
354 | # correct order, i.e. DUK_F_xxx helpers may depend on each other (as | |
355 | # long as there are no circular dependencies). | |
356 | # | |
357 | # XXX: this can be simplified a lot | |
358 | def fill_dependencies_for_snippets(snippets, idx_deps): | |
359 | # graph[A] = [ B, ... ] <-> B, ... provide something A requires. | |
360 | graph = {} | |
361 | snlist = [] | |
362 | resolved = [] # for printing only | |
363 | ||
364 | def add(sn): | |
365 | if sn in snlist: | |
366 | return # already present | |
367 | snlist.append(sn) | |
368 | ||
369 | to_add = [] | |
370 | ||
371 | for k in sn.requires.keys(): | |
372 | if assumed_provides.has_key(k): | |
373 | continue | |
374 | ||
375 | found = False | |
376 | for sn2 in snlist: | |
377 | if sn2.provides.has_key(k): | |
378 | if not graph.has_key(sn): | |
379 | graph[sn] = [] | |
380 | graph[sn].append(sn2) | |
381 | found = True # at least one other node provides 'k' | |
382 | ||
383 | if not found: | |
384 | #print 'Resolving %r' % k | |
385 | resolved.append(k) | |
386 | ||
387 | # Find a header snippet which provides the missing define. | |
388 | # Some DUK_F_xxx files provide multiple defines, so we don't | |
389 | # necessarily know the snippet filename here. | |
390 | ||
391 | sn_req = None | |
392 | for sn2 in helper_snippets: | |
393 | if sn2.provides.has_key(k): | |
394 | sn_req = sn2 | |
395 | break | |
396 | if sn_req is None: | |
397 | print(repr(sn.lines)) | |
398 | raise Exception('cannot resolve missing require: %r' % k) | |
399 | ||
400 | # Snippet may have further unresolved provides; add recursively | |
401 | to_add.append(sn_req) | |
402 | ||
403 | if not graph.has_key(sn): | |
404 | graph[sn] = [] | |
405 | graph[sn].append(sn_req) | |
406 | ||
407 | for sn in to_add: | |
408 | add(sn) | |
409 | ||
410 | # Add original snippets. This fills in the required nodes | |
411 | # recursively. | |
412 | for sn in snippets: | |
413 | add(sn) | |
414 | ||
415 | # Figure out fill-ins by looking for snippets not in original | |
416 | # list and without any unserialized dependent nodes. | |
417 | handled = {} | |
418 | for sn in snippets: | |
419 | handled[sn] = True | |
420 | keepgoing = True | |
421 | while keepgoing: | |
422 | keepgoing = False | |
423 | for sn in snlist: | |
424 | if handled.has_key(sn): | |
425 | continue | |
426 | ||
427 | success = True | |
428 | for dep in graph.get(sn, []): | |
429 | if not handled.has_key(dep): | |
430 | success = False | |
431 | if success: | |
432 | snippets.insert(idx_deps, sn) | |
433 | idx_deps += 1 | |
434 | snippets.insert(idx_deps, Snippet([ '' ])) | |
435 | idx_deps += 1 | |
436 | handled[sn] = True | |
437 | keepgoing = True | |
438 | break | |
439 | ||
440 | # XXX: detect and handle loops cleanly | |
441 | for sn in snlist: | |
442 | if handled.has_key(sn): | |
443 | continue | |
444 | print('UNHANDLED KEY') | |
445 | print('PROVIDES: %r' % sn.provides) | |
446 | print('REQUIRES: %r' % sn.requires) | |
447 | print('\n'.join(sn.lines)) | |
448 | ||
449 | # print(repr(graph)) | |
450 | # print(repr(snlist)) | |
451 | print 'Resolved helper defines: %r' % resolved | |
452 | ||
453 | def serialize_snippet_list(snippets): | |
454 | ret = [] | |
455 | ||
456 | emitted_provides = {} | |
457 | for k in assumed_provides.keys(): | |
458 | emitted_provides[k] = True | |
459 | ||
460 | for sn in snippets: | |
461 | ret += sn.lines | |
462 | for k in sn.provides.keys(): | |
463 | emitted_provides[k] = True | |
464 | for k in sn.requires.keys(): | |
465 | if not emitted_provides.has_key(k): | |
466 | # XXX: conditional warning, happens in some normal cases | |
467 | #print('WARNING: define %r required, not provided so far' % k) | |
468 | pass | |
469 | ||
470 | return '\n'.join(ret) | |
471 | ||
472 | def remove_duplicate_newlines(x): | |
473 | ret = [] | |
474 | empty = False | |
475 | for line in x.split('\n'): | |
476 | if line == '': | |
477 | if empty: | |
478 | pass | |
479 | else: | |
480 | ret.append(line) | |
481 | empty = True | |
482 | else: | |
483 | empty = False | |
484 | ret.append(line) | |
485 | return '\n'.join(ret) | |
486 | ||
487 | def scan_use_defs(dirname): | |
488 | global use_defs, use_defs_list | |
489 | use_defs = {} | |
490 | use_defs_list = [] | |
491 | ||
492 | for fn in os.listdir(dirname): | |
493 | root, ext = os.path.splitext(fn) | |
494 | if not root.startswith('DUK_USE_') or ext != '.yaml': | |
495 | continue | |
496 | with open(os.path.join(dirname, fn), 'rb') as f: | |
497 | doc = yaml.load(f) | |
498 | if doc.get('example', False): | |
499 | continue | |
500 | if doc.get('unimplemented', False): | |
501 | print('WARNING: unimplemented: %s' % fn) | |
502 | continue | |
503 | dockeys = doc.keys() | |
504 | for k in dockeys: | |
505 | if not k in allowed_use_meta_keys: | |
506 | print('WARNING: unknown key %s in metadata file %s' % (k, fn)) | |
507 | for k in required_use_meta_keys: | |
508 | if not k in dockeys: | |
509 | print('WARNING: missing key %s in metadata file %s' % (k, fn)) | |
510 | ||
511 | use_defs[doc['define']] = doc | |
512 | ||
513 | keys = use_defs.keys() | |
514 | keys.sort() | |
515 | for k in keys: | |
516 | use_defs_list.append(use_defs[k]) | |
517 | ||
518 | def scan_opt_defs(dirname): | |
519 | global opt_defs, opt_defs_list | |
520 | opt_defs = {} | |
521 | opt_defs_list = [] | |
522 | ||
523 | for fn in os.listdir(dirname): | |
524 | root, ext = os.path.splitext(fn) | |
525 | if not root.startswith('DUK_OPT_') or ext != '.yaml': | |
526 | continue | |
527 | with open(os.path.join(dirname, fn), 'rb') as f: | |
528 | doc = yaml.load(f) | |
529 | if doc.get('example', False): | |
530 | continue | |
531 | if doc.get('unimplemented', False): | |
532 | print('WARNING: unimplemented: %s' % fn) | |
533 | continue | |
534 | dockeys = doc.keys() | |
535 | for k in dockeys: | |
536 | if not k in allowed_opt_meta_keys: | |
537 | print('WARNING: unknown key %s in metadata file %s' % (k, fn)) | |
538 | for k in required_opt_meta_keys: | |
539 | if not k in dockeys: | |
540 | print('WARNING: missing key %s in metadata file %s' % (k, fn)) | |
541 | ||
542 | opt_defs[doc['define']] = doc | |
543 | ||
544 | keys = opt_defs.keys() | |
545 | keys.sort() | |
546 | for k in keys: | |
547 | opt_defs_list.append(opt_defs[k]) | |
548 | ||
549 | def scan_use_tags(): | |
550 | global use_tags, use_tags_list | |
551 | use_tags = {} | |
552 | ||
553 | for doc in use_defs_list: | |
554 | for tag in doc.get('tags', []): | |
555 | use_tags[tag] = True | |
556 | ||
557 | use_tags_list = use_tags.keys() | |
558 | use_tags_list.sort() | |
559 | ||
560 | def scan_tags_meta(filename): | |
561 | global tags_meta | |
562 | ||
563 | with open(filename, 'rb') as f: | |
564 | tags_meta = yaml.load(f) | |
565 | ||
566 | def scan_snippets(dirname): | |
567 | global helper_snippets | |
568 | helper_snippets = [] | |
569 | ||
570 | for fn in os.listdir(dirname): | |
571 | if (fn[0:6] != 'DUK_F_'): | |
572 | continue | |
573 | #print('Autoscanning snippet: %s' % fn) | |
574 | helper_snippets.append(Snippet.fromFile(os.path.join(dirname, fn))) | |
575 | ||
576 | def validate_platform_file(filename): | |
577 | sn = Snippet.fromFile(filename) | |
578 | ||
579 | # XXX: move required provides/defines into metadata only | |
580 | for req in platform_required_provides: | |
581 | if req not in sn.provides: | |
582 | raise Exception('Platform %s is missing %s' % (filename, req)) | |
583 | ||
584 | if not ('DUK_USE_SETJMP' in sn.provides or 'DUK_USE_UNDERSCORE_SETJMP' in sn.provides or | |
585 | 'DUK_USE_SIGSETJMP' in sn.provides): | |
586 | raise Exception('Platform %s is missing a setjmp provider' % filename) | |
587 | ||
588 | def validate_architecture_file(filename): | |
589 | sn = Snippet.fromFile(filename) | |
590 | ||
591 | # XXX: move required provides/defines into metadata only | |
592 | for req in architecture_required_provides: | |
593 | if req not in sn.provides: | |
594 | raise Exception('Architecture %s is missing %s' % (filename, req)) | |
595 | ||
596 | def validate_compiler_file(filename): | |
597 | sn = Snippet.fromFile(filename) | |
598 | ||
599 | # XXX: move required provides/defines into metadata only | |
600 | for req in compiler_required_provides: | |
601 | if req not in sn.provides: | |
602 | raise Exception('Architecture %s is missing %s' % (filename, req)) | |
603 | ||
604 | def get_tag_title(tag): | |
605 | meta = tags_meta.get(tag, None) | |
606 | if meta is None: | |
607 | return tag | |
608 | else: | |
609 | return meta.get('title', tag) | |
610 | ||
611 | def get_tag_description(tag): | |
612 | meta = tags_meta.get(tag, None) | |
613 | if meta is None: | |
614 | return None | |
615 | else: | |
616 | return meta.get('description', None) | |
617 | ||
618 | def get_tag_list_with_preferred_order(preferred): | |
619 | tags = [] | |
620 | ||
621 | # Preferred tags first | |
622 | for tag in preferred: | |
623 | if tag not in tags: | |
624 | tags.append(tag) | |
625 | ||
626 | # Remaining tags in alphabetic order | |
627 | for tag in use_tags_list: | |
628 | if tag not in tags: | |
629 | tags.append(tag) | |
630 | ||
631 | #print('Effective tag order: %r' % tags) | |
632 | return tags | |
633 | ||
634 | def rst_format(text): | |
635 | # XXX: placeholder, need to decide on markup conventions for YAML files | |
636 | ret = [] | |
637 | for para in text.split('\n'): | |
638 | if para == '': | |
639 | continue | |
640 | ret.append(para) | |
641 | return '\n\n'.join(ret) | |
642 | ||
643 | def cint_encode(x): | |
644 | if not isinstance(x, (int, long)): | |
645 | raise Exception('invalid input: %r' % x) | |
646 | ||
647 | # XXX: unsigned constants? | |
648 | if x > 0x7fffffff or x < -0x80000000: | |
649 | return '%dLL' % x | |
650 | elif x > 0x7fff or x < -0x8000: | |
651 | return '%dL' % x | |
652 | else: | |
653 | return '%d' % x | |
654 | ||
655 | def cstr_encode(x): | |
656 | if isinstance(x, unicode): | |
657 | x = x.encode('utf-8') | |
658 | if not isinstance(x, str): | |
659 | raise Exception('invalid input: %r' % x) | |
660 | ||
661 | res = '"' | |
662 | term = False | |
663 | has_terms = False | |
664 | for c in x: | |
665 | if term: | |
666 | # Avoid ambiguous hex escapes | |
667 | res += '" "' | |
668 | term = False | |
669 | has_terms = True | |
670 | o = ord(c) | |
671 | if o < 0x20 or o > 0x7e or c in '"\\': | |
672 | res += '\\x%02x' % o | |
673 | term = True | |
674 | else: | |
675 | res += c | |
676 | res += '"' | |
677 | ||
678 | if has_terms: | |
679 | res = '(' + res + ')' | |
680 | ||
681 | return res | |
682 | ||
683 | # | |
684 | # Autogeneration of option documentation | |
685 | # | |
686 | ||
687 | # Shared helper to generate DUK_OPT_xxx and DUK_USE_xxx documentation. | |
688 | # XXX: unfinished placeholder | |
689 | def generate_option_documentation(opts, opt_list=None, rst_title=None, include_default=False): | |
690 | ret = FileBuilder(use_cpp_warning=opts.use_cpp_warning) | |
691 | ||
692 | tags = get_tag_list_with_preferred_order(doc_tag_order) | |
693 | ||
694 | title = rst_title | |
695 | ret.rst_heading(title, '=', doubled=True) | |
696 | ||
697 | handled = {} | |
698 | ||
699 | for tag in tags: | |
700 | first = True | |
701 | ||
702 | for doc in opt_list: | |
703 | if tag != doc['tags'][0]: # sort under primary tag | |
704 | continue | |
705 | dname = doc['define'] | |
706 | desc = doc.get('description', None) | |
707 | ||
708 | if handled.has_key(dname): | |
709 | raise Exception('define handled twice, should not happen: %r' % dname) | |
710 | handled[dname] = True | |
711 | ||
712 | if first: # emit tag heading only if there are subsections | |
713 | ret.empty() | |
714 | ret.rst_heading(get_tag_title(tag), '=') | |
715 | ||
716 | tag_desc = get_tag_description(tag) | |
717 | if tag_desc is not None: | |
718 | ret.empty() | |
719 | ret.line(rst_format(tag_desc)) | |
720 | first = False | |
721 | ||
722 | ret.empty() | |
723 | ret.rst_heading(dname, '-') | |
724 | ||
725 | if desc is not None: | |
726 | ret.empty() | |
727 | ret.line(rst_format(desc)) | |
728 | ||
729 | if include_default: | |
730 | ret.empty() | |
731 | ret.line('Default: ``' + str(doc['default']) + '``') # XXX: rst or other format | |
732 | ||
733 | for doc in opt_list: | |
734 | dname = doc['define'] | |
735 | if not handled.has_key(dname): | |
736 | raise Exception('unhandled define (maybe missing from tags list?): %r' % dname) | |
737 | ||
738 | ret.empty() | |
739 | return ret.join() | |
740 | ||
741 | def generate_feature_option_documentation(opts): | |
742 | return generate_option_documentation(opts, opt_list=opt_defs_list, rst_title='Duktape feature options', include_default=False) | |
743 | ||
744 | def generate_config_option_documentation(opts): | |
745 | return generate_option_documentation(opts, opt_list=use_defs_list, rst_title='Duktape config options', include_default=True) | |
746 | ||
747 | # | |
748 | # Helpers for duk_config.h generation | |
749 | # | |
750 | ||
751 | def get_forced_options(opts): | |
752 | # Forced options, last occurrence wins (allows a base config file to be | |
753 | # overridden by a more specific one). | |
754 | forced_opts = {} | |
755 | for val in opts.force_options_yaml: | |
756 | doc = yaml.load(StringIO.StringIO(val)) | |
757 | for k in doc.keys(): | |
758 | if use_defs.has_key(k): | |
759 | pass # key is known | |
760 | else: | |
761 | print 'WARNING: option override key %s not defined in metadata, ignoring' % k | |
762 | forced_opts[k] = doc[k] # shallow copy | |
763 | ||
764 | print 'Overrides: %s' % json.dumps(forced_opts) | |
765 | ||
766 | return forced_opts | |
767 | ||
768 | # Emit a default #define / #undef for an option based on | |
769 | # a config option metadata node (parsed YAML doc). | |
770 | def emit_default_from_config_meta(ret, doc, forced_opts, undef_done): | |
771 | defname = doc['define'] | |
772 | defval = forced_opts.get(defname, doc['default']) | |
773 | ||
774 | if defval == True: | |
775 | ret.line('#define ' + defname) | |
776 | elif defval == False: | |
777 | if not undef_done: | |
778 | ret.line('#undef ' + defname) | |
779 | else: | |
780 | # Default value is false, and caller has emitted | |
781 | # an unconditional #undef, so don't emit a duplicate | |
782 | pass | |
783 | elif isinstance(defval, (int, long)): | |
784 | # integer value | |
785 | ret.line('#define ' + defname + ' ' + cint_encode(defval)) | |
786 | elif isinstance(defval, (str, unicode)): | |
787 | # verbatim value | |
788 | ret.line('#define ' + defname + ' ' + defval) | |
789 | elif isinstance(defval, dict): | |
790 | if defval.has_key('verbatim'): | |
791 | # verbatim text for the entire line | |
792 | ret.line(defval['verbatim']) | |
793 | elif defval.has_key('string'): | |
794 | # C string value | |
795 | ret.line('#define ' + defname + ' ' + cstr_encode(defval['string'])) | |
796 | else: | |
797 | raise Exception('unsupported value for option %s: %r' % (defname, defval)) | |
798 | else: | |
799 | raise Exception('unsupported value for option %s: %r' % (defname, defval)) | |
800 | ||
801 | # Add a header snippet for detecting presence of DUK_OPT_xxx feature | |
802 | # options which will be removed in Duktape 2.x. | |
803 | def add_legacy_feature_option_checks(opts, ret): | |
804 | ret.chdr_block_heading('Checks for legacy feature options (DUK_OPT_xxx)') | |
805 | ||
806 | defs = [] | |
807 | for doc in opt_defs_list: | |
808 | if doc['define'] not in defs: | |
809 | defs.append(doc['define']) | |
810 | for doc in use_defs_list: | |
811 | for dname in doc.get('related_feature_defines', []): | |
812 | if dname not in defs: | |
813 | defs.append(dname) | |
814 | defs.sort() | |
815 | ||
816 | for optname in defs: | |
817 | suggested = [] | |
818 | for doc in use_defs_list: | |
819 | if optname in doc.get('related_feature_defines', []): | |
820 | suggested.append(doc['define']) | |
821 | ret.empty() | |
822 | ret.line('#if defined(%s)' % optname) | |
823 | if len(suggested) > 0: | |
824 | ret.cpp_warning_or_error('unsupported legacy feature option %s used, consider options: %s' % (optname, ', '.join(suggested)), opts.sanity_strict) | |
825 | else: | |
826 | ret.cpp_warning_or_error('unsupported legacy feature option %s used' % optname, opts.sanity_strict) | |
827 | ret.line('#endif') | |
828 | ||
829 | ret.empty() | |
830 | ||
831 | # Add a header snippet for checking consistency of DUK_USE_xxx config | |
832 | # options, e.g. inconsistent options, invalid option values. | |
833 | def add_config_option_checks(opts, ret): | |
834 | ret.chdr_block_heading('Checks for config option consistency (DUK_USE_xxx)') | |
835 | ||
836 | defs = [] | |
837 | for doc in use_defs_list: | |
838 | if doc['define'] not in defs: | |
839 | defs.append(doc['define']) | |
840 | defs.sort() | |
841 | ||
842 | for optname in defs: | |
843 | doc = use_defs[optname] | |
844 | dname = doc['define'] | |
845 | ||
846 | # XXX: more checks | |
847 | ||
848 | if doc.get('removed', None) is not None: | |
849 | ret.empty() | |
850 | ret.line('#if defined(%s)' % dname) | |
851 | ret.cpp_warning_or_error('unsupported config option used (option has been removed): %s' % dname, opts.sanity_strict) | |
852 | ret.line('#endif') | |
853 | elif doc.get('deprecated', None) is not None: | |
854 | ret.empty() | |
855 | ret.line('#if defined(%s)' % dname) | |
856 | ret.cpp_warning_or_error('unsupported config option used (option has been deprecated): %s' % dname, opts.sanity_strict) | |
857 | ret.line('#endif') | |
858 | ||
859 | for req in doc.get('requires', []): | |
860 | ret.empty() | |
861 | ret.line('#if defined(%s) && !defined(%s)' % (dname, req)) | |
862 | ret.cpp_warning_or_error('config option %s requires option %s (which is missing)' % (dname, req), opts.sanity_strict) | |
863 | ret.line('#endif') | |
864 | ||
865 | for req in doc.get('conflicts', []): | |
866 | ret.empty() | |
867 | ret.line('#if defined(%s) && defined(%s)' % (dname, req)) | |
868 | ret.cpp_warning_or_error('config option %s conflicts with option %s (which is also defined)' % (dname, req), opts.sanity_strict) | |
869 | ret.line('#endif') | |
870 | ||
871 | ret.empty() | |
872 | ||
873 | # Add a header snippet for providing a __OVERRIDE_DEFINES__ section. | |
874 | def add_override_defines_section(opts, ret): | |
875 | ret.empty() | |
876 | ret.line('/*') | |
877 | ret.line(' * You may add overriding #define/#undef directives below for') | |
878 | ret.line(' * customization. You of course cannot un-#include or un-typedef') | |
879 | ret.line(' * anything; these require direct changes above.') | |
880 | ret.line(' */') | |
881 | ret.empty() | |
882 | ret.line('/* __OVERRIDE_DEFINES__ */') | |
883 | ret.empty() | |
884 | ||
885 | # Add automatic DUK_OPT_XXX and DUK_OPT_NO_XXX handling for backwards | |
886 | # compatibility with Duktape 1.2 and before. | |
887 | def add_feature_option_handling(opts, ret, forced_opts): | |
888 | ret.chdr_block_heading('Feature option handling') | |
889 | ||
890 | for doc in use_defs_list: | |
891 | # If a related feature option exists, it can be used to force | |
892 | # enable/disable the target feature. If neither feature option | |
893 | # (DUK_OPT_xxx or DUK_OPT_NO_xxx) is given, revert to default. | |
894 | ||
895 | config_define = doc['define'] | |
896 | ||
897 | feature_define = None | |
898 | feature_no_define = None | |
899 | inverted = False | |
900 | if doc.has_key('feature_enables'): | |
901 | feature_define = doc['feature_enables'] | |
902 | elif doc.has_key('feature_disables'): | |
903 | feature_define = doc['feature_disables'] | |
904 | inverted = True | |
905 | else: | |
906 | pass | |
907 | ||
908 | if feature_define is not None: | |
909 | feature_no_define = 'DUK_OPT_NO_' + feature_define[8:] | |
910 | ret.line('#if defined(%s)' % feature_define) | |
911 | if inverted: | |
912 | ret.line('#undef %s' % config_define) | |
913 | else: | |
914 | ret.line('#define %s' % config_define) | |
915 | ret.line('#elif defined(%s)' % feature_no_define) | |
916 | if inverted: | |
917 | ret.line('#define %s' % config_define) | |
918 | else: | |
919 | ret.line('#undef %s' % config_define) | |
920 | ret.line('#else') | |
921 | undef_done = False | |
922 | emit_default_from_config_meta(ret, doc, forced_opts, undef_done) | |
923 | ret.line('#endif') | |
924 | elif doc.has_key('feature_snippet'): | |
925 | ret.lines(doc['feature_snippet']) | |
926 | else: | |
927 | pass | |
928 | ||
929 | ret.empty() | |
930 | ||
931 | ret.empty() | |
932 | ||
933 | # Development time helper: add DUK_ACTIVE which provides a runtime C string | |
934 | # indicating what DUK_USE_xxx config options are active at run time. This | |
935 | # is useful in genconfig development so that one can e.g. diff the active | |
936 | # run time options of two headers. This is intended just for genconfig | |
937 | # development and is not available in normal headers. | |
938 | def add_duk_active_defines_macro(ret): | |
939 | ret.chdr_block_heading('DUK_ACTIVE_DEFINES macro (development only)') | |
940 | ||
941 | idx = 0 | |
942 | for doc in use_defs_list: | |
943 | defname = doc['define'] | |
944 | ||
945 | ret.line('#if defined(%s)' % defname) | |
946 | ret.line('#define DUK_ACTIVE_DEF%d " %s"' % (idx, defname)) | |
947 | ret.line('#else') | |
948 | ret.line('#define DUK_ACTIVE_DEF%d ""' % idx) | |
949 | ret.line('#endif') | |
950 | ||
951 | idx += 1 | |
952 | ||
953 | tmp = [] | |
954 | for i in xrange(idx): | |
955 | tmp.append('DUK_ACTIVE_DEF%d' % i) | |
956 | ||
957 | ret.line('#define DUK_ACTIVE_DEFINES ("Active: ["' + ' '.join(tmp) + ' " ]")') | |
958 | ||
959 | # | |
960 | # duk_config.h generation | |
961 | # | |
962 | ||
963 | # Generate the default duk_config.h which provides automatic detection of | |
964 | # platform, compiler, architecture, and features for major platforms. | |
965 | # Use manually written monolithic header snippets from Duktape 1.2 for | |
966 | # generating the header. This header is Duktape 1.2 compatible and supports | |
967 | # DUK_OPT_xxx feature options. Later on the DUK_OPT_xxx options will be | |
968 | # removed and users can override DUK_USE_xxx flags directly by modifying | |
969 | # duk_config.h or by generating a new header using genconfig. | |
970 | def generate_autodetect_duk_config_header(opts, meta_dir): | |
971 | ret = FileBuilder(base_dir=os.path.join(meta_dir, 'header-snippets'), \ | |
972 | use_cpp_warning=opts.use_cpp_warning) | |
973 | ||
974 | forced_opts = get_forced_options(opts) | |
975 | ||
976 | ret.snippet_relative('comment_prologue.h.in') | |
977 | ret.empty() | |
978 | ||
979 | ret.line('#ifndef DUK_CONFIG_H_INCLUDED') | |
980 | ret.line('#define DUK_CONFIG_H_INCLUDED') | |
981 | ret.empty() | |
982 | ||
983 | # Compiler features, processor/architecture, OS, compiler | |
984 | ret.snippet_relative('compiler_features.h.in') | |
985 | ret.empty() | |
986 | ret.snippet_relative('rdtsc.h.in') # XXX: move downwards | |
987 | ret.empty() | |
988 | ret.snippet_relative('platform1.h.in') | |
989 | ret.empty() | |
990 | ||
991 | # Feature selection, system include, Date provider | |
992 | # Most #include statements are here | |
993 | ret.snippet_relative('platform2.h.in') | |
994 | ret.empty() | |
995 | ret.snippet_relative('ullconsts.h.in') | |
996 | ret.empty() | |
997 | ret.snippet_relative('libc.h.in') | |
998 | ret.empty() | |
999 | ||
1000 | # Number types | |
1001 | ret.snippet_relative('types1.h.in') | |
1002 | ret.line('#if defined(DUK_F_HAVE_INTTYPES)') | |
1003 | ret.line('/* C99 or compatible */') | |
1004 | ret.empty() | |
1005 | ret.snippet_relative('types_c99.h.in') | |
1006 | ret.empty() | |
1007 | ret.line('#else /* C99 types */') | |
1008 | ret.empty() | |
1009 | ret.snippet_relative('types_legacy.h.in') | |
1010 | ret.empty() | |
1011 | ret.line('#endif /* C99 types */') | |
1012 | ret.empty() | |
1013 | ret.snippet_relative('types2.h.in') | |
1014 | ret.empty() | |
1015 | ret.snippet_relative('64bitops.h.in') | |
1016 | ret.empty() | |
1017 | ||
1018 | # Alignment | |
1019 | ret.snippet_relative('alignment.h.in') | |
1020 | ret.empty() | |
1021 | ||
1022 | # Object layout | |
1023 | ret.snippet_relative('object_layout.h.in') | |
1024 | ret.empty() | |
1025 | ||
1026 | # Byte order | |
1027 | ret.snippet_relative('byteorder.h.in') | |
1028 | ret.empty() | |
1029 | ||
1030 | # Packed duk_tval | |
1031 | ret.snippet_relative('packed_tval.h.in') | |
1032 | ret.empty() | |
1033 | ||
1034 | # Detect 'fast math' | |
1035 | ret.snippet_relative('reject_fast_math.h.in') | |
1036 | ret.empty() | |
1037 | ||
1038 | # IEEE double constants | |
1039 | ret.snippet_relative('double_const.h.in') | |
1040 | ret.empty() | |
1041 | ||
1042 | # Math and other ANSI replacements, NetBSD workaround, paranoid math, paranoid Date | |
1043 | ret.snippet_relative('repl_math.h.in') | |
1044 | ret.empty() | |
1045 | ret.snippet_relative('paranoid_date.h.in') | |
1046 | ret.empty() | |
1047 | ret.snippet_relative('repl_ansi.h.in') | |
1048 | ret.empty() | |
1049 | ||
1050 | # Platform function pointers | |
1051 | ret.snippet_relative('platform_funcptr.h.in') | |
1052 | ret.empty() | |
1053 | ||
1054 | # General compiler stuff | |
1055 | ret.snippet_relative('stringify.h.in') | |
1056 | ret.empty() | |
1057 | ret.snippet_relative('segfault.h.in') | |
1058 | ret.empty() | |
1059 | ret.snippet_relative('unreferenced.h.in') | |
1060 | ret.empty() | |
1061 | ret.snippet_relative('noreturn.h.in') | |
1062 | ret.empty() | |
1063 | ret.snippet_relative('unreachable.h.in') | |
1064 | ret.empty() | |
1065 | ret.snippet_relative('likely.h.in') | |
1066 | ret.empty() | |
1067 | ret.snippet_relative('inline.h.in') | |
1068 | ret.empty() | |
1069 | ret.snippet_relative('visibility.h.in') | |
1070 | ret.empty() | |
1071 | ret.snippet_relative('file_line_func.h.in') | |
1072 | ret.empty() | |
1073 | ret.snippet_relative('byteswap.h.in') | |
1074 | ret.empty() | |
1075 | ||
1076 | # Arhitecture, OS, and compiler strings | |
1077 | ret.snippet_relative('arch_string.h.in') | |
1078 | ret.empty() | |
1079 | ret.snippet_relative('os_string.h.in') | |
1080 | ret.empty() | |
1081 | ret.snippet_relative('compiler_string.h.in') | |
1082 | ret.empty() | |
1083 | ||
1084 | # Target info | |
1085 | ret.snippet_relative('target_info.h.in') | |
1086 | ret.empty() | |
1087 | ||
1088 | # Longjmp handling | |
1089 | ret.snippet_relative('longjmp.h.in') | |
1090 | ret.empty() | |
1091 | ||
1092 | # Unsorted flags, contains almost all actual Duktape-specific | |
1093 | # but platform independent features | |
1094 | ret.snippet_relative('unsorted_flags.h.in') | |
1095 | ret.empty() | |
1096 | ||
1097 | # User declarations | |
1098 | ret.snippet_relative('user_declare.h.in') | |
1099 | ret.empty() | |
1100 | ||
1101 | # Emit forced options. If a corresponding option is already defined | |
1102 | # by a snippet above, #undef it first. | |
1103 | ||
1104 | tmp = Snippet(ret.join().split('\n')) | |
1105 | first_forced = True | |
1106 | for doc in use_defs_list: | |
1107 | defname = doc['define'] | |
1108 | ||
1109 | if doc.get('removed', None) is not None and opts.omit_removed_config_options: | |
1110 | continue | |
1111 | if doc.get('deprecated', None) is not None and opts.omit_deprecated_config_options: | |
1112 | continue | |
1113 | if doc.get('unused', False) == True and opts.omit_unused_config_options: | |
1114 | continue | |
1115 | if not forced_opts.has_key(defname): | |
1116 | continue | |
1117 | ||
1118 | if not doc.has_key('default'): | |
1119 | raise Exception('config option %s is missing default value' % defname) | |
1120 | ||
1121 | if first_forced: | |
1122 | ret.chdr_block_heading('Forced options') | |
1123 | first_forced = False | |
1124 | ||
1125 | undef_done = False | |
1126 | if tmp.provides.has_key(defname): | |
1127 | ret.line('#undef ' + defname) | |
1128 | undef_done = True | |
1129 | ||
1130 | emit_default_from_config_meta(ret, doc, forced_opts, undef_done) | |
1131 | ||
1132 | ret.empty() | |
1133 | ||
1134 | # If manually-edited snippets don't #define or #undef a certain | |
1135 | # config option, emit a default value here. This is useful to | |
1136 | # fill-in for new config options not covered by manual snippets | |
1137 | # (which is intentional). | |
1138 | ||
1139 | tmp = Snippet(ret.join().split('\n')) | |
1140 | need = {} | |
1141 | for doc in use_defs_list: | |
1142 | if doc.get('removed', None) is not None: # XXX: check version | |
1143 | continue | |
1144 | need[doc['define']] = True | |
1145 | for k in tmp.provides.keys(): | |
1146 | if need.has_key(k): | |
1147 | del need[k] | |
1148 | need_keys = sorted(need.keys()) | |
1149 | ||
1150 | if len(need_keys) > 0: | |
1151 | ret.chdr_block_heading('Autogenerated defaults') | |
1152 | ||
1153 | for k in need_keys: | |
1154 | #print('config option %s not covered by manual snippets, emitting default automatically' % k) | |
1155 | emit_default_from_config_meta(ret, use_defs[k], {}, False) | |
1156 | ||
1157 | ret.empty() | |
1158 | ||
1159 | ret.snippet_relative('custom_header.h.in') | |
1160 | ret.empty() | |
1161 | ||
1162 | if len(opts.fixup_header_lines) > 0: | |
1163 | ret.chdr_block_heading('Fixups') | |
1164 | for line in opts.fixup_header_lines: | |
1165 | ret.line(line) | |
1166 | ret.empty() | |
1167 | ||
1168 | add_override_defines_section(opts, ret) | |
1169 | ||
1170 | # Date provider snippet is after custom header and overrides, so that | |
1171 | # the user may define e.g. DUK_USE_DATE_NOW_GETTIMEOFDAY in their | |
1172 | # custom header. | |
1173 | ret.snippet_relative('date_provider.h.in') | |
1174 | ret.empty() | |
1175 | ||
1176 | # Sanity checks | |
1177 | # XXX: use autogenerated sanity checks instead | |
1178 | ret.snippet_relative('sanity.h.in') | |
1179 | ret.empty() | |
1180 | ||
1181 | if opts.emit_legacy_feature_check: | |
1182 | # XXX: this doesn't really make sense for the autodetect | |
1183 | # header yet, because sanity.h.in already covers these. | |
1184 | add_legacy_feature_option_checks(opts, ret) | |
1185 | if opts.emit_config_sanity_check: | |
1186 | add_config_option_checks(opts, ret) | |
1187 | if opts.add_active_defines_macro: | |
1188 | add_duk_active_defines_macro(ret) | |
1189 | ||
1190 | ret.line('#endif /* DUK_CONFIG_H_INCLUDED */') | |
1191 | ret.empty() # for trailing newline | |
1192 | return remove_duplicate_newlines(ret.join()) | |
1193 | ||
1194 | # Generate a duk_config.h where platform, architecture, and compiler are | |
1195 | # all either autodetected or specified by user. When autodetection is | |
1196 | # used, the generated header is based on modular snippets and metadata to | |
1197 | # be more easily maintainable than manually edited monolithic snippets. | |
1198 | # | |
1199 | # This approach will replace the legacy autodetect header in Duktape 1.4, | |
1200 | # and most likely the separate barebones header also. | |
1201 | # | |
1202 | # The generated header is Duktape 1.2 compatible for now, and supports | |
1203 | # DUK_OPT_xxx feature options. Later on the DUK_OPT_xxx options will be | |
1204 | # removed and user code overrides DUK_USE_xxx flags directly by modifying | |
1205 | # duk_config.h manually or by generating a new header using genconfig. | |
1206 | def generate_autodetect_duk_config_header_modular(opts, meta_dir): | |
1207 | ret = FileBuilder(base_dir=os.path.join(meta_dir, 'header-snippets'), \ | |
1208 | use_cpp_warning=opts.use_cpp_warning) | |
1209 | ||
1210 | forced_opts = get_forced_options(opts) | |
1211 | ||
1212 | platforms = None | |
1213 | with open(os.path.join(meta_dir, 'platforms.yaml'), 'rb') as f: | |
1214 | platforms = yaml.load(f) | |
1215 | architectures = None | |
1216 | with open(os.path.join(meta_dir, 'architectures.yaml'), 'rb') as f: | |
1217 | architectures = yaml.load(f) | |
1218 | compilers = None | |
1219 | with open(os.path.join(meta_dir, 'compilers.yaml'), 'rb') as f: | |
1220 | compilers = yaml.load(f) | |
1221 | ||
1222 | ret.line('/*') | |
1223 | ret.line(' * duk_config.h autodetect header generated by genconfig.py.') | |
1224 | ret.line(' *') | |
1225 | ret.line(' * Git commit: %s' % opts.git_commit or 'n/a') | |
1226 | ret.line(' * Git describe: %s' % opts.git_describe or 'n/a') | |
1227 | ret.line(' *') | |
1228 | if opts.platform is not None: | |
1229 | ret.line(' * Platform: ' + opts.platform) | |
1230 | else: | |
1231 | ret.line(' * Supported platforms:') | |
1232 | for platf in platforms['autodetect']: | |
1233 | ret.line(' * - %s' % platf.get('name', platf.get('check'))) | |
1234 | ret.line(' *') | |
1235 | if opts.architecture is not None: | |
1236 | ret.line(' * Architecture: ' + opts.architecture) | |
1237 | else: | |
1238 | ret.line(' * Supported architectures:') | |
1239 | for arch in architectures['autodetect']: | |
1240 | ret.line(' * - %s' % arch.get('name', arch.get('check'))) | |
1241 | ret.line(' *') | |
1242 | if opts.compiler is not None: | |
1243 | ret.line(' * Compiler: ' + opts.compiler) | |
1244 | else: | |
1245 | ret.line(' * Supported compilers:') | |
1246 | for comp in compilers['autodetect']: | |
1247 | ret.line(' * - %s' % comp.get('name', comp.get('check'))) | |
1248 | ret.line(' *') | |
1249 | ret.line(' */') | |
1250 | ret.empty() | |
1251 | ret.line('#ifndef DUK_CONFIG_H_INCLUDED') | |
1252 | ret.line('#define DUK_CONFIG_H_INCLUDED') | |
1253 | ret.empty() | |
1254 | ||
1255 | ret.chdr_block_heading('Intermediate helper defines') | |
1256 | ||
1257 | idx_deps = len(ret.vals) # position where to emit dependencies | |
1258 | ||
1259 | # Feature selection, system include, Date provider | |
1260 | # Most #include statements are here | |
1261 | ||
1262 | if opts.platform is not None: | |
1263 | ret.chdr_block_heading('Platform: ' + opts.platform) | |
1264 | ||
1265 | ret.snippet_relative('platform_cppextras.h.in') | |
1266 | ret.empty() | |
1267 | ||
1268 | # XXX: better to lookup platforms metadata | |
1269 | include = 'platform_%s.h.in' % opts.platform | |
1270 | validate_platform_file(os.path.join(meta_dir, 'header-snippets', include)) | |
1271 | ret.snippet_relative(include) | |
1272 | else: | |
1273 | ret.chdr_block_heading('Platform autodetection') | |
1274 | ||
1275 | ret.snippet_relative('platform_cppextras.h.in') | |
1276 | ret.empty() | |
1277 | ||
1278 | for idx, platf in enumerate(platforms['autodetect']): | |
1279 | check = platf.get('check', None) | |
1280 | include = platf['include'] | |
1281 | validate_platform_file(os.path.join(meta_dir, 'header-snippets', include)) | |
1282 | ||
1283 | if idx == 0: | |
1284 | ret.line('#if defined(%s)' % check) | |
1285 | else: | |
1286 | if check is None: | |
1287 | ret.line('#else') | |
1288 | else: | |
1289 | ret.line('#elif defined(%s)' % check) | |
1290 | ret.snippet_relative(include) | |
1291 | ret.line('#endif /* autodetect platform */') | |
1292 | ||
1293 | ret.snippet_relative('platform_sharedincludes.h.in') | |
1294 | ret.empty() | |
1295 | ||
1296 | if opts.architecture is not None: | |
1297 | ret.chdr_block_heading('Architecture: ' + opts.architecture) | |
1298 | ||
1299 | # XXX: better to lookup architectures metadata | |
1300 | include = 'architecture_%s.h.in' % opts.architecture | |
1301 | validate_architecture_file(os.path.join(meta_dir, 'header-snippets', include)) | |
1302 | ret.snippet_relative(include) | |
1303 | else: | |
1304 | ret.chdr_block_heading('Architecture autodetection') | |
1305 | ||
1306 | for idx, arch in enumerate(architectures['autodetect']): | |
1307 | check = arch.get('check', None) | |
1308 | include = arch['include'] | |
1309 | validate_architecture_file(os.path.join(meta_dir, 'header-snippets', include)) | |
1310 | ||
1311 | if idx == 0: | |
1312 | ret.line('#if defined(%s)' % check) | |
1313 | else: | |
1314 | if check is None: | |
1315 | ret.line('#else') | |
1316 | else: | |
1317 | ret.line('#elif defined(%s)' % check) | |
1318 | ret.snippet_relative(include) | |
1319 | ret.line('#endif /* autodetect architecture */') | |
1320 | ||
1321 | if opts.compiler is not None: | |
1322 | ret.chdr_block_heading('Compiler: ' + opts.compiler) | |
1323 | ||
1324 | # XXX: better to lookup compilers metadata | |
1325 | include = 'compiler_%s.h.in' % opts.compiler | |
1326 | validate_compiler_file(os.path.join(meta_dir, 'header-snippets', include)) | |
1327 | ret.snippet_relative(include) | |
1328 | else: | |
1329 | ret.chdr_block_heading('Compiler autodetection') | |
1330 | ||
1331 | for idx, comp in enumerate(compilers['autodetect']): | |
1332 | check = comp.get('check', None) | |
1333 | include = comp['include'] | |
1334 | validate_compiler_file(os.path.join(meta_dir, 'header-snippets', include)) | |
1335 | ||
1336 | if idx == 0: | |
1337 | ret.line('#if defined(%s)' % check) | |
1338 | else: | |
1339 | if check is None: | |
1340 | ret.line('#else') | |
1341 | else: | |
1342 | ret.line('#elif defined(%s)' % check) | |
1343 | ret.snippet_relative(include) | |
1344 | ret.line('#endif /* autodetect compiler */') | |
1345 | ||
1346 | # FIXME: The snippets below have some conflicts with the platform, | |
1347 | # architecture, and compiler snippets files included above. These | |
1348 | # need to be resolved so that (a) each define is only provided from | |
1349 | # one place or (b) the latter definition is a "fill-in" which is | |
1350 | # only used when a certain define is missing from e.g. a compiler | |
1351 | # snippet (useful for e.g. compiler defines which have sane, standard | |
1352 | # defaults). | |
1353 | ||
1354 | # FIXME: __uclibc__ needs stdlib.h, but it really is the only exception | |
1355 | ret.snippet_relative('libc.h.in') | |
1356 | ret.empty() | |
1357 | ||
1358 | # Number types | |
1359 | ret.snippet_relative('types1.h.in') | |
1360 | ret.line('#if defined(DUK_F_HAVE_INTTYPES)') | |
1361 | ret.line('/* C99 or compatible */') | |
1362 | ret.empty() | |
1363 | ret.snippet_relative('types_c99.h.in') | |
1364 | ret.empty() | |
1365 | ret.line('#else /* C99 types */') | |
1366 | ret.empty() | |
1367 | ret.snippet_relative('types_legacy.h.in') | |
1368 | ret.empty() | |
1369 | ret.line('#endif /* C99 types */') | |
1370 | ret.empty() | |
1371 | ret.snippet_relative('types2.h.in') | |
1372 | ret.empty() | |
1373 | ret.snippet_relative('64bitops.h.in') | |
1374 | ret.empty() | |
1375 | ||
1376 | # Alignment | |
1377 | ret.snippet_relative('alignment.h.in') | |
1378 | ret.empty() | |
1379 | ||
1380 | # Object layout | |
1381 | ret.snippet_relative('object_layout.h.in') | |
1382 | ret.empty() | |
1383 | ||
1384 | # Byte order | |
1385 | # FIXME: from the architecture snippet | |
1386 | ret.snippet_relative('byteorder.h.in') | |
1387 | ret.empty() | |
1388 | ||
1389 | # Packed duk_tval | |
1390 | # FIXME: from the architecture snippet | |
1391 | ret.snippet_relative('packed_tval.h.in') | |
1392 | ret.empty() | |
1393 | ||
1394 | # Detect 'fast math' | |
1395 | ret.snippet_relative('reject_fast_math.h.in') | |
1396 | ret.empty() | |
1397 | ||
1398 | # IEEE double constants | |
1399 | # FIXME: these should maybe be 'fill-ins' if previous headers | |
1400 | # didn't provide something | |
1401 | ret.snippet_relative('double_const.h.in') | |
1402 | ret.empty() | |
1403 | ||
1404 | # Math and other ANSI replacements, NetBSD workaround, paranoid math, paranoid Date | |
1405 | ret.snippet_relative('repl_math.h.in') | |
1406 | ret.empty() | |
1407 | ret.snippet_relative('paranoid_date.h.in') | |
1408 | ret.empty() | |
1409 | ret.snippet_relative('repl_ansi.h.in') | |
1410 | ret.empty() | |
1411 | ||
1412 | # Platform function pointers | |
1413 | ret.snippet_relative('platform_funcptr.h.in') | |
1414 | ret.empty() | |
1415 | ||
1416 | # General compiler stuff | |
1417 | ret.snippet_relative('stringify.h.in') | |
1418 | ret.empty() | |
1419 | ret.snippet_relative('segfault.h.in') | |
1420 | ret.empty() | |
1421 | ret.snippet_relative('unreferenced.h.in') | |
1422 | ret.empty() | |
1423 | ret.snippet_relative('noreturn.h.in') | |
1424 | ret.empty() | |
1425 | ret.snippet_relative('unreachable.h.in') | |
1426 | ret.empty() | |
1427 | ret.snippet_relative('likely.h.in') | |
1428 | ret.empty() | |
1429 | ret.snippet_relative('inline.h.in') | |
1430 | ret.empty() | |
1431 | ret.snippet_relative('visibility.h.in') | |
1432 | ret.empty() | |
1433 | ret.snippet_relative('file_line_func.h.in') | |
1434 | ret.empty() | |
1435 | ret.snippet_relative('byteswap.h.in') | |
1436 | ret.empty() | |
1437 | ||
1438 | # These come directly from platform, architecture, and compiler | |
1439 | # snippets. | |
1440 | #ret.snippet_relative('arch_string.h.in') | |
1441 | #ret.empty() | |
1442 | #ret.snippet_relative('os_string.h.in') | |
1443 | #ret.empty() | |
1444 | #ret.snippet_relative('compiler_string.h.in') | |
1445 | #ret.empty() | |
1446 | #ret.snippet_relative('longjmp.h.in') | |
1447 | #ret.empty() | |
1448 | ||
1449 | # Target info | |
1450 | ret.snippet_relative('target_info.h.in') | |
1451 | ret.empty() | |
1452 | ||
1453 | # Automatic DUK_OPT_xxx feature option handling | |
1454 | # FIXME: platform setjmp/longjmp defines now conflict with this | |
1455 | if True: | |
1456 | # Unsorted flags, contains almost all actual Duktape-specific | |
1457 | # but platform independent features | |
1458 | #ret.snippet_relative('unsorted_flags.h.in') | |
1459 | #ret.empty() | |
1460 | ||
1461 | add_feature_option_handling(opts, ret, forced_opts) | |
1462 | ||
1463 | ret.snippet_relative('user_declare.h.in') | |
1464 | ret.empty() | |
1465 | ||
1466 | # Emit forced options. If a corresponding option is already defined | |
1467 | # by a snippet above, #undef it first. | |
1468 | ||
1469 | tmp = Snippet(ret.join().split('\n')) | |
1470 | first_forced = True | |
1471 | for doc in use_defs_list: | |
1472 | defname = doc['define'] | |
1473 | ||
1474 | if doc.get('removed', None) is not None and opts.omit_removed_config_options: | |
1475 | continue | |
1476 | if doc.get('deprecated', None) is not None and opts.omit_deprecated_config_options: | |
1477 | continue | |
1478 | if doc.get('unused', False) == True and opts.omit_unused_config_options: | |
1479 | continue | |
1480 | if not forced_opts.has_key(defname): | |
1481 | continue | |
1482 | ||
1483 | if not doc.has_key('default'): | |
1484 | raise Exception('config option %s is missing default value' % defname) | |
1485 | ||
1486 | if first_forced: | |
1487 | ret.chdr_block_heading('Forced options') | |
1488 | first_forced = False | |
1489 | ||
1490 | undef_done = False | |
1491 | if tmp.provides.has_key(defname): | |
1492 | ret.line('#undef ' + defname) | |
1493 | undef_done = True | |
1494 | ||
1495 | emit_default_from_config_meta(ret, doc, forced_opts, undef_done) | |
1496 | ||
1497 | ret.empty() | |
1498 | ||
1499 | # If manually-edited snippets don't #define or #undef a certain | |
1500 | # config option, emit a default value here. This is useful to | |
1501 | # fill-in for new config options not covered by manual snippets | |
1502 | # (which is intentional). | |
1503 | ||
1504 | tmp = Snippet(ret.join().split('\n')) | |
1505 | need = {} | |
1506 | for doc in use_defs_list: | |
1507 | if doc.get('removed', None) is not None: # XXX: check version | |
1508 | continue | |
1509 | need[doc['define']] = True | |
1510 | for k in tmp.provides.keys(): | |
1511 | if need.has_key(k): | |
1512 | del need[k] | |
1513 | need_keys = sorted(need.keys()) | |
1514 | ||
1515 | if len(need_keys) > 0: | |
1516 | ret.chdr_block_heading('Autogenerated defaults') | |
1517 | ||
1518 | for k in need_keys: | |
1519 | #print('config option %s not covered by manual snippets, emitting default automatically' % k) | |
1520 | emit_default_from_config_meta(ret, use_defs[k], {}, False) | |
1521 | ||
1522 | ret.empty() | |
1523 | ||
1524 | ret.snippet_relative('custom_header.h.in') | |
1525 | ret.empty() | |
1526 | ||
1527 | if len(opts.fixup_header_lines) > 0: | |
1528 | ret.chdr_block_heading('Fixups') | |
1529 | for line in opts.fixup_header_lines: | |
1530 | ret.line(line) | |
1531 | ret.empty() | |
1532 | ||
1533 | add_override_defines_section(opts, ret) | |
1534 | ||
1535 | # Date provider snippet is after custom header and overrides, so that | |
1536 | # the user may define e.g. DUK_USE_DATE_NOW_GETTIMEOFDAY in their | |
1537 | # custom header. | |
1538 | ret.snippet_relative('date_provider.h.in') | |
1539 | ret.empty() | |
1540 | ||
1541 | ret.fill_dependencies_for_snippets(idx_deps) | |
1542 | ||
1543 | # FIXME: use autogenerated sanity instead of sanity.h.in | |
1544 | ||
1545 | ret.snippet_relative('sanity.h.in') | |
1546 | ret.empty() | |
1547 | ||
1548 | if opts.emit_legacy_feature_check: | |
1549 | # FIXME: this doesn't really make sense for the autodetect header yet | |
1550 | add_legacy_feature_option_checks(opts, ret) | |
1551 | if opts.emit_config_sanity_check: | |
1552 | add_config_option_checks(opts, ret) | |
1553 | if opts.add_active_defines_macro: | |
1554 | add_duk_active_defines_macro(ret) | |
1555 | ||
1556 | ret.line('#endif /* DUK_CONFIG_H_INCLUDED */') | |
1557 | ret.empty() # for trailing newline | |
1558 | return remove_duplicate_newlines(ret.join()) | |
1559 | ||
1560 | # Generate a barebones duk_config.h header for a specific platform, architecture, | |
1561 | # and compiler. The header won't do automatic feature detection and does not | |
1562 | # support DUK_OPT_xxx feature options (which will be removed in Duktape 2.x). | |
1563 | # Users can then modify this barebones header for very exotic platforms and manage | |
1564 | # the needed changes either as a YAML file or by appending a fixup header snippet. | |
1565 | # | |
1566 | # XXX: to be replaced by generate_modular_duk_config_header(). | |
1567 | def generate_barebones_duk_config_header(opts, meta_dir): | |
1568 | ret = FileBuilder(base_dir=os.path.join(meta_dir, 'header-snippets'), \ | |
1569 | use_cpp_warning=opts.use_cpp_warning) | |
1570 | ||
1571 | # XXX: Provide more defines from YAML config files so that such | |
1572 | # defines can be overridden more conveniently (e.g. DUK_COS). | |
1573 | ||
1574 | forced_opts = get_forced_options(opts) | |
1575 | ||
1576 | ret.line('/*') | |
1577 | ret.line(' * duk_config.h generated by genconfig.py for:') | |
1578 | ret.line(' * platform: %s' % opts.platform) | |
1579 | ret.line(' * compiler: %s' % opts.compiler) | |
1580 | ret.line(' * architecture: %s' % opts.architecture) | |
1581 | ret.line(' *') | |
1582 | ret.line(' * Git commit: %s' % opts.git_commit or 'n/a') | |
1583 | ret.line(' * Git describe: %s' % opts.git_describe or 'n/a') | |
1584 | ret.line(' */') | |
1585 | ret.empty() | |
1586 | ret.line('#ifndef DUK_CONFIG_H_INCLUDED') | |
1587 | ret.line('#define DUK_CONFIG_H_INCLUDED') | |
1588 | ||
1589 | ret.chdr_block_heading('Intermediate helper defines') | |
1590 | ||
1591 | idx_deps = len(ret.vals) # position where to emit dependencies | |
1592 | ||
1593 | ret.chdr_block_heading('Platform headers and typedefs') | |
1594 | ||
1595 | if opts.platform is None: | |
1596 | raise Exception('no platform specified') | |
1597 | ||
1598 | fn = 'platform_%s.h.in' % opts.platform | |
1599 | ret.snippet_relative(fn) | |
1600 | ret.empty() | |
1601 | ret.snippet_relative('types_c99.h.in') # XXX: C99 typedefs forced for now | |
1602 | ret.snippet_relative('types2.h.in') # XXX: boilerplate type stuff | |
1603 | ||
1604 | ret.chdr_block_heading('Platform features') | |
1605 | ||
1606 | # XXX: double constants | |
1607 | # XXX: replacement functions | |
1608 | # XXX: inherit definitions (like '#define DUK_FFLUSH fflush') from a | |
1609 | # generic set of defaults, allow platform configs to override | |
1610 | ||
1611 | ret.snippet_relative('platform_generic.h.in') | |
1612 | ||
1613 | ret.chdr_block_heading('Compiler features') | |
1614 | ||
1615 | if opts.compiler is None: | |
1616 | raise Exception('no compiler specified') | |
1617 | ||
1618 | fn = 'compiler_%s.h.in' % opts.compiler | |
1619 | ret.snippet_relative(fn) | |
1620 | ||
1621 | # noreturn, vacopy, etc | |
1622 | # visibility attributes | |
1623 | ||
1624 | ret.chdr_block_heading('Architecture features') | |
1625 | ||
1626 | if opts.architecture is None: | |
1627 | raise Exception('no architecture specified') | |
1628 | ||
1629 | fn = 'architecture_%s.h.in' % opts.architecture | |
1630 | ret.snippet_relative(fn) | |
1631 | ||
1632 | ret.chdr_block_heading('Config options') | |
1633 | ||
1634 | tags = get_tag_list_with_preferred_order(header_tag_order) | |
1635 | ||
1636 | handled = {} | |
1637 | ||
1638 | # Mark all defines 'provided' by the snippets so far as handled. | |
1639 | # For example, if the system header provides a DUK_USE_OS_STRING, | |
1640 | # we won't emit it again below with its default value (but will | |
1641 | # emit an override value if specified). | |
1642 | ||
1643 | for sn in ret.vals: | |
1644 | for k in sn.provides.keys(): | |
1645 | handled[k] = True | |
1646 | ||
1647 | for tag in tags: | |
1648 | ret.line('/* ' + get_tag_title(tag) + ' */') | |
1649 | ||
1650 | for doc in use_defs_list: | |
1651 | defname = doc['define'] | |
1652 | ||
1653 | if doc.get('removed', None) is not None and opts.omit_removed_config_options: | |
1654 | continue | |
1655 | if doc.get('deprecated', None) is not None and opts.omit_deprecated_config_options: | |
1656 | continue | |
1657 | if doc.get('unused', False) == True and opts.omit_unused_config_options: | |
1658 | continue | |
1659 | ||
1660 | if tag != doc['tags'][0]: # sort under primary tag | |
1661 | continue | |
1662 | ||
1663 | if not doc.has_key('default'): | |
1664 | raise Exception('config option %s is missing default value' % defname) | |
1665 | ||
1666 | undef_done = False | |
1667 | ||
1668 | if handled.has_key(defname): | |
1669 | defval = forced_opts.get(defname, None) | |
1670 | if defval is None: | |
1671 | ret.line('/* %s already emitted above */' % defname) | |
1672 | continue | |
1673 | ||
1674 | # Define already emitted by snippets above but | |
1675 | # an explicit override wants to redefine it. | |
1676 | # Undef first and then use shared handler to | |
1677 | # setup the forced value. | |
1678 | ret.line('#undef ' + defname) | |
1679 | undef_done = True | |
1680 | ||
1681 | # FIXME: macro args; DUK_USE_USER_DECLARE vs. DUK_USE_USER_DECLARE() | |
1682 | # vs. DUK_USE_USER_DECLARE(x,y) | |
1683 | ||
1684 | handled[defname] = True | |
1685 | emit_default_from_config_meta(ret, doc, forced_opts, undef_done) | |
1686 | ||
1687 | ret.empty() | |
1688 | ||
1689 | if len(opts.fixup_header_lines) > 0: | |
1690 | ret.chdr_block_heading('Fixups') | |
1691 | for line in opts.fixup_header_lines: | |
1692 | ret.line(line) | |
1693 | ||
1694 | add_override_defines_section(opts, ret) | |
1695 | ||
1696 | # Date provider snippet is after custom header and overrides, so that | |
1697 | # the user may define e.g. DUK_USE_DATE_NOW_GETTIMEOFDAY in their | |
1698 | # custom header. | |
1699 | ret.empty() | |
1700 | ret.snippet_relative('date_provider.h.in') | |
1701 | ret.empty() | |
1702 | ||
1703 | ret.fill_dependencies_for_snippets(idx_deps) | |
1704 | ||
1705 | # XXX: ensure no define is unhandled at the end | |
1706 | ||
1707 | # Check for presence of legacy feature options (DUK_OPT_xxx), | |
1708 | # and consistency of final DUK_USE_xxx options. | |
1709 | # | |
1710 | # These could also be emitted into Duktape source code, but it's | |
1711 | # probably better that the checks can be easily disabled from | |
1712 | # duk_config.h. | |
1713 | ||
1714 | if opts.emit_legacy_feature_check: | |
1715 | add_legacy_feature_option_checks(opts, ret) | |
1716 | if opts.emit_config_sanity_check: | |
1717 | add_config_option_checks(opts, ret) | |
1718 | if opts.add_active_defines_macro: | |
1719 | add_duk_active_defines_macro(ret) | |
1720 | ||
1721 | ret.line('#endif /* DUK_CONFIG_H_INCLUDED */') | |
1722 | ret.empty() # for trailing newline | |
1723 | ||
1724 | return remove_duplicate_newlines(serialize_snippet_list(ret.vals)) # XXX: refactor into FileBuilder | |
1725 | ||
1726 | # | |
1727 | # Main | |
1728 | # | |
1729 | ||
1730 | def main(): | |
1731 | # Forced options from multiple sources are gathered into a shared list | |
1732 | # so that the override order remains the same as on the command line. | |
1733 | force_options_yaml = [] | |
1734 | def add_force_option_yaml(option, opt, value, parser): | |
1735 | # XXX: check that YAML parses | |
1736 | force_options_yaml.append(value) | |
1737 | def add_force_option_file(option, opt, value, parser): | |
1738 | # XXX: check that YAML parses | |
1739 | with open(value, 'rb') as f: | |
1740 | force_options_yaml.append(f.read()) | |
1741 | def add_force_option_define(option, opt, value, parser): | |
1742 | tmp = value.split('=') | |
1743 | if len(tmp) == 1: | |
1744 | doc = { tmp[0]: True } | |
1745 | elif len(tmp) == 2: | |
1746 | doc = { tmp[0]: tmp[1] } | |
1747 | else: | |
1748 | raise Exception('invalid option value: %r' % value) | |
1749 | force_options_yaml.append(yaml.safe_dump(doc)) | |
1750 | def add_force_option_undefine(option, opt, value, parser): | |
1751 | tmp = value.split('=') | |
1752 | if len(tmp) == 1: | |
1753 | doc = { tmp[0]: False } | |
1754 | else: | |
1755 | raise Exception('invalid option value: %r' % value) | |
1756 | force_options_yaml.append(yaml.safe_dump(doc)) | |
1757 | ||
1758 | fixup_header_lines = [] | |
1759 | def add_fixup_header_line(option, opt, value, parser): | |
1760 | fixup_header_lines.append(value) | |
1761 | def add_fixup_header_file(option, opt, value, parser): | |
1762 | with open(value, 'rb') as f: | |
1763 | for line in f: | |
1764 | if line[-1] == '\n': | |
1765 | line = line[:-1] | |
1766 | fixup_header_lines.append(line) | |
1767 | ||
1768 | commands = [ | |
1769 | 'autodetect-header', | |
1770 | 'barebones-header', | |
1771 | 'feature-documentation', | |
1772 | 'config-documentation' | |
1773 | ] | |
1774 | parser = optparse.OptionParser( | |
1775 | usage='Usage: %prog [options] COMMAND', | |
1776 | description='Generate a duk_config.h or config option documentation based on config metadata.', | |
1777 | epilog='COMMAND can be one of: ' + ', '.join(commands) + '.' | |
1778 | ) | |
1779 | parser.add_option('--metadata', dest='metadata', default=None, help='metadata directory or metadata tar.gz file') | |
1780 | parser.add_option('--output', dest='output', default=None, help='output filename for C header or RST documentation file') | |
1781 | parser.add_option('--platform', dest='platform', default=None, help='platform (for "barebones-header" command)') | |
1782 | parser.add_option('--compiler', dest='compiler', default=None, help='compiler (for "barebones-header" command)') | |
1783 | parser.add_option('--architecture', dest='architecture', default=None, help='architecture (for "barebones-header" command)') | |
1784 | parser.add_option('--dll', dest='dll', action='store_true', default=False, help='dll build of Duktape, affects symbol visibility macros especially on Windows') # FIXME: unimplemented | |
1785 | 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)') | |
1786 | 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)') | |
1787 | 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') | |
1788 | 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') | |
1789 | 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') | |
1790 | 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') | |
1791 | 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"') | |
1792 | 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"') | |
1793 | 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"') | |
1794 | 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"') | |
1795 | 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")') | |
1796 | 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') | |
1797 | 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') | |
1798 | 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")') | |
1799 | 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') | |
1800 | parser.add_option('--use-cpp-warning', dest='use_cpp_warning', action='store_true', default=False, help='emit a (non-portable) #warning when appropriate') | |
1801 | parser.add_option('--git-commit', dest='git_commit', default=None, help='git commit hash to be included in header comments') | |
1802 | parser.add_option('--git-describe', dest='git_describe', default=None, help='git describe string to be included in header comments') | |
1803 | (opts, args) = parser.parse_args() | |
1804 | ||
1805 | meta_dir = opts.metadata | |
1806 | if opts.metadata is None: | |
1807 | if os.path.isfile(os.path.join('.', 'genconfig_metadata.tar.gz')): | |
1808 | opts.metadata = 'genconfig_metadata.tar.gz' | |
1809 | elif os.path.isdir(os.path.join('.', 'config-options')): | |
1810 | opts.metadata = '.' | |
1811 | ||
1812 | if opts.metadata is not None and os.path.isdir(opts.metadata): | |
1813 | meta_dir = opts.metadata | |
1814 | print 'Using metadata directory: %r' % meta_dir | |
1815 | elif opts.metadata is not None and os.path.isfile(opts.metadata) and tarfile.is_tarfile(opts.metadata): | |
1816 | meta_dir = get_auto_delete_tempdir() | |
1817 | tar = tarfile.open(name=opts.metadata, mode='r:*') | |
1818 | tar.extractall(path=meta_dir) | |
1819 | print 'Using metadata tar file %r, unpacked to directory: %r' % (opts.metadata, meta_dir) | |
1820 | else: | |
1821 | raise Exception('metadata source must be a directory or a tar.gz file') | |
1822 | ||
1823 | scan_snippets(os.path.join(meta_dir, 'header-snippets')) | |
1824 | scan_use_defs(os.path.join(meta_dir, 'config-options')) | |
1825 | scan_opt_defs(os.path.join(meta_dir, 'feature-options')) | |
1826 | scan_use_tags() | |
1827 | scan_tags_meta(os.path.join(meta_dir, 'tags.yaml')) | |
1828 | print('Scanned %d DUK_OPT_xxx, %d DUK_USE_XXX, %d helper snippets' % \ | |
1829 | (len(opt_defs.keys()), len(use_defs.keys()), len(helper_snippets))) | |
1830 | #print('Tags: %r' % use_tags_list) | |
1831 | ||
1832 | if len(args) == 0: | |
1833 | raise Exception('missing command') | |
1834 | cmd = args[0] | |
1835 | ||
1836 | if cmd == 'autodetect-header': | |
1837 | cmd = 'autodetect-header-legacy' | |
1838 | ||
1839 | if cmd == 'autodetect-header-legacy': | |
1840 | # Generate a duk_config.h similar to Duktape 1.2 feature detection, | |
1841 | # based on manually written monolithic snippets. | |
1842 | # To be replaced by modular header. | |
1843 | result = generate_autodetect_duk_config_header(opts, meta_dir) | |
1844 | with open(opts.output, 'wb') as f: | |
1845 | f.write(result) | |
1846 | elif cmd == 'autodetect-header-modular': | |
1847 | # Generate a duk_config.h similar to Duktape 1.2 feature detection. | |
1848 | # Platform, architecture, and compiler can each be either autodetected | |
1849 | # or specified by user. Generated header is based on modular snippets | |
1850 | # rather than a monolithic platform detection header. | |
1851 | result = generate_autodetect_duk_config_header_modular(opts, meta_dir) | |
1852 | with open(opts.output, 'wb') as f: | |
1853 | f.write(result) | |
1854 | elif cmd == 'barebones-header': | |
1855 | # Generate a duk_config.h with default options for a specific platform, | |
1856 | # compiler, and architecture. | |
1857 | result = generate_barebones_duk_config_header(opts, meta_dir) | |
1858 | with open(opts.output, 'wb') as f: | |
1859 | f.write(result) | |
1860 | elif cmd == 'feature-documentation': | |
1861 | result = generate_feature_option_documentation(opts) | |
1862 | with open(opts.output, 'wb') as f: | |
1863 | f.write(result) | |
1864 | elif cmd == 'config-documentation': | |
1865 | result = generate_config_option_documentation(opts) | |
1866 | with open(opts.output, 'wb') as f: | |
1867 | f.write(result) | |
1868 | else: | |
1869 | raise Exception('invalid command: %r' % cmd) | |
1870 | ||
1871 | if __name__ == '__main__': | |
1872 | main() |