2019-10-18 07:43:44 +00:00
|
|
|
# -*- coding: utf-8 -*-
|
|
|
|
#
|
|
|
|
# QAPI code generation
|
|
|
|
#
|
|
|
|
# Copyright (c) 2018-2019 Red Hat Inc.
|
|
|
|
#
|
|
|
|
# Authors:
|
|
|
|
# Markus Armbruster <armbru@redhat.com>
|
|
|
|
# Marc-André Lureau <marcandre.lureau@redhat.com>
|
|
|
|
#
|
|
|
|
# This work is licensed under the terms of the GNU GPL, version 2.
|
|
|
|
# See the COPYING file in the top-level directory.
|
|
|
|
|
2020-10-09 16:15:28 +00:00
|
|
|
from contextlib import contextmanager
|
2019-10-18 07:43:44 +00:00
|
|
|
import errno
|
|
|
|
import os
|
|
|
|
import re
|
|
|
|
|
2020-10-09 16:15:28 +00:00
|
|
|
from .common import (
|
|
|
|
c_fname,
|
|
|
|
gen_endif,
|
|
|
|
gen_if,
|
|
|
|
guardend,
|
|
|
|
guardstart,
|
|
|
|
mcgen,
|
|
|
|
)
|
qapi: Prefer explicit relative imports
All of the QAPI include statements are changed to be package-aware, as
explicit relative imports.
A quirk of Python packages is that the name of the package exists only
*outside* of the package. This means that to a module inside of the qapi
folder, there is inherently no such thing as the "qapi" package. The
reason these imports work is because the "qapi" package exists in the
context of the caller -- the execution shim, where sys.path includes a
directory that has a 'qapi' folder in it.
When we write "from qapi import sibling", we are NOT referencing the folder
'qapi', but rather "any package named qapi in sys.path". If you should
so happen to have a 'qapi' package in your path, it will use *that*
package.
When we write "from .sibling import foo", we always reference explicitly
our sibling module; guaranteeing consistency in *where* we are importing
these modules from.
This can be useful when working with virtual environments and packages
in development mode. In development mode, a package is installed as a
series of symlinks that forwards to your same source files. The problem
arises because code quality checkers will follow "import qapi.x" to the
"installed" version instead of the sibling file and -- even though they
are the same file -- they have different module paths, and this causes
cyclic import problems, false positive type mismatch errors, and more.
It can also be useful when dealing with hierarchical packages, e.g. if
we allow qemu.core.qmp, qemu.qapi.parser, etc.
Signed-off-by: John Snow <jsnow@redhat.com>
Reviewed-by: Eduardo Habkost <ehabkost@redhat.com>
Reviewed-by: Cleber Rosa <crosa@redhat.com>
Reviewed-by: Philippe Mathieu-Daudé <philmd@redhat.com>
Message-Id: <20201009161558.107041-6-jsnow@redhat.com>
Reviewed-by: Markus Armbruster <armbru@redhat.com>
Signed-off-by: Markus Armbruster <armbru@redhat.com>
2020-10-09 16:15:27 +00:00
|
|
|
from .schema import QAPISchemaVisitor
|
2019-10-18 07:43:44 +00:00
|
|
|
|
|
|
|
|
2020-03-04 15:59:29 +00:00
|
|
|
class QAPIGen:
|
2019-10-18 07:43:44 +00:00
|
|
|
|
|
|
|
def __init__(self, fname):
|
|
|
|
self.fname = fname
|
|
|
|
self._preamble = ''
|
|
|
|
self._body = ''
|
|
|
|
|
|
|
|
def preamble_add(self, text):
|
|
|
|
self._preamble += text
|
|
|
|
|
|
|
|
def add(self, text):
|
|
|
|
self._body += text
|
|
|
|
|
|
|
|
def get_content(self):
|
|
|
|
return self._top() + self._preamble + self._body + self._bottom()
|
|
|
|
|
|
|
|
def _top(self):
|
|
|
|
return ''
|
|
|
|
|
|
|
|
def _bottom(self):
|
|
|
|
return ''
|
|
|
|
|
|
|
|
def write(self, output_dir):
|
2020-02-24 14:30:08 +00:00
|
|
|
# Include paths starting with ../ are used to reuse modules of the main
|
|
|
|
# schema in specialised schemas. Don't overwrite the files that are
|
|
|
|
# already generated for the main schema.
|
|
|
|
if self.fname.startswith('../'):
|
|
|
|
return
|
2019-10-18 07:43:44 +00:00
|
|
|
pathname = os.path.join(output_dir, self.fname)
|
2020-03-04 15:59:32 +00:00
|
|
|
odir = os.path.dirname(pathname)
|
|
|
|
if odir:
|
2019-10-18 07:43:44 +00:00
|
|
|
try:
|
2020-03-04 15:59:32 +00:00
|
|
|
os.makedirs(odir)
|
2019-10-18 07:43:44 +00:00
|
|
|
except os.error as e:
|
|
|
|
if e.errno != errno.EEXIST:
|
|
|
|
raise
|
|
|
|
fd = os.open(pathname, os.O_RDWR | os.O_CREAT, 0o666)
|
2020-03-04 15:59:30 +00:00
|
|
|
f = open(fd, 'r+', encoding='utf-8')
|
2019-10-18 07:43:44 +00:00
|
|
|
text = self.get_content()
|
|
|
|
oldtext = f.read(len(text) + 1)
|
|
|
|
if text != oldtext:
|
|
|
|
f.seek(0)
|
|
|
|
f.truncate(0)
|
|
|
|
f.write(text)
|
|
|
|
f.close()
|
|
|
|
|
|
|
|
|
|
|
|
def _wrap_ifcond(ifcond, before, after):
|
|
|
|
if before == after:
|
|
|
|
return after # suppress empty #if ... #endif
|
|
|
|
|
|
|
|
assert after.startswith(before)
|
|
|
|
out = before
|
|
|
|
added = after[len(before):]
|
|
|
|
if added[0] == '\n':
|
|
|
|
out += '\n'
|
|
|
|
added = added[1:]
|
|
|
|
out += gen_if(ifcond)
|
|
|
|
out += added
|
|
|
|
out += gen_endif(ifcond)
|
|
|
|
return out
|
|
|
|
|
|
|
|
|
|
|
|
class QAPIGenCCode(QAPIGen):
|
|
|
|
|
|
|
|
def __init__(self, fname):
|
2020-03-04 15:59:31 +00:00
|
|
|
super().__init__(fname)
|
2019-10-18 07:43:44 +00:00
|
|
|
self._start_if = None
|
|
|
|
|
|
|
|
def start_if(self, ifcond):
|
|
|
|
assert self._start_if is None
|
|
|
|
self._start_if = (ifcond, self._body, self._preamble)
|
|
|
|
|
|
|
|
def end_if(self):
|
|
|
|
assert self._start_if
|
|
|
|
self._wrap_ifcond()
|
|
|
|
self._start_if = None
|
|
|
|
|
|
|
|
def _wrap_ifcond(self):
|
|
|
|
self._body = _wrap_ifcond(self._start_if[0],
|
|
|
|
self._start_if[1], self._body)
|
|
|
|
self._preamble = _wrap_ifcond(self._start_if[0],
|
|
|
|
self._start_if[2], self._preamble)
|
|
|
|
|
|
|
|
def get_content(self):
|
|
|
|
assert self._start_if is None
|
2020-03-04 15:59:31 +00:00
|
|
|
return super().get_content()
|
2019-10-18 07:43:44 +00:00
|
|
|
|
|
|
|
|
|
|
|
class QAPIGenC(QAPIGenCCode):
|
|
|
|
|
|
|
|
def __init__(self, fname, blurb, pydoc):
|
2020-03-04 15:59:31 +00:00
|
|
|
super().__init__(fname)
|
2019-10-18 07:43:44 +00:00
|
|
|
self._blurb = blurb
|
|
|
|
self._copyright = '\n * '.join(re.findall(r'^Copyright .*', pydoc,
|
|
|
|
re.MULTILINE))
|
|
|
|
|
|
|
|
def _top(self):
|
|
|
|
return mcgen('''
|
|
|
|
/* AUTOMATICALLY GENERATED, DO NOT MODIFY */
|
|
|
|
|
|
|
|
/*
|
|
|
|
%(blurb)s
|
|
|
|
*
|
|
|
|
* %(copyright)s
|
|
|
|
*
|
|
|
|
* This work is licensed under the terms of the GNU LGPL, version 2.1 or later.
|
|
|
|
* See the COPYING.LIB file in the top-level directory.
|
|
|
|
*/
|
|
|
|
|
|
|
|
''',
|
|
|
|
blurb=self._blurb, copyright=self._copyright)
|
|
|
|
|
|
|
|
def _bottom(self):
|
|
|
|
return mcgen('''
|
|
|
|
|
|
|
|
/* Dummy declaration to prevent empty .o file */
|
|
|
|
char qapi_dummy_%(name)s;
|
|
|
|
''',
|
|
|
|
name=c_fname(self.fname))
|
|
|
|
|
|
|
|
|
|
|
|
class QAPIGenH(QAPIGenC):
|
|
|
|
|
|
|
|
def _top(self):
|
2020-03-04 15:59:31 +00:00
|
|
|
return super()._top() + guardstart(self.fname)
|
2019-10-18 07:43:44 +00:00
|
|
|
|
|
|
|
def _bottom(self):
|
|
|
|
return guardend(self.fname)
|
|
|
|
|
|
|
|
|
|
|
|
@contextmanager
|
|
|
|
def ifcontext(ifcond, *args):
|
2020-10-09 16:15:24 +00:00
|
|
|
"""
|
|
|
|
A with-statement context manager that wraps with `start_if()` / `end_if()`.
|
2019-10-18 07:43:44 +00:00
|
|
|
|
2020-10-09 16:15:24 +00:00
|
|
|
:param ifcond: A list of conditionals, passed to `start_if()`.
|
|
|
|
:param args: any number of `QAPIGenCCode`.
|
2019-10-18 07:43:44 +00:00
|
|
|
|
|
|
|
Example::
|
|
|
|
|
|
|
|
with ifcontext(ifcond, self._genh, self._genc):
|
|
|
|
modify self._genh and self._genc ...
|
|
|
|
|
|
|
|
Is equivalent to calling::
|
|
|
|
|
|
|
|
self._genh.start_if(ifcond)
|
|
|
|
self._genc.start_if(ifcond)
|
|
|
|
modify self._genh and self._genc ...
|
|
|
|
self._genh.end_if()
|
|
|
|
self._genc.end_if()
|
|
|
|
"""
|
|
|
|
for arg in args:
|
|
|
|
arg.start_if(ifcond)
|
|
|
|
yield
|
|
|
|
for arg in args:
|
|
|
|
arg.end_if()
|
|
|
|
|
|
|
|
|
|
|
|
class QAPISchemaMonolithicCVisitor(QAPISchemaVisitor):
|
|
|
|
|
|
|
|
def __init__(self, prefix, what, blurb, pydoc):
|
|
|
|
self._prefix = prefix
|
|
|
|
self._what = what
|
|
|
|
self._genc = QAPIGenC(self._prefix + self._what + '.c',
|
|
|
|
blurb, pydoc)
|
|
|
|
self._genh = QAPIGenH(self._prefix + self._what + '.h',
|
|
|
|
blurb, pydoc)
|
|
|
|
|
|
|
|
def write(self, output_dir):
|
|
|
|
self._genc.write(output_dir)
|
|
|
|
self._genh.write(output_dir)
|
|
|
|
|
|
|
|
|
|
|
|
class QAPISchemaModularCVisitor(QAPISchemaVisitor):
|
|
|
|
|
2019-11-20 18:25:51 +00:00
|
|
|
def __init__(self, prefix, what, user_blurb, builtin_blurb, pydoc):
|
2019-10-18 07:43:44 +00:00
|
|
|
self._prefix = prefix
|
|
|
|
self._what = what
|
2019-11-20 18:25:51 +00:00
|
|
|
self._user_blurb = user_blurb
|
|
|
|
self._builtin_blurb = builtin_blurb
|
2019-10-18 07:43:44 +00:00
|
|
|
self._pydoc = pydoc
|
|
|
|
self._genc = None
|
|
|
|
self._genh = None
|
|
|
|
self._module = {}
|
|
|
|
self._main_module = None
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
def _is_user_module(name):
|
|
|
|
return name and not name.startswith('./')
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
def _is_builtin_module(name):
|
|
|
|
return not name
|
|
|
|
|
|
|
|
def _module_dirname(self, what, name):
|
|
|
|
if self._is_user_module(name):
|
|
|
|
return os.path.dirname(name)
|
|
|
|
return ''
|
|
|
|
|
|
|
|
def _module_basename(self, what, name):
|
|
|
|
ret = '' if self._is_builtin_module(name) else self._prefix
|
|
|
|
if self._is_user_module(name):
|
|
|
|
basename = os.path.basename(name)
|
|
|
|
ret += what
|
|
|
|
if name != self._main_module:
|
|
|
|
ret += '-' + os.path.splitext(basename)[0]
|
|
|
|
else:
|
|
|
|
name = name[2:] if name else 'builtin'
|
|
|
|
ret += re.sub(r'-', '-' + name + '-', what)
|
|
|
|
return ret
|
|
|
|
|
|
|
|
def _module_filename(self, what, name):
|
|
|
|
return os.path.join(self._module_dirname(what, name),
|
|
|
|
self._module_basename(what, name))
|
|
|
|
|
|
|
|
def _add_module(self, name, blurb):
|
|
|
|
basename = self._module_filename(self._what, name)
|
|
|
|
genc = QAPIGenC(basename + '.c', blurb, self._pydoc)
|
|
|
|
genh = QAPIGenH(basename + '.h', blurb, self._pydoc)
|
|
|
|
self._module[name] = (genc, genh)
|
2019-11-20 18:25:51 +00:00
|
|
|
self._genc, self._genh = self._module[name]
|
2019-10-18 07:43:44 +00:00
|
|
|
|
|
|
|
def _add_user_module(self, name, blurb):
|
|
|
|
assert self._is_user_module(name)
|
|
|
|
if self._main_module is None:
|
|
|
|
self._main_module = name
|
|
|
|
self._add_module(name, blurb)
|
|
|
|
|
|
|
|
def _add_system_module(self, name, blurb):
|
|
|
|
self._add_module(name and './' + name, blurb)
|
|
|
|
|
|
|
|
def write(self, output_dir, opt_builtins=False):
|
|
|
|
for name in self._module:
|
|
|
|
if self._is_builtin_module(name) and not opt_builtins:
|
|
|
|
continue
|
|
|
|
(genc, genh) = self._module[name]
|
|
|
|
genc.write(output_dir)
|
|
|
|
genh.write(output_dir)
|
|
|
|
|
2020-03-04 15:59:32 +00:00
|
|
|
def _begin_system_module(self, name):
|
|
|
|
pass
|
|
|
|
|
2019-10-18 07:43:44 +00:00
|
|
|
def _begin_user_module(self, name):
|
|
|
|
pass
|
|
|
|
|
|
|
|
def visit_module(self, name):
|
2019-11-20 18:25:51 +00:00
|
|
|
if name is None:
|
|
|
|
if self._builtin_blurb:
|
|
|
|
self._add_system_module(None, self._builtin_blurb)
|
|
|
|
self._begin_system_module(name)
|
|
|
|
else:
|
|
|
|
# The built-in module has not been created. No code may
|
|
|
|
# be generated.
|
|
|
|
self._genc = None
|
|
|
|
self._genh = None
|
2019-10-18 07:43:44 +00:00
|
|
|
else:
|
2019-11-20 18:25:51 +00:00
|
|
|
self._add_user_module(name, self._user_blurb)
|
2019-10-18 07:43:44 +00:00
|
|
|
self._begin_user_module(name)
|
|
|
|
|
|
|
|
def visit_include(self, name, info):
|
|
|
|
relname = os.path.relpath(self._module_filename(self._what, name),
|
|
|
|
os.path.dirname(self._genh.fname))
|
|
|
|
self._genh.preamble_add(mcgen('''
|
|
|
|
#include "%(relname)s.h"
|
|
|
|
''',
|
|
|
|
relname=relname))
|