aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--tools/interface_generator/BUILD.bazel28
-rw-r--r--tools/interface_generator/LICENSE.md21
-rw-r--r--tools/interface_generator/MODULE.bazel3
-rw-r--r--tools/interface_generator/defs.bzl53
-rw-r--r--tools/interface_generator/interface_definition.py46
-rwxr-xr-xtools/interface_generator/interface_generator.py95
-rw-r--r--tools/interface_generator/requirements.txt1
-rw-r--r--tools/interface_generator/requirements_lock.txt28
-rw-r--r--tools/interface_generator/templates.py9
-rw-r--r--tools/interface_generator/templates/__c_functions.mako4
-rw-r--r--tools/interface_generator/templates/__c_system_include.mako (renamed from tools/interface_generator/templates/c_system_include.mako)0
-rw-r--r--tools/interface_generator/templates/__c_types.mako (renamed from tools/interface_generator/templates/c_types.mako)4
-rw-r--r--tools/interface_generator/templates/__header.mako5
-rw-r--r--tools/interface_generator/templates/c_functions.mako4
-rw-r--r--tools/interface_generator/templates/c_header.mako13
-rw-r--r--tools/interface_generator/templates/interface.h.mako8
-rw-r--r--tools/interface_generator/templates/interface_mock.cpp.mako32
-rw-r--r--tools/interface_generator/templates/interface_mock.hpp.mako26
-rwxr-xr-xtools/interface_generator/templates_unittest.py21
-rw-r--r--tools/interface_generator/test/BUILD.bazel17
-rw-r--r--tools/interface_generator/test/test_kstdio.cpp20
21 files changed, 366 insertions, 72 deletions
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
index d6a9d09..d6a9d09 100644
--- a/tools/interface_generator/templates/c_system_include.mako
+++ b/tools/interface_generator/templates/__c_system_include.mako
diff --git a/tools/interface_generator/templates/c_types.mako b/tools/interface_generator/templates/__c_types.mako
index ac32469..ce6b6b5 100644
--- a/tools/interface_generator/templates/c_types.mako
+++ b/tools/interface_generator/templates/__c_types.mako
@@ -1,6 +1,6 @@
/* Types */
% for type in types:
-typedef struct ${type['name']} {
+typedef struct ${name}_${type['name']} {
${ "\n".join([ " {};".format(member) for member in type['members'] ]) }
-} ${type['name']};
+} ${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/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 <stdexcept>
+#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 <gtest/gtest.h>
+#include <gmock/gmock.h>
+
+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<I${name}_mock>;
+
+#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");
+}