#!/usr/bin/env python from __future__ import print_function import os, re, sys, textwrap from dist import compare_srcfile, format_srcfile test_config = False # This file serves two purposes, it can generate configuration for the main wiredtiger library and, # it can generate configuration for the c and cpp suite tests. To avoid duplication we import the # differing apis here and then treat them as the same for the remainder of the script. However we # do have different logic depending on whether we intend to generate the test api or not, which is # managed with a boolean flag. if len(sys.argv) == 1 or sys.argv[1] != "-t": import api_data as api_data_def else: test_config = True import test_data as api_data_def # Temporary file. tmp_file = '__tmp' ##################################################################### # Update wiredtiger.in with doxygen comments ##################################################################### f='../src/include/wiredtiger.in' tfile = open(tmp_file, 'w') whitespace_re = re.compile(r'\s+') cbegin_re = re.compile(r'(\s*\*\s*)@config(?:empty|start)\{(.*?),.*\}') def gettype(c): '''Derive the type of a config item''' checks = c.flags ctype = checks.get('type', None) if not ctype and ('min' in checks or 'max' in checks): ctype = 'int' return ctype or 'string' def typedesc(c): '''Descripe what type of value is expected for the given config item''' checks = c.flags cmin = str(checks.get('min', '')) cmax = str(checks.get('max', '')) choices = checks.get('choices', []) ctype = gettype(c) desc = { 'boolean' : 'a boolean flag', 'format' : 'a format string', 'int' : 'an integer', 'list' : 'a list', 'category': 'a set of related configuration options defined below', 'string' : 'a string'}[ctype] if cmin and cmax: desc += ' between ' + cmin + ' and ' + cmax elif cmin: desc += ' greater than or equal to ' + cmin elif cmax: desc += ' no more than ' + cmax if choices: if ctype == 'list': desc += ', with values chosen from the following options: ' else: desc += ', chosen from the following options: ' desc += ', '.join('\\c "' + c + '"' for c in choices) elif ctype == 'list': desc += ' of strings' return desc def parseconfig(c, method_name, name_indent=''): c.method_name = method_name ctype = gettype(c) desc = whitespace_re.sub(' ', c.desc.strip()) desc = desc.strip('.') + '.' desc = desc.replace(',', '\\,') default = '\\c ' + str(c.default) if c.default or ctype == 'int' \ else 'empty' name = name_indent + c.name tdesc = typedesc(c) if ctype != 'category': tdesc += '; default ' + default else: name += ' = (' tdesc += '.' tdesc = tdesc.replace(',', '\\,') output = '@config{' + ', '.join((name, desc, tdesc)) + '}\n' if ctype == 'category': for subc in sorted(c.subconfig): output += parseconfig(subc, method_name, \ name_indent + (' ' * 4)) output += '@config{ ),,}\n' return output def getconfcheck(c): check = '{ "' + c.name + '", "' + gettype(c) + '",' cstr = checkstr(c) sstr = getsubconfigstr(c) if cstr != 'NULL': cstr = '"\n\t "'.join(w.wrap(cstr)) # Manually re-wrap when there is a check string to avoid ugliness # between string and non-string wrapping if len(check + ' ' + cstr + ',\n\t ' + sstr + '},') >= 68: check = check + '\n\t ' + cstr + ',\n\t ' + sstr + ' },' else: check = check + ' ' + cstr + ', ' + sstr + ' },' else: check = '\n\t '.join( w.wrap(check + ' ' + cstr + ', ' + sstr + ' },')) return check if not test_config: skip = False for line in open(f, 'r'): if skip: if '@configend' in line: skip = False continue m = cbegin_re.match(line) if not m: tfile.write(line) continue prefix, config_name = m.groups() if config_name not in api_data_def.methods: print("Missing configuration for " + config_name, file=sys.stderr) tfile.write(line) continue skip = ('@configstart' in line) if not api_data_def.methods[config_name].config: tfile.write(prefix + '@configempty{' + config_name + ', see dist/api_data.py}\n') continue tfile.write(prefix + '@configstart{' + config_name + ', see dist/api_data.py}\n') w = textwrap.TextWrapper(width=100-len(prefix.expandtabs()), break_on_hyphens=False, break_long_words=False, replace_whitespace=False, fix_sentence_endings=True) # Separate at spaces, and after a set of non-breaking space indicators. w.wordsep_re = w.wordsep_simple_re = \ re.compile(r'(\s+|(?<= )[\w_,.;:]+)') for c in api_data_def.methods[config_name].config: if 'undoc' in c.flags: continue output = parseconfig(c, config_name) for l in w.wrap(output): tfile.write(prefix + l.replace('\n', '\n' + prefix) + '\n') tfile.write(prefix + '@configend\n') tfile.close() compare_srcfile(tmp_file, f) ##################################################################### # Create config_def.c with defaults for each config string ##################################################################### f='../src/config/config_def.c' if test_config: f = '../src/config/test_config.c' tfile = open(tmp_file, 'w') tfile.write('''/* DO NOT EDIT: automatically built by dist/api_config.py. */ #include "wt_internal.h" ''') # Make a TextWrapper that wraps at commas. w = textwrap.TextWrapper(width=64, break_on_hyphens=False, break_long_words=False) w.wordsep_re = w.wordsep_simple_re = re.compile(r'(,)') # TextWrapper that wraps at whitespace. ws = textwrap.TextWrapper(width=64, break_on_hyphens=False, break_long_words=False) def checkstr(c): '''Generate the function reference and JSON string used by __wt_config_check to validate the config string''' checks = c.flags cfunc = str(checks.get('func', '')) if not cfunc: cfunc = 'NULL'; cmin = str(checks.get('min', '')) cmax = str(checks.get('max', '')) choices = checks.get('choices', []) result = [] if cmin: result.append('min=' + cmin) if cmax: result.append('max=' + cmax) if choices: result.append('choices=' + '[' + ','.join('\\"' + s + '\\"' for s in choices) + ']') if result: return cfunc + ', "' + ','.join(result) + '"' else: return cfunc + ', NULL' def get_default(c): t = gettype(c) if c.default == 'false': return 'false' elif c.default == 'true': return 'true' elif t == 'string' and c.default == 'none' and \ not c.flags.get('choices', []): return '' elif t == 'category': return '(%s)' % (','.join('%s=%s' % (subc.name, get_default(subc)) for subc in sorted(c.subconfig))) elif c.default or t == 'int': return str(c.default).replace('"', '\\"') else: return '' created_subconfigs=set() def add_subconfig(c, cname): if cname in created_subconfigs: return created_subconfigs.add(cname) tfile.write(''' %(name)s[] = { \t%(check)s \t{ NULL, NULL, NULL, NULL, NULL, 0 } }; ''' % { 'name' : '\n '.join(ws.wrap(\ 'static const WT_CONFIG_CHECK confchk_' + cname + '_subconfigs')), 'check' : '\n\t'.join(getconfcheck(subc) for subc in sorted(c.subconfig)), }) def getcname(c): '''Return the C name of a sub configuration''' prefix = c.method_name.replace('.', '_') + '_' \ if hasattr(c, 'method_name') else '' return prefix + c.name def getsubconfigstr(c): '''Return a string indicating if an item has sub configuration''' ctype = gettype(c) if ctype == 'category': cname = getcname(c) add_subconfig(c, cname) return 'confchk_' + cname + '_subconfigs, ' + str(len(c.subconfig)) else: return 'NULL, 0' # Write structures of arrays of allowable configuration options, including a # NULL as a terminator for iteration. for name in sorted(api_data_def.methods.keys()): config = api_data_def.methods[name].config if config: tfile.write(''' static const WT_CONFIG_CHECK confchk_%(name)s[] = { \t%(check)s \t{ NULL, NULL, NULL, NULL, NULL, 0 } }; ''' % { 'name' : name.replace('.', '_'), 'check' : '\n\t'.join(getconfcheck(c) for c in config), }) # Write the initialized list of configuration entry structures. tfile.write('\n') tfile.write('static const WT_CONFIG_ENTRY config_entries[] = {') slot=-1 config_defines = '' for name in sorted(api_data_def.methods.keys()): config = api_data_def.methods[name].config slot += 1 # Build a list of #defines that reference specific slots in the list (the # #defines are used to avoid a list search where we know the correct slot). config_defines +=\ '#define\tWT_CONFIG_ENTRY_' + name.replace('.', '_') + '\t' * \ max(1, 6 - (len('WT_CONFIG_ENTRY_' + name) // 8)) + \ "%2s" % str(slot) + '\n' # Write the method name and base. tfile.write(''' \t{ "%(name)s", %(config)s,''' % { 'config' : '\n'.join('\t "%s"' % line for line in w.wrap(','.join('%s=%s' % (c.name, get_default(c)) for c in config)) or [""]), 'name' : name }) # Write the checks reference, or NULL if no related checks structure. tfile.write('\n\t ') if config: tfile.write( 'confchk_' + name.replace('.', '_') + ', ' + str(len(config))) else: tfile.write('NULL, 0') tfile.write('\n\t},') # Write a NULL as a terminator for iteration. tfile.write('\n\t{ NULL, NULL, NULL, 0 }') tfile.write('\n};\n') # Write the routine that connects the WT_CONNECTION_IMPL structure to the list # of configuration entry structures. if not test_config: tfile.write(''' int __wt_conn_config_init(WT_SESSION_IMPL *session) { \tWT_CONNECTION_IMPL *conn; \tconst WT_CONFIG_ENTRY *ep, **epp; \tconn = S2C(session); \t/* Build a list of pointers to the configuration information. */ \tWT_RET(__wt_calloc_def(session, WT_ELEMENTS(config_entries), &epp)); \tconn->config_entries = epp; \t/* Fill in the list to reference the default information. */ \tfor (ep = config_entries;;) { \t\t*epp++ = ep++; \t\tif (ep->method == NULL) \t\t\tbreak; \t} \treturn (0); } void __wt_conn_config_discard(WT_SESSION_IMPL *session) { \tWT_CONNECTION_IMPL *conn; \tconn = S2C(session); \t__wt_free(session, conn->config_entries); } /* * __wt_conn_config_match -- * Return the static configuration entry for a method. */ const WT_CONFIG_ENTRY * __wt_conn_config_match(const char *method) { \tconst WT_CONFIG_ENTRY *ep; \tfor (ep = config_entries; ep->method != NULL; ++ep) \t\tif (strcmp(method, ep->method) == 0) \t\t\treturn (ep); \treturn (NULL); } ''') else: tfile.write( ''' /* * __wt_test_config_match -- * Return the static configuration entry for a test. */ const WT_CONFIG_ENTRY * __wt_test_config_match(const char *test_name) { const WT_CONFIG_ENTRY *ep; for (ep = config_entries; ep->method != NULL; ++ep) if (strcmp(test_name, ep->method) == 0) return (ep); return (NULL); } ''' ) tfile.close() format_srcfile(tmp_file) compare_srcfile(tmp_file, f) # Update the config.h file with the #defines for the configuration entries. if not test_config: tfile = open(tmp_file, 'w') skip = 0 config_file = '../src/include/config.h' for line in open(config_file, 'r'): if skip: if 'configuration section: END' in line: tfile.write('/*\n' + line) skip = 0 else: tfile.write(line) if 'configuration section: BEGIN' in line: skip = 1 tfile.write(' */\n') tfile.write(config_defines) tfile.close() format_srcfile(tmp_file) compare_srcfile(tmp_file, config_file)