From dcd2f898af9d4efcb22417c19fa1b3abc8c548c5 Mon Sep 17 00:00:00 2001 From: aqua Date: Wed, 7 Aug 2024 17:36:57 +0300 Subject: Added bazel rule for interface_generator --- tools/interface_generator/BUILD.bazel | 28 +++++-- tools/interface_generator/LICENSE.md | 21 ++--- tools/interface_generator/MODULE.bazel | 3 +- tools/interface_generator/defs.bzl | 53 ++++++++++++ tools/interface_generator/interface_definition.py | 46 +++++++++++ tools/interface_generator/interface_generator.py | 95 ++++++++++++++-------- tools/interface_generator/requirements.txt | 1 + tools/interface_generator/requirements_lock.txt | 28 +++++++ tools/interface_generator/templates.py | 9 ++ .../templates/__c_functions.mako | 4 + .../templates/__c_system_include.mako | 4 + tools/interface_generator/templates/__c_types.mako | 6 ++ tools/interface_generator/templates/__header.mako | 5 ++ .../interface_generator/templates/c_functions.mako | 4 - tools/interface_generator/templates/c_header.mako | 13 --- .../templates/c_system_include.mako | 4 - tools/interface_generator/templates/c_types.mako | 6 -- .../interface_generator/templates/interface.h.mako | 8 ++ .../templates/interface_mock.cpp.mako | 32 ++++++++ .../templates/interface_mock.hpp.mako | 26 ++++++ tools/interface_generator/templates_unittest.py | 21 +++++ tools/interface_generator/test/BUILD.bazel | 17 ++++ tools/interface_generator/test/test_kstdio.cpp | 20 +++++ 23 files changed, 374 insertions(+), 80 deletions(-) create mode 100644 tools/interface_generator/defs.bzl create mode 100644 tools/interface_generator/interface_definition.py create mode 100644 tools/interface_generator/templates.py create mode 100644 tools/interface_generator/templates/__c_functions.mako create mode 100644 tools/interface_generator/templates/__c_system_include.mako create mode 100644 tools/interface_generator/templates/__c_types.mako create mode 100644 tools/interface_generator/templates/__header.mako delete mode 100644 tools/interface_generator/templates/c_functions.mako delete mode 100644 tools/interface_generator/templates/c_header.mako delete mode 100644 tools/interface_generator/templates/c_system_include.mako delete mode 100644 tools/interface_generator/templates/c_types.mako create mode 100644 tools/interface_generator/templates/interface.h.mako create mode 100644 tools/interface_generator/templates/interface_mock.cpp.mako create mode 100644 tools/interface_generator/templates/interface_mock.hpp.mako create mode 100755 tools/interface_generator/templates_unittest.py create mode 100644 tools/interface_generator/test/BUILD.bazel create mode 100644 tools/interface_generator/test/test_kstdio.cpp (limited to 'tools/interface_generator') diff --git a/tools/interface_generator/BUILD.bazel b/tools/interface_generator/BUILD.bazel index 2e700a2..c6f4a79 100644 --- a/tools/interface_generator/BUILD.bazel +++ b/tools/interface_generator/BUILD.bazel @@ -1,4 +1,6 @@ load("@rules_python//python:pip.bzl", "compile_pip_requirements") +load("@rules_python//python/entry_points:py_console_script_binary.bzl", "py_console_script_binary") +load(":defs.bzl", "generate_interface") package(default_visibility = ["//visibility:public"]) @@ -9,17 +11,31 @@ compile_pip_requirements( requirements_txt = "requirements_lock.txt", ) +py_console_script_binary( + name = "pylint", + pkg = "@pip//pylint", + script = "pylint", +) + py_binary( name = "interface_generator", - srcs = ["interface_generator.py"], - args = ["-l", "$(location LICENSE.md)"], - data = glob(["templates/*"]) + ["LICENSE.md"], + srcs = [ + "interface_definition.py", + "interface_generator.py", + "templates.py", + ], + data = glob(["templates/*"]), + main = "interface_generator.py", deps = [ "@pip//mako", ], ) -alias( - name = "smokeTest", - actual = ":interface_generator", +py_test( + name = "templates_unittest", + srcs = ["templates_unittest.py", "templates.py"], + data = glob(["templates/*"]), + deps = ["@pip//mako"], ) + +exports_files(["LICENSE.md"]) diff --git a/tools/interface_generator/LICENSE.md b/tools/interface_generator/LICENSE.md index 39318a8..ddb4966 100644 --- a/tools/interface_generator/LICENSE.md +++ b/tools/interface_generator/LICENSE.md @@ -1,15 +1,16 @@ Copyright (c) 2024 aqua@iserlohn-fortress.net -Permission to use, copy, modify, and distribute this software for any purpose -with or without fee is hereby granted, provided that the above copyright notice -and this permission notice appear in all copies. +Permission to use, copy, modify, and distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. - THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL + WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE + AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA + OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER + TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + PERFORMANCE OF THIS SOFTWARE. SPDX License Identifier: [ISC](https://spdx.org/licenses/ISC.html) diff --git a/tools/interface_generator/MODULE.bazel b/tools/interface_generator/MODULE.bazel index d87d7d8..b5cbae6 100644 --- a/tools/interface_generator/MODULE.bazel +++ b/tools/interface_generator/MODULE.bazel @@ -5,7 +5,8 @@ # For more details, please check https://github.com/bazelbuild/bazel/issues/18958 ############################################################################### -bazel_dep(name = "rules_python", version = "0.31.0") +bazel_dep(name = "googletest", version = "1.15.2") +bazel_dep(name = "rules_python", version = "0.34.0") pip = use_extension("@rules_python//python/extensions:pip.bzl", "pip") pip.parse( diff --git a/tools/interface_generator/defs.bzl b/tools/interface_generator/defs.bzl new file mode 100644 index 0000000..d5c6f91 --- /dev/null +++ b/tools/interface_generator/defs.bzl @@ -0,0 +1,53 @@ +def _generate_interface_impl(ctx): + out_hdrs = [ + ctx.actions.declare_file(ctx.attr.interface + ".h"), + ctx.actions.declare_file(ctx.attr.interface + "_mock.hpp"), + ] + out_srcs = [ + ctx.actions.declare_file(ctx.attr.interface + "_mock.cpp"), + ] + args = ["-l", ctx.file.license.path, "-o", out_hdrs[0].dirname] + + ctx.actions.run( + tools = ctx.attr._interface_generator_tool[DefaultInfo].data_runfiles.files, + inputs = [ctx.file.license], + outputs = out_srcs + out_hdrs, + executable = ctx.executable._interface_generator_tool, + arguments = args, + mnemonic = "GenerateInterface", + ) + + compilation_ctx = cc_common.create_compilation_context( + headers = depset(out_hdrs), + includes = depset([out_hdrs[0].dirname]), + ) + + return [ + DefaultInfo(files = depset(out_srcs)), + CcInfo(compilation_context = compilation_ctx), + ] + +generate_interface_rule = rule( + implementation = _generate_interface_impl, + attrs = { + "interface": attr.string(), + "license": attr.label(allow_single_file = True), + "_interface_generator_tool": attr.label( + executable = True, + cfg = "exec", + allow_files = True, + providers = [DefaultInfo], + default = Label("//:interface_generator"), + ), + "outs": attr.output_list(), + }, +) + +def generate_interface(name, interface, license, visibility = None): + generate_interface_rule( + name = name, + interface = interface, + outs = [name + "_mock.cpp"], + license = license, + visibility = visibility, + ) diff --git a/tools/interface_generator/interface_definition.py b/tools/interface_generator/interface_definition.py new file mode 100644 index 0000000..10d1f60 --- /dev/null +++ b/tools/interface_generator/interface_definition.py @@ -0,0 +1,46 @@ +""" +interface_definition.py +""" + +def __read_license(path): + """read and starrify a license""" + license_text = "" + with open(path, encoding="utf-8") as license_file: + license_text = "".join( + [ + f" * { line.rstrip().ljust(72)[:72] } * \n" + for line in license_file.readlines() + ] + ).rstrip() + + return license_text + + +def parse(args): + """return a mock interface definition""" + + interface_dict = { + "name": "kstdio", + "license": __read_license(args.license), + "system_includes": ["stdarg.h"], + "types": [ + { + "name": "File", + "members": [ + "int fd", + "int (*putc)(const struct kstdio_File*, const char)", + "int (*puts)(const struct kstdio_File*, const char*)", + ], + }, + ], + "functions": [ + { + "name": "printf", + "return": "int", + "arguments": ["const char* format"], + "argument_names": ["format"], + }, + ], + } + + return interface_dict diff --git a/tools/interface_generator/interface_generator.py b/tools/interface_generator/interface_generator.py index 4eb8d69..4d3afa4 100755 --- a/tools/interface_generator/interface_generator.py +++ b/tools/interface_generator/interface_generator.py @@ -1,8 +1,15 @@ #!/usr/bin/env python3 +""" +interface_generator.py +""" + from argparse import ArgumentParser -from mako.template import Template +from pathlib import Path +import sys from mako.lookup import TemplateLookup +from interface_definition import parse as parse_interface +from templates import get_templates PROG = { "name": "interface_generator", @@ -10,50 +17,66 @@ PROG = { } -def generate_file(template, kwargs): - """generate file using a tempalte""" +def generate_file(template: Path, templates: Path, output, kwargs): + """generate file using a tempalte and write it to the output location""" lookup = TemplateLookup(directories=[".", "templates"]) - template = lookup.get_template(template) - result = template.render(**kwargs, PROG=PROG) + mako_template = lookup.get_template(str(template.relative_to(templates))) + result = mako_template.render(**kwargs, PROG=PROG) + + output_name = template.stem.replace("interface", kwargs["name"]) + print(f'{kwargs["name"]} via {template.name} => {output_name}') - # print(kwargs) - print(result) + if isinstance(output, Path): + # print(f"writing to {(output / output_name).absolute()}") + with open(output / output_name, "w") as output_file: + print(result, file=output_file) + else: + print(result, file=output) def main(): + """main function""" parser = ArgumentParser( prog="interface_generator", description="Generate a C header file from an interface definition", ) - parser.add_argument("-l", "--license", required=True) - - parser_args = parser.parse_args() - print(parser_args) - - args = { - "name": "stdio", - "license": parser_args.license, - "system_includes": ["stdarg.h"], - "types": [ - { - "name": "FILE", - "members": [ - "int fd", - "int (*putc)(const struct FILE*, const char)", - "int (*puts)(const struct FILE*, const char*)", - ], - }, - ], - "functions": [ - { - "name": "printf", - "return": "int", - "arguments": ["const char*__restrict__ format", "..."], - }, - ], - } - - generate_file("c_header.mako", args) + parser.add_argument( + "-i", + "--interface", + type=Path, + # required=True, + help="path to interface file", + ) + parser.add_argument( + "-t", + "--templates", + type=Path, + default=Path("templates"), + help="templates location", + ) + parser.add_argument( + "-l", + "--license", + type=Path, + required=True, + help="path to license file", + ) + parser.add_argument( + "-o", + "--output", + type=Path, + default=sys.stdout, + help="path to output, stdout by default", + ) + + args = parser.parse_args() + # print(args) + + interface = parse_interface(args) + # print(interface) + + for template in get_templates(args.templates): + generate_file(template, args.templates, args.output, interface) if __name__ == "__main__": diff --git a/tools/interface_generator/requirements.txt b/tools/interface_generator/requirements.txt index 9c39cd4..ad75266 100644 --- a/tools/interface_generator/requirements.txt +++ b/tools/interface_generator/requirements.txt @@ -1,2 +1,3 @@ Mako==1.3.3 MarkupSafe==2.1.5 +pylint diff --git a/tools/interface_generator/requirements_lock.txt b/tools/interface_generator/requirements_lock.txt index 85ae312..e502874 100644 --- a/tools/interface_generator/requirements_lock.txt +++ b/tools/interface_generator/requirements_lock.txt @@ -4,6 +4,18 @@ # # bazel run //:requirements.update # +astroid==3.2.4 \ + --hash=sha256:0e14202810b30da1b735827f78f5157be2bbd4a7a59b7707ca0bfc2fb4c0063a \ + --hash=sha256:413658a61eeca6202a59231abb473f932038fbcbf1666587f66d482083413a25 + # via pylint +dill==0.3.8 \ + --hash=sha256:3ebe3c479ad625c4553aca177444d89b486b1d84982eeacded644afc0cf797ca \ + --hash=sha256:c36ca9ffb54365bdd2f8eb3eff7d2a21237f8452b57ace88b1ac615b7e815bd7 + # via pylint +isort==5.13.2 \ + --hash=sha256:48fdfcb9face5d58a4f6dde2e72a1fb8dcaf8ab26f95ab49fab84c2ddefb0109 \ + --hash=sha256:8ca5e72a8d85860d5a3fa69b8745237f2939afe12dbf656afbcb47fe72d947a6 + # via pylint mako==1.3.3 \ --hash=sha256:5324b88089a8978bf76d1629774fcc2f1c07b82acdf00f4c5dd8ceadfffc4b40 \ --hash=sha256:e16c01d9ab9c11f7290eef1cfefc093fb5a45ee4a3da09e2fec2e4d1bae54e73 @@ -72,3 +84,19 @@ markupsafe==2.1.5 \ # via # -r requirements.txt # mako +mccabe==0.7.0 \ + --hash=sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325 \ + --hash=sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e + # via pylint +platformdirs==4.2.2 \ + --hash=sha256:2d7a1657e36a80ea911db832a8a6ece5ee53d8de21edd5cc5879af6530b1bfee \ + --hash=sha256:38b7b51f512eed9e84a22788b4bce1de17c0adb134d6becb09836e37d8654cd3 + # via pylint +pylint==3.2.6 \ + --hash=sha256:03c8e3baa1d9fb995b12c1dbe00aa6c4bcef210c2a2634374aedeb22fb4a8f8f \ + --hash=sha256:a5d01678349454806cff6d886fb072294f56a58c4761278c97fb557d708e1eb3 + # via -r requirements.txt +tomlkit==0.13.0 \ + --hash=sha256:08ad192699734149f5b97b45f1f18dad7eb1b6d16bc72ad0c2335772650d7b72 \ + --hash=sha256:7075d3042d03b80f603482d69bf0c8f345c2b30e41699fd8883227f89972b264 + # via pylint diff --git a/tools/interface_generator/templates.py b/tools/interface_generator/templates.py new file mode 100644 index 0000000..ecf925d --- /dev/null +++ b/tools/interface_generator/templates.py @@ -0,0 +1,9 @@ +from pathlib import Path +import re + + +def get_templates(path: Path): + template_pattern = re.compile(r"^[^_]\S+\.mako$") + result = list(path.glob("*.mako")) + result = [item for item in result if template_pattern.match(item.name)] + return result diff --git a/tools/interface_generator/templates/__c_functions.mako b/tools/interface_generator/templates/__c_functions.mako new file mode 100644 index 0000000..2e40119 --- /dev/null +++ b/tools/interface_generator/templates/__c_functions.mako @@ -0,0 +1,4 @@ +/* Functions */ +% for fn in functions: +${fn['return']} ${name}_${fn['name']}(${ ", ".join(fn['arguments']) }); +% endfor diff --git a/tools/interface_generator/templates/__c_system_include.mako b/tools/interface_generator/templates/__c_system_include.mako new file mode 100644 index 0000000..d6a9d09 --- /dev/null +++ b/tools/interface_generator/templates/__c_system_include.mako @@ -0,0 +1,4 @@ +/* System includes */ +% for path in system_includes: +#include <${path}> +% endfor diff --git a/tools/interface_generator/templates/__c_types.mako b/tools/interface_generator/templates/__c_types.mako new file mode 100644 index 0000000..ce6b6b5 --- /dev/null +++ b/tools/interface_generator/templates/__c_types.mako @@ -0,0 +1,6 @@ +/* Types */ +% for type in types: +typedef struct ${name}_${type['name']} { +${ "\n".join([ " {};".format(member) for member in type['members'] ]) } +} ${name}_${type['name']}; +% endfor diff --git a/tools/interface_generator/templates/__header.mako b/tools/interface_generator/templates/__header.mako new file mode 100644 index 0000000..24b0381 --- /dev/null +++ b/tools/interface_generator/templates/__header.mako @@ -0,0 +1,5 @@ +/* This file is generated by ${PROG['name']} v${PROG['version']} */ + +/****************************************************************************** +${ license } + ******************************************************************************/ diff --git a/tools/interface_generator/templates/c_functions.mako b/tools/interface_generator/templates/c_functions.mako deleted file mode 100644 index a7f88be..0000000 --- a/tools/interface_generator/templates/c_functions.mako +++ /dev/null @@ -1,4 +0,0 @@ -/* Functions */ -% for fn in functions: -${fn['return']} ${fn['name']}(${ ", ".join(fn['arguments']) }); -% endfor diff --git a/tools/interface_generator/templates/c_header.mako b/tools/interface_generator/templates/c_header.mako deleted file mode 100644 index 069b449..0000000 --- a/tools/interface_generator/templates/c_header.mako +++ /dev/null @@ -1,13 +0,0 @@ -/* This file is generated by ${PROG['name']} v${PROG['version']} */ - -/****************************************************************************** -<%include file="${license}" /> - ******************************************************************************/ - -#ifndef ${ name.upper() }_H -#define ${ name.upper() }_H - -<%include file="c_system_include.mako" /> -<%include file="c_types.mako" /> -<%include file="c_functions.mako" /> -#endif /* ${ name.upper() }_H */ diff --git a/tools/interface_generator/templates/c_system_include.mako b/tools/interface_generator/templates/c_system_include.mako deleted file mode 100644 index d6a9d09..0000000 --- a/tools/interface_generator/templates/c_system_include.mako +++ /dev/null @@ -1,4 +0,0 @@ -/* System includes */ -% for path in system_includes: -#include <${path}> -% endfor diff --git a/tools/interface_generator/templates/c_types.mako b/tools/interface_generator/templates/c_types.mako deleted file mode 100644 index ac32469..0000000 --- a/tools/interface_generator/templates/c_types.mako +++ /dev/null @@ -1,6 +0,0 @@ -/* Types */ -% for type in types: -typedef struct ${type['name']} { -${ "\n".join([ " {};".format(member) for member in type['members'] ]) } -} ${type['name']}; -% endfor diff --git a/tools/interface_generator/templates/interface.h.mako b/tools/interface_generator/templates/interface.h.mako new file mode 100644 index 0000000..47ea940 --- /dev/null +++ b/tools/interface_generator/templates/interface.h.mako @@ -0,0 +1,8 @@ +<%include file="__header.mako" /> +#ifndef ${ name.upper() } +#define ${ name.upper() } + +<%include file="__c_system_include.mako" /> +<%include file="__c_types.mako" /> +<%include file="__c_functions.mako" /> +#endif /* ${ name.upper() } */ diff --git a/tools/interface_generator/templates/interface_mock.cpp.mako b/tools/interface_generator/templates/interface_mock.cpp.mako new file mode 100644 index 0000000..721f2c4 --- /dev/null +++ b/tools/interface_generator/templates/interface_mock.cpp.mako @@ -0,0 +1,32 @@ +<%include file="__header.mako" /> + +#include +#include "${name}_mock.hpp" + +static I${name}_mock *s_instance = nullptr; + +I${name}_mock::I${name}_mock() +{ + if(s_instance != nullptr) + { + throw std::runtime_error("Creating a second instance of mock object"); + } + s_instance = this; +} + +I${name}_mock::~I${name}_mock() +{ + // destructors shouldn't throw exceptions + s_instance = nullptr; +} + +% for fn in functions: +${fn['return']} ${name}_${fn['name']}(${ ", ".join(fn['arguments']) }) +{ + if(s_instance == nullptr) + { + throw std::runtime_error("No mock created to handle function ${name}_${fn['name']}"); + } + return s_instance->${fn['name']}(${ ", ".join(fn['argument_names']) }); +} +% endfor diff --git a/tools/interface_generator/templates/interface_mock.hpp.mako b/tools/interface_generator/templates/interface_mock.hpp.mako new file mode 100644 index 0000000..e33d50e --- /dev/null +++ b/tools/interface_generator/templates/interface_mock.hpp.mako @@ -0,0 +1,26 @@ +<%include file="__header.mako" /> +#ifndef ${ name.upper() }_MOCK +#define ${ name.upper() }_MOCK + +extern "C" { +#include "${ name }.h" +} + +#include +#include + +class I${name}_mock +{ +public: + I${name}_mock(); + ~I${name}_mock(); + + /* Functions */ +% for fn in functions: + MOCK_METHOD(${fn['return']}, ${fn['name']}, (${ ", ".join(fn['arguments']) })); +% endfor +}; + +using ${name}_mock = ::testing::NiceMock; + +#endif /* ${ name.upper() }_MOCK */ diff --git a/tools/interface_generator/templates_unittest.py b/tools/interface_generator/templates_unittest.py new file mode 100755 index 0000000..594753d --- /dev/null +++ b/tools/interface_generator/templates_unittest.py @@ -0,0 +1,21 @@ +#!/usr/bin/env python3 + +import unittest +import templates +from pathlib import Path + + +class Templates(unittest.TestCase): + def test_get_templates(self): + path = Path("templates") + result = templates.get_templates(path) + + self.assertGreater(len(result), 0) + for template in result: + self.assertTrue(isinstance(template, Path)) + self.assertTrue(template.exists()) + self.assertTrue(template.is_file()) + + +if __name__ == "__main__": + unittest.main() diff --git a/tools/interface_generator/test/BUILD.bazel b/tools/interface_generator/test/BUILD.bazel new file mode 100644 index 0000000..5d6348d --- /dev/null +++ b/tools/interface_generator/test/BUILD.bazel @@ -0,0 +1,17 @@ +load("//:defs.bzl", "generate_interface") + +generate_interface( + name = "kstdio", + interface = "kstdio", + license = "//:LICENSE.md", +) + +cc_test( + name = "test_kstdio", + srcs = ["kstdio_mock.cpp", "test_kstdio.cpp"], + deps = [ + ":kstdio", + "@googletest//:gtest", + "@googletest//:gtest_main", + ], +) diff --git a/tools/interface_generator/test/test_kstdio.cpp b/tools/interface_generator/test/test_kstdio.cpp new file mode 100644 index 0000000..95540a4 --- /dev/null +++ b/tools/interface_generator/test/test_kstdio.cpp @@ -0,0 +1,20 @@ +#include "kstdio_mock.hpp" + +using ::testing::StrEq; + +class kstdioFixture : public ::testing::Test { +protected: + void + SetUp() + { + } + + kstdio_mock m_kstdio_mock; +}; + +TEST_F(kstdioFixture, printfCallsMock) +{ + EXPECT_CALL(m_kstdio_mock, printf(StrEq("hello world"))); + + kstdio_printf("hello world"); +} -- cgit v1.2.1