2026-08-11 09:44:25 -03:00
|
|
|
#ifndef LOGOS_SHARED_API_H
|
|
|
|
|
#define LOGOS_SHARED_API_H
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @file logos_shared_api.h
|
|
|
|
|
* @brief Marks the runtime types that must exist EXACTLY ONCE per process.
|
|
|
|
|
*
|
2026-08-20 22:18:42 -03:00
|
|
|
* Every image that links liblogos_protocol.a / liblogos_qt_host.a statically
|
|
|
|
|
* gets its OWN copy of the code, and therefore its own copy of every
|
|
|
|
|
* function-local static inside it: TokenManager::instance, the per-identity
|
|
|
|
|
* StoreRegistry, the host-services grant, the deferred event-subscription
|
|
|
|
|
* registry. The host saves a capability token into its copy, another in-process
|
|
|
|
|
* image reads its own empty copy, and every cross-module call is refused
|
|
|
|
|
* ("ModuleProxy: rejecting unauthorized call") — the package manager never
|
|
|
|
|
* appears in the sidebar.
|
2026-08-11 09:44:25 -03:00
|
|
|
*
|
2026-08-20 22:18:42 -03:00
|
|
|
* WHICH PLATFORMS. This was long documented as Windows-only, on the premise
|
|
|
|
|
* that "ELF and Mach-O interpose symbols across the whole process image set".
|
|
|
|
|
* That is true of ELF and FALSE of Mach-O, and the false half was measured:
|
|
|
|
|
*
|
|
|
|
|
* - PE no interposition at all. A symbol is either in a DLL's export
|
|
|
|
|
* table and reached through an import thunk, or it is resolved
|
|
|
|
|
* image-locally. Measured on the Basecamp payload: NINE images each
|
|
|
|
|
* defining TokenManager::instance()::instance.
|
|
|
|
|
* - Mach-O two-level namespace, so it behaves like PE, not like ELF. It
|
|
|
|
|
* appears to work only while the consumer image has NO definition of
|
|
|
|
|
* its own, so ld binds the undefined symbol to the provider. The
|
|
|
|
|
* moment any reference drags an archive member in, that image gets
|
|
|
|
|
* its own copy, silently. Measured in logos-basecamp: ONE reference
|
|
|
|
|
* to LogosAPI::forIdentity pulled logos_api.cpp.o and
|
|
|
|
|
* token_manager.cpp.o into the executable, which then produced 31
|
|
|
|
|
* refused calls against a baseline of 0.
|
|
|
|
|
* - ELF flat namespace, first definition wins process-wide. This one
|
|
|
|
|
* genuinely does collapse duplicates.
|
|
|
|
|
*
|
2026-08-21 15:35:45 -03:00
|
|
|
* WHERE THE DEFINITIONS LIVE NOW. Each type is owned by ONE shared library, and
|
|
|
|
|
* every in-process image imports it:
|
2026-08-11 09:44:25 -03:00
|
|
|
*
|
2026-08-21 15:35:45 -03:00
|
|
|
* liblogos_protocol TokenManager, LogosAPIClient, the StoreRegistry
|
|
|
|
|
* liblogos_qt_host LogosAPI
|
2026-08-11 09:44:25 -03:00
|
|
|
*
|
2026-08-21 15:35:45 -03:00
|
|
|
* liblogos_core is NOT the provider. It used to be: it absorbed both static
|
|
|
|
|
* archives with --whole-archive and re-published their symbols through a
|
|
|
|
|
* generated .def, so that one image could stand in for types it did not own.
|
|
|
|
|
* That scheme is gone (logos-protocol#65, logos-plugin-qt#22,
|
|
|
|
|
* logos-liblogos#182) and liblogos_core imports the runtime like every other
|
|
|
|
|
* consumer. Measured after the migration: liblogos_core defines ZERO runtime
|
|
|
|
|
* symbols, liblogos_protocol 78, liblogos_qt_host 23.
|
2026-08-11 09:44:25 -03:00
|
|
|
*
|
2026-08-21 15:35:45 -03:00
|
|
|
* The two fixes that look obvious are both wrong, and were both tried:
|
2026-08-11 09:44:25 -03:00
|
|
|
*
|
2026-08-21 15:35:45 -03:00
|
|
|
* - Exporting everything from one image (-Wl,--export-all-symbols) makes its
|
|
|
|
|
* import library a second definition of symbols the static archives also
|
|
|
|
|
* define, and the link dies with "multiple definition of
|
|
|
|
|
* `LogosAPI::LogosAPI'".
|
|
|
|
|
* - Hand-marking a class list with __declspec(dllexport) is not a smaller
|
|
|
|
|
* version of the right answer, it is a moving target: it exported 116
|
|
|
|
|
* symbols and the Qt host runtime still failed to link with ELEVEN
|
|
|
|
|
* undefined references across five classes, plus free functions nobody had
|
|
|
|
|
* thought to mark. A curated list is correct only until the next consumer
|
|
|
|
|
* touches a symbol nobody marked, and the failure lands in a downstream
|
|
|
|
|
* repo, far from the cause.
|
|
|
|
|
*
|
|
|
|
|
* So on Windows the export table is GENERATED from the objects
|
|
|
|
|
* (cmake/gen-shared-exports.sh) rather than curated, and this header resolves
|
|
|
|
|
* its "building the shared library" branch to nothing there, so the two
|
|
|
|
|
* mechanisms never compete. Off Windows a shared library exports its non-hidden
|
|
|
|
|
* symbols by default and there is nothing to generate.
|
|
|
|
|
*
|
|
|
|
|
* The dllimport half is the load-bearing one for consumers. It rewrites the
|
2026-08-11 09:44:25 -03:00
|
|
|
* reference to go through `__imp_`, so the plain symbol is never undefined and
|
2026-08-21 15:35:45 -03:00
|
|
|
* GNU ld never pulls an archive member that would redefine it — regardless of
|
|
|
|
|
* where a static archive sits on the link line. Without it the link still
|
|
|
|
|
* succeeds and binds to the archive, with no diagnostic at all.
|
2026-08-11 09:44:25 -03:00
|
|
|
*
|
2026-08-21 15:35:45 -03:00
|
|
|
* IN-PROCESS vs OUT-OF-PROCESS, which is the distinction that decides who links
|
|
|
|
|
* what, and the one most likely to be "simplified" away by someone tidying up:
|
|
|
|
|
*
|
|
|
|
|
* IN-PROCESS the app executable, liblogos_core, and anything QPluginLoader
|
|
|
|
|
* pulls into the app (view replica factories, legacy widget
|
|
|
|
|
* plugins, logos-standalone-app's third-party plugins). These
|
|
|
|
|
* link the SHARED libraries. A second copy here is the
|
|
|
|
|
* split-brain.
|
|
|
|
|
*
|
|
|
|
|
* OUT-OF-PROCESS logos_host, ui-host, module plugins and ui_qml backends.
|
|
|
|
|
* These keep linking the STATIC archive, and that is CORRECT, not
|
|
|
|
|
* a leftover. Each runs in its own process, so its own copy of
|
|
|
|
|
* TokenManager IS the right per-process singleton — measured,
|
|
|
|
|
* logos_host and ui-host define ~100 runtime symbols each and are
|
|
|
|
|
* deliberately exempt from the symbol gates. Staying static also
|
|
|
|
|
* keeps a .lgx self-contained: a .lgx records an EMPTY nix
|
|
|
|
|
* closure, so a shared library would not travel with it, and it
|
|
|
|
|
* keeps those plugins immune to ABI skew against a separately
|
|
|
|
|
* updated .so.
|
|
|
|
|
*
|
|
|
|
|
* Do not "unify" the two by moving modules onto the shared libraries. It would
|
|
|
|
|
* couple every .lgx to the exact runtime build it was packaged against, to fix
|
|
|
|
|
* a duplication that is not a bug in a separate process.
|
|
|
|
|
*
|
|
|
|
|
* WHAT ASSERTS ANY OF THIS. Nothing did, for a long time, which is how nine
|
|
|
|
|
* images came to define the same singleton. There are now symbol gates in
|
|
|
|
|
* logos-basecamp, logos-logoscore-cli and logos-standalone-app
|
|
|
|
|
* (nix/symbol-gate.nix) asserting that each type has exactly one definer across
|
|
|
|
|
* the in-process image set, each shipped with a negative control that plants a
|
|
|
|
|
* real duplicate and requires the gate to reject it.
|
2026-08-11 09:44:25 -03:00
|
|
|
*/
|
2026-08-20 22:18:42 -03:00
|
|
|
|
|
|
|
|
/* The primitives. Kept separate so the per-library macros below read as a
|
|
|
|
|
* three-state choice (export / import / neither) rather than as nested #ifdefs.
|
|
|
|
|
* Off Windows there is nothing to import: a shared library exports its
|
|
|
|
|
* non-hidden symbols by default, and consumers just reference them. */
|
|
|
|
|
#if defined(_WIN32)
|
|
|
|
|
# define LOGOS_SHARED_EXPORT __declspec(dllexport)
|
|
|
|
|
# define LOGOS_SHARED_IMPORT __declspec(dllimport)
|
|
|
|
|
#else
|
|
|
|
|
# define LOGOS_SHARED_EXPORT __attribute__((visibility("default")))
|
|
|
|
|
# define LOGOS_SHARED_IMPORT
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
/* logos-protocol's own single-instance types: TokenManager, LogosAPIClient,
|
|
|
|
|
* and the LogosResult stream operators.
|
|
|
|
|
*
|
|
|
|
|
* EXPORT while building the shared library that owns them, IMPORT while
|
|
|
|
|
* consuming that library, and EMPTY for the static archive — which is what
|
|
|
|
|
* every current consumer gets, so this is a no-op until a build opts in. */
|
|
|
|
|
#if defined(LOGOS_PROTOCOL_BUILDING_SHARED)
|
|
|
|
|
/* Building the library that owns the symbol.
|
|
|
|
|
*
|
|
|
|
|
* On Windows this resolves to NOTHING, and that is deliberate: the PE export
|
|
|
|
|
* table is generated from the objects (cmake/gen-shared-exports.sh), because a
|
|
|
|
|
* hand-marked list is a moving target -- it exported 116 symbols and the Qt host
|
|
|
|
|
* runtime still failed to link with eleven undefined references across five
|
|
|
|
|
* classes. Marking here as well would put two mechanisms on the same table for
|
|
|
|
|
* no gain, and __declspec(dllexport) additionally makes CMake's
|
|
|
|
|
* WINDOWS_EXPORT_ALL_SYMBOLS inert, so the annotation actively forecloses the
|
|
|
|
|
* automatic route.
|
|
|
|
|
*
|
|
|
|
|
* Off Windows the annotation is what a hidden-visibility build would need, and
|
|
|
|
|
* is harmless today because nothing sets -fvisibility=hidden. */
|
|
|
|
|
# if defined(_WIN32)
|
|
|
|
|
# define LOGOS_SHARED_API
|
|
|
|
|
# else
|
|
|
|
|
# define LOGOS_SHARED_API LOGOS_SHARED_EXPORT
|
|
|
|
|
# endif
|
|
|
|
|
#elif defined(_WIN32) && defined(LOGOS_SHARED_USE_DLL)
|
|
|
|
|
# define LOGOS_SHARED_API LOGOS_SHARED_IMPORT
|
2026-08-11 09:44:25 -03:00
|
|
|
#else
|
|
|
|
|
# define LOGOS_SHARED_API
|
|
|
|
|
#endif
|
|
|
|
|
|
2026-08-20 22:18:42 -03:00
|
|
|
/* logos-plugin-qt's LogosAPI, which lives in a DIFFERENT library.
|
|
|
|
|
*
|
|
|
|
|
* It needs its own macro rather than reusing LOGOS_SHARED_API, because the two
|
|
|
|
|
* are not the same choice in the same translation unit: while building the Qt
|
|
|
|
|
* host runtime shared library, LogosAPI must be EXPORTED while TokenManager —
|
|
|
|
|
* owned by logos-protocol — must be IMPORTED. One macro cannot say both, and on
|
|
|
|
|
* PE getting it wrong means the type is defined twice in the process.
|
|
|
|
|
*
|
|
|
|
|
* Off Windows this distinction is moot (both resolve to default visibility),
|
|
|
|
|
* which is exactly why it would go unnoticed until a Windows build. */
|
|
|
|
|
#if defined(LOGOS_QT_HOST_BUILDING_SHARED)
|
|
|
|
|
/* Building the library that owns the symbol.
|
|
|
|
|
*
|
|
|
|
|
* On Windows this resolves to NOTHING, and that is deliberate: the PE export
|
|
|
|
|
* table is generated from the objects (cmake/gen-shared-exports.sh), because a
|
|
|
|
|
* hand-marked list is a moving target -- it exported 116 symbols and the Qt host
|
|
|
|
|
* runtime still failed to link with eleven undefined references across five
|
|
|
|
|
* classes. Marking here as well would put two mechanisms on the same table for
|
|
|
|
|
* no gain, and __declspec(dllexport) additionally makes CMake's
|
|
|
|
|
* WINDOWS_EXPORT_ALL_SYMBOLS inert, so the annotation actively forecloses the
|
|
|
|
|
* automatic route.
|
|
|
|
|
*
|
|
|
|
|
* Off Windows the annotation is what a hidden-visibility build would need, and
|
|
|
|
|
* is harmless today because nothing sets -fvisibility=hidden. */
|
|
|
|
|
# if defined(_WIN32)
|
|
|
|
|
# define LOGOS_QT_HOST_API
|
|
|
|
|
# else
|
|
|
|
|
# define LOGOS_QT_HOST_API LOGOS_SHARED_EXPORT
|
|
|
|
|
# endif
|
|
|
|
|
#elif defined(_WIN32) && defined(LOGOS_SHARED_USE_DLL)
|
|
|
|
|
# define LOGOS_QT_HOST_API LOGOS_SHARED_IMPORT
|
|
|
|
|
#else
|
|
|
|
|
# define LOGOS_QT_HOST_API
|
|
|
|
|
#endif
|
|
|
|
|
|
2026-08-11 09:44:25 -03:00
|
|
|
#endif // LOGOS_SHARED_API_H
|