Skip to content

Fix the static bundle missing session-deps libraries - #181

Merged
jagerman merged 6 commits into
session-foundation:devfrom
mpretty-cyro:fix/static-bundle-session-deps
Oct 7, 2026
Merged

jagerman merged 6 commits into
session-foundation:devfrom
mpretty-cyro:fix/static-bundle-session-deps

Conversation

@mpretty-cyro

@mpretty-cyro mpretty-cyro commented Oct 6, 2026 •

Copy link
Copy Markdown
Collaborator

The 1.9.3 iOS xcframework doesn't link. Session iOS fails with 74 undefined symbols from libquic, oxen-logging, spdlog, fmt, nettle and libevent. The release libsession-util.a dropped from 2303 object files (1.9.2) to 260. Its simulator slice went from 64 MB to 22 MB.

Cause

STATIC_BUNDLE builds libsession-util.a from LIBSESSION_STATIC_BUNDLE_LIBS, which targets join through libsession_static_bundle(). Before #179, every dependency was registered explicitly: libquic via libsession_system_or_submodule, and gnutls/nettle/gmp/libevent via StaticBuild.cmake's add_static_target. The session-deps switch removed both. session_dep, sessiondep_or_submodule and oxen-logging's load.cmake don't register anything, so only sodium, zstd, simdutf and protobuf were still bundled.

The recursive walk in libsession_static_bundle() couldn't recover them:

  • add_libsession_util_library registers each library right after add_library, before its target_link_libraries, so the walk finds no dependencies.
  • It reads LINK_LIBRARIES for static targets. session-deps' sessiondep_ext_* targets are imported, so their own deps (e.g. hogweed → nettle → gmp) are only in INTERFACE_LINK_LIBRARIES.

Changes

  • libsession_static_bundle_resolve(), called just before combine_archives. It walks the full static closure of the registered targets once everything is linked. It follows INTERFACE_LINK_LIBRARIES for imported/interface targets and LINK_LIBRARIES for built ones, unwraps $<LINK_ONLY:>, and dedupes through aliases. The bundle list goes from 10 to 25 libraries, adding oxen-logging, fmt, spdlog, quic, ngtcp2 (+ crypto_gnutls), gnutls, hogweed, nettle, gmp, idn2, unistring, tasn1, and libevent core + pthreads.
  • utils/ios.sh adds link "iconv" to the generated modulemap. libunistring calls iconv_open. 1.9.2 bundled GNU libiconv. session-deps deliberately uses the system libiconv on Apple and doesn't bundle it, so the bundle now needs -liconv, and an SPM binary target can't declare linker flags. With the module link declaration, import SessionUtil autolinks it.

Verification (Xcode 27.0, 27A266a)

  • CMake configure with ios.sh's flags: stock bundles 10 libraries, this branch 25.
  • utils/ios.sh with the bundle change: the xcframework builds. It has 2283 arm64 object files in both device and simulator slices, and all 74 symbols that were undefined in 1.9.3 are now defined.
  • I swapped that xcframework into Session iOS in place of the 1.9.3 release (headers byte-identical) and built against libsession 1.9.3 + quic_max_udp_payload:
    • Without the iconv change: the link fails only on _iconv, _iconv_open and _iconv_close.
    • With link "iconv" added to the modulemap: it links. 2251/2252 tests pass. The one failure, ThreadDisappearingMessagesSettingsViewModelSpec "can change to another setting and change back", is a flaky test that predates this change. Against the unmodified 1.9.2 release artifact, that spec failed in 4 of 5 iterations.
  • Not yet done: a fresh ios.sh run with the final ios.sh edit. The iconv line was tested by editing the generated modulemap by hand. The ios.sh change itself passed bash -n, and a dry run of its modulemap block produces the same output.

Notes

  • Version is left at 1.9.3 for a retag.
  • Session iOS's from-source path (Scripts/build_libSession_util.sh) generates its own modulemap. It will need the same link "iconv" (or -liconv) once it builds against session-deps libsession.
  • I only ran this through ios.sh. Android and the other bundle consumers go through the same combine_archives path, but I didn't build them.

mpretty-cyro and others added 5 commits October 6, 2026 17:13
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.
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.
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.
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.
@jagerman

jagerman commented Oct 6, 2026

Copy link
Copy Markdown
Member

I've pushed four commits on top of this:

  • 79750412 Bump session-deps for libunistring's iconv on Apple. The missing iconv was a session-deps bug. libunistring links $(LTLIBICONV), which is -liconv on Apple, and the recipe never passed it on. That's fixed in session-deps 67d347c. The link "iconv" in ios.sh is still needed, because a merged archive can't carry link dependencies, but any ordinary static Apple link through session-deps now gets iconv on its own.

  • 741df684 Build the static bundle from one walk of the link closure. This replaces both the old libsession_static_bundle() and the _resolve() walk added here. The old function recursed into dependencies right after add_library(), when there weren't any yet. It also had hand registrations spread across src/, proto/ and external/, so adding a second walk on top left two mechanisms plus an alias-dedupe loop to reconcile them. Now there's a single walk, after everything is linked, starting from the libraries we export. Every static library in that closure goes into the bundle, so the bundle can't drift from what is actually linked.

    • What the bundle can't contain is returned separately. That means system libraries, frameworks and Threads::Threads.
    • The walk fails loudly instead of skipping. A generator expression it can't follow, or an imported target it can't see, is now a configure error. The walk used to skip these silently, which is how 1.9.3 shipped broken.
    • Same 25 libraries as this PR's walk. It also works with session-router enabled.
  • 6824566e Make static-bundle-test link the bundle alone, and build it in CI. The old test couldn't have caught this. It linked quic and oxenc next to libsession-util.a and only called config code. No CI job built it either: the debian jobs use shared libraries, and the static jobs ran static-bundle.sh with WITH_TESTS=OFF.

    • It now links only the bundle plus the system requirements the walk reports, and it calls into the network code too.
    • It's built in every static job. It's defined next to the bundle in the top-level CMakeLists.txt, and static-bundle.sh builds it after the bundle. That covers every static CI job (Linux, Windows, macOS, iOS), so a library missing from the bundle fails the build that produces it.
    • It catches the 1.9.3 failure. On Linux, linking it without libquic fails with 147 undefined oxen::quic symbols.
    • Session iOS's build_libSession_util.sh also calls static-bundle.sh, so it will build the test as well.
  • 6c0c3307 Bump version to 1.9.4. Please don't re-tag 1.9.3. It's already published, and SPM pins a checksum per version, so re-tagging would change the zip checksum under anyone who has already resolved it.

Tested on Linux only: the normal build and tests pass, and static-bundle.sh runs clean and builds the test. The iOS and Windows links of static-bundle-test haven't run anywhere yet; CI will be the first to try them. Could you rebuild the xcframework from this and check it in Session iOS again?

service_node.hpp, session_network.hpp and session_network_types.hpp all
include <nlohmann/json.hpp>, 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).
@jagerman
jagerman merged commit c3507ad into session-foundation:dev Oct 7, 2026
20 of 26 checks passed
@mpretty-cyro
mpretty-cyro deleted the fix/static-bundle-session-deps branch October 7, 2026 01:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants