diff --git a/CMakeLists.txt b/CMakeLists.txt index 1bf2cb3..66f79b2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,6 +6,7 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON) # include source includes include_directories(${CMAKE_CURRENT_SOURCE_DIR}/src) +include_directories(${CMAKE_CURRENT_SOURCE_DIR}/include) # find python interpreter find_package(Python3 COMPONENTS Interpreter REQUIRED) @@ -16,38 +17,54 @@ file(MAKE_DIRECTORY ${GENERATED_DIR}) set(RPC_GENERATOR ${CMAKE_CURRENT_SOURCE_DIR}/tools/generate_rpc.py) set(RPC_TEMPLATES ${CMAKE_CURRENT_SOURCE_DIR}/tools/templates) -# inputs to parse -set(RPC_INPUTS - ${CMAKE_CURRENT_SOURCE_DIR}/src/MyService.h -) +# helper to generate RPC code for an annotated header/source pair +function(autocode_annotated_file OUT_BASENAME HEADER SOURCE) + set(out_proxy_h "${GENERATED_DIR}/${OUT_BASENAME}.proxy.h") + set(out_proxy_cpp "${GENERATED_DIR}/${OUT_BASENAME}.proxy.cpp") + set(out_skeleton_h "${GENERATED_DIR}/${OUT_BASENAME}.skeleton.h") + set(out_skeleton_cpp "${GENERATED_DIR}/${OUT_BASENAME}.skeleton.cpp") -# command to run generator -add_custom_command( - OUTPUT - ${GENERATED_DIR}/MyService.proxy.h - ${GENERATED_DIR}/MyService.proxy.cpp - ${GENERATED_DIR}/MyService.skeleton.h - ${GENERATED_DIR}/MyService.skeleton.cpp - COMMAND ${CMAKE_COMMAND} -E echo "Generating RPC stubs..." - COMMAND ${Python3_EXECUTABLE} ${RPC_GENERATOR} --out-dir ${GENERATED_DIR} - --compile-commands ${CMAKE_BINARY_DIR}/compile_commands.json - --templates ${RPC_TEMPLATES} - ${RPC_INPUTS} - DEPENDS ${RPC_GENERATOR} ${RPC_TEMPLATES} ${RPC_INPUTS} - WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} - COMMENT "Running RPC code generator" - VERBATIM -) + add_custom_command( + OUTPUT + ${out_proxy_h} + ${out_proxy_cpp} + ${out_skeleton_h} + ${out_skeleton_cpp} + COMMAND ${CMAKE_COMMAND} -E echo "Generating RPC stubs for ${OUT_BASENAME}..." + COMMAND ${Python3_EXECUTABLE} ${RPC_GENERATOR} --out-dir ${GENERATED_DIR} + --compile-commands ${CMAKE_BINARY_DIR}/compile_commands.json + --templates ${RPC_TEMPLATES} + --header ${HEADER} + --source ${SOURCE} + --out-base ${OUT_BASENAME} + DEPENDS ${RPC_GENERATOR} ${RPC_TEMPLATES} ${HEADER} ${SOURCE} + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + COMMENT "Running RPC code generator for ${OUT_BASENAME}" + VERBATIM + ) + + # accumulate all generated files into a global property so we can + # have one umbrella target that depends on all of them + set_property(GLOBAL APPEND PROPERTY AUTOCODE_GENERATED_FILES + ${out_proxy_h} + ${out_proxy_cpp} + ${out_skeleton_h} + ${out_skeleton_cpp} + ) +endfunction() include_directories(${GENERATED_DIR}) -add_custom_target(rpc_generated DEPENDS - ${GENERATED_DIR}/MyService.proxy.h - ${GENERATED_DIR}/MyService.proxy.cpp - ${GENERATED_DIR}/MyService.skeleton.h - ${GENERATED_DIR}/MyService.skeleton.cpp +# declare all annotated files here +autocode_annotated_file(MyService + ${CMAKE_CURRENT_SOURCE_DIR}/src/MyService.h + ${CMAKE_CURRENT_SOURCE_DIR}/src/MyService.cpp ) +# umbrella target that depends on all generated RPC files +get_property(_all_generated GLOBAL PROPERTY AUTOCODE_GENERATED_FILES) +add_custom_target(rpc_generated DEPENDS ${_all_generated}) + # Server add_executable(server src/server.cpp diff --git a/README.md b/README.md index c29909c..ed8014c 100644 --- a/README.md +++ b/README.md @@ -20,17 +20,21 @@ project/ ├── CMakeLists.txt ├── README.md ├── src -│   ├── client.cpp # пример клиента, использующего MyServiceProxy и IpcPipeChannel -│   ├── server.cpp # пример сервера, использующего MyServiceSkeleton и IpcPipeChannel +│   ├── client.cpp +│   ├── common +│   │   ├── ipc +│   │   │   ├── IpcChannel.h +│   │   │   ├── IpcMessage.h +│   │   │   └── IpcPipeChannel.h +│   │   ├── proxy +│   │   │   └── ProxyMarshaller.h +│   │   └── rpc +│   │   ├── rpc_export.h +│   │   ├── RpcInvoker.h +│   │   └── RpcSerializer.h │   ├── MyService.cpp │   ├── MyService.h -│   ├── rpc_export.h -│   └── rpc -│   ├── IpcMessage.h # типизированное IPC‑сообщение (add/get) -│   ├── IpcPipeChannel.h# реализация RpcChannel поверх FIFO -│   ├── RpcChannel.h # абстрактный канал для RPC -│   ├── ProxyMarshaller.h # клиентское ядро RPC (call(method, args...)) -│   └── RpcInvoker.h # серверное ядро RPC (dispatch) +│   └── server.cpp ├── tools │ ├── generate_rpc.py │ └── templates diff --git a/src/rpc/IpcChannel.h b/include/ipc/IpcChannel.h similarity index 100% rename from src/rpc/IpcChannel.h rename to include/ipc/IpcChannel.h diff --git a/src/rpc/IpcMessage.h b/include/ipc/IpcMessage.h similarity index 100% rename from src/rpc/IpcMessage.h rename to include/ipc/IpcMessage.h diff --git a/src/rpc/IpcPipeChannel.h b/include/ipc/IpcPipeChannel.h similarity index 100% rename from src/rpc/IpcPipeChannel.h rename to include/ipc/IpcPipeChannel.h diff --git a/src/rpc/ProxyMarshaller.h b/include/proxy/ProxyMarshaller.h similarity index 95% rename from src/rpc/ProxyMarshaller.h rename to include/proxy/ProxyMarshaller.h index 9476377..6f28ea3 100644 --- a/src/rpc/ProxyMarshaller.h +++ b/include/proxy/ProxyMarshaller.h @@ -1,6 +1,6 @@ #pragma once -#include "IpcChannel.h" +#include class ProxyMarshaller { public: diff --git a/src/rpc/RpcInvoker.h b/include/rpc/RpcInvoker.h similarity index 98% rename from src/rpc/RpcInvoker.h rename to include/rpc/RpcInvoker.h index 27e4e47..34beb86 100644 --- a/src/rpc/RpcInvoker.h +++ b/include/rpc/RpcInvoker.h @@ -1,6 +1,6 @@ #pragma once -#include "IpcMessage.h" +#include #include #include diff --git a/src/rpc/RpcSerializer.h b/include/rpc/RpcSerializer.h similarity index 100% rename from src/rpc/RpcSerializer.h rename to include/rpc/RpcSerializer.h diff --git a/src/rpc_export.h b/include/rpc/rpc_export.h similarity index 100% rename from src/rpc_export.h rename to include/rpc/rpc_export.h diff --git a/src/MyService.h b/src/MyService.h index 445e070..be7bdb3 100644 --- a/src/MyService.h +++ b/src/MyService.h @@ -1,6 +1,6 @@ #pragma once -#include +#include // annotate with clang attribute or via comment annotation recognized by libclang // Use ANNOTATE attribute supported by clang: __attribute__((annotate("export"))) diff --git a/src/client.cpp b/src/client.cpp index 7e63066..b1fa9e0 100644 --- a/src/client.cpp +++ b/src/client.cpp @@ -1,5 +1,5 @@ #include "MyService.proxy.h" -#include "rpc/IpcPipeChannel.h" +#include "ipc/IpcPipeChannel.h" #include diff --git a/src/server.cpp b/src/server.cpp index f50aed0..b9097aa 100644 --- a/src/server.cpp +++ b/src/server.cpp @@ -1,7 +1,7 @@ #include "MyService.h" #include "MyService.skeleton.h" -#include "rpc/IpcPipeChannel.h" +#include "ipc/IpcPipeChannel.h" #include diff --git a/tools/generate_rpc.py b/tools/generate_rpc.py index ec8fb9b..de9146b 100755 --- a/tools/generate_rpc.py +++ b/tools/generate_rpc.py @@ -73,11 +73,22 @@ def load_compile_flags(compile_commands_path, src_path): if skip_next: skip_next = False continue - if tok.endswith('g++') or tok.endswith('clang++') or tok.endswith('clang') or tok.endswith('g++') or tok.endswith('cc'): + # skip the compiler binary itself + if ( + tok.endswith("g++") + or tok.endswith("clang++") + or tok.endswith("clang") + or tok.endswith("cc") + or tok.endswith("c++") + ): continue if tok == "-c": skip_next = True continue + # drop output file flag: -o + if tok == "-o": + skip_next = True + continue filtered.append(tok) return filtered return [] @@ -183,7 +194,13 @@ def main(): p = argparse.ArgumentParser() p.add_argument("--out-dir", "-o", required=True) p.add_argument("--compile-commands", "-c", default=None) - p.add_argument("inputs", nargs="+") + p.add_argument("--header", required=True, help="C++ header file to scan for exported RPC classes") + p.add_argument("--source", required=True, help="C++ source file to use for resolving compile flags (from compile_commands.json)") + p.add_argument( + "--out-base", + required=True, + help="Base name for generated files: .proxy.[h|cpp], .skeleton.[h|cpp]", + ) p.add_argument("--templates", "-t", default=os.path.join(os.path.dirname(__file__), "templates")) args = p.parse_args() @@ -199,28 +216,68 @@ def main(): except Exception as e: print("WARNING: cannot set libclang path:", e) + # determine compile flags from the source file (which is what appears in compile_commands.json) + if not os.path.exists(args.header): + print("ERROR: header file not found:", args.header) + return 1 + if not os.path.exists(args.source): + print("ERROR: source file not found:", args.source) + return 1 + + compile_args = load_compile_flags(args.compile_commands, args.source) + if not any(a.startswith("-x") for a in compile_args): + compile_args = ["-x", "c++", "-std=c++17"] + compile_args + + print("Parsing", args.header, "with args:", compile_args) + index = Index.create() - all_classes = [] - - for inp in args.inputs: - if not os.path.exists(inp): - print("WARN: input file not found:", inp) - continue - compile_args = load_compile_flags(args.compile_commands, inp) - # ensure -x c++ if missing - if not any(a.startswith("-x") for a in compile_args): - compile_args = ["-x", "c++", "-std=c++17"] + compile_args - print("Parsing", inp, "with args:", compile_args) - classes = parse_file(index, inp, compile_args) - all_classes.extend(classes) - - if not all_classes: - print("No exported classes/methods found. Nothing to generate.") + classes = parse_file(index, args.header, compile_args) + + if not classes: + print("No exported classes/methods found in header. Nothing to generate.") return 0 - # render templates - render_templates(all_classes, out_dir, args.templates) - print("Generated files for classes:", ", ".join(c.name for c in all_classes)) + # For this PoC we expect a single exported service per header. + # If there are multiple, refuse to guess which one should define the filenames. + if len(classes) > 1: + print( + "ERROR: multiple exported classes found in header; " + "current generator expects exactly one when using --out-base." + ) + for c in classes: + print(" -", c.name) + return 1 + + # render templates using the single discovered class but the user‑provided base name + cls = classes[0] + + env = Environment( + loader=FileSystemLoader(args.templates), + autoescape=False, + trim_blocks=True, + lstrip_blocks=True, + ) + + proxy_h = env.get_template("proxy.h.j2") + proxy_cpp = env.get_template("proxy.cpp.j2") + skeleton_h = env.get_template("skeleton.h.j2") + skeleton_cpp = env.get_template("skeleton.cpp.j2") + + base = args.out_base + + with open(f"{out_dir}/{base}.proxy.h", "w") as f: + f.write(proxy_h.render(cls=cls)) + + with open(f"{out_dir}/{base}.proxy.cpp", "w") as f: + f.write(proxy_cpp.render(cls=cls)) + + with open(f"{out_dir}/{base}.skeleton.h", "w") as f: + f.write(skeleton_h.render(cls=cls)) + + with open(f"{out_dir}/{base}.skeleton.cpp", "w") as f: + f.write(skeleton_cpp.render(cls=cls)) + + print("Generated files for class", cls.name, "into base", base) return 0 if __name__ == "__main__": diff --git a/tools/templates/proxy.cpp.j2 b/tools/templates/proxy.cpp.j2 index 13f466c..c0530b7 100644 --- a/tools/templates/proxy.cpp.j2 +++ b/tools/templates/proxy.cpp.j2 @@ -1,5 +1,5 @@ #include "{{ cls.name }}.proxy.h" -#include "rpc/ProxyMarshaller.h" +#include "proxy/ProxyMarshaller.h" class {{ cls.name }}Proxy::Impl { public: diff --git a/tools/templates/proxy.h.j2 b/tools/templates/proxy.h.j2 index 300bbd3..eb5117d 100644 --- a/tools/templates/proxy.h.j2 +++ b/tools/templates/proxy.h.j2 @@ -1,6 +1,6 @@ #pragma once -#include "rpc/IpcChannel.h" +#include "ipc/IpcChannel.h" class {{ cls.name }}Proxy { public: