feat: statically link libchat and rln via rust-bundle

Add rust-bundle, a single staticlib crate that depends on both libchat
and rln as rlibs. This ensures rustc links Rust std exactly once,
eliminating duplicate symbol errors on all platforms. Nim targets link
against liblogos_chat_bundle.a; Nix uses a bundleDrv instead of
separate libchat and rln derivations.

Reference: https://doc.rust-lang.org/reference/linkage.html#mixed-rust-and-foreign-codebases
This commit is contained in:
osmaczko 2026-02-23 21:43:49 +01:00
parent 07da6e2dec
commit dfedfb10a4
No known key found for this signature in database
GPG Key ID: 6A385380FD275B44
11 changed files with 6586 additions and 39 deletions

1
.gitignore vendored
View File

@ -4,6 +4,7 @@
*.dSYM
nimble.develop
nimble.paths
/rust-bundle
/nimcache

View File

@ -28,6 +28,7 @@ ifneq (,$(findstring MINGW,$(detected_OS)))
detected_OS := Windows
endif
##########
## Main ##
##########
@ -73,38 +74,31 @@ NIM_PARAMS := $(NIM_PARAMS) -d:git_version=\"$(GIT_VERSION)\"
##################
## Dependencies ##
##################
.PHONY: build-waku-librln
LIBRLN_VERSION := v0.7.0
RUST_BUNDLE_LIB := rust-bundle/target/release/librust_bundle.a
ifeq ($(detected_OS),Windows)
LIBRLN_FILE := rln.lib
else
LIBRLN_FILE := librln_$(LIBRLN_VERSION).a
endif
build-waku-librln:
@echo "Start building waku librln"
# libchat and rln each embed Rust std when built as staticlibs; linking both
# causes duplicate-symbol errors. rust-bundle/ links them as rlibs so std
# is emitted once. [1]
# [1] https://doc.rust-lang.org/reference/linkage.html#mixed-rust-and-foreign-codebases
.PHONY: build-rust-bundle
build-rust-bundle:
@echo "Building Rust bundle (libchat + rln)"
$(MAKE) -C vendor/nwaku librln
$(eval NIM_PARAMS += --passL:./vendor/nwaku/${LIBRLN_FILE} --passL:-lm)
@echo "Completed building librln"
cargo build --release --manifest-path rust-bundle/Cargo.toml
@echo "Bundle library: $(RUST_BUNDLE_LIB)"
build-waku-nat:
@echo "Start building waku nat-libs"
$(MAKE) -C vendor/nwaku nat-libs
@echo "Completed building nat-libs"
.PHONY: build-libchat
build-libchat:
@echo "Start building libchat"
cd vendor/libchat && cargo build --release
@echo "Completed building libchat"
.PHONY: tests
tests: | build-waku-librln build-waku-nat build-libchat logos_chat.nims
tests: | build-rust-bundle build-waku-nat logos_chat.nims
echo -e $(BUILD_MSG) "build/$@" && \
$(ENV_SCRIPT) nim tests $(NIM_PARAMS) logos_chat.nims
$(ENV_SCRIPT) nim tests $(NIM_PARAMS) \
--passL:$(RUST_BUNDLE_LIB) --passL:-lm \
logos_chat.nims
##########
@ -112,9 +106,11 @@ tests: | build-waku-librln build-waku-nat build-libchat logos_chat.nims
##########
# Ensure there is a nimble task with a name that matches the target
tui bot_echo pingpong: | build-waku-librln build-waku-nat build-libchat logos_chat.nims
tui bot_echo pingpong: | build-rust-bundle build-waku-nat logos_chat.nims
echo -e $(BUILD_MSG) "build/$@" && \
$(ENV_SCRIPT) nim $@ $(NIM_PARAMS) --path:src logos_chat.nims
$(ENV_SCRIPT) nim $@ $(NIM_PARAMS) \
--passL:$(RUST_BUNDLE_LIB) --passL:-lm \
--path:src logos_chat.nims
###########
## Library ##
@ -132,9 +128,11 @@ endif
LIBLOGOSCHAT := build/liblogoschat.$(LIBLOGOSCHAT_EXT)
.PHONY: liblogoschat
liblogoschat: | build-waku-librln build-waku-nat build-libchat logos_chat.nims
liblogoschat: | build-rust-bundle build-waku-nat logos_chat.nims
echo -e $(BUILD_MSG) "$(LIBLOGOSCHAT)" && \
$(ENV_SCRIPT) nim liblogoschat $(NIM_PARAMS) --path:src logos_chat.nims && \
$(ENV_SCRIPT) nim liblogoschat $(NIM_PARAMS) \
--passL:$(RUST_BUNDLE_LIB) --passL:-lm \
--path:src logos_chat.nims && \
echo -e "\n\x1B[92mLibrary built successfully:\x1B[39m" && \
echo " $(shell pwd)/$(LIBLOGOSCHAT)"

View File

@ -25,11 +25,11 @@
overlays = [ rust-overlay.overlays.default ];
};
libchatDrv = pkgs.callPackage ./nix/libchat.nix {};
librln = pkgs.callPackage ./nix/librln.nix {};
rustBundleDrv = pkgs.callPackage ./nix/bundle.nix { src = self; };
in {
packages.default = pkgs.callPackage ./nix/default.nix {
src = self;
inherit libchatDrv librln;
inherit rustBundleDrv;
};
devShells.default = pkgs.callPackage ./nix/shell.nix {
inherit libchatDrv;

34
nix/bundle.nix Normal file
View File

@ -0,0 +1,34 @@
{ lib, stdenv, rust-bin, makeRustPlatform, perl, pkg-config, cmake, src }:
let
rustToolchain = rust-bin.fromRustupToolchainFile (src + "/vendor/libchat/rust_toolchain.toml");
rPlatform = makeRustPlatform {
cargo = rustToolchain;
rustc = rustToolchain;
};
in rPlatform.buildRustPackage {
pname = "rust-bundle";
version = "0.1.0";
inherit src;
cargoRoot = "rust-bundle";
cargoLock = {
lockFile = src + "/rust-bundle/Cargo.lock";
outputHashes = {
"chat-proto-0.1.0" = "sha256-aCl80VOIkd/GK3gnmRuFoSAvPBfeE/FKCaNlLt5AbUU=";
};
};
nativeBuildInputs = [ perl pkg-config cmake ];
doCheck = false;
buildAndTestSubdir = "rust-bundle";
installPhase = ''
runHook preInstall
mkdir -p $out/lib
find . -name "librust_bundle.a" -path "*/release/*" -exec cp {} $out/lib/ \;
test -f $out/lib/librust_bundle.a
runHook postInstall
'';
}

View File

@ -1,8 +1,7 @@
{ lib, stdenv, nim, which, pkg-config, writeScriptBin,
openssl, miniupnpc, libnatpmp,
src, # logos-chat source (self from flake, with submodules=1)
libchatDrv, # result of libchat.nix
librln }: # result of librln.nix
rustBundleDrv }: # result of bundle.nix
# NOTE: this build requires git submodules to be present in src.
# When fetching from GitHub use '?submodules=1#', e.g.:
@ -14,21 +13,18 @@ assert lib.assertMsg ((src.submodules or false) == true)
let
revision = lib.substring 0 8 (src.rev or "dirty");
libExt = if stdenv.isDarwin then "dylib" else "so";
libchatPath = "${libchatDrv}/lib/liblibchat.${libExt}";
in stdenv.mkDerivation {
pname = "liblogoschat";
version = "0.1.0";
inherit src;
NIMFLAGS = lib.concatStringsSep " " [
"--passL:vendor/nwaku/librln_v0.7.0.a"
"--passL:${rustBundleDrv}/lib/librust_bundle.a"
"--passL:-lm"
"-d:miniupnpcUseSystemLibs"
"-d:libnatpmpUseSystemLibs"
"--passL:-lminiupnpc"
"--passL:-lnatpmp"
"-d:CONVERSATIONS_LIB:${libchatPath}"
"-d:git_version=${revision}"
];
@ -62,8 +58,6 @@ in stdenv.mkDerivation {
'';
preBuild = ''
# Place pre-built librln where the Nim linker expects it
cp ${librln}/lib/librln_v0.7.0.a vendor/nwaku/librln_v0.7.0.a
mkdir -p build
'';

View File

@ -1,4 +1,4 @@
{ lib, stdenv, rust-bin, makeRustPlatform, perl, pkg-config, cmake }:
{ rust-bin, makeRustPlatform, perl, pkg-config, cmake }:
let
rustToolchain = rust-bin.fromRustupToolchainFile ../vendor/libchat/rust_toolchain.toml;

View File

@ -10,8 +10,7 @@ in pkgs.mkShell {
];
shellHook = ''
export CONVERSATIONS_LIB="${libchatDrv}/lib/liblibchat.${libExt}"
echo "logos-chat dev shell. CONVERSATIONS_LIB=$CONVERSATIONS_LIB"
echo "logos-chat dev shell."
echo "Build: make liblogoschat"
echo "Nix build: nix build '.?submodules=1#'"
'';

6505
rust-bundle/Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

12
rust-bundle/Cargo.toml Normal file
View File

@ -0,0 +1,12 @@
[package]
name = "rust-bundle"
version = "0.1.0"
edition = "2021"
[lib]
name = "rust_bundle"
crate-type = ["staticlib"]
[dependencies]
libchat = { path = "../vendor/libchat/conversations" }
rln = { path = "../vendor/nwaku/vendor/zerokit/rln", features = ["arkzkey"] }

4
rust-bundle/src/lib.rs Normal file
View File

@ -0,0 +1,4 @@
// Force both rlibs into this staticlib.
// Their #[no_mangle] pub extern "C" symbols are exported from lib_rust_bundle.a.
extern crate libchat;
extern crate rln;

2
vendor/libchat vendored

@ -1 +1 @@
Subproject commit 960bb195a13657a09fa2df66262b3b2ced738f30
Subproject commit 73a598e5159a68651b1b6caca0a7f0d7dcf23910