diff options
author | Ole Troan <ot@cisco.com> | 2019-09-19 01:08:30 +0200 |
---|---|---|
committer | Dave Barach <openvpp@barachs.net> | 2019-09-25 12:02:32 +0000 |
commit | 2a1ca787b9ca0021bd5da73450caedc2ff41c76d (patch) | |
tree | f9d879e07099f727ca565cfd8bc1624d3e7dc405 /src/tools/vppapigen | |
parent | 33af8c1ed89f15cf0601ee891e9603bef16f2c93 (diff) |
api: split api generated files
Split generation of API message enum and the typedefs into
separate files, so that the type file can be included from
elsewhere.
Generate a C file for VPP that contains the API registration,
this was previously done via X macros by the C pre-
processor.
This allows deleting lots of skeleton/copy paste code
for each feature.
plugins/flowprobe
plugins/map
examples/sample-plugin
vnet/ipip
used as Guinea pigs.
Generate a C Test file for VAT, that does the same for VAT plugins.
Also add support for a per-message CLI option, that is currently
limited to VAT help text. option vat_help = "<help text>";
Type: refactor
Change-Id: I245e3104bb76f7e1fb69a59ab20cc7c8dfcdd460
Signed-off-by: Ole Troan <ot@cisco.com>
Diffstat (limited to 'src/tools/vppapigen')
-rwxr-xr-x | src/tools/vppapigen/vppapigen.py | 9 | ||||
-rw-r--r-- | src/tools/vppapigen/vppapigen_c.py | 324 | ||||
-rw-r--r-- | src/tools/vppapigen/vppapigen_json.py | 2 |
3 files changed, 261 insertions, 74 deletions
diff --git a/src/tools/vppapigen/vppapigen.py b/src/tools/vppapigen/vppapigen.py index 861b71ddf43..57a30fd3c29 100755 --- a/src/tools/vppapigen/vppapigen.py +++ b/src/tools/vppapigen/vppapigen.py @@ -259,12 +259,12 @@ class Define(): self.name = name self.flags = flags self.block = block - self.crc = str(block).encode() self.dont_trace = False self.manual_print = False self.manual_endian = False self.autoreply = False self.singular = False + self.options = {} for f in flags: if f == 'dont_trace': self.dont_trace = True @@ -279,8 +279,12 @@ class Define(): if isinstance(b, Option): if b[1] == 'singular' and b[2] == 'true': self.singular = True + else: + self.options[b.option] = b.value block.remove(b) + self.vla = vla_is_last_check(name, block) + self.crc = str(block).encode() def __repr__(self): return self.name + str(self.flags) + str(self.block) @@ -920,6 +924,7 @@ def main(): cliparser = argparse.ArgumentParser(description='VPP API generator') cliparser.add_argument('--pluginpath', default=""), cliparser.add_argument('--includedir', action='append'), + cliparser.add_argument('--outputdir', action='store'), cliparser.add_argument('--input', type=argparse.FileType('r', encoding='UTF-8'), default=sys.stdin) @@ -1011,7 +1016,7 @@ def main(): .format(module_path, err)) return 1 - result = plugin.run(filename, s) + result = plugin.run(args, filename, s) if result: print(result, file=args.output) else: diff --git a/src/tools/vppapigen/vppapigen_c.py b/src/tools/vppapigen/vppapigen_c.py index 7c383c27472..bab255c58a2 100644 --- a/src/tools/vppapigen/vppapigen_c.py +++ b/src/tools/vppapigen/vppapigen_c.py @@ -4,6 +4,7 @@ import os import time import sys from io import StringIO +import shutil datestring = datetime.datetime.utcfromtimestamp( int(os.environ.get('SOURCE_DATE_EPOCH', time.time()))) @@ -95,80 +96,16 @@ def api2c(fieldtype): return fieldtype -def typedefs(objs, filename): - name = filename.replace('.', '_') - output = '''\ +def typedefs(filename): + output = '''\ /****** Typedefs ******/ #ifdef vl_typedefs -#ifndef included_{module}_typedef -#define included_{module}_typedef -''' - output = output.format(module=name) - - for o in objs: - tname = o.__class__.__name__ - if tname == 'Using': - if 'length' in o.alias: - output += 'typedef %s vl_api_%s_t[%s];\n' % (o.alias['type'], o.name, o.alias['length']) - else: - output += 'typedef %s vl_api_%s_t;\n' % (o.alias['type'], o.name) - elif tname == 'Enum': - if o.enumtype == 'u32': - output += "typedef enum {\n" - else: - output += "typedef enum __attribute__((__packed__)) {\n" - - for b in o.block: - output += " %s = %s,\n" % (b[0], b[1]) - output += '} vl_api_%s_t;\n' % o.name - if o.enumtype != 'u32': - size1 = 'sizeof(vl_api_%s_t)' % o.name - size2 = 'sizeof(%s)' % o.enumtype - err_str = 'size of API enum %s is wrong' % o.name - output += ('STATIC_ASSERT(%s == %s, "%s");\n' - % (size1, size2, err_str)) - else: - if tname == 'Union': - output += "typedef VL_API_PACKED(union _vl_api_%s {\n" % o.name - else: - output += ("typedef VL_API_PACKED(struct _vl_api_%s {\n" - % o.name) - for b in o.block: - if b.type == 'Option': - continue - if b.type == 'Field': - output += " %s %s;\n" % (api2c(b.fieldtype), - b.fieldname) - elif b.type == 'Array': - if b.lengthfield: - output += " %s %s[0];\n" % (api2c(b.fieldtype), - b.fieldname) - else: - # Fixed length strings decay to nul terminated u8 - if b.fieldtype == 'string': - if b.modern_vla: - output += (' {} {};\n' - .format(api2c(b.fieldtype), - b.fieldname)) - else: - output += (' u8 {}[{}];\n' - .format(b.fieldname, b.length)) - else: - output += (" %s %s[%s];\n" % - (api2c(b.fieldtype), b.fieldname, - b.length)) - else: - raise ValueError("Error in processing type {} for {}" - .format(b, o.name)) - - output += '}) vl_api_%s_t;\n' % o.name - - output += "\n#endif" - output += "\n#endif\n\n" - +#include "{include}.api_types.h" +#endif +'''.format(include=filename) return output @@ -566,14 +503,258 @@ def version_tuple(s, module): return output +def generate_include_enum(s, module, stream): + write = stream.write + + if len(s['Define']): + write('typedef enum {\n') + for t in s['Define']: + write(' VL_API_{},\n'.format(t.name.upper())) + write(' VL_MSG_FIRST_AVAILABLE\n') + write('}} vl_api_{}_enum_t;\n'.format(module)) + +# +# Generate separate API _types file. +# +def generate_include_types(s, module, stream): + write = stream.write + + write('#ifndef included_{module}_api_types_h\n'.format(module=module)) + write('#define included_{module}_api_types_h\n'.format(module=module)) + + if len(s['Import']): + write('/* Imported API files */\n') + for i in s['Import']: + filename = i.filename.replace('plugins/', '') + write('#include <{}_types.h>\n'.format(filename)) + + for o in s['types'] + s['Define']: + tname = o.__class__.__name__ + if tname == 'Using': + if 'length' in o.alias: + write('typedef %s vl_api_%s_t[%s];\n' % (o.alias['type'], o.name, o.alias['length'])) + else: + write('typedef %s vl_api_%s_t;\n' % (o.alias['type'], o.name)) + elif tname == 'Enum': + if o.enumtype == 'u32': + write("typedef enum {\n") + else: + write("typedef enum __attribute__((packed)) {\n") + + for b in o.block: + write(" %s = %s,\n" % (b[0], b[1])) + write('} vl_api_%s_t;\n' % o.name) + if o.enumtype != 'u32': + size1 = 'sizeof(vl_api_%s_t)' % o.name + size2 = 'sizeof(%s)' % o.enumtype + err_str = 'size of API enum %s is wrong' % o.name + write('STATIC_ASSERT(%s == %s, "%s");\n' + % (size1, size2, err_str)) + else: + if tname == 'Union': + write("typedef union __attribute__ ((packed)) _vl_api_%s {\n" % o.name) + else: + write(("typedef struct __attribute__ ((packed)) _vl_api_%s {\n") + % o.name) + for b in o.block: + if b.type == 'Option': + continue + if b.type == 'Field': + write(" %s %s;\n" % (api2c(b.fieldtype), + b.fieldname)) + elif b.type == 'Array': + if b.lengthfield: + write(" %s %s[0];\n" % (api2c(b.fieldtype), + b.fieldname)) + else: + # Fixed length strings decay to nul terminated u8 + if b.fieldtype == 'string': + if b.modern_vla: + write(' {} {};\n' + .format(api2c(b.fieldtype), + b.fieldname)) + else: + write(' u8 {}[{}];\n' + .format(b.fieldname, b.length)) + else: + write(" %s %s[%s];\n" % + (api2c(b.fieldtype), b.fieldname, + b.length)) + else: + raise ValueError("Error in processing type {} for {}" + .format(b, o.name)) + + write('} vl_api_%s_t;\n' % o.name) + + write("\n#endif\n") + + +def generate_c_boilerplate(services, defines, file_crc, module, stream): + write = stream.write + + hdr = '''\ +#define vl_endianfun /* define message structures */ +#include "{module}.api.h" +#undef vl_endianfun + +/* instantiate all the print functions we know about */ +#define vl_print(handle, ...) vlib_cli_output (handle, __VA_ARGS__) +#define vl_printfun +#include "{module}.api.h" +#undef vl_printfun + +''' + + write(hdr.format(module=module)) + write('static u16\n') + write('setup_message_id_table (void) {\n') + write(' api_main_t *am = &api_main;\n') + write(' u16 msg_id_base = vl_msg_api_get_msg_ids ("{}_{crc:08x}", VL_MSG_FIRST_AVAILABLE);\n' + .format(module, crc=file_crc)) + + + for d in defines: + write(' vl_msg_api_add_msg_name_crc (am, "{n}_{crc:08x}",\n' + ' VL_API_{ID} + msg_id_base);\n' + .format(n=d.name, ID=d.name.upper(), crc=d.crc)) + for s in services: + write(' vl_msg_api_set_handlers(VL_API_{ID} + msg_id_base, "{n}",\n' + ' vl_api_{n}_t_handler, vl_noop_handler,\n' + ' vl_api_{n}_t_endian, vl_api_{n}_t_print,\n' + ' sizeof(vl_api_{n}_t), 1);\n' + .format(n=s.caller, ID=s.caller.upper())) + + write(' return msg_id_base;\n') + write('}\n') + + +def generate_c_test_plugin_boilerplate(services, defines, file_crc, module, stream): + write = stream.write + + define_hash = {d.name:d for d in defines} + replies = {} + + hdr = '''\ +#define vl_endianfun /* define message structures */ +#include "{module}.api.h" +#undef vl_endianfun + +/* instantiate all the print functions we know about */ +#define vl_print(handle, ...) vlib_cli_output (handle, __VA_ARGS__) +#define vl_printfun +#include "{module}.api.h" +#undef vl_printfun + +''' + + write(hdr.format(module=module)) + for s in services: + try: + d = define_hash[s.reply] + except: + continue + if d.manual_print: + write('/* Manual definition requested for: vl_api_{n}_t_hander() */\n' + .format(n=s.reply)) + continue + if not define_hash[s.caller].autoreply: + write('/* Only autoreply is supported (vl_api_{n}_t_hander()) */\n' + .format(n=s.reply)) + continue + write('static void\n') + write('vl_api_{n}_t_handler (vl_api_{n}_t * mp) {{\n'.format(n=s.reply)) + write(' vat_main_t * vam = {}_test_main.vat_main;\n'.format(module)) + write(' i32 retval = ntohl(mp->retval);\n') + write(' if (vam->async_mode) {\n') + write(' vam->async_errors += (retval < 0);\n') + write(' } else {\n') + write(' vam->retval = retval;\n') + write(' vam->result_ready = 1;\n') + write(' }\n') + write('}\n') + + write('static void\n') + write('setup_message_id_table (vat_main_t * vam, u16 msg_id_base) {\n') + for s in services: + write(' vl_msg_api_set_handlers(VL_API_{ID} + msg_id_base, "{n}",\n' + ' vl_api_{n}_t_handler, vl_noop_handler,\n' + ' vl_api_{n}_t_endian, vl_api_{n}_t_print,\n' + ' sizeof(vl_api_{n}_t), 1);\n' + .format(n=s.reply, ID=s.reply.upper())) + write(' hash_set_mem (vam->function_by_name, "{n}", api_{n});\n'.format(n=s.caller)) + try: + write(' hash_set_mem (vam->help_by_name, "{n}", "{help}");\n' + .format(n=s.caller, help=define_hash[s.caller].options['vat_help'])) + except: + pass + + write('}\n') + + write('clib_error_t * vat_plugin_register (vat_main_t *vam)\n') + write('{\n') + write(' {n}_test_main_t * mainp = &{n}_test_main;\n'.format(n=module)) + write(' mainp->vat_main = vam;\n') + write(' mainp->msg_id_base = vl_client_get_first_plugin_msg_id ("{n}_{crc:08x}");\n' + .format(n=module, crc=file_crc)) + write(' if (mainp->msg_id_base == (u16) ~0)\n') + write(' return clib_error_return (0, "{} plugin not loaded...");\n'.format(module)) + write(' setup_message_id_table (vam, mainp->msg_id_base);\n') + write(' return 0;\n') + write('}\n') + + # # Plugin entry point # -def run(input_filename, s): +def run(args, input_filename, s): stream = StringIO() + + if not args.outputdir: + sys.stderr.write('Missing --outputdir argument') + return None + basename = os.path.basename(input_filename) filename, file_extension = os.path.splitext(basename) modulename = filename.replace('.', '_') + filename_enum = os.path.join(args.outputdir + '/' + basename + '_enum.h') + filename_types = os.path.join(args.outputdir + '/' + basename + '_types.h') + filename_c = os.path.join(args.outputdir + '/' + basename + '.c') + filename_c_test = os.path.join(args.outputdir + '/' + basename + '_test.c') + + # Generate separate types file + st = StringIO() + generate_include_types(s, modulename, st) + with open (filename_types, 'w') as fd: + st.seek (0) + shutil.copyfileobj (st, fd) + st.close() + + # Generate separate enum file + st = StringIO() + generate_include_enum(s, modulename, st) + with open (filename_enum, 'w') as fd: + st.seek (0) + shutil.copyfileobj (st, fd) + st.close() + + # Generate separate C file + st = StringIO() + generate_c_boilerplate(s['Service'], s['Define'], s['file_crc'], + modulename, st) + with open (filename_c, 'w') as fd: + st.seek (0) + shutil.copyfileobj(st, fd) + st.close() + + # Generate separate C test file + # This is only supported for plugins at the moment + st = StringIO() + generate_c_test_plugin_boilerplate(s['Service'], s['Define'], s['file_crc'], + modulename, st) + with open (filename_c_test, 'w') as fd: + st.seek (0) + shutil.copyfileobj(st, fd) + st.close() output = top_boilerplate.format(datestring=datestring, input_filename=basename) @@ -581,10 +762,11 @@ def run(input_filename, s): output += msg_ids(s) output += msg_names(s) output += msg_name_crc_list(s, filename) - output += typedefs(s['types'] + s['Define'], filename + file_extension) + output += typedefs(modulename) printfun_types(s['types'], stream, modulename) printfun(s['Define'], stream, modulename) output += stream.getvalue() + stream.close() output += endianfun(s['types'] + s['Define'], modulename) output += version_tuple(s, basename) output += bottom_boilerplate.format(input_filename=basename, diff --git a/src/tools/vppapigen/vppapigen_json.py b/src/tools/vppapigen/vppapigen_json.py index f67a3d62da1..c2a2b7d0682 100644 --- a/src/tools/vppapigen/vppapigen_json.py +++ b/src/tools/vppapigen/vppapigen_json.py @@ -61,7 +61,7 @@ def walk_defs(s, is_message=False): # # Plugin entry point # -def run(filename, s): +def run(args, filename, s): j = {} j['types'] = (walk_defs([o for o in s['types'] |