2026-07-09 14:51:42 -03:00

41 lines
2.7 KiB
Markdown

# skeleton — new-library template
A minimal, compilable nim-ffi library: a constructor, one async method, a library-initiated event, and a destructor, plus a nimble file and checked-in C++ bindings. Copy this directory instead of reverse-engineering `examples/timer/` (which is a feature tour, not a starting point).
## What's here
- `skeleton.nim` — the library. Every nim-ffi lib needs these four pieces, and nothing else:
- `declareLibrary("skeleton", Skeleton)` — names the lib and its state object; must come before any FFI annotation.
- `{.ffiCtor.}` constructor returning `Future[Result[T, string]]`.
- one `{.ffi.}` async method, same return contract.
- `{.ffiEvent.}` for a library-initiated callback.
- `{.ffiDtor.}` destructor.
- `genBindings()` as the **last** top-level call.
- `skeleton.nimble``build` and `genbindings_cpp` tasks.
- `cpp_bindings/` — generated C++ bindings, checked in and diff-verified by CI (`nimble check_bindings`).
## Copy-rename checklist
Pick your library name in two casings: a snake_case wire name (e.g. `my_lib`) and its PascalCase state type (e.g. `MyLib`).
1. Copy the directory: `cp -r examples/skeleton examples/my_lib`
2. Rename both files: `skeleton.nim``my_lib.nim`, `skeleton.nimble``my_lib.nimble`.
3. In `my_lib.nim`, replace every `skeleton``my_lib` and `Skeleton``MyLib`. That covers `declareLibrary("my_lib", MyLib)`, the state type, and the `myLibCreate` / `myLibHello` / `my_lib_destroy` proc names. Rename the request/response/event/config types to whatever your API needs.
4. In `my_lib.nimble`, update `packageName`, `description`, and every `libskeleton``libmy_lib` / `skeleton.nim``my_lib.nim`. The `--nimMainPrefix:libmy_lib` must match your `declareLibrary` name.
5. Regenerate bindings: `cd examples/my_lib && nimble genbindings_cpp` (add `-d:targetLang=rust` / `c` / `c_abi` variants as needed — see the root `ffi.nimble` for the full flag set).
6. If you keep the library long-term, wire it into the root `ffi.nimble` `check_bindings` task the same way `skeleton` is, so CI catches binding drift.
## Contracts worth remembering
- Every `{.ffi.}` / `{.ffiCtor.}` returns `Future[Result[T, string]]`: `return ok(value)` on success, `return err("reason")` on failure.
- A `{.ffi.}` method takes the state object as its **first** parameter.
- The dtor's exported symbol name (`skeleton_destroy`) avoids the camelCase→snake_case derivation and reads naturally in C; name yours the same way.
- `genBindings()` reads compile-time registries populated by the pragmas above it. Anything declared after it — or in a module imported after it — is silently missing from the output.
## Build & generate
```sh
cd examples/skeleton
nimble genbindings_cpp # writes cpp_bindings/
```