real PoC of autocode
This commit is contained in:
+50
-6
@@ -1,22 +1,66 @@
|
|||||||
cmake_minimum_required(VERSION 3.10)
|
cmake_minimum_required(VERSION 3.10)
|
||||||
|
|
||||||
project(SimpleRPCExample CXX)
|
project(SimpleRPCExample CXX)
|
||||||
|
|
||||||
set(CMAKE_CXX_STANDARD 17)
|
set(CMAKE_CXX_STANDARD 17)
|
||||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||||
|
|
||||||
# Папка с исходниками
|
# include source includes
|
||||||
include_directories(${CMAKE_CURRENT_SOURCE_DIR}/src)
|
include_directories(${CMAKE_CURRENT_SOURCE_DIR}/src)
|
||||||
|
|
||||||
# Сервер
|
# find python interpreter
|
||||||
|
find_package(Python3 COMPONENTS Interpreter REQUIRED)
|
||||||
|
|
||||||
|
set(GENERATED_DIR ${CMAKE_BINARY_DIR}/generated)
|
||||||
|
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
|
||||||
|
)
|
||||||
|
|
||||||
|
# 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
|
||||||
|
)
|
||||||
|
|
||||||
|
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
|
||||||
|
)
|
||||||
|
|
||||||
|
# Server
|
||||||
add_executable(server
|
add_executable(server
|
||||||
src/server.cpp
|
src/server.cpp
|
||||||
src/MyService.cpp
|
src/MyService.cpp
|
||||||
src/MyService.skeleton.cpp
|
${GENERATED_DIR}/MyService.skeleton.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
# Клиент
|
add_dependencies(server rpc_generated)
|
||||||
|
|
||||||
|
# Client
|
||||||
add_executable(client
|
add_executable(client
|
||||||
src/client.cpp
|
src/client.cpp
|
||||||
src/MyService.proxy.cpp
|
${GENERATED_DIR}/MyService.proxy.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
|
add_dependencies(client rpc_generated)
|
||||||
|
|||||||
@@ -0,0 +1,146 @@
|
|||||||
|
# Minimal C++ RPC PoC with Auto-Code Generation
|
||||||
|
|
||||||
|
Простой Proof-of-Concept реализации RPC для C++ с автоматической генерацией прокси и скелета по аннотациям в исходниках.
|
||||||
|
|
||||||
|
Проект демонстрирует:
|
||||||
|
|
||||||
|
* Парсинг исходников C++ с помощью `libclang` для поиска аннотированных классов и методов.
|
||||||
|
* Автоматическую генерацию файлов:
|
||||||
|
* `*.proxy.h/cpp` — клиентский прокси для вызова удалённых методов.
|
||||||
|
* `*.skeleton.h/cpp` — серверный скелет для приёма запросов и вызова реальных методов.
|
||||||
|
* Минимальный протокол передачи данных через **именованные каналы (FIFO)**.
|
||||||
|
* Поддержка только типов `int` для аргументов и возвращаемого значения (PoC).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Структура проекта
|
||||||
|
|
||||||
|
```
|
||||||
|
project/
|
||||||
|
├── CMakeLists.txt
|
||||||
|
├── README.md
|
||||||
|
├── src
|
||||||
|
│ ├── client.cpp
|
||||||
|
│ ├── MyService.cpp
|
||||||
|
│ ├── MyService.h
|
||||||
|
│ ├── rpc_export.h
|
||||||
|
│ └── server.cpp
|
||||||
|
├── tools
|
||||||
|
│ ├── generate_rpc.py
|
||||||
|
│ └── templates
|
||||||
|
│ ├── proxy.cpp.j2
|
||||||
|
│ ├── proxy.h.j2
|
||||||
|
│ ├── skeleton.cpp.j2
|
||||||
|
│ └── skeleton.h.j2
|
||||||
|
└─ build/ # создаётся при сборке
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Зависимости
|
||||||
|
|
||||||
|
* CMake ≥ 3.10
|
||||||
|
* GCC или Clang с поддержкой C++17
|
||||||
|
* Python 3.8+ с пакетами:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pip3 install jinja2 clang
|
||||||
|
```
|
||||||
|
* libclang (для Python)
|
||||||
|
Если GCC используется только для сборки — libclang нужен только для генератора.
|
||||||
|
|
||||||
|
**Важно**: Сам `libclang` и пакет `clang` для Python должны быть одной версии.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Аннотации для экспорта
|
||||||
|
|
||||||
|
Управление тем, какие атрибуты каких классов следует экспортировать происходит с помощью
|
||||||
|
аннотаций в исходном коде.
|
||||||
|
|
||||||
|
См. файл [MyService.h](src/MyService.h):
|
||||||
|
|
||||||
|
```c
|
||||||
|
class RPC_EXPORT MyService {
|
||||||
|
public:
|
||||||
|
RPC_EXPORT
|
||||||
|
int add(int a, int b);
|
||||||
|
int minus(int a, int b);
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
* Экспортируем класс `MyService`:
|
||||||
|
* Экспортируем метод `MyService::add`;
|
||||||
|
* Но **не** экспортируем метод `MyService::minus`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Сборка проекта
|
||||||
|
|
||||||
|
1. Конфигурируем CMake:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cmake -DCMAKE_EXPORT_COMPILE_COMMANDS=ON -B build
|
||||||
|
```
|
||||||
|
|
||||||
|
Директива `CMAKE_EXPORT_COMPILE_COMMANDS=ON` нужна для корректного парсинга в `libclang`.
|
||||||
|
|
||||||
|
2. Собираем проект:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cmake --build build
|
||||||
|
```
|
||||||
|
|
||||||
|
В результате получаем два бинарника:
|
||||||
|
|
||||||
|
```
|
||||||
|
./build/server
|
||||||
|
./build/client
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Генерация кода
|
||||||
|
|
||||||
|
Автоматическая генерация прокси и скелета происходит **при сборке** через Python-скрипт `tools/generate_rpc.py`.
|
||||||
|
|
||||||
|
Пример ручного запуска генератора:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python3 tools/generate_rpc.py \
|
||||||
|
--out-dir build/generated \
|
||||||
|
--compile-commands build/compile_commands.json \
|
||||||
|
src/MyService.h
|
||||||
|
```
|
||||||
|
|
||||||
|
Сгенерированные файлы попадут в:
|
||||||
|
|
||||||
|
```
|
||||||
|
build/generated/
|
||||||
|
├─ MyService.proxy.h
|
||||||
|
├─ MyService.proxy.cpp
|
||||||
|
├─ MyService.skeleton.h
|
||||||
|
└─ MyService.skeleton.cpp
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Запуск
|
||||||
|
|
||||||
|
1. В одном терминале запускаем сервер:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./server
|
||||||
|
```
|
||||||
|
|
||||||
|
2. В другом терминале — клиент:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./client
|
||||||
|
```
|
||||||
|
|
||||||
|
**Ожидаемый вывод:**
|
||||||
|
|
||||||
|
```
|
||||||
|
RESULT: 15
|
||||||
|
```
|
||||||
+7
-2
@@ -1,7 +1,12 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
class [[annotate("export")]] MyService {
|
#include <rpc_export.h>
|
||||||
|
|
||||||
|
// annotate with clang attribute or via comment annotation recognized by libclang
|
||||||
|
// Use ANNOTATE attribute supported by clang: __attribute__((annotate("export")))
|
||||||
|
class RPC_EXPORT MyService {
|
||||||
public:
|
public:
|
||||||
[[annotate("export")]]
|
RPC_EXPORT
|
||||||
int add(int a, int b);
|
int add(int a, int b);
|
||||||
|
int minus(int a, int b);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,22 +0,0 @@
|
|||||||
#include "MyService.proxy.h"
|
|
||||||
#include <unistd.h>
|
|
||||||
#include <fcntl.h>
|
|
||||||
#include <sstream>
|
|
||||||
|
|
||||||
MyServiceProxy::MyServiceProxy(const char* pipeIn, const char* pipeOut) {
|
|
||||||
fdIn = open(pipeIn, O_WRONLY);
|
|
||||||
fdOut = open(pipeOut, O_RDONLY);
|
|
||||||
}
|
|
||||||
|
|
||||||
int MyServiceProxy::add(int a, int b) {
|
|
||||||
std::ostringstream out;
|
|
||||||
out << "add " << a << " " << b << "\n";
|
|
||||||
|
|
||||||
write(fdIn, out.str().c_str(), out.str().size());
|
|
||||||
|
|
||||||
char buf[128];
|
|
||||||
int n = read(fdOut, buf, sizeof(buf)-1);
|
|
||||||
buf[n] = 0;
|
|
||||||
|
|
||||||
return std::atoi(buf);
|
|
||||||
}
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
#include <string>
|
|
||||||
|
|
||||||
class MyServiceProxy {
|
|
||||||
public:
|
|
||||||
MyServiceProxy(const char* pipeIn, const char* pipeOut);
|
|
||||||
|
|
||||||
int add(int a, int b);
|
|
||||||
|
|
||||||
private:
|
|
||||||
int fdIn; // клиент пишет сюда → сервер читает
|
|
||||||
int fdOut; // сервер пишет сюда → клиент читает
|
|
||||||
};
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
#include "MyService.skeleton.h"
|
|
||||||
#include <sstream>
|
|
||||||
|
|
||||||
MyServiceSkeleton::MyServiceSkeleton(MyService& o) : obj(o) {}
|
|
||||||
|
|
||||||
std::string MyServiceSkeleton::handleRequest(const std::string& req) {
|
|
||||||
std::istringstream in(req);
|
|
||||||
std::string method;
|
|
||||||
in >> method;
|
|
||||||
|
|
||||||
if (method == "add") {
|
|
||||||
int a, b;
|
|
||||||
in >> a >> b;
|
|
||||||
int r = obj.add(a, b);
|
|
||||||
return std::to_string(r);
|
|
||||||
}
|
|
||||||
|
|
||||||
return "ERR";
|
|
||||||
}
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
#include "MyService.h"
|
|
||||||
#include <string>
|
|
||||||
|
|
||||||
class MyServiceSkeleton {
|
|
||||||
public:
|
|
||||||
MyServiceSkeleton(MyService& obj);
|
|
||||||
|
|
||||||
// обработка одной строки запроса и возврат ответа
|
|
||||||
std::string handleRequest(const std::string& req);
|
|
||||||
|
|
||||||
private:
|
|
||||||
MyService& obj;
|
|
||||||
};
|
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#ifdef __clang__
|
||||||
|
# define RPC_EXPORT __attribute__((annotate("export")))
|
||||||
|
#else
|
||||||
|
# define RPC_EXPORT
|
||||||
|
#endif
|
||||||
Executable
+227
@@ -0,0 +1,227 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Minimal RPC code generator PoC.
|
||||||
|
|
||||||
|
Parses C++ sources with libclang, finds classes and methods annotated with
|
||||||
|
ANNOTATE("export") (or [[annotate("export")]]), and generates proxy + skeleton
|
||||||
|
for methods that use only `int` arguments and return type `int`.
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
tools/generate_rpc.py --out-dir build/generated --compile-commands build/compile_commands.json src/MyService.h src/other.h
|
||||||
|
"""
|
||||||
|
import argparse
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
from dataclasses import dataclass, field
|
||||||
|
from typing import List
|
||||||
|
|
||||||
|
# Try imports
|
||||||
|
try:
|
||||||
|
from clang.cindex import Index, CursorKind, Config
|
||||||
|
except Exception as e:
|
||||||
|
print("ERROR: clang.cindex import failed:", e)
|
||||||
|
print("Make sure libclang python bindings are installed and libclang is reachable.")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
try:
|
||||||
|
from jinja2 import Environment, FileSystemLoader
|
||||||
|
except Exception as e:
|
||||||
|
print("ERROR: jinja2 import failed:", e)
|
||||||
|
print("Install with: pip3 install jinja2")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
# ========== IR dataclasses ==========
|
||||||
|
@dataclass
|
||||||
|
class Arg:
|
||||||
|
name: str
|
||||||
|
type: str
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class Method:
|
||||||
|
name: str
|
||||||
|
args: List[Arg] = field(default_factory=list)
|
||||||
|
return_type: str = "void"
|
||||||
|
signature: str = "" # unique signature string used for dispatch (name:types)
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class Class:
|
||||||
|
name: str
|
||||||
|
methods: List[Method] = field(default_factory=list)
|
||||||
|
|
||||||
|
# ========== helpers for compile flags ==========
|
||||||
|
def load_compile_flags(compile_commands_path, src_path):
|
||||||
|
if not compile_commands_path or not os.path.exists(compile_commands_path):
|
||||||
|
return []
|
||||||
|
with open(compile_commands_path) as f:
|
||||||
|
compile_commands = json.load(f)
|
||||||
|
# find matching entry by file (exact or basename)
|
||||||
|
abs_src = os.path.abspath(src_path)
|
||||||
|
for entry in compile_commands:
|
||||||
|
entry_file = os.path.abspath(entry.get('file') or "")
|
||||||
|
if entry_file == abs_src or os.path.basename(entry_file) == os.path.basename(abs_src):
|
||||||
|
# get arguments
|
||||||
|
if 'arguments' in entry and isinstance(entry['arguments'], list):
|
||||||
|
cmd = entry['arguments']
|
||||||
|
else:
|
||||||
|
cmd = (entry.get('command') or "").split()
|
||||||
|
# remove compiler and -c <file>
|
||||||
|
# keep flags like -I, -D
|
||||||
|
filtered = []
|
||||||
|
skip_next = False
|
||||||
|
for i, tok in enumerate(cmd):
|
||||||
|
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'):
|
||||||
|
continue
|
||||||
|
if tok == "-c":
|
||||||
|
skip_next = True
|
||||||
|
continue
|
||||||
|
filtered.append(tok)
|
||||||
|
return filtered
|
||||||
|
return []
|
||||||
|
|
||||||
|
# ========== AST utilities ==========
|
||||||
|
def has_annotation(cursor, annotation_text="export"):
|
||||||
|
# Check children for ANNOTATE_ATTR or spelling containing annotation_text
|
||||||
|
for c in cursor.get_children():
|
||||||
|
if c.kind == CursorKind.ANNOTATE_ATTR and annotation_text in (c.spelling or ""):
|
||||||
|
return True
|
||||||
|
# also check attributes spelling (fallback)
|
||||||
|
# cursor.get_tokens() might include annotate attribute for new attribute syntax but libclang may not expose
|
||||||
|
return False
|
||||||
|
|
||||||
|
def type_is_int(t):
|
||||||
|
if not t:
|
||||||
|
return False
|
||||||
|
# check canonical type spelling - this may vary; keep simple
|
||||||
|
s = t.spelling
|
||||||
|
# accept "int" and "signed int"
|
||||||
|
return s in ("int", "signed int", "int32_t", "long int") # minimal
|
||||||
|
|
||||||
|
# ========== parse file ==========
|
||||||
|
def parse_file(index, filepath, args):
|
||||||
|
tu = index.parse(filepath, args=args)
|
||||||
|
classes = []
|
||||||
|
|
||||||
|
for cursor in tu.cursor.get_children():
|
||||||
|
# top-level class/struct declarations
|
||||||
|
if cursor.kind == CursorKind.CLASS_DECL or cursor.kind == CursorKind.STRUCT_DECL:
|
||||||
|
# only records with a name
|
||||||
|
if not cursor.spelling:
|
||||||
|
continue
|
||||||
|
if has_annotation(cursor, "export"):
|
||||||
|
cls = Class(name=cursor.spelling)
|
||||||
|
# iterate its children for methods
|
||||||
|
for c in cursor.get_children():
|
||||||
|
if c.kind == CursorKind.CXX_METHOD or c.kind == CursorKind.FUNCTION_DECL:
|
||||||
|
if not has_annotation(c, "export"):
|
||||||
|
continue
|
||||||
|
# build method IR
|
||||||
|
# only support non-template, non-variadic, simple methods
|
||||||
|
m = Method(name=c.spelling)
|
||||||
|
# return type
|
||||||
|
try:
|
||||||
|
m.return_type = c.result_type.spelling
|
||||||
|
except Exception:
|
||||||
|
m.return_type = "void"
|
||||||
|
# args
|
||||||
|
ok = True
|
||||||
|
for arg in c.get_arguments():
|
||||||
|
t = arg.type
|
||||||
|
if not type_is_int(t):
|
||||||
|
ok = False
|
||||||
|
break
|
||||||
|
m.args.append(Arg(name=arg.spelling or "arg", type=t.spelling))
|
||||||
|
if not ok:
|
||||||
|
print(f"Skipping method {c.spelling} of {cursor.spelling}: unsupported arg types")
|
||||||
|
continue
|
||||||
|
# check return type is int
|
||||||
|
if not type_is_int(c.result_type):
|
||||||
|
print(f"Skipping method {c.spelling} of {cursor.spelling}: unsupported return type {c.result_type.spelling}")
|
||||||
|
continue
|
||||||
|
# signature: name:comma-separated types
|
||||||
|
sig_types = ",".join(a.type for a in m.args)
|
||||||
|
m.signature = f"{m.name}:{sig_types}"
|
||||||
|
cls.methods.append(m)
|
||||||
|
if cls.methods:
|
||||||
|
classes.append(cls)
|
||||||
|
return classes
|
||||||
|
|
||||||
|
# ========== templating ==========
|
||||||
|
def render_templates(classes, out_dir, templates_dir):
|
||||||
|
env = Environment(
|
||||||
|
loader=FileSystemLoader(templates_dir),
|
||||||
|
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")
|
||||||
|
|
||||||
|
for cls in classes:
|
||||||
|
name = cls.name
|
||||||
|
|
||||||
|
with open(f"{out_dir}/{name}.proxy.h", "w") as f:
|
||||||
|
f.write(proxy_h.render(cls=cls))
|
||||||
|
|
||||||
|
with open(f"{out_dir}/{name}.proxy.cpp", "w") as f:
|
||||||
|
f.write(proxy_cpp.render(cls=cls))
|
||||||
|
|
||||||
|
with open(f"{out_dir}/{name}.skeleton.h", "w") as f:
|
||||||
|
f.write(skeleton_h.render(cls=cls))
|
||||||
|
|
||||||
|
with open(f"{out_dir}/{name}.skeleton.cpp", "w") as f:
|
||||||
|
f.write(skeleton_cpp.render(cls=cls))
|
||||||
|
|
||||||
|
# ========== main ==========
|
||||||
|
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("--templates", "-t", default=os.path.join(os.path.dirname(__file__), "templates"))
|
||||||
|
args = p.parse_args()
|
||||||
|
|
||||||
|
out_dir = args.out_dir
|
||||||
|
os.makedirs(out_dir, exist_ok=True)
|
||||||
|
|
||||||
|
# configure libclang: optionally allow user to set LIBCLANG_PATH env var
|
||||||
|
# If needed, user can set: export LIBCLANG_PATH=/usr/lib/llvm-10/lib
|
||||||
|
libclang_path = os.environ.get("LIBCLANG_PATH")
|
||||||
|
if libclang_path:
|
||||||
|
try:
|
||||||
|
Config.set_library_path(libclang_path)
|
||||||
|
except Exception as e:
|
||||||
|
print("WARNING: cannot set libclang path:", e)
|
||||||
|
|
||||||
|
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.")
|
||||||
|
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))
|
||||||
|
return 0
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
sys.exit(main())
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
#include "{{ cls.name }}.proxy.h"
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <sstream>
|
||||||
|
#include <cstdlib>
|
||||||
|
#include <cstring>
|
||||||
|
|
||||||
|
{{ cls.name }}Proxy::{{ cls.name }}Proxy(const char* pipeIn, const char* pipeOut) {
|
||||||
|
fdIn = open(pipeIn, O_WRONLY);
|
||||||
|
if (fdIn < 0) {
|
||||||
|
perror("open pipeIn");
|
||||||
|
}
|
||||||
|
fdOut = open(pipeOut, O_RDONLY);
|
||||||
|
if (fdOut < 0) {
|
||||||
|
perror("open pipeOut");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{% for m in cls.methods %}
|
||||||
|
{{ m.return_type }} {{ cls.name }}Proxy::{{ m.name }}({% for a in m.args %}{{ a.type }} {{ a.name }}{% if not loop.last %}, {% endif %}{% endfor %}) {
|
||||||
|
std::ostringstream out;
|
||||||
|
out << "{{ m.name }}";
|
||||||
|
{% for a in m.args %}
|
||||||
|
out << " " << {{ a.name }};
|
||||||
|
{% endfor %}
|
||||||
|
out << "\n";
|
||||||
|
std::string s = out.str();
|
||||||
|
write(fdIn, s.c_str(), (ssize_t)s.size());
|
||||||
|
|
||||||
|
char buf[256];
|
||||||
|
ssize_t n = read(fdOut, buf, sizeof(buf)-1);
|
||||||
|
if (n <= 0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
buf[n] = '\0';
|
||||||
|
return std::atoi(buf);
|
||||||
|
}
|
||||||
|
{% endfor %}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <string>
|
||||||
|
#include <memory>
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
class {{ cls.name }}Proxy {
|
||||||
|
public:
|
||||||
|
{{ cls.name }}Proxy(const char* pipeIn, const char* pipeOut);
|
||||||
|
{% for m in cls.methods %}
|
||||||
|
{{ m.return_type }} {{ m.name }}({% for a in m.args %}{{ a.type }} {{ a.name }}{% if not loop.last %}, {% endif %}{% endfor %});
|
||||||
|
{% endfor %}
|
||||||
|
private:
|
||||||
|
int fdIn;
|
||||||
|
int fdOut;
|
||||||
|
};
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
#include "{{ cls.name }}.skeleton.h"
|
||||||
|
#include <sstream>
|
||||||
|
|
||||||
|
{{ cls.name }}Skeleton::{{ cls.name }}Skeleton({{ cls.name }}& o) : obj(o) {}
|
||||||
|
|
||||||
|
std::string {{ cls.name }}Skeleton::handleRequest(const std::string& req) {
|
||||||
|
std::istringstream in(req);
|
||||||
|
std::string method;
|
||||||
|
in >> method;
|
||||||
|
{% for m in cls.methods %}
|
||||||
|
if (method == "{{ m.name }}") {
|
||||||
|
{% for a in m.args %}int {{ a.name }}; in >> {{ a.name }};
|
||||||
|
{% endfor %}
|
||||||
|
int res = obj.{{ m.name }}({% for a in m.args %}{{ a.name }}{% if not loop.last %}, {% endif %}{% endfor %});
|
||||||
|
return std::to_string(res);
|
||||||
|
}
|
||||||
|
{% endfor %}
|
||||||
|
return std::string("ERR");
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
#pragma once
|
||||||
|
#include "{{ cls.name }}.h"
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
class {{ cls.name }}Skeleton {
|
||||||
|
public:
|
||||||
|
{{ cls.name }}Skeleton({{ cls.name }}& obj);
|
||||||
|
std::string handleRequest(const std::string& req);
|
||||||
|
private:
|
||||||
|
{{ cls.name }}& obj;
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user