MODULE, not SHARED

add_library( Circle MODULE Circle.cpp )
set_target_properties( Circle PROPERTIES
    PREFIX "lib"
    POSITION_INDEPENDENT_CODE ON )
target_link_libraries( Circle PRIVATE plugin )

CMake's MODULE library is a shared object that is only ever dlopened, never linked at build time. That is exactly a plugin. SHARED is for libraries you also link against — the host's Shape support library is SHARED (or STATIC), the plugins are MODULE.

Set PREFIX "lib" if the host sets DSOLoader::prefix("lib") and uses loadAll.

The plugin SDK

Ship these to a plugin author, and nothing else:

  • the base class header(s) — Shape.hh
  • any Config structs (often in the same header)
  • plugin/PluginRegister.hh, plugin/Plugin.hh, plugin/PluginCatalog.hh, plugin/PluginCreator.hh — installed by the plugin component to <prefix>/include/plugin/
  • dso/DSOVisibility.hh — pulled in by the plugin headers

The plugin does not link dsold and does not define CREATECATALOG — both belong to the host.

Versioning the ABI

The plugin boundary is a C++ vtable — it is only safe while the base class layout matches on both sides. Two mitigations:

  1. A SOVERSION on the host's support library, bumped on any change to a base class. Plugins built against the old header fail to load against the new soname.
  2. The signed-manifest abiMajor / abiMinor fields. dso-sign --abi-major N --abi-minor M stamps them; a host can read VerifyResult::data.abiMajor and refuse a mismatch. (Wiring an ABI floor directly into loadVerified is on the roadmap; today the host checks the field itself.)

Keep base classes small and pure-virtual — no data members, no inline non-virtual methods that could change — and this is a rare event.

Installing into a plugin directory

install( TARGETS Circle Square Ellipse
         LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}/myapp/plugins )

The host then does:

DSOLoader::prefix( "lib" );
DSOLoader::loadAll( ".so", CMAKE_INSTALL_FULL_LIBDIR "/myapp/plugins" );

or reads a path from config / an environment variable via loadAllByEnvironment.

Shipping signed add-ons

If the host uses loadVerified, the add-on package must include the signature. dso-sign modifies the .so in place, so sign as a post-build step:

find_program( DSO_SIGN dso-sign )
if( DSO_SIGN AND DEFINED PLUGIN_SIGNING_KEY )
    add_custom_command( TARGET Circle POST_BUILD
        COMMAND ${DSO_SIGN} $<TARGET_FILE:Circle>
                --key ${PLUGIN_SIGNING_KEY}
                --name circle-plugin
                --version ${PROJECT_VERSION} )
endif()

Distribute the signer's *.pub (or its fingerprint) through a channel the operator already trusts — the same package repository, a documentation page — so they can populate their trusted-keys directory.

CPack for the host + plugins

plugin's own build splits into plugin (runtime: libdsold.so), plugin_development (headers, plugin-sign.a, plugin.pc) and plugin_apps (the three tools + completions). Mirror that split in your application so a deployment that only runs plugins does not ship the SDK headers.

Next: FAQ.