Skip to content

Commit a7cba1a

Browse files
committed
feat: generated sync wrappers
1 parent 3e57751 commit a7cba1a

22 files changed

Lines changed: 2115 additions & 179 deletions

File tree

.github/workflows/ci.yml

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,9 @@ jobs:
9393
nimble-version: ${{ needs.versions.outputs.nimble }}
9494

9595
cpp-e2e:
96+
# Native-binding e2e across the OS matrix. C++ runs on every OS; the C and
97+
# Rust suites (added below) run Linux-only, so the job id stays `cpp-e2e`
98+
# for the required-check name even though it now covers all three backends.
9699
# Codegen output doesn't vary with mm, so we matrix over OS and Nim only.
97100
# Windows runs MSVC by default and may surface codegen tweaks needed in
98101
# the generated CMake (e.g. /EHsc, dllexport) — track follow-ups as bugs
@@ -176,6 +179,21 @@ jobs:
176179
shell: bash
177180
run: nimble test_c_abi_e2e -y
178181

182+
# CBOR C bindings: exercises the async callback API and the blocking
183+
# `_sync` wrappers. Linux-only (codegen is platform-checked by
184+
# check-bindings; one OS is enough for the runtime round-trip).
185+
- name: Run C e2e tests
186+
if: matrix.label == 'Linux'
187+
shell: bash
188+
run: nimble test_c_e2e -y
189+
190+
# Rust bindings: drives the generated blocking + tokio-async wrappers
191+
# through the full FFI round-trip. Linux-only for the same reason.
192+
- name: Run Rust e2e tests
193+
if: matrix.label == 'Linux'
194+
shell: bash
195+
run: nimble test_rust_e2e -y
196+
179197
check-bindings:
180198
# Single OS is enough — codegen output is platform-independent; the Nim
181199
# matrix catches version-sensitive output (the PR #39 drift class).

.github/workflows/tests-sanitized.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,5 +102,8 @@ jobs:
102102
- name: Run C++ e2e tests (${{ inputs.sanitizer }})
103103
run: nimble test_cpp_e2e_sanitized -y
104104

105+
- name: Run C e2e tests (${{ inputs.sanitizer }})
106+
run: nimble test_c_e2e_sanitized -y
107+
105108
- name: Run abi=c C e2e test (${{ inputs.sanitizer }})
106109
run: nimble test_c_abi_e2e_sanitized -y

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ examples/**/cpp_bindings/build/
2626

2727
# Cargo build artifacts (rust clients / harnesses)
2828
examples/**/rust_client/target/
29+
tests/e2e/rust/target/
2930

3031
# Development plans (local only — match PLAN.md and any `*-plan.md` notes)
3132
PLAN.md

examples/echo/c_bindings/CMakeLists.txt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,9 @@ find_package(Threads REQUIRED)
2828
add_library(echo_headers INTERFACE)
2929
target_include_directories(echo_headers INTERFACE "${CMAKE_CURRENT_SOURCE_DIR}")
3030
target_link_libraries(echo_headers INTERFACE echo tinycbor Threads::Threads)
31-
# The generated header is async (no blocking helper), but consumer code that
32-
# waits on a result callback typically uses nanosleep / pthreads, which need a
33-
# POSIX feature level that strict `-std=c11` hides. Define it for consumers.
31+
# The generated `_sync` wrappers block on a pthread condvar, and consumer code
32+
# that polls a result callback typically uses nanosleep — both need a POSIX
33+
# feature level that strict `-std=c11` hides. Define it for consumers.
3434
target_compile_definitions(echo_headers INTERFACE _POSIX_C_SOURCE=200809L)
3535

3636
if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/main.c")

examples/echo/c_bindings/echo.h

Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#ifndef NIM_FFI_LIB_ECHO_H_INCLUDED
22
#define NIM_FFI_LIB_ECHO_H_INCLUDED
33
#include "nim_ffi_cbor.h"
4+
#include "nim_ffi_sync.h"
45

56
/* ============================================================ */
67
/* Generated types (user-declared + per-proc request envelopes) */
@@ -287,6 +288,67 @@ static inline int echo_ctx_create(const EchoConfig* config, EchoCreateFn on_crea
287288
return 0;
288289
}
289290

291+
static inline int echo_ctx_create_sync(const EchoConfig* config, EchoCtx** out, char* err_buf, size_t err_len, uint32_t timeout_ms) {
292+
EchoCreateCtorReq ffi_req;
293+
memset(&ffi_req, 0, sizeof(ffi_req));
294+
ffi_req.config = *config;
295+
uint8_t* req_buf = NULL;
296+
size_t req_len = 0;
297+
char* enc_err = NULL;
298+
if (nimffi_encode_to_buf(echo_encv_EchoCreateCtorReq, &ffi_req, &req_buf, &req_len, &enc_err) != 0) {
299+
nimffi_copy_err(err_buf, err_len, enc_err ? enc_err : "encode failed");
300+
free(enc_err);
301+
return -1;
302+
}
303+
NimFfiSyncState* st = nimffi_sync_state_new();
304+
if (!st) {
305+
free(req_buf);
306+
nimffi_copy_err(err_buf, err_len, "out of memory");
307+
return -1;
308+
}
309+
(void)echo_create(req_buf, req_len, nimffi_sync_cb, st);
310+
free(req_buf);
311+
if (!nimffi_sync_wait(st, timeout_ms)) {
312+
nimffi_copy_err(err_buf, err_len, "FFI create timed out");
313+
nimffi_sync_state_release(st);
314+
return -1;
315+
}
316+
if (!st->ok) {
317+
nimffi_copy_err(err_buf, err_len, st->err ? st->err : "FFI create failed");
318+
int rc = st->ret_code ? st->ret_code : -1;
319+
nimffi_sync_state_release(st);
320+
return rc;
321+
}
322+
NimFfiStr addr;
323+
memset(&addr, 0, sizeof(addr));
324+
char* dec_err = NULL;
325+
if (nimffi_decode_from_buf(echo_decv_Str, st->bytes, st->bytes_len, &addr, &dec_err) != 0) {
326+
nimffi_copy_err(err_buf, err_len, dec_err ? dec_err : "decode failed");
327+
free(dec_err);
328+
nimffi_sync_state_release(st);
329+
return -1;
330+
}
331+
char* endp = NULL;
332+
unsigned long long a = addr.data ? strtoull(addr.data, &endp, 10) : 0;
333+
bool ok = addr.data && addr.len > 0 && endp && *endp == '\0';
334+
nimffi_free_str(&addr);
335+
if (!ok) {
336+
nimffi_copy_err(err_buf, err_len, "FFI create returned non-numeric address");
337+
nimffi_sync_state_release(st);
338+
return -1;
339+
}
340+
EchoCtx* c = (EchoCtx*)calloc(1, sizeof(EchoCtx));
341+
if (!c) {
342+
nimffi_copy_err(err_buf, err_len, "out of memory");
343+
nimffi_sync_state_release(st);
344+
return -1;
345+
}
346+
c->ptr = (void*)(uintptr_t)a;
347+
*out = c;
348+
nimffi_sync_state_release(st);
349+
return 0;
350+
}
351+
290352
static inline void echo_ctx_destroy(EchoCtx* ctx) {
291353
if (!ctx) return;
292354
if (ctx->ptr) { echo_destroy(ctx->ptr); ctx->ptr = NULL; }
@@ -353,6 +415,57 @@ static inline int echo_ctx_shout(const EchoCtx* ctx, const ShoutRequest* req, Ec
353415
return 0;
354416
}
355417

418+
static inline int echo_ctx_shout_sync(const EchoCtx* ctx, const ShoutRequest* req, ShoutResponse* out, char* err_buf, size_t err_len, uint32_t timeout_ms) {
419+
EchoShoutReq ffi_req;
420+
memset(&ffi_req, 0, sizeof(ffi_req));
421+
ffi_req.req = *req;
422+
uint8_t* req_buf = NULL;
423+
size_t req_len = 0;
424+
char* enc_err = NULL;
425+
if (nimffi_encode_to_buf(echo_encv_EchoShoutReq, &ffi_req, &req_buf, &req_len, &enc_err) != 0) {
426+
nimffi_copy_err(err_buf, err_len, enc_err ? enc_err : "encode failed");
427+
free(enc_err);
428+
return -1;
429+
}
430+
NimFfiSyncState* st = nimffi_sync_state_new();
431+
if (!st) {
432+
free(req_buf);
433+
nimffi_copy_err(err_buf, err_len, "out of memory");
434+
return -1;
435+
}
436+
int ret = echo_shout(ctx->ptr, nimffi_sync_cb, st, req_buf, req_len);
437+
free(req_buf);
438+
if (ret == NIMFFI_RET_MISSING_CALLBACK) {
439+
nimffi_copy_err(err_buf, err_len, "RET_MISSING_CALLBACK (internal error)");
440+
nimffi_sync_state_release(st);
441+
nimffi_sync_state_release(st);
442+
return -1;
443+
}
444+
if (!nimffi_sync_wait(st, timeout_ms)) {
445+
nimffi_copy_err(err_buf, err_len, "FFI call timed out");
446+
nimffi_sync_state_release(st);
447+
return -1;
448+
}
449+
if (!st->ok) {
450+
nimffi_copy_err(err_buf, err_len, st->err ? st->err : "FFI call failed");
451+
int rc = st->ret_code ? st->ret_code : -1;
452+
nimffi_sync_state_release(st);
453+
return rc;
454+
}
455+
memset(out, 0, sizeof(*out));
456+
char* dec_err = NULL;
457+
int dec = nimffi_decode_from_buf(echo_decv_ShoutResponse, st->bytes, st->bytes_len, out, &dec_err);
458+
if (dec != 0) {
459+
nimffi_copy_err(err_buf, err_len, dec_err ? dec_err : "decode failed");
460+
free(dec_err);
461+
echo_free_ShoutResponse(out);
462+
nimffi_sync_state_release(st);
463+
return -1;
464+
}
465+
nimffi_sync_state_release(st);
466+
return 0;
467+
}
468+
356469
typedef void (*EchoVersionReplyFn)(int err_code, const NimFfiStr* reply, const char* err_msg, void* user_data);
357470
typedef struct { EchoVersionReplyFn fn; void* user_data; } EchoVersionCallBox;
358471
static void echo_version_reply_trampoline(int ret, const char* msg, size_t len, void* ud) {
@@ -412,4 +525,54 @@ static inline int echo_ctx_version(const EchoCtx* ctx, EchoVersionReplyFn on_rep
412525
return 0;
413526
}
414527

528+
static inline int echo_ctx_version_sync(const EchoCtx* ctx, NimFfiStr* out, char* err_buf, size_t err_len, uint32_t timeout_ms) {
529+
EchoVersionReq ffi_req;
530+
memset(&ffi_req, 0, sizeof(ffi_req));
531+
uint8_t* req_buf = NULL;
532+
size_t req_len = 0;
533+
char* enc_err = NULL;
534+
if (nimffi_encode_to_buf(echo_encv_EchoVersionReq, &ffi_req, &req_buf, &req_len, &enc_err) != 0) {
535+
nimffi_copy_err(err_buf, err_len, enc_err ? enc_err : "encode failed");
536+
free(enc_err);
537+
return -1;
538+
}
539+
NimFfiSyncState* st = nimffi_sync_state_new();
540+
if (!st) {
541+
free(req_buf);
542+
nimffi_copy_err(err_buf, err_len, "out of memory");
543+
return -1;
544+
}
545+
int ret = echo_version(ctx->ptr, nimffi_sync_cb, st, req_buf, req_len);
546+
free(req_buf);
547+
if (ret == NIMFFI_RET_MISSING_CALLBACK) {
548+
nimffi_copy_err(err_buf, err_len, "RET_MISSING_CALLBACK (internal error)");
549+
nimffi_sync_state_release(st);
550+
nimffi_sync_state_release(st);
551+
return -1;
552+
}
553+
if (!nimffi_sync_wait(st, timeout_ms)) {
554+
nimffi_copy_err(err_buf, err_len, "FFI call timed out");
555+
nimffi_sync_state_release(st);
556+
return -1;
557+
}
558+
if (!st->ok) {
559+
nimffi_copy_err(err_buf, err_len, st->err ? st->err : "FFI call failed");
560+
int rc = st->ret_code ? st->ret_code : -1;
561+
nimffi_sync_state_release(st);
562+
return rc;
563+
}
564+
memset(out, 0, sizeof(*out));
565+
char* dec_err = NULL;
566+
int dec = nimffi_decode_from_buf(echo_decv_Str, st->bytes, st->bytes_len, out, &dec_err);
567+
if (dec != 0) {
568+
nimffi_copy_err(err_buf, err_len, dec_err ? dec_err : "decode failed");
569+
free(dec_err);
570+
nimffi_free_str(out);
571+
nimffi_sync_state_release(st);
572+
return -1;
573+
}
574+
nimffi_sync_state_release(st);
575+
return 0;
576+
}
577+
415578
#endif /* NIM_FFI_LIB_ECHO_H_INCLUDED */

0 commit comments

Comments
 (0)