This tool is used to generate parsexml/formatbuf functions.
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@massclouds.com>
---
po/POTFILES.in | 1 +
scripts/xmlgen/directive.py | 1115 +++++++++++++++++++++++++++++++++++
scripts/xmlgen/go | 7 +
scripts/xmlgen/main.py | 439 ++++++++++++++
scripts/xmlgen/utils.py | 121 ++++
5 files changed, 1683 insertions(+)
create mode 100644 scripts/xmlgen/directive.py
create mode 100755 scripts/xmlgen/go
create mode 100755 scripts/xmlgen/main.py
create mode 100644 scripts/xmlgen/utils.py
diff --git a/po/POTFILES.in b/po/POTFILES.in
index 3d6c20c..c9c0262 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@scripts/xmlgen/directive.py
@SRCDIR@src/access/viraccessdriverpolkit.c
@SRCDIR@src/access/viraccessmanager.c
@SRCDIR@src/admin/admin_server.c
diff --git a/scripts/xmlgen/directive.py b/scripts/xmlgen/directive.py
new file mode 100644
index 0000000..9b73931
--- /dev/null
+++ b/scripts/xmlgen/directive.py
@@ -0,0 +1,1115 @@
+#
+# 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, dedup, Block, Terms, render
+
+BUILTIN_TYPES = {
+ 'String': {},
+ 'Bool': {},
+ '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})'
+ },
+ 'Time': {
+ 'conv': 'virStrToTime(${mdvar}, &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}
+
+ def check(self, name):
+ return name in self
+
+
+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;',
+ '}'
+]
+
+
+def funcSignature(rtype, name, args):
+ alignment = ' ' * (len(name) + 1)
+ connector = ',\n' + alignment
+
+ ret = []
+ ret.append(rtype)
+ ret.append('%s(%s)' % (name, connector.join(args)))
+ return Block(ret)
+
+
+def counterName(member):
+ if isinstance(member['array'], bool):
+ return 'n' + member['name']
+ return member['array']
+
+
+def loop(count, block):
+ assert isinstance(block, list) and len(block) > 0
+ lp = ' {' if len(block) > 1 else ''
+
+ ret = Block()
+ ret.format('if (${count} > 0) {', count=count)
+ ret.format(' size_t i;')
+ ret.format(' for (i = 0; i < ${count}; i++)${lp}', count=count, lp=lp)
+ ret.mapfmt(' ${_each_line_}', block)
+ ret.format(' }' if lp else None)
+ ret.format('}')
+ return ret
+
+
+def ispointer(member):
+ mtype = TypeTable().get(member['type'])
+ return member['pointer'] and not mtype.get('external')
+
+
+def clearMember(member, pre_name=None):
+
+ # Callback for make_switch
+ def _switch_clear_cb(child, switch, _):
+ return clearMember(child, switch['name'])
+
+ if member.get('xmlswitch'):
+ return makeSwitch(member, _switch_clear_cb, None)
+
+ mtype = TypeTable().get(member['type'])
+
+ if pre_name:
+ ref = 'def->%s.%s' % (pre_name, member['name'])
+ else:
+ ref = 'def->%s' % member['name']
+
+ if member.get('array'):
+ ref += '[i]'
+
+ ret = Block()
+ if mtype['meta'] == 'Struct':
+ if ispointer(member):
+ ret.format('${tname}Clear(${ref});', tname=mtype['name'], ref=ref)
+ ret.format('g_free(${ref});', ref=ref)
+ ret.format('${ref} = NULL;', ref=ref)
+ else:
+ ret.format('${tname}Clear(&${ref});', tname=mtype['name'], ref=ref)
+ elif mtype['name'] == 'String':
+ ret.format('g_free(${ref});', ref=ref)
+ ret.format('${ref} = NULL;', ref=ref)
+ elif mtype['name'] in ['Chars', 'UChars']:
+ ret.format('memset(${ref}, 0, sizeof(${ref}));', ref=ref)
+ elif not member.get('array'):
+ ret.format('${ref} = 0;', ref=ref)
+
+ if member.get('specified'):
+ assert not member.get('array'), "'specified' can't come with 'array'."
+ if isinstance(member['specified'], str):
+ ret.format('def->${sname} = false;', sname=member['specified'])
+ else:
+ ret.format('${ref}_specified = false;', ref=ref)
+
+ if member.get('array') and len(ret) > 0:
+ count = 'def->' + counterName(member)
+ ret = loop(count, ret)
+ ret.format('g_free(def->${name});', name=member['name'])
+ ret.format('def->${name} = 0;', name=member['name'])
+ ret.format('${count} = 0;', count=count)
+
+ return ret
+
+
+def makeClearFunc(writer, atype):
+ if 'genparse' not in atype:
+ return
+
+ args = ['%sPtr def' % atype['name']]
+ signature = funcSignature('void', atype['name'] + 'Clear', args)
+ writer.write(atype, 'clearfunc', '.h', signature.output() + ';')
+
+ ret = Block()
+ ret.extend(signature)
+ ret.format('{')
+ ret.format(' if (!def)')
+ ret.format(' return;')
+ ret.newline()
+
+ delay_members = []
+ for member in atype['members']:
+ if member.get('delay_clear'):
+ delay_members.append(member)
+ continue
+
+ ret.mapfmt(' ${_each_line_}', clearMember(member))
+ ret.newline()
+
+ for member in delay_members:
+ ret.mapfmt(' ${_each_line_}', clearMember(member))
+ ret.newline()
+
+ if 'namespace' in atype:
+ ret.format(' if (def->namespaceData && def->ns.free)')
+ ret.format(' (def->ns.free)(def->namespaceData);')
+ else:
+ ret.pop() # Remove last newline
+
+ ret.format('}')
+ writer.write(atype, 'clearfunc', '.c', ret.output())
+
+
+def reportInvalid(tname, mdvar):
+ ret = Block()
+ ret.append('virReportError(VIR_ERR_XML_ERROR,')
+ ret.append(" _(\"Invalid '%s' setting '%s' in '%s'\"),")
+ ret.format(' "${tname}", ${mdvar}, instname);',
+ tname=tname, mdvar=mdvar)
+ return ret
+
+
+def reportMissing(tname):
+ ret = Block()
+ ret.append('virReportError(VIR_ERR_XML_ERROR,')
+ ret.append(" _(\"Missing '%s' setting in '%s'\"),")
+ ret.format(' "${tname}", instname);', tname=tname)
+ return ret
+
+
+def checkError(kind, cond, tname, mdvar):
+ if kind == 'Invalid':
+ report = reportInvalid(tname, mdvar)
+ elif kind == 'Missing':
+ report = reportMissing(tname)
+ else:
+ assert False, '%s is unsupported.' % kind
+
+ ret = Block()
+ ret.format('if (${cond}) {', cond=cond)
+ ret.mapfmt(' ${_each_line_}', report)
+ ret.append(' goto error;')
+ ret.append('}')
+ return ret
+
+
+def parseMember(member, parent, tmpvars, pre_name=None):
+ mtype = TypeTable().get(member['type'])
+
+ #
+ # Helper functions
+ #
+ def _middle_var(member):
+ ret = member['name'].replace('.', '_')
+ if member.get('xmlgroup'):
+ ret = 'node'
+ elif mtype['name'] == 'String':
+ ret = 'def->' + ret
+ elif member.get('xmlattr') or mtype['meta'] != 'Struct':
+ ret += 'Str'
+ else:
+ ret += 'Node'
+ return ret
+
+ def _read_xml_func(mdvar, tname):
+ if member.get('xmlattr'):
+ if '/' in tname:
+ funcname = 'virXMLChildPropString'
+ else:
+ funcname = 'virXMLPropString'
+ elif member.get('xmlelem'):
+ if mtype['meta'] == 'Struct':
+ funcname = 'virXMLChildNode'
+ else:
+ funcname = 'virXMLChildNodeContent'
+ else:
+ return None
+
+ return render('${mdvar} = ${funcname}(node, "${tname}");',
+ mdvar=mdvar, funcname=funcname, tname=tname)
+
+ def _assign_struct(name, member, mdvar):
+ ret = Block()
+ if ispointer(member):
+ ret.format('def->${name} = g_new0(typeof(*def->${name}), 1);',
+ name=name)
+
+ func = mtype['name'] + 'ParseXML'
+ amp = '' if ispointer(member) else '&'
+ tmpl = '${func}(${mdvar}, ${amp}def->${name}, instname, def, opaque)'
+ fn = render(tmpl, func=func, mdvar=mdvar, amp=amp, name=name)
+ return if_cond(fn + ' < 0', ['goto error;'])
+
+ def _assign_non_struct(name, member, mdvar):
+ if mtype['meta'] == 'Enum':
+ typename = mtype['name']
+ if not typename.endswith('Type'):
+ typename += 'Type'
+ expr = render('(def->${name} = ${typename}FromString(${mdvar}))',
+ name=name, typename=typename, mdvar=mdvar)
+ expr += ' <= 0'
+ elif mtype['name'] == 'Bool':
+ truevalue = member.get('truevalue', 'yes')
+ expr = render('virStrToBool(${mdvar}, "${truth}", &def->${name})',
+ mdvar=mdvar, truth=truevalue, name=name)
+ expr += ' < 0'
+ else:
+ builtin = BUILTIN_TYPES.get(mtype['name'])
+ assert builtin, mtype['name']
+ tmpl = builtin.get('conv', None)
+ if tmpl:
+ expr = render(tmpl, name=name, mdvar=mdvar, tname=tname)
+ expr += ' < 0'
+ else:
+ return None
+
+ return checkError('Invalid', expr, tname, mdvar)
+
+ def _parse_array(name, member, tname):
+ num = 'n%sNodes' % Terms.upperInitial(tname)
+ tmpvars.append(num)
+ tmpvars.append('nodes')
+ count = counterName(member)
+
+ if mtype['meta'] == 'Struct':
+ item = _assign_struct(name + '[i]', member, 'tnode')
+ else:
+ item = ['def->%s[i] = virXMLNodeContentString(tnode);' % name]
+
+ ret = Block()
+ ret.format('${num} = virXMLChildNodeSet(node, "${tname}", &nodes);',
+ num=num, tname=tname)
+ ret.format('if (${num} > 0) {', num=num)
+ ret.format(' size_t i;')
+ ret.newline()
+ ret.format(' def->${name} = g_new0(typeof(*def->${name}), ${num});',
+ name=name, num=num)
+ ret.format(' for (i = 0; i < ${num}; i++) {', num=num)
+ ret.format(' xmlNodePtr tnode = nodes[i];')
+ ret.mapfmt(' ${_each_line_}', item)
+ ret.format(' }')
+ ret.format(' def->${count} = ${num};', count=count, num=num)
+ ret.format(' g_free(nodes);')
+ ret.format(' nodes = NULL;')
+ ret.format('} else if (${num} < 0) {', num=num)
+ ret.format(' virReportError(VIR_ERR_XML_ERROR,')
+ ret.format(' _("Invalid %s element found."),')
+ ret.format(' "${tname}");', tname=tname)
+ ret.format(' goto error;')
+
+ if member.get('required'):
+ ret.append('} else {')
+ ret.mapfmt(' ${_each_line_}', reportMissing(tname))
+ ret.append(' goto error;')
+
+ ret.append('}')
+ return ret
+
+ #
+ # Main routine
+ #
+ if not member.get('xmlattr') and not member.get('xmlelem') \
+ and not member.get('xmlgroup'):
+ return None
+
+ if pre_name:
+ name = pre_name + '.' + member['name']
+ else:
+ name = member['name']
+
+ tname = None
+ if member.get('xmlattr'):
+ tname = member['xmlattr']
+ elif member.get('xmlelem'):
+ tname = member['xmlelem']
+
+ # For array member
+ if member.get('array'):
+ return _parse_array(name, member, tname)
+
+ # For common member
+ mdvar = _middle_var(member)
+ if mdvar.endswith('Str') or mdvar.endswith('Node'):
+ tmpvars.append(mdvar)
+
+ block = Block()
+ if tname:
+ block.append(_read_xml_func(mdvar, tname))
+ if member.get('required'):
+ cond = render('${mdvar} == NULL', mdvar=mdvar)
+ block.extend(checkError('Missing', cond, tname, mdvar))
+
+ if mtype['meta'] == 'Struct':
+ assignment = _assign_struct(name, member, mdvar)
+ else:
+ assignment = _assign_non_struct(name, member, mdvar)
+ if not assignment:
+ return block
+
+ if member.get('specified'):
+ if isinstance(member['specified'], str):
+ assignment.format('def->${name} = true;', name=member['specified'])
+ else:
+ assignment.format('def->${name}_specified = true;', name=name)
+
+ if tname:
+ block.extend(if_cond(mdvar, assignment))
+ else:
+ block.extend(assignment)
+
+ return block
+
+
+def makeParseFunc(writer, atype):
+
+ #
+ # Helper functions
+ #
+ def _switch_parse_cb(child, switch, tmpvars):
+ return parseMember(child, atype, tmpvars, switch['name'])
+
+ def _members_block(tmpvars):
+ block = Block()
+ for member in atype['members']:
+ if member.get('xmlswitch'):
+ block.extend(makeSwitch(member, _switch_parse_cb, tmpvars))
+ else:
+ block.extend(parseMember(member, atype, tmpvars))
+ block.newline()
+ return block
+
+ def _post_hook(tmpvars):
+ if atype['genparse'] not in ['withhook', 'concisehook']:
+ return None
+
+ args = ['node', 'def', 'instname', 'parent', 'opaque']
+ if 'namespace' in atype:
+ args.append('ctxt')
+ args.append('xmlopt')
+
+ if atype['genparse'] == 'withhook':
+ args.extend(tmpvars)
+ if 'nodes' in args:
+ args.remove('nodes')
+
+ funcname = atype['name'] + 'ParseXMLHook'
+ cond = '%s(%s) < 0' % (funcname, ', '.join(args))
+ return if_cond(cond, ['goto error;'])
+
+ def _handle_tmpvars(tmpvars):
+ args, heads, tails = [], [], []
+ for var in tmpvars:
+ if var == 'nodes':
+ heads.append('xmlNodePtr *nodes = NULL;')
+ tails.append('g_free(nodes);')
+ tails.append('nodes = NULL;')
+ elif var.endswith('Str'):
+ heads.append('g_autofree char *%s = NULL;' % var)
+ args.append('const char *%s' % var)
+ elif var.endswith('Node'):
+ heads.append('xmlNodePtr %s = NULL;' % var)
+ args.append('xmlNodePtr %s' % var)
+ else:
+ assert var.endswith('Nodes') and var.startswith('n')
+ heads.append('int %s = 0;' % var)
+ args.append('int %s' % var)
+
+ if atype['genparse'] != 'withhook':
+ args = []
+
+ if heads:
+ heads.append('')
+
+ heads.append('if (!def)')
+ heads.append(' goto error;')
+ tails.append('%sClear(def);' % atype['name'])
+ return args, heads, tails
+
+ #
+ # Composite
+ #
+ if 'genparse' not in atype:
+ return
+
+ typename = atype['name']
+ funcname = typename + 'ParseXML'
+
+ # Declare virXXXParseXML
+ args = Block([
+ 'xmlNodePtr node',
+ '%sPtr def' % typename,
+ 'const char *instname',
+ 'void *parent',
+ 'void *opaque'
+ ])
+
+ if 'namespace' in atype:
+ args.append('xmlXPathContextPtr ctxt')
+ args.append('virNetworkXMLOptionPtr xmlopt')
+
+ signature = funcSignature('int', funcname, args)
+ writer.write(atype, 'parsefunc', '.h', signature.output() + ';')
+
+ # Prepare for implementation
+ tmpvars = []
+ parseblock = _members_block(tmpvars)
+ tmpvars = dedup(tmpvars)
+ tmpargs, headlines, cleanup = _handle_tmpvars(tmpvars)
+ posthook = _post_hook(tmpvars)
+
+ if atype['genparse'] in ['withhook', 'concisehook']:
+ # Declare virXXXParseXMLHook
+ signature = funcSignature('int', funcname + 'Hook', args + tmpargs)
+ writer.write(atype, 'parsefunc', '.h', signature.output() + ';')
+ else:
+ # Without hook, instname, parent and opaque are unused.
+ args[2] += ' G_GNUC_UNUSED'
+ args[3] += ' G_GNUC_UNUSED'
+ args[4] += ' G_GNUC_UNUSED'
+
+ # Implement virXXXParseXML
+ impl = funcSignature('int', funcname, args)
+ impl.format('{')
+ impl.mapfmt(' ${_each_line_}', headlines)
+ impl.newline()
+ impl.mapfmt(' ${_each_line_}', parseblock)
+
+ if posthook:
+ impl.mapfmt(' ${_each_line_}', posthook)
+ impl.newline()
+
+ if 'namespace' in atype:
+ impl.extend(' ${_each_line_}', T_NAMESPACE_PARSE)
+
+ impl.format(' return 0;')
+ impl.newline()
+ impl.format(' error:')
+ impl.mapfmt(' ${_each_line_}', cleanup)
+ impl.format(' return -1;')
+ impl.format('}')
+
+ writer.write(atype, 'parsefunc', '.c', impl.output())
+
+
+def if_cond(condition, block):
+ assert isinstance(block, list) and len(block) > 0
+ lp = ' {' if len(block) > 1 else ''
+
+ ret = Block()
+ ret.format('if (${condition})${lp}', condition=condition, lp=lp)
+ ret.mapfmt(' ${_each_line_}', block)
+ ret.format('}' if lp else None)
+ return ret
+
+
+def formatMember(member, parent):
+ mtype = TypeTable().get(member['type'])
+
+ #
+ # Helper functions.
+ #
+ def _checkOnCondition(var):
+ ret = None
+ if ispointer(member):
+ ret = var
+ elif member.get('specified'):
+ if isinstance(member['specified'], str):
+ ret = 'def->' + member['specified']
+ else:
+ ret = var + '_specified'
+ if ret.startswith('&'):
+ ret = ret[1:]
+ elif mtype['meta'] == 'Struct':
+ ret = '%sCheck(&%s, def, opaque)' % (mtype['name'], var)
+ elif member.get('required'):
+ pass
+ elif mtype['meta'] == 'Enum':
+ ret = var
+ elif mtype['meta'] == 'Builtin':
+ if mtype['name'] in ['Chars', 'UChars']:
+ ret = var + '[0]'
+ else:
+ ret = var
+
+ if 'formatflag' in member:
+ flag = member['formatflag']
+ if flag == '_ALWAYS_':
+ return None
+
+ exclusive = False
+ reverse = False
+ if flag[0] == '%':
+ flag = flag[1:]
+ exclusive = True
+ if flag[0] == '!':
+ flag = flag[1:]
+ reverse = True
+ cond = '(virXMLFlag(opaque) & %s)' % flag
+ if reverse:
+ cond = '!' + cond
+
+ if ret and not exclusive:
+ ret = ret + ' && ' + cond
+ else:
+ ret = cond
+
+ return ret
+
+ def _handleMore(code):
+ counter = 'def->' + counterName(member)
+ return loop(counter, code), counter
+
+ def _format(layout, var):
+ if mtype['meta'] == 'Struct':
+ if not ispointer(member):
+ var = '&' + var
+
+ fname = mtype['name'] + 'FormatBuf'
+ cond = '%s(buf, "%s", %s, def, opaque) < 0' % (fname, layout, var)
+ return if_cond(cond, ['return -1;'])
+ elif mtype['meta'] == 'Enum':
+ name = mtype['name']
+ if not name.endswith('Type'):
+ name += 'Type'
+
+ ret = Block()
+ ret.format('const char *str = ${name}ToString(${var});',
+ name=name, var=var)
+ ret.format('if (!str) {')
+ ret.format(' virReportError(VIR_ERR_INTERNAL_ERROR,')
+ ret.format(' _("Unknown %s type %d"),')
+ ret.format(' "${tname}", ${var});',
+ tname=member['xmlattr'], var=var)
+ ret.format(' return -1;')
+ ret.format('}')
+ ret.format('virBufferAsprintf(buf, "${layout}", str);',
+ layout=layout)
+ return ret
+ 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)
+ return ['virBufferEscapeString(buf, "%s", %s);' % (layout, var)]
+ elif mtype['name'] == 'String':
+ return ['virBufferEscapeString(buf, "%s", %s);' % (layout, var)]
+ elif mtype['name'] == 'Time':
+ return ['virTimeFormatBuf(buf, "%s", %s);' % (layout, var)]
+ else:
+ return ['virBufferAsprintf(buf, "%s", %s);' % (layout, var)]
+
+ def _handleAttr(tagname, var):
+ if 'xmlattr' not in member:
+ return None
+
+ fmt = '%s'
+ if 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']
+ ret = _handleAttr(tagname, var)
+ else:
+ tagname = member['xmlelem']
+ ret = _handleElem(tagname, var)
+
+ if not ret:
+ return None, None
+
+ if member.get('array'):
+ return _handleMore(ret)
+
+ checks = _checkOnCondition(var)
+ if checks:
+ ret = if_cond(checks, ret)
+
+ if checks:
+ if '&&' in checks or '||' in checks:
+ checks = '(%s)' % checks
+
+ return ret, checks
+
+
+def makeSwitch(switch, callback, opaque=None):
+ assert switch.get('xmlswitch', None) and switch.get('switch_type', None)
+
+ captype = Terms.allcaps(switch['switch_type'])
+ block = Block()
+ block.format('switch (def->${etype}) {', etype=switch['xmlswitch'])
+ block.newline()
+ for child in switch['members']:
+ value = captype + '_' + Terms.allcaps(child['name'])
+ block.format('case ${value}:', value=value)
+ block.mapfmt(' ${_each_line_}', callback(child, switch, opaque))
+ block.format(' break;')
+ block.newline()
+ block.format('case ${captype}_NONE:', captype=captype)
+ block.format('case ${captype}_LAST:', captype=captype)
+ block.format(' break;')
+ block.format('}')
+ return block
+
+
+def makeFormatFunc(writer, atype):
+ if 'genformat' not in atype:
+ return
+
+ #
+ # Helper functions and classes.
+ #
+ def _handleHeads(tmpvars, typename, kind=''):
+ tmpvars = dedup(tmpvars)
+ heads = Block()
+ if tmpvars:
+ heads.mapfmt(
+ 'g_auto(virBuffer) ${_each_line_} = VIR_BUFFER_INITIALIZER;',
+ tmpvars
+ )
+ heads.newline()
+
+ heads.format('if (!def || !buf)')
+ heads.format(' return 0;')
+
+ if tmpvars:
+ funcname = typename + 'Format' + kind + 'Hook'
+
+ heads.newline()
+ args = Block(['def', 'parent', 'opaque'])
+ args.mapfmt('&${_each_line_}', tmpvars)
+ heads.format('if (%s(%s) < 0)' % (funcname, ', '.join(args)))
+ heads.format(' return -1;')
+
+ args = Block([
+ 'const %s *def' % typename,
+ 'const void *parent',
+ 'const void *opaque'
+ ])
+ args.mapfmt('virBufferPtr ${_each_line_}', tmpvars)
+ signature = funcSignature('int', funcname, args)
+ writer.write(atype, 'formatfunc', '.h', signature.output() + ';')
+
+ args = [
+ 'const %s *def' % typename,
+ 'const void *parent',
+ 'void *opaque',
+ 'bool value'
+ ]
+ funcname = typename + 'Check' + kind + 'Hook'
+ signature = funcSignature('bool', funcname, args)
+ writer.write(atype, 'formatfunc', '.h', signature.output() + ';')
+
+ return '\n'.join(heads)
+
+ def _format_group(child, switch, kind):
+ kind = kind if kind else ''
+ prefix = (switch['name'] + '.') if switch else ''
+ mtype = TypeTable().get(child['type'])
+ funcname = '%sFormat%s' % (mtype['name'], kind)
+ var = 'def->%s%s' % (prefix, child['name'])
+ if not ispointer(child):
+ var = '&' + var
+ r_check = '%sCheck%s(%s, def, opaque)' % (mtype['name'], kind, var)
+ cond = '%s(buf, %s, def, opaque) < 0' % (funcname, var)
+ return if_cond(cond, ['return -1;']), r_check
+
+ def _switch_format_cb(child, switch, kind):
+ return _format_group(child, switch, kind)[0]
+
+ def _handle_wrap_attr(member):
+ wrap, member['xmlattr'] = member['xmlattr'].split('/')
+ ret, check = formatMember(member, atype)
+ return ret, check, wrap
+
+ def _switch_check_cb(child, switch, kind):
+ return ['ret = %s;' % _format_group(child, switch, kind)[1]]
+
+ def _prepare_member(member, atype):
+ wrap, attr, attr_chk, elem, elem_chk = None, None, None, None, None
+ if member.get('xmlswitch'):
+ attr = makeSwitch(member, _switch_format_cb, 'Attr')
+ elem = makeSwitch(member, _switch_format_cb, 'Elem')
+ basename = atype['name'] + Terms.upperInitial(member['name'])
+ attr_chk = '%sCheckAttr(def, opaque)' % basename
+ elem_chk = '%sCheckElem(def, opaque)' % basename
+
+ # Declare virXXX<UnionName>Check[Attr|Elem] for switch.
+ for kind in ['Attr', 'Elem']:
+ args = ['const %s *def' % atype['name'], 'void *opaque']
+ decl = funcSignature('bool', basename + 'Check' + kind, args)
+ writer.write(atype, 'formatfunc', '.h', decl.output() + ';')
+
+ # Implement virXXX<UnionName>Check[Attr|Elem] for switch.
+ checks = makeSwitch(member, _switch_check_cb, kind)
+
+ args[1] += ' G_GNUC_UNUSED'
+ impl = funcSignature('bool', basename + 'Check' + kind, args)
+ impl.format('{')
+ impl.format(' bool ret = false;')
+ impl.format(' if (!def)')
+ impl.format(' return false;')
+ impl.newline()
+ impl.mapfmt(' ${_each_line_}', checks)
+ impl.newline()
+ impl.format(' return ret;')
+ impl.format('}')
+ writer.write(atype, 'formatfunc', '.c', impl.output())
+
+ elif member.get('xmlattr'):
+ if '/' in member['xmlattr']:
+ attr, attr_chk, wrap = _handle_wrap_attr(member)
+ else:
+ attr, attr_chk = formatMember(member, atype)
+ elif member.get('xmlelem'):
+ elem, elem_chk = formatMember(member, atype)
+ elif member.get('xmlgroup'):
+ attr, attr_chk = _format_group(member, None, 'Attr')
+ elem, elem_chk = _format_group(member, None, 'Elem')
+ return wrap, attr, attr_chk, elem, elem_chk
+
+ def _prepare_hook(member):
+ assert member.get('xmlattr') or member.get('xmlelem')
+ buf = member['name'] + 'Buf'
+ ret = if_cond('virBufferUse(&%s)' % buf,
+ ['virBufferAddBuffer(buf, &%s);' % buf])
+ return ret, buf
+
+ class _WrapItem:
+ def __init__(self):
+ self.attrs = Block()
+ self.checks = []
+ self.optional = True
+ self.pos = 0
+
+ def _prepare():
+ attrs = Block()
+ elems = Block()
+ attr_checks = []
+ elem_checks = []
+ attr_hook_vars = []
+ elem_hook_vars = []
+ attrs_optional = True
+ elems_optional = True
+ wraps = OrderedDict()
+ for member in atype['members']:
+ if member.get('formathook'):
+ block, hookvar = _prepare_hook(member)
+ if member.get('xmlattr'):
+ attrs.extend(block)
+ attrs.newline(block)
+ attr_hook_vars.append(hookvar)
+ elif member.get('xmlelem'):
+ elems.extend(block)
+ elems.newline(block)
+ elem_hook_vars.append(hookvar)
+ else:
+ assert False, 'formathook is only with [xmlattr|xmlelem].'
+ else:
+ wrap, attr, attr_chk, elem, elem_chk = \
+ _prepare_member(member, atype)
+ if wrap:
+ item = wraps.setdefault(wrap, _WrapItem())
+ item.pos = len(elems)
+ item.attrs.extend(attr)
+ item.attrs.newline()
+ item.checks.append(attr_chk)
+ if member.get('required'):
+ item.optional = False
+ continue
+
+ attrs.extend(attr)
+ attrs.newline(attr)
+ elems.extend(elem)
+ elems.newline(elem)
+ if attr_chk:
+ attr_checks.append(attr_chk)
+ if elem_chk:
+ elem_checks.append(elem_chk)
+ if member.get('required'):
+ attrs_optional = False
+
+ while wraps:
+ wrap, item = wraps.popitem()
+ lines = Block()
+ lines.format('virBufferAddLit(buf, "<${name}");', name=wrap)
+ lines.newline()
+ lines.extend(item.attrs)
+ lines.format('virBufferAddLit(buf, "/>\\n");')
+ if item.optional:
+ elem_checks.extend(item.checks)
+ lines = if_cond(' || '.join(item.checks), lines)
+ lines.newline()
+ else:
+ elems_optional = False
+
+ for line in reversed(lines):
+ elems.insert(item.pos, line)
+
+ attr_checks = dedup(attr_checks)
+ elem_checks = dedup(elem_checks)
+ return (attrs, attr_checks, attrs_optional, attr_hook_vars,
+ elems, elem_checks, elems_optional, elem_hook_vars)
+
+ def _check_null(optional, checks, has_hook, kind=''):
+ if not optional:
+ return None
+
+ # Declare virXXXCheck[Attr|Elem]
+ typename = atype['name']
+ funcname = typename + 'Check' + kind
+ args = [
+ 'const %s *def' % typename,
+ 'const void *parent',
+ 'void *opaque'
+ ]
+ signature = funcSignature('bool', funcname, args)
+ writer.write(atype, 'formatfunc', '.h', signature.output() + ';')
+
+ # Implement virXXXFormat[Attr|Elem]
+ check = ' || '.join(checks) if optional else 'true'
+ if not check:
+ check = 'true'
+
+ if has_hook:
+ check = '%sCheck%sHook(def, parent, opaque, %s)' \
+ % (typename, kind, check)
+
+ args[1] += ' G_GNUC_UNUSED'
+ args[2] += ' G_GNUC_UNUSED'
+
+ impl = funcSignature('bool', funcname, args)
+ impl.format('{')
+ impl.format(' if (!def)')
+ impl.format(' return false;')
+ impl.newline()
+ impl.format(' return ${check};', check=check)
+ impl.format('}')
+ writer.write(atype, 'formatfunc', '.c', impl.output())
+
+ return if_cond('!%s(def, parent, opaque)' % funcname, ['return 0;'])
+
+ def _compose_full(attrs, attr_checks, attrs_optional, attr_hook_vars,
+ elems, elem_checks, elems_optional, elem_hook_vars):
+ has_hook = (attr_hook_vars or elem_hook_vars)
+ typename = atype['name']
+
+ # Declare virXXXFormatBuf
+ args = [
+ 'virBufferPtr buf',
+ 'const char *name',
+ 'const %s *def' % typename,
+ 'const void *parent',
+ 'void *opaque'
+ ]
+ signature = funcSignature('int', typename + 'FormatBuf', args)
+ writer.write(atype, 'formatfunc', '.h', signature.output() + ';')
+
+ # Implement virXXXFormatBuf
+ headlines = _handleHeads(attr_hook_vars + elem_hook_vars, typename)
+ checknull = _check_null(attrs_optional and elems_optional,
+ attr_checks + elem_checks, has_hook)
+
+ args[3] += ' G_GNUC_UNUSED'
+ args[4] += ' G_GNUC_UNUSED'
+
+ impl = funcSignature('int', typename + 'FormatBuf', args)
+ impl.format('{')
+ impl.mapfmt(' ${_each_line_}', headlines.split('\n'))
+ impl.newline()
+
+ if checknull:
+ impl.mapfmt(' ${_each_line_}', checknull)
+ impl.newline()
+
+ impl.format(' virBufferAsprintf(buf, "<%s", name);')
+ impl.newline()
+
+ if 'namespace' in atype:
+ impl.mapfmt(' ${_each_line}', T_NAMESPACE_FORMAT_BEGIN)
+
+ impl.mapfmt(' ${_each_line_}', attrs)
+
+ if elems:
+ if attrs and elems_optional:
+ impl.format(' if (!(${checks})) {',
+ checks=' || '.join(elem_checks))
+ impl.format(' virBufferAddLit(buf, "/>\\n");')
+ impl.format(' return 0;')
+ impl.format(' }')
+ impl.newline()
+
+ if 'namespace' in atype:
+ impl.mapfmt(' ${_each_line}', T_NAMESPACE_FORMAT_END)
+
+ impl.format(' virBufferAddLit(buf, ">\\n");')
+ impl.newline()
+ impl.format(' virBufferAdjustIndent(buf, 2);')
+ impl.newline()
+ impl.mapfmt(' ${_each_line_}', elems)
+ impl.format(' virBufferAdjustIndent(buf, -2);')
+ impl.format(' virBufferAsprintf(buf, "</%s>\\n", name);')
+ else:
+ impl.format(' virBufferAddLit(buf, "/>\\n");')
+
+ impl.newline()
+ impl.format(' return 0;')
+ impl.format('}')
+ writer.write(atype, 'formatfunc', '.c', impl.output())
+
+ def _compose_part(kind, block, checks, optional, hook_vars):
+ typename = atype['name']
+ funcname = typename + 'Format' + kind
+ headlines = _handleHeads(hook_vars, atype['name'], kind)
+ if not block:
+ block = ['/* empty */', '']
+
+ checknull = _check_null(optional, checks, len(hook_vars), kind)
+
+ # Declare virXXXFormat[Attr|Elem]
+ args = [
+ 'virBufferPtr buf',
+ 'const %s *def' % typename,
+ 'const void *parent',
+ 'void *opaque'
+ ]
+ signature = funcSignature('int', funcname, args)
+ writer.write(atype, 'formatfunc', '.h', signature.output() + ';')
+
+ # Implement virXXXFormat[Attr|Elem]
+ args[2] += ' G_GNUC_UNUSED'
+ args[3] += ' G_GNUC_UNUSED'
+
+ impl = funcSignature('int', funcname, args)
+ impl.format('{')
+ impl.mapfmt(' ${_each_line_}', headlines.split('\n'))
+ impl.newline()
+
+ if checknull:
+ impl.mapfmt(' ${_each_line_}', checknull)
+ impl.newline()
+
+ impl.mapfmt(' ${_each_line_}', block)
+ impl.format(' return 0;')
+ impl.format('}')
+ writer.write(atype, 'formatfunc', '.c', impl.output())
+
+ #
+ # Main routine of formating.
+ #
+ (attrs, attr_checks, attrs_optional, attr_hook_vars,
+ elems, elem_checks, elems_optional, elem_hook_vars) = _prepare()
+
+ if atype['genformat'] in ['separate', 'onlyattrs', 'onlyelems']:
+ if atype['genformat'] in ['separate', 'onlyattrs']:
+ _compose_part('Attr', attrs, attr_checks,
+ attrs_optional, attr_hook_vars)
+
+ if atype['genformat'] in ['separate', 'onlyelems']:
+ _compose_part('Elem', elems, elem_checks,
+ elems_optional, elem_hook_vars)
+ else:
+ _compose_full(attrs, attr_checks, attrs_optional, attr_hook_vars,
+ elems, elem_checks, elems_optional, elem_hook_vars)
+
+
+def showDirective(atype):
+ print('\n[Directive]\n')
+ print(json.dumps(atype, indent=4))
diff --git a/scripts/xmlgen/go b/scripts/xmlgen/go
new file mode 100755
index 0000000..3821ce0
--- /dev/null
+++ b/scripts/xmlgen/go
@@ -0,0 +1,7 @@
+# This is a command-line tool
+
+export PYTHONDONTWRITEBYTECODE=1
+WORK_DIR=$(cd $(dirname $0); pwd)
+SRCDIR="${WORK_DIR}/../../src"
+BUILDDIR="${WORK_DIR}/../../build/src"
+${WORK_DIR}/main.py -s ${SRCDIR} -b ${BUILDDIR} $@
diff --git a/scripts/xmlgen/main.py b/scripts/xmlgen/main.py
new file mode 100755
index 0000000..5054640
--- /dev/null
+++ b/scripts/xmlgen/main.py
@@ -0,0 +1,439 @@
+#!/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 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',
+ 'time_t': 'Time',
+}
+
+
+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', 'xmlgroup',
+ 'required', 'array', 'specified', 'truevalue', 'formathook', 'xmlswitch',
+ 'formatflag'
+]
+
+
+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" (%s).' % (cursor.spelling, kind)
+
+ 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', 'formathook']:
+ if key in kvs and not kvs[key]:
+ kvs[key] = True
+
+ if 'formatflag' in kvs:
+ assert kvs.get('formatflag'), 'Directive "formatflag" is None'
+
+ for tag in ['xmlattr', 'xmlelem', 'xmlgroup']:
+ 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 findMember(struct, name):
+ for member in struct['members']:
+ if member['name'] == name:
+ return member
+
+ return None
+
+
+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)
+
+ last_kind = None
+ inner_members = []
+ for child in cursor.get_children():
+ if inner_members:
+ if last_kind == CursorKind.STRUCT_DECL:
+ # Flatten the members of embedded struct
+ for member in inner_members:
+ member['name'] = child.spelling + '.' + member['name']
+ struct['members'].append(member)
+ else:
+ # Embedded union
+ union = getDirectives(getTokens(child, tu), child)
+ if union and 'xmlswitch' in union:
+ switch = findMember(struct, union['xmlswitch'])
+ switch['delay_clear'] = True
+ union['switch_type'] = switch['type']
+ union['name'] = child.spelling
+ union['members'] = inner_members
+ struct['members'].append(union)
+ struct['xmlswitch'] = union['xmlswitch']
+
+ inner_members = []
+ last_kind = None
+ continue
+
+ if child.kind in [CursorKind.STRUCT_DECL, CursorKind.UNION_DECL]:
+ last_kind = child.kind
+ 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:]
+ if TypeTable().check(name):
+ continue
+ 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':
+ if not TypeTable().check(cursor.spelling):
+ enum = {'name': cursor.spelling, 'meta': 'Enum'}
+ TypeTable().register(enum)
+ except StopIteration:
+ pass
+
+
+class CodeWriter(object):
+ def __init__(self, args):
+ self._builddir = args.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 scripts/xmlgen */\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':
+ 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 + '/' + 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)
+ parser.add_argument('-s', dest='srcdir')
+ parser.add_argument('-b', dest='builddir')
+ 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 or not args.srcdir or not args.builddir:
+ parser.print_help()
+ sys.exit(1)
+
+ if args.cmd == 'generate':
+ print('###### xmlgen: start ... ######')
+ if not args.kinds:
+ print("[dry run]: no kinds specified for 'generate'")
+
+ timestamp = datetime.now()
+
+ # Examine all *.h in "$(srcdir)/[util|conf]"
+ index = Index.create()
+ srcdir = args.srcdir
+ hfiles = getHFiles(srcdir + '/util') + getHFiles(srcdir + '/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)
+
+ 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###### xmlgen: elapse %d(us) ######\n' % elapse)
+
+ sys.exit(0)
diff --git a/scripts/xmlgen/utils.py b/scripts/xmlgen/utils.py
new file mode 100644
index 0000000..4ac1f39
--- /dev/null
+++ b/scripts/xmlgen/utils.py
@@ -0,0 +1,121 @@
+#
+# 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/>.
+#
+
+from string import Template
+
+
+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'}
+ caps = {'NET_DEV': 'NETDEV', 'MACTABLE': 'MAC_TABLE'}
+
+ @classmethod
+ def _split(cls, word):
+ ret = []
+ if not word:
+ return ret
+ head = 0
+ for pos in range(1, len(word)):
+ if word[pos].isupper() and not word[pos - 1].isupper():
+ ret.append(word[head:pos])
+ head = pos
+ ret.append(word[head:])
+ return ret
+
+ @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:]
+
+ @classmethod
+ def allcaps(cls, word):
+ if len(word) == 0:
+ return word
+ parts = cls._split(word)
+ ret = '_'.join([part.upper() for part in parts])
+ for key, value in cls.caps.items():
+ ret = ret.replace('_%s_' % key, '_%s_' % value)
+ return ret
+
+
+def render(template, **kwargs):
+ return Template(template).substitute(kwargs)
+
+
+class Block(list):
+ def format(self, template, **args):
+ if template:
+ self.append(Template(template).substitute(**args))
+
+ def extend(self, block):
+ if isinstance(block, list):
+ super(Block, self).extend(block)
+
+ # ${_each_line_} is the only legal key for template
+ # and represents each line of the block.
+ def mapfmt(self, template, block):
+ if not block or not template:
+ return
+
+ assert isinstance(block, list), block
+ for line in block:
+ if line:
+ self.append(Template(template).substitute(_each_line_=line))
+ else:
+ self.append('')
+
+ def newline(self, condition=True):
+ if condition:
+ super(Block, self).append('')
+
+ 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
--
2.25.1
On Fri, Sep 04, 2020 at 11:34:53AM +0800, Shi Lei wrote:
> This tool is used to generate parsexml/formatbuf functions.
> 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.
In testing this I hit one problem with use of libclang python binding.
In RHEL-7, we have clang 3.4.1 via EPEL repos, and that only has a
python 2 binding available, no python 3 binding.
IOW, if we take te xml generator, we loose support for building on
RHEL-7 as we consume it today.
I see a few options
1 Wait until April 2021 to merge this series, because our
platform matrix lets us drop RHEL-7 support in April.
https://libvirt.org/platforms.html
2 Enable use of software collections in our RHEL-7 CI
container images and pull clang 7 from there
https://www.softwarecollections.org/en/scls/rhscl/llvm-toolset-7.0/
3 Merge this feature now, commit the generated files to git and
only run the generator manually when we need changes. Once
we drop RHEL7 we can delete the generated files from git.
4 Drop RHEL-7 immediately in violation of our platform support
matrix. I know of people, however, who still rely on being
able to build new libvirt on RHEL-7 this would harm.
My feeling is to go for option (3) and temporarily commit
the generated files to git, as it will only need to be a short
term hack for 4-5 months.
Regards,
Daniel
--
|: https://berrange.com -o- https://www.flickr.com/photos/dberrange :|
|: https://libvirt.org -o- https://fstop138.berrange.com :|
|: https://entangle-photo.org -o- https://www.instagram.com/dberrange :|
© 2016 - 2026 Red Hat, Inc.