This tool is used to generate parsexml/formatbuf functions for structs. It is based on libclang and its python-binding. Some directives (such as genparse, xmlattr, etc.) need to be added on the declarations of structs to direct the tool. Signed-off-by: Shi Lei <shi_lei@xxxxxxxxxxxxxx> --- build-aux/generator/directive.py | 839 +++++++++++++++++++++++++++++++ build-aux/generator/go | 14 + build-aux/generator/main.py | 416 +++++++++++++++ build-aux/generator/utils.py | 100 ++++ po/POTFILES.in | 1 + 5 files changed, 1370 insertions(+) create mode 100644 build-aux/generator/directive.py create mode 100755 build-aux/generator/go create mode 100755 build-aux/generator/main.py create mode 100644 build-aux/generator/utils.py diff --git a/build-aux/generator/directive.py b/build-aux/generator/directive.py new file mode 100644 index 0000000..c0d3c61 --- /dev/null +++ b/build-aux/generator/directive.py @@ -0,0 +1,839 @@ +# +# Copyright (C) 2020 Shandong Massclouds Co.,Ltd. +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library. If not, see +# <http://www.gnu.org/licenses/>. +# + +import json +from collections import OrderedDict +from utils import singleton +from utils import dedup, counterName +from utils import BlockAssembler +from utils import Terms, singleline, indent, render, renderByDict + +BUILTIN_TYPES = { + 'Bool': {}, + 'String': {}, + 'Chars': { + 'conv': 'virStrcpyStatic(def->${name}, ${name}Str)' + }, + 'UChars': { + 'conv': 'virStrcpyStatic((char *)def->${name}, ${mdvar})' + }, + 'Int': { + 'fmt': '%d', + 'conv': 'virStrToLong_i(${mdvar}, NULL, 0, &def->${name})' + }, + 'UInt': { + 'fmt': '%u', + 'conv': 'virStrToLong_uip(${mdvar}, NULL, 0, &def->${name})' + }, + 'ULong': { + 'fmt': '%lu', + 'conv': 'virStrToLong_ulp(${mdvar}, NULL, 0, &def->${name})' + }, + 'ULongLong': { + 'fmt': '%llu', + 'conv': 'virStrToLong_ullp(${mdvar}, NULL, 0, &def->${name})' + }, + 'U8': { + 'fmt': '%u', + 'conv': 'virStrToLong_u8p(${mdvar}, NULL, 0, &def->${name})' + }, + 'U32': { + 'fmt': '%u', + 'conv': 'virStrToLong_uip(${mdvar}, NULL, 0, &def->${name})' + }, +} + + +@singleton +class TypeTable(OrderedDict): + def __init__(self): + OrderedDict.__init__(self) + for name, kvs in BUILTIN_TYPES.items(): + kvs['name'] = name + kvs['meta'] = 'Builtin' + self[name] = kvs + + def register(self, kvs): + name = kvs['name'] + if name not in self: + self[name] = kvs + return name + + def get(self, name): + if name in self: + return self[name] + return {'meta': 'Struct', 'name': name, 'external': True} + + +T_NAMESPACE_PARSE = ''' +if (xmlopt) + def->ns = xmlopt->ns; +if (def->ns.parse) { + if (virXMLNamespaceRegister(ctxt, &def->ns) < 0) + goto error; + if ((def->ns.parse)(ctxt, &def->namespaceData) < 0) + goto error; +} +''' + +T_NAMESPACE_FORMAT_BEGIN = ''' +if (def->namespaceData && def->ns.format) + virXMLNamespaceFormatNS(buf, &def->ns); +''' + +T_NAMESPACE_FORMAT_END = ''' +if (def->namespaceData && def->ns.format) { + if ((def->ns.format)(buf, def->namespaceData) < 0) + return -1; +} +''' + +T_CLEAR_FUNC_IMPL = ''' +void +${typename}Clear(${typename}Ptr def) +{ + if (!def) + return; + + ${body} +} +''' + +T_CLEAR_FUNC_DECL = ''' +void +${typename}Clear(${typename}Ptr def); +''' + + +def clearMember(member): + mtype = TypeTable().get(member['type']) + + refname = 'def->%s' % member['name'] + if member.get('array'): + refname += '[i]' + + code = '' + if mtype['meta'] == 'Struct': + if member['pointer'] and not mtype.get('external'): + code = '%sClear(%s);' % (mtype['name'], refname) + code += '\nVIR_FREE(%s);' % refname + else: + code = '%sClear(&%s);' % (mtype['name'], refname) + elif mtype['name'] == 'String': + code = 'VIR_FREE(%s);' % refname + elif mtype['name'] in ['Chars', 'UChars']: + code = 'memset(%s, 0, sizeof(%s));' % (refname, refname) + elif not member.get('array'): + code = '%s = 0;' % refname + + if member.get('specified'): + assert not member.get('array'), "'specified' can't come with 'array'." + code += '\n%s_specified = false;' % refname + + if member.get('array') and code: + counter = counterName(member['name']) + if singleline(code): + code = render(T_LOOP_SINGLE, counter=counter, body=code) + else: + code = render(T_LOOP_MULTI, + counter=counter, body=indent(code, 2)) + code += '\nVIR_FREE(def->%s);' % member['name'] + code += '\ndef->%s = 0;' % counter + + return code + + +T_CLEAR_NAMESPACE = ''' +if (def->namespaceData && def->ns.free) + (def->ns.free)(def->namespaceData); +''' + + +def makeClearFunc(writer, atype): + if 'genparse' not in atype: + return + + blocks = BlockAssembler() + for member in atype['members']: + blocks.append(clearMember(member)) + + if 'namespace' in atype: + blocks.append(T_CLEAR_NAMESPACE.strip()) + + body = indent(blocks.output('\n\n'), 1) + + impl = render(T_CLEAR_FUNC_IMPL, typename=atype['name'], body=body) + writer.write(atype, 'clearfunc', '.c', impl) + + decl = render(T_CLEAR_FUNC_DECL, typename=atype['name']) + writer.write(atype, 'clearfunc', '.h', decl) + + +# +# Templates for parsing member block +# +T_READ_ATTR_BY_PROP = '${mdvar} = virXMLPropString(node, "${tname}");' +T_READ_ELEM_BY_PROP = '${mdvar} = virXMLChildNode(node, "${tname}");' +T_READ_ELEM_CONTENT = '${mdvar} = virXMLChildNodeContent(node, "${tname}");' + +T_PARSE_MEMBER_MORE = ''' +${number} = virXMLChildNodeSet(node, "${tname}", &nodes); +if (${number} > 0) { + size_t i; + + if (VIR_ALLOC_N(def->${name}, ${number}) < 0) + goto error; + + for (i = 0; i < ${number}; i++) { + xmlNodePtr tnode = nodes[i]; + ${item} + } + def->${counter} = ${number}; + VIR_FREE(nodes); +} else if (${number} < 0) { + virReportError(VIR_ERR_XML_ERROR, "%s", + _("Invalid ${tname} element found.")); + goto error; +}${report_missing} +''' + +T_CHECK_INVALID_ERROR = ''' +if (${tmpl}) { + virReportError(VIR_ERR_XML_ERROR, + _("Invalid '${tname}' setting '%s' in '%s'"), + ${mdvar}, instname); + goto error; +} +''' + +T_MISSING_ERROR = ''' +{ + virReportError(VIR_ERR_XML_ERROR, + _("Missing '${tname}' setting in '%s'"), + instname); + goto error; +} +''' + +T_CHECK_MISSING_ERROR = 'if (${mdvar} == NULL) ' + T_MISSING_ERROR.strip() + +T_ALLOC_MEMORY = ''' +if (VIR_ALLOC(def->${name}) < 0) + goto error; +''' + +T_STRUCT_ASSIGNMENT_TEMPLATE = ''' +if (${funcname}(${mdvar}, ${amp}${refname}, instname, NULL) < 0) + goto error; +''' + + +def parseMember(member, atype, tmpvars): + if not member.get('xmlattr') and not member.get('xmlelem'): + return None + + tname = member['xmlattr'] if member.get('xmlattr') else member['xmlelem'] + mtype = TypeTable().get(member['type']) + + # + # Helper functions + # + def _readXMLByXPath(mdvar): + if member.get('xmlattr'): + tmpl = T_READ_ATTR_BY_PROP + elif mtype['meta'] == 'Struct': + tmpl = T_READ_ELEM_BY_PROP + else: + tmpl = T_READ_ELEM_CONTENT + return render(tmpl, mdvar=mdvar, tname=tname) + + def _assignValue(name, mdvar): + refname = 'def->' + name + if member.get('callback') or mtype['meta'] == 'Struct': + if member.get('callback'): + funcname = member['callback'] + 'ParseXML' + else: + funcname = mtype['name'] + 'ParseXML' + + tmpl = '' + if member['pointer'] and not mtype.get('external'): + tmpl += T_ALLOC_MEMORY + tmpl += T_STRUCT_ASSIGNMENT_TEMPLATE + + if (member['pointer'] and not mtype.get('external')) or \ + mtype['name'] in ['Chars', 'UChars']: + amp = '' + else: + amp = '&' + + tmpl = render(tmpl, funcname=funcname, amp=amp, mdvar=mdvar) + elif mtype['meta'] == 'Enum': + formalname = mtype['name'] + if not formalname.endswith('Type'): + formalname += 'Type' + tmpl = '(def->${name} = %sFromString(%s)) <= 0' \ + % (formalname, mdvar) + elif mtype['name'] == 'Bool': + tmpl = 'virStrToBool(${mdvar}, "%s", &def->${name}) < 0' \ + % member.get('truevalue', 'yes') + elif mtype['name'] == 'String': + tmpl = 'def->${name} = g_strdup(${mdvar});' + else: + tmpl = None + builtin = BUILTIN_TYPES.get(mtype['name']) + if builtin: + tmpl = builtin.get('conv', None) + if tmpl: + tmpl += ' < 0' + + if not tmpl: + return None + + if not member.get('callback') and mtype['meta'] != 'Struct' and \ + mdvar.endswith('Str') and (mtype['name'] != 'String'): + tmpl = render(T_CHECK_INVALID_ERROR, + tmpl=tmpl, tname=tname, mdvar=mdvar) + + ret = render(tmpl, refname=refname, name=name, + tname=tname, mdvar=mdvar) + + if member.get('specified') and not member.get('array'): + ret += '\ndef->%s_specified = true;' % name + return ret + + def _assignValueOnCondition(name, mdvar): + block = _assignValue(name, mdvar) + if not block: + return None + + if member.get('required'): + ret = render(T_CHECK_MISSING_ERROR, mdvar=mdvar, tname=tname) + ret += '\n\n' + block + return ret + + if singleline(block): + return render(T_IF_SINGLE, condition=mdvar, body=block) + else: + return render(T_IF_MULTI, condition=mdvar, body=indent(block, 1)) + + # + # Main routine + # + name = member['name'] + + # For sequence-type member + if member.get('array'): + node_num = 'n%sNodes' % Terms.upperInitial(tname) + tmpvars.append(node_num) + tmpvars.append('nodes') + counter = counterName(member['name']) + + report_missing = '' + if member.get('required'): + report_missing = ' else ' + render(T_MISSING_ERROR, tname=tname) + + if mtype['meta'] != 'Struct': + item = 'def->%s[i] = virXMLNodeContentString(tnode);' % name + else: + item = _assignValue(name + '[i]', 'tnode') + + return render(T_PARSE_MEMBER_MORE, name=name, counter=counter, + number=node_num, item=indent(item, 2), + report_missing=report_missing, tname=tname) + + # For ordinary member + mdvar = member['name'].replace('.', '_') + if member.get('xmlattr') or mtype['meta'] != 'Struct': + mdvar += 'Str' + else: + mdvar += 'Node' + tmpvars.append(mdvar) + + blocks = BlockAssembler() + blocks.append(_readXMLByXPath(mdvar)) + blocks.append(_assignValueOnCondition(name, mdvar)) + return blocks.output() + + +def align(funcname): + return ' ' * (len(funcname) + 1) + + +T_PARSE_FUNC_DECL = ''' +int +${funcname}(${args}); +''' + +T_PARSE_FUNC_IMPL = ''' +int +${funcname}(${args}) +{ + ${declare_vars} + VIR_USED(instname); + VIR_USED(opaque); + + if (!def) + goto error; + + ${body} + + return 0; + + error: + ${cleanup_vars} + ${typename}Clear(def); + return -1; +} +''' + +T_PARSE_FUNC_POST_INVOKE = ''' +if (${funcname}Hook(${args}) < 0) + goto error; +''' + + +def _handleTmpVars(tmpvars): + heads, tails = [], [] + tmpvars = dedup(tmpvars) + for var in tmpvars: + if var == 'nodes': + heads.append('xmlNodePtr *nodes = NULL;') + tails.append('VIR_FREE(nodes);') + elif var.endswith('Str'): + heads.append('g_autofree char *%s = NULL;' % var) + elif var.endswith('Node'): + heads.append('xmlNodePtr %s = NULL;' % var) + else: + assert var.endswith('Nodes') and var.startswith('n') + heads.append('int %s = 0;' % var) + + return '\n'.join(heads), '\n'.join(tails) + + +def makeParseFunc(writer, atype): + if 'genparse' not in atype: + return + + typename = atype['name'] + funcname = typename + 'ParseXML' + alignment = align(funcname) + + formal_args = [ + 'xmlNodePtr node', typename + 'Ptr def', + 'const char *instname', 'void *opaque' + ] + + actual_args = ['node', 'def', 'instname', 'opaque'] + + if 'namespace' in atype: + formal_args.append('xmlXPathContextPtr ctxt') + actual_args.append('ctxt') + formal_args.append('virNetworkXMLOptionPtr xmlopt') + actual_args.append('xmlopt') + + kwargs = {'funcname': funcname, 'typename': typename, + 'args': (',\n%s' % alignment).join(formal_args)} + + tmpvars = [] + blocks = BlockAssembler() + for member in atype['members']: + blocks.append(parseMember(member, atype, tmpvars)) + + decl = renderByDict(T_PARSE_FUNC_DECL, kwargs) + + if atype['genparse'] in ['withhook', 'concisehook']: + if atype['genparse'] == 'withhook': + for var in tmpvars: + if var.endswith('Str') or var.endswith('Node') or \ + var.endswith('Nodes') and var.startswith('n'): + actual_args.append(var) + + actual_args = ', '.join(actual_args) if actual_args else '' + post = render(T_PARSE_FUNC_POST_INVOKE, funcname=funcname, + args=actual_args) + blocks.append(post) + + if atype['genparse'] == 'withhook': + for var in tmpvars: + line = None + if var.endswith('Str'): + line = 'const char *' + var + elif var.endswith('Node'): + line = 'xmlNodePtr ' + var + elif var.endswith('Nodes') and var.startswith('n'): + line = 'int ' + var + + if line: + formal_args.append(line) + + connector = ',\n' + alignment + 4 * ' ' + decl += '\n' + render(T_PARSE_FUNC_DECL, funcname=funcname + 'Hook', + args=connector.join(formal_args)) + + writer.write(atype, 'parsefunc', '.h', decl) + + if 'namespace' in atype: + blocks.append(T_NAMESPACE_PARSE.strip()) + + kwargs['body'] = indent(blocks.output('\n\n'), 1) + + declare_vars, cleanup_vars = _handleTmpVars(tmpvars) + kwargs['declare_vars'] = indent(declare_vars, 1) + kwargs['cleanup_vars'] = indent(cleanup_vars, 1) + + impl = renderByDict(T_PARSE_FUNC_IMPL, kwargs) + writer.write(atype, 'parsefunc', '.c', impl) + + +T_FORMAT_FUNC_DECL = ''' +int +${typename}FormatBuf(virBufferPtr buf, +${alignment}const char *name, +${alignment}const ${typename} *def, +${alignment}void *opaque); +''' + +T_FORMAT_FUNC_IMPL = ''' +int +${typename}FormatBuf(virBufferPtr buf, +${alignment}const char *name, +${alignment}const ${typename} *def, +${alignment}void *opaque) +{ + VIR_USED(opaque); + + if (!def) + return 0; + + ${format_members} + + return 0; +} +''' + +T_FORMAT_CHECK_DECL = ''' +bool +${typename}Check(const ${typename} *def, void *opaque); +''' + +T_FORMAT_CHECK_IMPL = ''' +bool +${typename}Check(const ${typename} *def, void *opaque) +{ + VIR_USED(opaque); + + if (!def) + return false; + + return ${check}; +} +''' + +T_FORMAT_ELEMENTS = ''' +virBufferAddLit(buf, ">\\n"); + +virBufferAdjustIndent(buf, 2); + +${elements} + +virBufferAdjustIndent(buf, -2); +virBufferAsprintf(buf, "</%s>\\n", name); +''' + +T_FORMAT_SHORTHAND = ''' +if (!(${checks})) { + virBufferAddLit(buf, "/>\\n"); + return 0; +} +''' + +T_IF_SINGLE = ''' +if (${condition}) + ${body} +''' + +T_IF_MULTI = ''' +if (${condition}) { + ${body} +} +''' + +T_LOOP_SINGLE = ''' +if (def->${counter} > 0) { + size_t i; + for (i = 0; i < def->${counter}; i++) + ${body} +} +''' + +T_LOOP_MULTI = ''' +if (def->${counter} > 0) { + size_t i; + for (i = 0; i < def->${counter}; i++) { + ${body} + } +} +''' + +T_FORMAT_MEMBER_OF_ENUM = ''' +const char *str = ${fullname}ToString(${var}); +if (!str) { + virReportError(VIR_ERR_INTERNAL_ERROR, + _("Unknown ${tname} type %d"), + ${var}); + return -1; +} +virBufferAsprintf(buf, "${layout}", str); +''' + + +def formatMember(member, require, ret_checks): + if not member.get('xmlattr') and not member.get('xmlelem'): + return None + + mtype = TypeTable().get(member['type']) + + # + # Helper functions. + # + def _checkOnCondition(var): + if member.get('array'): + return None + + t = TypeTable().get(member['type']) + + ret = None + if 'checkformat' in member: + ret = '%s(&%s, opaque)' % (member['checkformat'], var) + elif member['pointer']: + ret = var + elif member.get('specified'): + ret = var + '_specified' + if ret.startswith('&'): + ret = ret[1:] + elif t['meta'] == 'Struct': + ret = '%sCheck(&%s, opaque)' % (t['name'], var) + elif member.get('required'): + pass + elif t['meta'] == 'Enum': + ret = var + elif t['meta'] == 'Builtin': + if t['name'] in ['Chars', 'UChars']: + ret = var + '[0]' + else: + ret = var + + return ret + + def _handleMore(code): + code = indent(code, 2) + counter = counterName(member['name']) + ret_checks.append('def->' + counter) + if singleline(code): + return render(T_LOOP_SINGLE, counter=counter, body=code) + else: + return render(T_LOOP_MULTI, counter=counter, body=code) + + def _format(layout, var): + tmpl = '${funcname}(buf, "${layout}", ${var})' + + funcname = 'virBufferAsprintf' + has_return = False + if member.get('callback') or mtype['meta'] == 'Struct': + if member.get('callback'): + funcname = member['callback'] + 'FormatBuf' + else: + funcname = mtype['name'] + 'FormatBuf' + + has_return = True + if not member['pointer'] and \ + mtype['name'] not in ['Chars', 'UChars']: + var = '&' + var + + var = '%s, opaque' % var + elif mtype['meta'] == 'Enum': + name = mtype['name'] + if not name.endswith('Type'): + name += 'Type' + tmpl = render(T_FORMAT_MEMBER_OF_ENUM, + fullname=name, tname=member['xmlattr']) + elif mtype['meta'] in ['String', 'Chars', 'UChars']: + funcname = 'virBufferEscapeString' + elif mtype['name'] == 'Bool': + truevalue = member.get('truevalue', 'yes') + if truevalue == 'yes': + var = '%s ? "yes" : "no"' % var + elif truevalue == 'on': + var = '%s ? "on" : "off"' % var + else: + var = '%s ? "%s" : ""' % (var, truevalue) + + code = render(tmpl, funcname=funcname, layout=layout, var=var) + if has_return: + code += ' < 0' + code = render(T_IF_SINGLE, condition=code, body='return -1;') + elif mtype['meta'] not in ['Enum']: + code += ';' + + return code + + def _handleAttr(tagname, var): + if 'xmlattr' not in member: + return None + + fmt = '%s' + if member.get('format.fmt'): + fmt = member['format.fmt'] + elif mtype['meta'] == 'Builtin': + fmt = BUILTIN_TYPES[mtype['name']].get('fmt', '%s') + + layout = " %s='%s'" % (tagname, fmt) + return _format(layout, var) + + def _handleElem(tagname, var): + if 'xmlattr' in member: + return None + + if mtype['meta'] != 'Struct': + layout = '<%s>%%s</%s>\\n' % (tagname, tagname) + else: + layout = tagname + + code = _format(layout, var) + return code + + # + # Main routine + # + name = member['name'] + if member.get('array'): + name = name + '[i]' + var = 'def->' + name + + ret = None + if 'xmlattr' in member: + tagname = member['xmlattr'] + else: + tagname = member['xmlelem'] + + if require == 'attribute': + ret = _handleAttr(tagname, var) + else: + ret = _handleElem(tagname, var) + + if not ret: + return None + + checks = _checkOnCondition(var) + if checks: + ret = indent(ret, 1) + if singleline(ret): + ret = render(T_IF_SINGLE, condition=checks, body=ret) + else: + ret = render(T_IF_MULTI, condition=checks, body=ret) + + if member.get('array'): + return _handleMore(ret) + + if checks: + if '&&' in checks or '||' in checks: + checks = '(%s)' % checks + ret_checks.append(checks) + + return ret + + +def makeFormatFunc(writer, atype): + if 'genformat' not in atype: + return + + # + # Helper functions. + # + def _formatMembers(): + attrs = [] + elems = [] + check_attrs = [] + check_elems = [] + + for member in atype['members']: + attr = formatMember(member, 'attribute', check_attrs) + if attr: + attrs.append(attr) + + elem = formatMember(member, 'element', check_elems) + if elem: + elems.append(elem) + + ret = BlockAssembler() + if len(check_attrs) == len(attrs) \ + and len(check_elems) == len(elems): + checks = ' || '.join(check_attrs + check_elems) + atype['check'] = checks + ret.append(render(T_IF_SINGLE, condition='!(%s)' % checks, + body='return 0;')) + + ret.append('virBufferAsprintf(buf, "<%s", name);') + + if 'namespace' in atype: + ret.append(T_NAMESPACE_FORMAT_BEGIN.strip()) + + ret.extend(attrs) + + if elems: + if attrs and len(check_elems) == len(elems): + checks = ' || '.join(check_elems) + ret.append(render(T_FORMAT_SHORTHAND, checks=checks)) + + elements = '\n\n'.join(elems) + if 'namespace' in atype: + elements += '\n\n' + T_NAMESPACE_FORMAT_END.strip() + + ret.append(render(T_FORMAT_ELEMENTS, elements=elements)) + else: + ret.append('virBufferAddLit(buf, "/>\\n");') + + return ret.output('\n\n') + + # + # Main routine of formating. + # + typename = atype['name'] + alignment = align(typename + 'FormatBuf') + + kwargs = {'alignment': alignment, 'typename': typename, + 'format_members': indent(_formatMembers(), 1)} + + decl = renderByDict(T_FORMAT_FUNC_DECL, kwargs) + writer.write(atype, 'formatfunc', '.h', decl) + + impl = renderByDict(T_FORMAT_FUNC_IMPL, kwargs) + writer.write(atype, 'formatfunc', '.c', impl) + + if atype.get('check'): + decl = render(T_FORMAT_CHECK_DECL, typename=typename) + writer.write(atype, 'formatfunc', '.h', decl) + + impl = render(T_FORMAT_CHECK_IMPL, + typename=typename, check=atype['check']) + writer.write(atype, 'formatfunc', '.c', impl) + + +def showDirective(atype): + print('\n###### Directive ######\n') + print(json.dumps(atype, indent=4)) diff --git a/build-aux/generator/go b/build-aux/generator/go new file mode 100755 index 0000000..9e30e08 --- /dev/null +++ b/build-aux/generator/go @@ -0,0 +1,14 @@ +# This is a command-line tool + +libclang_line=`ldconfig -p | grep libclang` +export libclang_path=`expr "$libclang_line" : '.* => \(.*\)$'` +if test -z "$libclang_path"; then + echo "libclang is required by libvirt\n" + exit -1 +fi + +WORK_DIR=$(cd $(dirname $0); pwd) +export PYTHONDONTWRITEBYTECODE=1 +export topdir="${WORK_DIR}/../.." +export builddir="${WORK_DIR}/../../build" +${WORK_DIR}/main.py $@ diff --git a/build-aux/generator/main.py b/build-aux/generator/main.py new file mode 100755 index 0000000..78a90f1 --- /dev/null +++ b/build-aux/generator/main.py @@ -0,0 +1,416 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2020 Shandong Massclouds Co.,Ltd. +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library. If not, see +# <http://www.gnu.org/licenses/>. +# + +import os +import re +import sys +import argparse +from clang.cindex import Config, Index, CursorKind +from clang.cindex import SourceLocation, SourceRange, TokenKind +from datetime import datetime +from directive import TypeTable, showDirective +from directive import makeClearFunc, makeParseFunc, makeFormatFunc +from utils import Terms + +TOOL_DESC = ''' +Generate xml parse/format functions based on directives. + +Subcommand:\n + list: List types. By default, only list structs tagged by + 'genparse'/'genformat'. When the option '-a' is specified, + list all types discovered by this tool.\n + show: Show the target type's directives and its code for preview. + Specify target type by its name. The option '-k' indicates + the kinds of code for preview.\n + generate: Generate code. To be called by Makefile. + It needs option -k to filter output. + +Option:\n + -k: Specify kinds to filter code output. More than one kind can be + specified, 'c' for clearfunc; 'p' for parsefunc; + 'f' for formatfunc.\n + The option '-k' is only valid for show and generate. +''' + +# Three builtin types need to be handled specially: +# 'char *' => String +# 'char XXXX[...]' => Chars +# 'unsigned char XXXX[...]' => UChars +BUILTIN_MAP = { + 'bool': 'Bool', + 'char': 'Char', + 'unsigned char': 'UChar', + 'int': 'Int', + 'unsigned int': 'UInt', + 'long': 'Long', + 'unsigned long': 'ULong', + 'long long': 'LongLong', + 'unsigned long long': 'ULongLong', + 'uint8_t': 'U8', + 'uint32_t': 'U32', +} + + +def getBuiltinType(ctype, ptr=False, size=None): + if ctype == 'char': + if ptr: + return 'String' + elif size: + return 'Chars' + + if ctype == 'unsigned char' and size: + return 'UChars' + + return BUILTIN_MAP.get(ctype, None) + + +def cursorLineExtent(cursor, tu): + loc = cursor.location + start = SourceLocation.from_position(tu, loc.file, loc.line, 1) + end = SourceLocation.from_position(tu, loc.file, loc.line, -1) + return SourceRange.from_locations(start, end) + + +def getTokens(cursor, tu): + return tu.get_tokens(extent=cursorLineExtent(cursor, tu)) + + +DIRECTIVES = [ + 'genparse', 'genformat', 'namespace', 'xmlattr', 'xmlelem', + 'required', 'array', 'specified', 'callback', 'truevalue', 'checkformat' +] + + +def createDirectives(text): + tlist = re.findall(r'/\*(.*)\*/', text) + if len(tlist) != 1: + return None + + tlist = tlist[0].split(',') + if len(tlist) == 0: + return None + + directives = {} + for item in tlist: + item = item.strip() + if ':' in item: + key, value = item.split(':') + else: + key, value = item, None + + if key in DIRECTIVES: + directives[key] = value + return directives + + +def getDirectives(tokens, cursor): + for token in tokens: + if token.location.column <= cursor.location.column: + continue + if token.kind == TokenKind.COMMENT: + directive = createDirectives(token.spelling) + if directive: + return directive + return None + + +def determinType(kvs, tokens, cursor): + prefix = [] + kind = None + for token in tokens: + if token.location.column >= cursor.location.column: + break + if not kind: + kind = token.kind + prefix.append(token.spelling) + + suffix = [] + for token in tokens: + if token.spelling == ';': + break + suffix.append(token.spelling) + + size = None + if len(suffix) == 3 and suffix[0] == '[' and suffix[2] == ']': + size = suffix[1] + + assert kind in [TokenKind.IDENTIFIER, TokenKind.KEYWORD], \ + 'Bad field "%s".' % cursor.spelling + + assert prefix + typename = ' '.join(prefix) + + # For array, remove the most-outer pointer + if kvs.get('array'): + if typename.endswith('Ptr'): + typename = typename[:-3] + elif typename.endswith('*'): + typename = typename[:-1].strip() + + ptr = False + if typename.endswith('Ptr'): + typename = typename[:-3] + ptr = True + elif prefix[-1] == '*': + typename = typename[:-1].strip() + ptr = True + + ret = getBuiltinType(typename, ptr, size) + if ret: + typename = ret + + kvs.update({'type': typename, 'pointer': ptr}) + if size: + kvs['size'] = size + return kvs + + +def analyseMember(cursor, tu): + dvs = getDirectives(getTokens(cursor, tu), cursor) + if not dvs: + return None + + kvs = {'name': cursor.spelling} + kvs.update(dvs) + + # Formalize member + for key in ['array', 'required', 'specified']: + if key in kvs: + kvs[key] = True + + if 'checkformat' in kvs: + assert kvs.get('checkformat'), 'Directive "checkformat" is None' + + for tag in ['xmlattr', 'xmlelem']: + if tag in kvs: + if not kvs[tag]: + if kvs.get('array'): + kvs[tag] = Terms.singularize(kvs['name']) + else: + kvs[tag] = kvs['name'] + + return determinType(kvs, getTokens(cursor, tu), cursor) + + +def analyseStruct(struct, cursor, tu): + tokens = getTokens(cursor, tu) + kvs = getDirectives(tokens, cursor) + if kvs: + path, _ = os.path.splitext(cursor.location.file.name) + path, filename = os.path.split(path) + _, dirname = os.path.split(path) + kvs['output'] = dirname + '/' + filename + struct.update(kvs) + + inner_members = [] + for child in cursor.get_children(): + if inner_members: + # Flatten the members of embedded struct + for member in inner_members: + member['name'] = child.spelling + '.' + member['name'] + struct['members'].append(member) + inner_members = [] + continue + + if child.kind == CursorKind.STRUCT_DECL: + for ichild in child.get_children(): + member = analyseMember(ichild, tu) + if member: + inner_members.append(member) + continue + + member = analyseMember(child, tu) + if member: + struct['members'].append(member) + + return struct + + +def discoverStructures(tu): + for cursor in tu.cursor.get_children(): + if cursor.kind == CursorKind.STRUCT_DECL and cursor.is_definition(): + # Detect structs + name = cursor.spelling + if not name: + continue + if name.startswith('_'): + name = name[1:] + struct = {'name': name, 'meta': 'Struct', 'members': []} + analyseStruct(struct, cursor, tu) + TypeTable().register(struct) + elif cursor.kind == CursorKind.TYPEDEF_DECL: + # Detect enums + # We can't seek out enums by CursorKind.ENUM_DECL, + # since almost all enums are anonymous. + token = cursor.get_tokens() + try: + next(token) # skip 'typedef' + if next(token).spelling == 'enum': + enum = {'name': cursor.spelling, 'meta': 'Enum'} + TypeTable().register(enum) + except StopIteration: + pass + + +class CodeWriter(object): + def __init__(self, args, builddir): + self._builddir = builddir + self._cmd = args.cmd + self._files = {} + self._filters = {} + self._filters['clearfunc'] = args.kinds and 'c' in args.kinds + self._filters['parsefunc'] = args.kinds and 'p' in args.kinds + self._filters['formatfunc'] = args.kinds and 'f' in args.kinds + if args.cmd == 'show': + self._filters['target'] = args.target + + def _getFile(self, path, ext): + assert ext in ['.h', '.c'] + _, basename = os.path.split(path) + path = '%s.generated%s' % (path, ext) + f = self._files.get(path) + if f is None: + f = open(path, 'w') + f.write('/* Generated by build-aux/generator */\n\n') + if ext in ['.c']: + f.write('#include <config.h>\n') + f.write('#include "%s.h"\n' % basename) + f.write('#include "viralloc.h"\n') + f.write('#include "virerror.h"\n') + f.write('#include "virstring.h"\n\n') + f.write('#define VIR_FROM_THIS VIR_FROM_NONE\n') + else: + f.write('#pragma once\n\n') + f.write('#include "internal.h"\n') + f.write('#include "virxml.h"\n') + self._files[path] = f + return f + + def write(self, atype, kind, extname, content): + if not self._filters[kind]: + return + + if self._cmd == 'show': + target = self._filters['target'] + if not target or target == atype['name']: + if extname == '.h': + info = Terms.upperInitial(kind) + print('\n###### %s ######' % info) + print('\n[.h]') + else: + print('\n[.c]') + print('\n' + content) + return + + assert self._cmd == 'generate' + + if atype.get('output'): + lfs = '\n' if extname == '.h' else '\n\n' + path = self._builddir + '/src/' + atype['output'] + f = self._getFile(path, extname) + f.write(lfs + content + '\n') + + def complete(self): + for name in self._files: + self._files[name].close() + self._files.clear() + + +def getHFiles(path): + retlist = [] + for fname in os.listdir(path): + if fname.endswith('.h'): + retlist.append(os.path.join(path, fname)) + return retlist + + +HELP_LIST = 'list structs tagged by "genparse"/"genformat"' +HELP_LIST_ALL = 'list all discovered types' + +if __name__ == "__main__": + parser = argparse.ArgumentParser( + formatter_class=argparse.RawDescriptionHelpFormatter, + description=TOOL_DESC) + subparsers = parser.add_subparsers(dest='cmd') + parser_list = subparsers.add_parser('list', help=HELP_LIST) + parser_list.add_argument('-a', dest='list_all', action='store_true', + default=False, help=HELP_LIST_ALL) + parser_show = subparsers.add_parser('show', help='show target code') + parser_show.add_argument('target', help='target for being previewed') + parser_show.add_argument('-k', dest='kinds', + help='kinds of code to be previewed') + parser_generate = subparsers.add_parser('generate', help='generate code') + parser_generate.add_argument('-k', dest='kinds', + help='kinds of code to be generated') + args = parser.parse_args() + + if not args.cmd: + parser.print_help() + sys.exit(1) + + if args.cmd == 'generate': + print('###### Generator: start ... ######') + if not args.kinds: + print("[dry run]: no kinds specified for 'generate'") + + timestamp = datetime.now() + topdir = os.environ.get('topdir', None) + builddir = os.environ.get('builddir', None) + assert topdir and builddir, 'Set env "topdir" and "builddir".' + + libclang_path = os.environ.get('libclang_path') + assert libclang_path, 'No libclang library.' + Config.set_library_file(libclang_path) + + # Examine all *.h in "$(topdir)/src/[util|conf]" + index = Index.create() + hfiles = getHFiles(topdir + '/src/util') + getHFiles(topdir + '/src/conf') + for hfile in hfiles: + tu = index.parse(hfile) + discoverStructures(tu) # find all structs and enums + + if args.cmd == 'list': + print('%-64s %s' % ('TYPENAME', 'META')) + for name, kvs in TypeTable().items(): + if not args.list_all: + if not ('genparse' in kvs or 'genparse' in kvs): + continue + print('%-64s %s' % (name, kvs['meta'])) + sys.exit(0) + elif args.cmd == 'show': + assert args.target, args + atype = TypeTable().get(args.target) + if not atype: + sys.exit(0) + showDirective(atype) + + writer = CodeWriter(args, builddir) + + for atype in TypeTable().values(): + makeClearFunc(writer, atype) + makeParseFunc(writer, atype) + makeFormatFunc(writer, atype) + + writer.complete() + + if args.cmd == 'generate': + elapse = (datetime.now() - timestamp).microseconds + print('\n###### Generator: elapse %d(us) ######\n' % elapse) + + sys.exit(0) diff --git a/build-aux/generator/utils.py b/build-aux/generator/utils.py new file mode 100644 index 0000000..c65045a --- /dev/null +++ b/build-aux/generator/utils.py @@ -0,0 +1,100 @@ +# +# Copyright (C) 2020 Shandong Massclouds Co.,Ltd. +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library. If not, see +# <http://www.gnu.org/licenses/>. +# + +import re +import string + + +def singleton(cls): + _instances = {} + + def inner(): + if cls not in _instances: + _instances[cls] = cls() + return _instances[cls] + return inner + + +class Terms(object): + abbrs = ['uuid', 'pci', 'zpci', 'ptr', 'mac', 'mtu', 'dns', 'ip', 'dhcp'] + plurals = {'addresses': 'address'} + + @classmethod + def singularize(cls, name): + ret = cls.plurals.get(name, None) + if ret: + return ret + assert name.endswith('s') + return name[:-1] + + # Don't use str.capitalize() which force other letters to be lowercase. + @classmethod + def upperInitial(cls, word): + if not word: + return '' + if word in cls.abbrs: + return word.upper() + if len(word) > 0 and word[0].isupper(): + return word + return word[0].upper() + word[1:] + + +def singleline(code): + return len(re.findall(r'\n', code.strip())) == 0 + + +def indent(block, count, unit=4): + if not block: + return '' + lines = [] + for line in block.strip().split('\n'): + lines.append(' ' * unit * count + line if line else '') + return '\n'.join(lines).strip() + + +def render(template, **kwargs): + return string.Template(template).safe_substitute(kwargs).strip() + + +def renderByDict(template, dictionary): + return string.Template(template).safe_substitute(**dictionary).strip() + + +class BlockAssembler(list): + def append(self, block): + if block: + super(BlockAssembler, self).append(block) + + def output(self, connector='\n'): + return connector.join(self) + + +def dedup(alist): + assert isinstance(alist, list) + ret = [] + for e in alist: + if e not in ret: + ret.append(e) + + return ret + + +def counterName(name): + if not name.islower(): + name = Terms.upperInitial(name) + return 'n' + name diff --git a/po/POTFILES.in b/po/POTFILES.in index 6607e29..2d7eb1e 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -5,6 +5,7 @@ @BUILDDIR@/src/admin/admin_server_dispatch_stubs.h @BUILDDIR@/src/remote/remote_client_bodies.h @BUILDDIR@/src/remote/remote_daemon_dispatch_stubs.h +@SRCDIR@/build-aux/generator/directive.py @SRCDIR@/src/access/viraccessdriverpolkit.c @SRCDIR@/src/access/viraccessmanager.c @SRCDIR@/src/admin/admin_server.c -- 2.17.1