Skip to content

Commit eba4827

Browse files
committed
chore(cbind): streams (nim-ffi part 4)
1 parent fb5cac6 commit eba4827

10 files changed

Lines changed: 670 additions & 398 deletions

File tree

cbind/.pinned

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
taskpools;https://github.com/status-im/nim-taskpools@#9e8ccc754631ac55ac2fd495e167e74e86293edb
22
cbor_serialization;https://github.com/vacp2p/nim-cbor-serialization@#1664160e04d153573373afddc552b9cbf6fbe4dc
3-
ffi;https://github.com/logos-messaging/nim-ffi@#3e57751e3a5fd8e0f9b66a9a01ef24a9447863aa
3+
ffi;https://github.com/logos-messaging/nim-ffi@#7ef58f4bf5cf683cfdf2abbdb3d7688727763d65

cbind/Makefile

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,14 @@
1-
.PHONY: all deps build clean
1+
.PHONY: all build genbindings deps nixbuild clean
22

3-
all: build
3+
all: build genbindings
4+
5+
# Builds ../build/liblibp2p.so plus the C/CDDL bindings. Run `nimble setup` once first.
6+
build:
7+
nimble buildffi
8+
9+
genbindings:
10+
nimble genbindings_c
11+
nimble genbindings_cddl
412

513
nimble.lock:
614
nimble lock
@@ -10,9 +18,9 @@ nimble.lock:
1018

1119
deps: ../nix/cbind-deps.nix
1220

13-
build: deps
21+
nixbuild: deps
1422
nix build .#cbind
1523

1624
clean:
1725
$(RM) nimble.lock ../nix/cbind-deps.nix
18-
26+
$(RM) -r c_bindings cddl_bindings nimcache nimcache_c nimcache_cddl

cbind/cbind.nimble

Lines changed: 62 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -55,42 +55,37 @@ task libDynamic, "Generate dynamic bindings":
5555
task libStatic, "Generate static bindings":
5656
buildCBindings "static", ""
5757

58-
proc findNatPkgDir(): string =
59-
# Match the top-level Makefile: nimble installs deps under pkgs2 on newer
60-
# versions and pkgs on older ones; resolve from either.
61-
for base in ["../nimbledeps/pkgs2", "../nimbledeps/pkgs"]:
62-
if dirExists(base):
63-
for d in listDirs(base):
64-
if d.extractFilename().startsWith("nat_traversal-"):
65-
return d
66-
quit "nat_traversal package not found under ../nimbledeps/pkgs2 or " &
67-
"../nimbledeps/pkgs; run 'nimble install_pinned' first"
68-
69-
task examples, "Build and run C bindings examples":
70-
buildCBindings "static", ""
71-
# libp2p.a transitively references miniupnpc and libnatpmp via nat_traversal.
72-
# Build the vendored .a's via the parent Makefile and link them in.
73-
exec "make -C .. nat_libs"
74-
let natPkg = findNatPkgDir()
75-
# miniupnpc's unix Makefile drops the .a under build/, but its Makefile.mingw
76-
# drops it at the package root. Match the parent Makefile's per-OS choice.
77-
let upnpA =
78-
when defined(windows):
79-
natPkg / "vendor/miniupnp/miniupnpc/libminiupnpc.a"
80-
else:
81-
natPkg / "vendor/miniupnp/miniupnpc/build/libminiupnpc.a"
82-
let pmpA = natPkg / "vendor/libnatpmp-upstream/libnatpmp.a"
83-
let natLibs = upnpA & " " & pmpA
84-
exec "g++ -I. -o ../build/cbindings ./examples/cbindings.c ../build/libp2p.a " &
85-
natLibs & " -pthread"
86-
exec "g++ -I. -o ../build/echo ./examples/echo.c ../build/libp2p.a " & natLibs &
87-
" -pthread"
88-
exec "../build/cbindings"
89-
exec "../build/echo"
90-
9158
# nim-ffi library, built in parallel to the legacy cbind above (see libp2p_ffi.nim).
9259
# Renamed over libp2p.nim at the flip PR, which drops everything above this line.
9360

61+
proc findInstalledPkgDir(prefix: string): string =
62+
## Path of an installed dep dir matching `prefix` (e.g. "ffi-"). install_pinned
63+
## drops cbind's pinned deps under the project-local `nimbledeps/pkgs2`; a plain
64+
## `nimble install` uses the global store. Check both.
65+
var bases = @["nimbledeps/pkgs2", "../nimbledeps/pkgs2"]
66+
let home = getEnv("HOME")
67+
if home.len > 0:
68+
bases.add home & "/.nimble/pkgs2"
69+
for base in bases:
70+
if not dirExists(base):
71+
continue
72+
for entry in listDirs(base):
73+
if entry.extractFilename().startsWith(prefix):
74+
return entry
75+
raise newException(
76+
IOError,
77+
"could not locate installed package '" & prefix &
78+
"*'; run `nimble install_pinned` first",
79+
)
80+
81+
proc ffiDepPaths(): string =
82+
# ffi and cbor_serialization aren't cbind `requires` (they're not in the nimble
83+
# registry, so setup can't resolve them onto nimble.paths); point the compiler
84+
# at the installed copies. Their transitive deps (chronos, serialization, stew,
85+
# results, faststreams, …) are libp2p deps already on the inherited root paths.
86+
" --path:" & findInstalledPkgDir("ffi-") & " --path:" &
87+
findInstalledPkgDir("cbor_serialization-")
88+
9489
proc ffiLibExt(): string =
9590
when defined(windows):
9691
"dll"
@@ -110,7 +105,7 @@ proc buildFfiLib() =
110105
# 1500ms default is too tight for libp2pDestroy's switch.stop() over many conns.
111106
exec "nim c --out:" & buildDir & "/liblibp2p." & ffiLibExt() &
112107
" --threads:on --app:lib --opt:size --noMain --mm:refc -d:metrics" &
113-
" -d:ffiThreadExitTimeoutMs=5000" &
108+
" -d:ffiThreadExitTimeoutMs=5000" & ffiDepPaths() &
114109
" --nimMainPrefix:liblibp2p --nimcache:nimcache libp2p_ffi.nim"
115110

116111
task buildffi, "Build the FFI shared library":
@@ -119,11 +114,43 @@ task buildffi, "Build the FFI shared library":
119114
proc genBindingsFor(lang, outDir: string) =
120115
exec "nim c --threads:on --app:lib --noMain --mm:refc -d:metrics" &
121116
" --nimMainPrefix:liblibp2p -d:ffiGenBindings -d:targetLang=" & lang &
122-
" -d:ffiOutputDir=" & outDir & " -d:ffiSrcPath=libp2p_ffi.nim" &
117+
" -d:ffiOutputDir=" & outDir & " -d:ffiSrcPath=libp2p_ffi.nim" & ffiDepPaths() &
123118
" --nimcache:nimcache_" & lang & " -o:/dev/null libp2p_ffi.nim"
124119

125120
task genbindings_c, "Generate C bindings (cbind/c_bindings)":
126121
genBindingsFor("c", "c_bindings")
127122

128123
task genbindings_cddl, "Generate CDDL schema (cbind/cddl_bindings)":
129124
genBindingsFor("cddl", "cddl_bindings")
125+
126+
proc findFfiVendorDir(): string =
127+
## TinyCBOR sources vendored inside the installed nim-ffi package.
128+
let vendor = findInstalledPkgDir("ffi-") & "/ffi/codegen/templates/cpp/vendor"
129+
if not fileExists(vendor & "/tinycbor/cbor.h"):
130+
raise newException(IOError, "vendored tinycbor missing under " & vendor)
131+
vendor
132+
133+
task examples, "Build and run the C bindings examples":
134+
let lib = "../build/liblibp2p." & ffiLibExt()
135+
if not fileExists(lib):
136+
buildFfiLib()
137+
if not fileExists("c_bindings/libp2p.h"):
138+
genBindingsFor("c", "c_bindings")
139+
140+
let vendor = findFfiVendorDir()
141+
var cborObjs: seq[string]
142+
for name in [
143+
"cborencoder", "cborencoder_close_container_checked", "cborparser",
144+
"cborparser_dup_string", "cborerrorstrings",
145+
]:
146+
let obj = "../build/" & name & ".o"
147+
exec "gcc -std=c99 -O2 -fPIC -I " & vendor & " -I " & vendor & "/tinycbor -c " &
148+
vendor & "/tinycbor/" & name & ".c -o " & obj
149+
cborObjs.add obj
150+
let cborObjsStr = cborObjs.join(" ")
151+
152+
for example in ["echo"]:
153+
let outBin = "../build/" & example
154+
exec "gcc -std=c11 -O2 -I c_bindings -I " & vendor & " examples/" & example & ".c " &
155+
cborObjsStr & " " & lib & " -pthread -Wl,-rpath,'$ORIGIN' -o " & outBin
156+
exec outBin

cbind/examples/.clang-format

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
BasedOnStyle: LLVM
3+
IndentWidth: 2
4+
ColumnLimit: 80
5+
PointerAlignment: Right

cbind/examples/CMakeLists.txt

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
cmake_minimum_required(VERSION 3.14)
2+
project(libp2p_cbind_examples C)
3+
4+
set(CMAKE_C_STANDARD 11)
5+
set(CMAKE_C_STANDARD_REQUIRED ON)
6+
7+
set(CBIND_DIR "${CMAKE_CURRENT_SOURCE_DIR}/..")
8+
set(GEN_DIR "${CBIND_DIR}/c_bindings")
9+
set(BUILD_DIR "${CBIND_DIR}/../build")
10+
11+
if(APPLE)
12+
set(LIB_FILE "${BUILD_DIR}/liblibp2p.dylib")
13+
elseif(WIN32)
14+
set(LIB_FILE "${BUILD_DIR}/liblibp2p.dll")
15+
else()
16+
set(LIB_FILE "${BUILD_DIR}/liblibp2p.so")
17+
endif()
18+
19+
if(NOT EXISTS "${GEN_DIR}/libp2p.h")
20+
message(FATAL_ERROR
21+
"Generated header ${GEN_DIR}/libp2p.h not found. "
22+
"Run `nimble genbindings_c` in cbind/ first.")
23+
endif()
24+
if(NOT EXISTS "${LIB_FILE}")
25+
message(FATAL_ERROR
26+
"Runtime library ${LIB_FILE} not found. Run `nimble buildffi` in cbind/ first.")
27+
endif()
28+
29+
# ── Locate TinyCBOR (vendored inside the nim-ffi package) ────────────────────
30+
# The generated header includes <tinycbor/cbor.h> and defers to the vendored
31+
# sources. Override with -DTINYCBOR_VENDOR=/path/to/vendor if auto-detection
32+
# fails.
33+
if(NOT TINYCBOR_VENDOR)
34+
file(GLOB _ffi_vendors
35+
"${CBIND_DIR}/nimbledeps/pkgs2/ffi-*/ffi/codegen/templates/cpp/vendor"
36+
"${CBIND_DIR}/../nimbledeps/pkgs2/ffi-*/ffi/codegen/templates/cpp/vendor"
37+
"$ENV{HOME}/.nimble/pkgs2/ffi-*/ffi/codegen/templates/cpp/vendor")
38+
foreach(_v ${_ffi_vendors})
39+
if(EXISTS "${_v}/tinycbor/cbor.h")
40+
set(TINYCBOR_VENDOR "${_v}")
41+
break()
42+
endif()
43+
endforeach()
44+
endif()
45+
if(NOT TINYCBOR_VENDOR OR NOT EXISTS "${TINYCBOR_VENDOR}/tinycbor/cbor.h")
46+
message(FATAL_ERROR
47+
"Could not locate the vendored TinyCBOR sources. "
48+
"Pass -DTINYCBOR_VENDOR=/path/to/ffi/codegen/templates/cpp/vendor")
49+
endif()
50+
51+
add_library(tinycbor STATIC
52+
"${TINYCBOR_VENDOR}/tinycbor/cborencoder.c"
53+
"${TINYCBOR_VENDOR}/tinycbor/cborencoder_close_container_checked.c"
54+
"${TINYCBOR_VENDOR}/tinycbor/cborparser.c"
55+
"${TINYCBOR_VENDOR}/tinycbor/cborparser_dup_string.c"
56+
"${TINYCBOR_VENDOR}/tinycbor/cborerrorstrings.c")
57+
target_include_directories(tinycbor PUBLIC
58+
"${TINYCBOR_VENDOR}"
59+
"${TINYCBOR_VENDOR}/tinycbor")
60+
set_property(TARGET tinycbor PROPERTY C_STANDARD 99)
61+
set_property(TARGET tinycbor PROPERTY POSITION_INDEPENDENT_CODE ON)
62+
63+
find_package(Threads REQUIRED)
64+
65+
foreach(example echo)
66+
add_executable(${example} "${example}.c")
67+
target_include_directories(${example} PRIVATE "${GEN_DIR}")
68+
target_link_libraries(${example} PRIVATE "${LIB_FILE}" tinycbor Threads::Threads)
69+
# nanosleep needs a POSIX feature level that strict `-std=c11` hides.
70+
target_compile_definitions(${example} PRIVATE _POSIX_C_SOURCE=200809L)
71+
# So the loader finds libp2p.{so,dylib} next to it at runtime.
72+
set_target_properties(${example} PROPERTIES BUILD_RPATH "${BUILD_DIR}")
73+
endforeach()

cbind/examples/README.md

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
# cbind examples
2+
3+
Worked examples of consuming nim-libp2p from C through the FFI bindings generated by [nim-ffi](https://github.com/logos-messaging/nim-ffi).
4+
5+
The bindings are a header-only C API over a C ABI. Every generated method is asynchronous: it takes a result callback and returns immediately, and the callback fires from the library's dispatch thread. Library-initiated events (incoming streams, pub/sub messages) arrive through `libp2p_ctx_add_on_…_listener` callbacks. The examples turn each async call back into a blocking step with the small helpers in `common.h`.
6+
7+
- `echo.c` — two TCP nodes; the server mounts a custom `/cbind/echo/1.0.0` protocol and echoes the bytes it reads, the client dials it and verifies the round-trip. The incoming-stream handler only hands the stream id to `main`, which serves it inline, since calling back into the library from the dispatch thread would deadlock.
8+
9+
## Build
10+
11+
From `cbind/`, produce the runtime library and the generated header first:
12+
13+
```sh
14+
nimble setup
15+
nimble buildffi # -> ../build/liblibp2p.so
16+
nimble genbindings_c # -> c_bindings/libp2p.h
17+
```
18+
19+
Then build the examples:
20+
21+
```sh
22+
cd examples
23+
cmake -S . -B build
24+
cmake --build build
25+
```
26+
27+
If the vendored TinyCBOR sources can't be located automatically, point CMake at them:
28+
29+
```sh
30+
cmake -S . -B build -DTINYCBOR_VENDOR=$(nimble path ffi)/ffi/codegen/templates/cpp/vendor
31+
```
32+
33+
## Run
34+
35+
```sh
36+
./build/echo
37+
```
38+
39+
Each program exits `0` on success and prints what it sent and received.

0 commit comments

Comments
 (0)