From 867ea7bbbc907c5d746b52809d14d6c6e3f6ee2f Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Tue, 6 Oct 2026 17:13:33 +1100 Subject: [PATCH 1/6] Fix the static bundle missing session-deps libraries Moving to session-deps dropped the explicit libsession_static_bundle() registrations, so libsession-util.a shipped without libquic, oxen-logging, fmt, spdlog, ngtcp2, gnutls, nettle/hogweed, gmp, idn2, unistring, tasn1 and libevent. The 1.9.3 iOS xcframework therefore failed to link (74 undefined symbols in Session iOS). Resolve the bundle's full static closure once all targets are linked, instead of relying on per-dependency registration. The iOS modulemap now autolinks iconv. libunistring needs it, and session-deps takes it from the system on Apple rather than bundling it as 1.9.2 did. --- CMakeLists.txt | 1 + cmake/AddStaticBundleLib.cmake | 59 ++++++++++++++++++++++++++++++++++ utils/ios.sh | 4 +++ 3 files changed, 64 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index a9389a577..de61a0169 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -167,6 +167,7 @@ if(STATIC_BUNDLE) include(combine_archives) + libsession_static_bundle_resolve() combine_archives(session-util libsession-static-bundle "${LIBSESSION_STATIC_BUNDLE_LIBS}") set(lib_folder lib) if(IOS) diff --git a/cmake/AddStaticBundleLib.cmake b/cmake/AddStaticBundleLib.cmake index 2e37c0b67..3c9690165 100644 --- a/cmake/AddStaticBundleLib.cmake +++ b/cmake/AddStaticBundleLib.cmake @@ -34,3 +34,62 @@ function(libsession_static_bundle) endif() endforeach() endfunction() + +function(_libsession_static_bundle_walk tgt) + if(tgt MATCHES "^\\$$") + set(tgt "${CMAKE_MATCH_1}") + endif() + if(NOT TARGET "${tgt}") + return() + endif() + get_target_property(aliased ${tgt} ALIASED_TARGET) + if(aliased) + set(tgt "${aliased}") + endif() + + get_property(visited GLOBAL PROPERTY _libsession_static_bundle_visited) + if("${tgt}" IN_LIST visited) + return() + endif() + set_property(GLOBAL APPEND PROPERTY _libsession_static_bundle_visited "${tgt}") + + get_target_property(tgt_type ${tgt} TYPE) + if(tgt_type STREQUAL STATIC_LIBRARY) + # The list can hold alias names (eg. protobuf::libprotobuf-lite), so compare what they resolve to + set(already_bundled FALSE) + foreach(bundled IN LISTS LIBSESSION_STATIC_BUNDLE_LIBS) + get_target_property(bundled_aliased ${bundled} ALIASED_TARGET) + if(bundled STREQUAL tgt OR bundled_aliased STREQUAL tgt) + set(already_bundled TRUE) + break() + endif() + endforeach() + if(NOT already_bundled) + message(STATUS "Adding ${tgt} to libsession-util bundled library list") + _libsession_static_bundle_append("${tgt}") + endif() + endif() + + # An imported target (which is what session-deps produces for each static dependency) carries + # its own dependencies only in INTERFACE_LINK_LIBRARIES; a target built here has them in + # LINK_LIBRARIES. + get_target_property(deps ${tgt} INTERFACE_LINK_LIBRARIES) + get_target_property(imported ${tgt} IMPORTED) + if(NOT tgt_type STREQUAL INTERFACE_LIBRARY AND NOT imported) + get_target_property(link_deps ${tgt} LINK_LIBRARIES) + list(APPEND deps ${link_deps}) + endif() + foreach(dep IN LISTS deps) + _libsession_static_bundle_walk("${dep}") + endforeach() +endfunction() + +# Adds every static library that the already-registered targets link against, transitively. Call +# once all targets have their links set up: libsession_static_bundle() runs as each library is +# created, before its target_link_libraries(), so on its own it never sees a library's dependencies. +function(libsession_static_bundle_resolve) + set_property(GLOBAL PROPERTY _libsession_static_bundle_visited "") + foreach(tgt IN LISTS LIBSESSION_STATIC_BUNDLE_LIBS) + _libsession_static_bundle_walk("${tgt}") + endforeach() +endfunction() diff --git a/utils/ios.sh b/utils/ios.sh index 623656695..01605b1d9 100755 --- a/utils/ios.sh +++ b/utils/ios.sh @@ -222,6 +222,10 @@ if [ "${SHOULD_CREATE_FRAMEWORK}" == "SHOULD_CREATE_FRAMEWORK" ]; then # The 'module.modulemap' is needed for XCode to be able to find the headers modmap="${OUTPUT_DIR}/module.modulemap" echo "module SessionUtil {" >"$modmap" + # libunistring (via gnutls -> libidn2) calls iconv, which session-deps takes from the system on + # Apple rather than bundling. An SPM binary target can't declare linker flags, so autolink it + # from the module instead. + echo " link \"iconv\"" >>"$modmap" echo " module capi {" >>"$modmap" for x in $(cd include && find session -name '*.h'); do echo " header \"$x\"" >>"$modmap" From 79750412f69ef000962c4a6a5b1e95ddd0ead163 Mon Sep 17 00:00:00 2001 From: Jason Rhinelander Date: Tue, 6 Oct 2026 12:22:52 -0300 Subject: [PATCH 2/6] Bump session-deps for libunistring's iconv on Apple session-deps' libunistring recipe never passed on the -liconv that libunistring needs on Apple, where iconv is its own system dylib rather than part of libSystem. --- cmake/session-deps | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmake/session-deps b/cmake/session-deps index e809a6419..67d347c3a 160000 --- a/cmake/session-deps +++ b/cmake/session-deps @@ -1 +1 @@ -Subproject commit e809a64190494683935b63a8b8481178c1b1a8ab +Subproject commit 67d347c3ae0bddcffadf8b1231a772798eacd640 From 741df684f43fffa5ef4a08abd3341449a1f08d90 Mon Sep 17 00:00:00 2001 From: Jason Rhinelander Date: Tue, 6 Oct 2026 12:49:12 -0300 Subject: [PATCH 3/6] Build the static bundle from one walk of the link closure The bundle's archive list came from libsession_static_bundle() calls scattered across src/, proto/ and external/, each of which also tried to recurse into the target's dependencies at a point (right after add_library) where it had none yet. When dependencies stopped being registered by hand with the move to session-deps, the bundle silently lost libquic, oxen-logging, fmt, spdlog, ngtcp2, gnutls, nettle and the rest, and the 1.9.3 iOS build failed to link. Replace all of that with a single walk, run once every target is linked, from the libraries we export: every static library in their link closure goes into the bundle, so it can no longer drift from what is actually linked. Everything else in the closure (system libraries, frameworks, imported non-static targets such as Threads::Threads) is returned separately, since it has to be supplied by whatever links the bundle. A generator expression or imported target the walk cannot see into is a configure error rather than a silent omission. --- CMakeLists.txt | 9 +- cmake/AddStaticBundleLib.cmake | 147 ++++++++++++++------------------- external/CMakeLists.txt | 4 - proto/CMakeLists.txt | 5 -- src/CMakeLists.txt | 3 - 5 files changed, 70 insertions(+), 98 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index de61a0169..550b8edf3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -167,8 +167,13 @@ if(STATIC_BUNDLE) include(combine_archives) - libsession_static_bundle_resolve() - combine_archives(session-util libsession-static-bundle "${LIBSESSION_STATIC_BUNDLE_LIBS}") + # Threads::Threads comes from find_package() calls in subdirectories, which make it visible only + # there; the bundle walk has to be able to see it. + find_package(Threads REQUIRED) + libsession_static_bundle_libs(bundle_archives bundle_system_libs ${libsession_export_targets}) + message(STATUS "Static bundle libsession-util.a combines: ${bundle_archives}") + message(STATUS "Static bundle libsession-util.a also needs: ${bundle_system_libs}") + combine_archives(session-util libsession-static-bundle ${bundle_archives}) set(lib_folder lib) if(IOS) set(lib_folder "${lib_folder}-${ARCH}") diff --git a/cmake/AddStaticBundleLib.cmake b/cmake/AddStaticBundleLib.cmake index 3c9690165..8f8aff11a 100644 --- a/cmake/AddStaticBundleLib.cmake +++ b/cmake/AddStaticBundleLib.cmake @@ -1,95 +1,74 @@ -set(LIBSESSION_STATIC_BUNDLE_LIBS "" CACHE INTERNAL "list of libs to go into the static bundle lib") - -function(_libsession_static_bundle_append tgt) - list(APPEND LIBSESSION_STATIC_BUNDLE_LIBS "${tgt}") - set(LIBSESSION_STATIC_BUNDLE_LIBS "${LIBSESSION_STATIC_BUNDLE_LIBS}" CACHE INTERNAL "") -endfunction() - # Call as: # -# libsession_static_bundle(target [target2 ...]) +# libsession_static_bundle_libs(ARCHIVES_VAR SYSTEM_VAR target [target2 ...]) # -# to append the given target(s) to the list of libraries that will be combined to make the static -# bundled libsession-util.a. -function(libsession_static_bundle) - foreach(tgt IN LISTS ARGN) - if(TARGET "${tgt}" AND NOT "${tgt}" IN_LIST LIBSESSION_STATIC_BUNDLE_LIBS) - get_target_property(tgt_type ${tgt} TYPE) - - if(tgt_type STREQUAL STATIC_LIBRARY) - message(STATUS "Adding ${tgt} to libsession-util bundled library list") - _libsession_static_bundle_append("${tgt}") - endif() - - if(tgt_type STREQUAL INTERFACE_LIBRARY) - get_target_property(tgt_link_deps ${tgt} INTERFACE_LINK_LIBRARIES) - else() - get_target_property(tgt_link_deps ${tgt} LINK_LIBRARIES) +# to walk the link closure of the given targets. ARCHIVES_VAR is set to every static library in +# it: the archives that combine into the STATIC_BUNDLE libsession-util.a. SYSTEM_VAR is set to +# everything else the closure links: system libraries such as iconv, Apple frameworks, linker flags +# and imported non-static targets such as Threads::Threads. None of those can go into an archive, +# so whatever links the bundle has to supply them. +# +# The closure is read from the targets' link properties, so this has to be called after every +# target_link_libraries() it depends on. +function(libsession_static_bundle_libs archives_out system_out) + set(pending ${ARGN}) + set(visited) + set(archives) + set(system) + while(NOT "${pending}" STREQUAL "") + list(POP_FRONT pending tgt) + if(tgt MATCHES "^\\$$") + set(tgt "${CMAKE_MATCH_1}") + endif() + if(NOT TARGET "${tgt}") + # Silently skipping either of these could drop a library from the bundle, which then + # only shows up when something links against it. A `::` name is an imported target + # created in some subdirectory without GLOBAL, which we cannot see (let alone look + # inside) from here: find_package() it before calling this. + if(tgt MATCHES "\\$<") + message(FATAL_ERROR "Static bundle: don't know how to follow link item ${tgt}") endif() - - if(tgt_link_deps) - libsession_static_bundle(${tgt_link_deps}) + if(tgt MATCHES "::") + message(FATAL_ERROR "Static bundle: imported target ${tgt} is not visible here") endif() + list(APPEND system "${tgt}") + continue() endif() - endforeach() -endfunction() - -function(_libsession_static_bundle_walk tgt) - if(tgt MATCHES "^\\$$") - set(tgt "${CMAKE_MATCH_1}") - endif() - if(NOT TARGET "${tgt}") - return() - endif() - get_target_property(aliased ${tgt} ALIASED_TARGET) - if(aliased) - set(tgt "${aliased}") - endif() - - get_property(visited GLOBAL PROPERTY _libsession_static_bundle_visited) - if("${tgt}" IN_LIST visited) - return() - endif() - set_property(GLOBAL APPEND PROPERTY _libsession_static_bundle_visited "${tgt}") - - get_target_property(tgt_type ${tgt} TYPE) - if(tgt_type STREQUAL STATIC_LIBRARY) - # The list can hold alias names (eg. protobuf::libprotobuf-lite), so compare what they resolve to - set(already_bundled FALSE) - foreach(bundled IN LISTS LIBSESSION_STATIC_BUNDLE_LIBS) - get_target_property(bundled_aliased ${bundled} ALIASED_TARGET) - if(bundled STREQUAL tgt OR bundled_aliased STREQUAL tgt) - set(already_bundled TRUE) - break() - endif() - endforeach() - if(NOT already_bundled) - message(STATUS "Adding ${tgt} to libsession-util bundled library list") - _libsession_static_bundle_append("${tgt}") + get_target_property(aliased ${tgt} ALIASED_TARGET) + if(aliased) + set(tgt "${aliased}") endif() - endif() + if(tgt IN_LIST visited) + continue() + endif() + list(APPEND visited "${tgt}") - # An imported target (which is what session-deps produces for each static dependency) carries - # its own dependencies only in INTERFACE_LINK_LIBRARIES; a target built here has them in - # LINK_LIBRARIES. - get_target_property(deps ${tgt} INTERFACE_LINK_LIBRARIES) - get_target_property(imported ${tgt} IMPORTED) - if(NOT tgt_type STREQUAL INTERFACE_LIBRARY AND NOT imported) - get_target_property(link_deps ${tgt} LINK_LIBRARIES) - list(APPEND deps ${link_deps}) - endif() - foreach(dep IN LISTS deps) - _libsession_static_bundle_walk("${dep}") - endforeach() -endfunction() + get_target_property(type ${tgt} TYPE) + get_target_property(imported ${tgt} IMPORTED) + if(type STREQUAL STATIC_LIBRARY) + list(APPEND archives "${tgt}") + elseif(imported) + # Something the system provides (Threads::Threads). Linking it by name brings + # everything it carries, including link options, which this walk does not read. + list(APPEND system "${tgt}") + continue() + endif() -# Adds every static library that the already-registered targets link against, transitively. Call -# once all targets have their links set up: libsession_static_bundle() runs as each library is -# created, before its target_link_libraries(), so on its own it never sees a library's dependencies. -function(libsession_static_bundle_resolve) - set_property(GLOBAL PROPERTY _libsession_static_bundle_visited "") - foreach(tgt IN LISTS LIBSESSION_STATIC_BUNDLE_LIBS) - _libsession_static_bundle_walk("${tgt}") - endforeach() + # An imported target (which is what session-deps produces for each static dependency) + # carries its dependencies only in INTERFACE_LINK_LIBRARIES; one built here also has its + # private ones in LINK_LIBRARIES. + get_target_property(deps ${tgt} INTERFACE_LINK_LIBRARIES) + if(NOT type STREQUAL INTERFACE_LIBRARY AND NOT imported) + get_target_property(link_deps ${tgt} LINK_LIBRARIES) + list(APPEND deps ${link_deps}) + endif() + # ::@(dir) ... ::@ are cmake's own markers around items that target_link_libraries() added + # from another directory, not link items themselves. + list(FILTER deps EXCLUDE REGEX "-NOTFOUND$|^::@") + list(APPEND pending ${deps}) + endwhile() + list(REMOVE_DUPLICATES system) + set(${archives_out} "${archives}" PARENT_SCOPE) + set(${system_out} "${system}" PARENT_SCOPE) endfunction() diff --git a/external/CMakeLists.txt b/external/CMakeLists.txt index 9753a5410..932e2e1c5 100644 --- a/external/CMakeLists.txt +++ b/external/CMakeLists.txt @@ -24,7 +24,6 @@ endfunction() # Stock libsodium, not the libsodium-internal fork: the one thing the fork exposed that we used was # the field arithmetic behind xed25519::pubkey(), which src/xed25519-tweetnacl.cpp now supplies. session_dep(libsodium 1.0.21) -libsession_static_bundle(sessiondep::libsodium) # Needed even without networking: oxenc is part of the public interface of `common`. if(NOT TARGET oxenc::oxenc) @@ -40,7 +39,6 @@ if(ENABLE_NETWORKING AND ENABLE_NETWORKING_SROUTER) set(SROUTER_JEMALLOC OFF CACHE BOOL "") add_static_subdirectory(session-router EXCLUDE_FROM_ALL) - libsession_static_bundle(session-router::core) endif() # session-router provides oxen::quic when it is built (above); otherwise pull in oxen-libquic alone. @@ -80,7 +78,6 @@ sessiondep_or_submodule(protobuf-lite 3.21 protobuf protobuf::libprotobuf-lite) # configs get deduplicated at the swarm. set(BUILD_STATIC_libzstd ON CACHE BOOL "" FORCE) session_dep(libzstd 1.5) -libsession_static_bundle(sessiondep::libzstd) if(NOT TARGET nlohmann_json::nlohmann_json) set(JSON_BuildTests OFF CACHE INTERNAL "") @@ -89,7 +86,6 @@ if(NOT TARGET nlohmann_json::nlohmann_json) endif() session_dep(simdutf 7) -libsession_static_bundle(sessiondep::simdutf) # We need Howard Hinnant's header-only date library for now because the STL implementation of # std::chrono::parse() is spotty or broken on: diff --git a/proto/CMakeLists.txt b/proto/CMakeLists.txt index a7ab778af..5f13e7d27 100644 --- a/proto/CMakeLists.txt +++ b/proto/CMakeLists.txt @@ -1,7 +1,4 @@ -libsession_static_bundle(protobuf::libprotobuf-lite) - - add_library(protos SessionProtos.pb.cc WebSocketResources.pb.cc) @@ -20,8 +17,6 @@ else() target_compile_options(protos PUBLIC -Wno-error=unused-parameter) endif() -libsession_static_bundle(protos) - add_library(libsession::protos ALIAS protos) list(APPEND libsession_export_targets protos) set(libsession_export_targets "${libsession_export_targets}" PARENT_SCOPE) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 00821b6da..ffd1702b0 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -35,8 +35,6 @@ macro(add_libsession_util_library name) OUTPUT_NAME session-${name} SOVERSION ${LIBSESSION_LIBVERSION}) - libsession_static_bundle(${name}) - list(APPEND export_targets ${name}) endmacro() @@ -205,7 +203,6 @@ else() endif() endif() add_library(version STATIC version.c) -libsession_static_bundle(version) target_include_directories(version PRIVATE ../include) target_link_libraries(common INTERFACE version) From 6824566e843997ae8101fae1452e2ec85f4e696c Mon Sep 17 00:00:00 2001 From: Jason Rhinelander Date: Tue, 6 Oct 2026 12:49:22 -0300 Subject: [PATCH 4/6] Make static-bundle-test link the bundle alone, and build it in CI static-bundle-test linked quic and oxenc next to libsession-util.a and only called config code, so it could not notice libquic or anything the network needs going missing from the bundle -- and no CI job built it anyway: the debian jobs build shared libraries, and the static jobs ran static-bundle.sh with WITH_TESTS=OFF and built only session-util. It now links the bundle and nothing but the system requirements the bundle walk reports, calls into the network as well as config, and is defined alongside the bundle rather than under tests/ so it does not need WITH_TESTS. static-bundle.sh, which every static job (Linux, Windows, macOS, iOS) goes through, builds it after the bundle, so a dependency missing from the bundle now fails the build that produces it. --- CMakeLists.txt | 19 +++++++++++++++++++ tests/CMakeLists.txt | 7 ------- tests/static_bundle.cpp | 11 ++++++++++- utils/static-bundle.sh | 4 +++- 4 files changed, 32 insertions(+), 9 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 550b8edf3..6afb257ff 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -174,6 +174,25 @@ if(STATIC_BUNDLE) message(STATUS "Static bundle libsession-util.a combines: ${bundle_archives}") message(STATUS "Static bundle libsession-util.a also needs: ${bundle_system_libs}") combine_archives(session-util libsession-static-bundle ${bundle_archives}) + + # Links the bundle and only what the walk says it needs from the system, so that a library + # missing from the bundle fails here rather than in whatever consumes it. Headers come from our + # targets' usage requirements, without linking them. + add_executable(static-bundle-test tests/static_bundle.cpp) + if(ENABLE_NETWORKING) + set(bundle_header_target network) + else() + set(bundle_header_target config) + target_compile_definitions(static-bundle-test PRIVATE DISABLE_NETWORKING) + endif() + target_include_directories(static-bundle-test PRIVATE + $) + target_compile_definitions(static-bundle-test PRIVATE + $) + target_link_libraries(static-bundle-test PRIVATE + "${CMAKE_CURRENT_BINARY_DIR}/libsession-util.a" ${bundle_system_libs}) + add_dependencies(static-bundle-test session-util) + set(lib_folder lib) if(IOS) set(lib_folder "${lib_folder}-${ARCH}") diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index d06b99f31..ae479eca3 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -91,10 +91,3 @@ endif() add_executable(swarm-auth-test EXCLUDE_FROM_ALL swarm-auth-test.cpp) target_link_libraries(swarm-auth-test PRIVATE config) - -if(STATIC_BUNDLE) - add_executable(static-bundle-test static_bundle.cpp) - target_include_directories(static-bundle-test PUBLIC ../include) - target_link_libraries(static-bundle-test PRIVATE "${PROJECT_BINARY_DIR}/libsession-util.a" oxenc::oxenc quic) - add_dependencies(static-bundle-test session-util) -endif() diff --git a/tests/static_bundle.cpp b/tests/static_bundle.cpp index a760a178d..7ba9f91a3 100644 --- a/tests/static_bundle.cpp +++ b/tests/static_bundle.cpp @@ -1,12 +1,21 @@ // This file isn't designed to do anything useful, but just to test that we can compile and link -// against the combined static bundle (when using cmake ... -DSTATIC_BUILD=ON) +// against the combined static bundle (when using cmake ... -DSTATIC_BUNDLE=ON) with nothing else. +// The calls are there to make the linker pull in the code that needs the bundle's dependencies: +// config for libsodium/protobuf/zstd, and the network for libquic, gnutls, ngtcp2 and libevent. #include #include +#ifndef DISABLE_NETWORKING +#include +#endif int main() { if (std::mt19937_64{}() == 123) { auto& k = *reinterpret_cast(12345); k.encrypt_message(std::span{}); +#ifndef DISABLE_NETWORKING + auto& n = *reinterpret_cast(12345); + n.close_connections(); +#endif } } diff --git a/utils/static-bundle.sh b/utils/static-bundle.sh index 7f6c88385..c0fd64473 100755 --- a/utils/static-bundle.sh +++ b/utils/static-bundle.sh @@ -71,7 +71,9 @@ cmake -G 'Unix Makefiles' \ "$@" \ "$projdir" -make -j${JOBS:-$(nproc 2>/dev/null || sysctl -n hw.ncpu 2>/dev/null || echo 1)} VERBOSE=1 session-util +# static-bundle-test links against nothing but the bundle and its declared system libraries, so +# building it is what catches a dependency missing from the bundle. +make -j${JOBS:-$(nproc 2>/dev/null || sysctl -n hw.ncpu 2>/dev/null || echo 1)} VERBOSE=1 session-util static-bundle-test if [ -z "$archive" ]; then exit 0 From 6c0c3307b18c1854388483e417485af6889c0c5b Mon Sep 17 00:00:00 2001 From: Jason Rhinelander Date: Tue, 6 Oct 2026 12:49:26 -0300 Subject: [PATCH 5/6] Bump version to 1.9.4 --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 6afb257ff..31aca0caa 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -17,7 +17,7 @@ if(CCACHE_PROGRAM) endif() project(libsession-util - VERSION 1.9.3 + VERSION 1.9.4 DESCRIPTION "Session client utility library" LANGUAGES ${LANGS}) From 07b533e2d0f405f5c839f2f749be289ee20c61fc Mon Sep 17 00:00:00 2001 From: Jason Rhinelander Date: Tue, 6 Oct 2026 20:11:47 -0300 Subject: [PATCH 6/6] Link nlohmann-json publicly to network service_node.hpp, session_network.hpp and session_network_types.hpp all include , so it is part of the network target's public interface, not a private detail. It only went unnoticed because testAll adds nlohmann itself, and most build machines have a system copy on the default include path; static-bundle-test, which takes its headers purely from network's usage requirements, failed to compile on every static CI job (Android, iOS, macOS, Windows). --- src/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index ffd1702b0..96a5b9880 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -140,8 +140,8 @@ if(ENABLE_NETWORKING) PUBLIC crypto oxen::quic - PRIVATE nlohmann_json::nlohmann_json + PRIVATE sessiondep::libsodium sessiondep::nettle date::date