7.9 KiB
External Libraries Guide
How to wrap external C/C++ libraries in Logos modules.
Overview
Logos modules can wrap external C/C++ libraries to expose their functionality to the Logos ecosystem. There are two approaches:
- Vendor/Pre-built — Library already compiled, in the
lib/directory (simplest) - Flake Input — Library source as a flake input, built during nix build
Approach 1: Vendor / Pre-built Library
Best for: Pre-built proprietary libraries or binaries you already have compiled.
Setup
- Place the pre-built library in
lib/and git-track it (Nix only sees tracked files):
cp /path/to/libmylib.dylib lib/
git add lib/libmylib.dylib lib/libmylib.h
- Configure
metadata.json:
{
"nix": {
"external_libraries": [
{ "name": "mylib", "vendor_path": "lib" }
],
"cmake": {
"extra_include_dirs": ["lib"]
}
}
}
flake.nixstays simple — no extra inputs needed:
{
inputs = {
logos-module-builder.url = "github:logos-co/logos-module-builder";
};
outputs = inputs@{ logos-module-builder, ... }:
logos-module-builder.lib.mkLogosModule {
src = ./.;
configFile = ./metadata.json;
flakeInputs = inputs;
};
}
Approach 2: Flake Input (Build from Source)
Best for: Libraries with clean build systems (make, cmake, etc.) whose source you want pinned as a flake input.
Configuration
flake.nix:
{
inputs = {
logos-module-builder.url = "github:logos-co/logos-module-builder";
my-lib-src = {
url = "github:org/my-lib/v1.0.0";
flake = false;
};
};
outputs = inputs@{ logos-module-builder, ... }:
logos-module-builder.lib.mkLogosModule {
src = ./.;
configFile = ./metadata.json;
flakeInputs = inputs;
externalLibInputs = {
mylib = inputs.my-lib-src;
};
};
}
metadata.json:
{
"nix": {
"external_libraries": [
{
"name": "mylib",
"build_command": "make shared",
"output_pattern": "build/libmylib.*"
}
],
"cmake": {
"extra_include_dirs": ["lib"]
}
}
}
Build Command Options
{ "build_command": "make" }
{ "build_command": "make shared-library" }
{ "build_command": "mkdir build && cd build && cmake .. && make" }
{ "build_command": "./build.sh" }
Go Libraries
For Go libraries that produce C shared libraries:
{
"nix": {
"external_libraries": [
{
"name": "gowalletsdk",
"build_command": "make shared-library",
"go_build": true
}
]
}
}
The go_build: true flag sets up GOCACHE, GOPATH, CGO_ENABLED=1, and the Go toolchain in the build environment.
Approach 3: Vendor Submodule (Build from Source in Repo)
Best for: Libraries requiring custom build scripts, where source lives in a git submodule.
Setup
- Add library as git submodule:
git submodule add https://github.com/org/my-lib vendor/my-lib
- Create build script:
# scripts/build-mylib.sh
#!/bin/bash
cd vendor/my-lib
make clean
make shared
cp build/libmylib.* ../../lib/
Custom Build Scripts
Build scripts receive no arguments and should:
- Build the library
- Copy outputs to
lib/directory
Example for nwaku/libwaku:
#!/bin/bash
set -e
cd vendor/nwaku
# Build libwaku
make libwaku
# Copy to lib/
mkdir -p ../../lib
cp build/libwaku.* ../../lib/
cp library/libwaku.h ../../lib/
- Configure
metadata.json:
{
"nix": {
"external_libraries": [
{
"name": "mylib",
"vendor_path": "vendor/my-lib",
"build_script": "scripts/build-mylib.sh"
}
]
}
}
CMake Integration
Basic Linking
In CMakeLists.txt:
logos_module(
NAME my_module
SOURCES ...
EXTERNAL_LIBS
mylib
)
This will:
- Search for library in
lib/ - Add
lib/to include directories - Link the library
- Copy library to output directory
Manual Linking
For more control:
# After logos_module()
find_library(EXTRA_LIB extralib PATHS ${CMAKE_CURRENT_SOURCE_DIR}/lib)
target_link_libraries(my_module_module_plugin PRIVATE ${EXTRA_LIB})
Plugin Implementation
Including Headers
// In my_module_plugin.h
#include "lib/libmylib.h" // Include the C header
Using the Library
// In my_module_plugin.cpp
#include "my_module_plugin.h"
#include "lib/libmylib.h"
void MyModulePlugin::init() {
mylib_handle* handle = mylib_init();
if (!handle) {
qWarning() << "Failed to initialize mylib";
return;
}
m_handle = handle;
}
void MyModulePlugin::cleanup() {
if (m_handle) {
mylib_cleanup(m_handle);
m_handle = nullptr;
}
}
Memory Management
C libraries often return allocated memory. Always free it:
QString MyModulePlugin::getData() {
char* result = mylib_get_data(m_handle);
QString output = QString::fromUtf8(result);
mylib_free_string(result); // Don't forget!
return output;
}
Callbacks
For C callbacks, use static methods:
// Header
class MyModulePlugin {
private:
static void callback(int code, const char* msg, void* user_data);
};
// Implementation
void MyModulePlugin::callback(int code, const char* msg, void* user_data) {
auto* plugin = static_cast<MyModulePlugin*>(user_data);
emit plugin->eventResponse("callback", QVariantList() << code << QString::fromUtf8(msg));
}
void MyModulePlugin::subscribe() {
mylib_subscribe(m_handle, callback, this); // Pass 'this' as user_data
}
Platform Considerations
macOS
Libraries need correct install names. The builder automatically runs:
install_name_tool -id "@rpath/libmylib.dylib" libmylib.dylib
For the plugin:
install_name_tool -change "/old/path/libmylib.dylib" "@rpath/libmylib.dylib" my_module_plugin.dylib
Linux
Libraries are found via $ORIGIN RPATH:
patchelf --set-rpath '$ORIGIN' my_module_plugin.so
Troubleshooting
Library not found at runtime:
# Check RPATH on macOS
otool -L my_module_plugin.dylib
# Check RPATH on Linux
readelf -d my_module_plugin.so | grep RPATH
ldd my_module_plugin.so
Symbol not found:
# List symbols in library
nm -gU libmylib.dylib
# Check if symbol is referenced
nm -u my_module_plugin.dylib | grep mylib
Library not copied to result/lib:
For vendor libraries: ensure the .dylib/.so is git-tracked:
git add lib/libmylib.dylib
Complete Example: Wallet Module
Here's how the wallet module wraps go-wallet-sdk:
flake.nix:
{
inputs = {
logos-module-builder.url = "github:logos-co/logos-module-builder";
go-wallet-sdk = {
url = "github:status-im/go-wallet-sdk/v1.0.0";
flake = false;
};
};
outputs = inputs@{ logos-module-builder, ... }:
logos-module-builder.lib.mkLogosModule {
src = ./.;
configFile = ./metadata.json;
flakeInputs = inputs;
externalLibInputs = {
gowalletsdk = inputs.go-wallet-sdk;
};
};
}
metadata.json:
{
"name": "wallet_module",
"version": "1.0.0",
"type": "core",
"category": "wallet",
"main": "wallet_module_plugin",
"dependencies": [],
"nix": {
"packages": { "build": ["gnumake", "go"], "runtime": [] },
"external_libraries": [
{
"name": "gowalletsdk",
"build_command": "make shared-library",
"go_build": true
}
],
"cmake": { "extra_include_dirs": ["lib"] }
}
}
wallet_module_plugin.cpp:
#include "lib/libgowalletsdk.h"
bool WalletModulePlugin::initWallet(const QString& rpcUrl) {
char* err = nullptr;
m_handle = GoWSK_ethclient_NewClient(rpcUrl.toUtf8().constData(), &err);
if (err) {
QString error = QString::fromUtf8(err);
GoWSK_FreeCString(err);
qWarning() << "Wallet init failed:" << error;
return false;
}
return true;
}