# Developing DPM Modules A DPM module is one shared object in the module directory. libdpm-core.so loads it, validates it completely, and dispatches commands to it on behalf of whatever asked — the `dpm` binary, a build system, or another module. This document covers writing, building, testing, and installing a module. ## The module contract A module includes ``, links `-ldpm-core`, and exports the following symbols as extern "C". All returned strings must be non-NULL, static or module-owned, and valid for the lifetime of the loaded module; callers never free them. **`int dpm_module_execute(dpm_ctx* ctx, const char* command, int argc, char** argv)`** The command entry point, and the module's entire functional surface. `ctx` is the host context that dispatched the call; the module reaches every service (`dpm_log`, `dpm_config_get`, `dpm_module_path`, ...) through it, and reaches peer modules through it as well. `command` is the subcommand name, equal to argv[0] when argc > 0. NULL or empty `command` must behave as the module's help command. Returns 0 on success, nonzero on failure. It must be callable immediately after load with no other setup. **`const char* dpm_module_version(void)`** The module's own version as an X.Y.Z string. libdpm-core.so reports this value to consumers, and each consumer decides for itself whether the version suits it. **`const char* dpm_module_description(void)`** A one-line human-readable description, shown in module listings. **`const char* dpm_module_core_min(void)`** The minimum library version (X.Y.Z) the module supports — the oldest one whose contract and services it was written against. A libdpm-core.so older than that refuses to load the module, and the refusal message says so. The `dpm_ctx` type and the service declarations all come from the installed public header: ``` #include ``` ## Your interface is your command vocabulary A module publishes no headers, no struct layouts, and no symbols to anything that calls it. Everything it offers is reached through `dpm_module_execute`, addressed by command string, with arguments passed as an argument vector and a status returned as an int. That is what a caller compiles against: a module name and a command name, both strings. Document your commands, their arguments, and their return codes — that documentation is your interface, and it is the only thing a consumer can depend on. **Symbol naming**: every functional export is prefixed with the module's name (mymodule_\*). The dpm_ prefix is reserved for the contract symbols and for libdpm-core.so. ## Calling another module A module reaches a peer by performing the same two steps its own caller performed — ask libdpm-core.so for the module by name, then ask libdpm-core.so to invoke it: ``` int dpm_module_execute(dpm_ctx* ctx, const char* command, int argc, char** argv) { dpm_module* peer = dpm_require(ctx, "othermodule"); if (!peer) { dpm_log(ctx, DPM_LOG_ERROR, dpm_last_error(ctx)); return 1; } return dpm_execute(ctx, peer, "somecommand", argc, argv); } ``` The `ctx` is the one handed to your entry point. Nothing else is needed to reach the library. **Never link, include, or hardcode anything belonging to a peer.** No peer headers, no shared types, no peer symbols. Modules are loaded with `RTLD_LOCAL`, so a peer's symbols are not reachable from your module even if you tried — libdpm-core.so is the only path, and the only knowledge you hold about a peer is its name and the commands it documents. A module that depends on a peer is the party that judges the peer's version. Require it, read its reported version with `dpm_module_info_of`, and decide whether it is suitable for the commands you intend to issue. libdpm-core.so reports; it does not rule. ## Validation at load libdpm-core.so is the sole authority on module validity, and validation is all-or-nothing. Before a module is offered to anyone, it verifies, in order: every reserved contract symbol resolves; the minimum-version handshake passes; and the version and description probes return well-formed values. A module failing any step is refused with an itemized reason, visible in the load-failure output. A module that loads is fully valid — consumers never defend against partial states. ## Building A module repository builds with CMake: ``` cmake_minimum_required(VERSION 3.22) project(mymodule) set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD_REQUIRED ON) add_library(mymodule MODULE mymodule.cpp) set_target_properties(mymodule PROPERTIES PREFIX "" SUFFIX ".so" ) target_link_libraries(mymodule PRIVATE dpm-core) install(TARGETS mymodule LIBRARY DESTINATION lib/dpm/modules) ``` ``` cmake -B cmake --build ``` libdpm-core.so is the only DPM link dependency a module ever has. Dependencies on other modules are runtime concerns, resolved by name through require and dispatch — a peer module is never linked, never included, and never needs to be present to build or to test. ## Running and testing locally Load the freshly built module through a locally run `dpm` binary without installing anything: ``` dpm --module-path mymodule ``` libdpm-core.so runs the full validation sequence on every load, so a contract mistake surfaces here, immediately and itemized, rather than after installation. The `--config-dir` flag points the module's configuration namespace at local files during development, and `--root` directs package operations at a scratch tree. Where your module calls a peer, put a **stub module** in the fixture module path: a small .so exporting the four reserved symbols and answering the commands your module issues. libdpm-core.so validates and dispatches to it exactly as it would the real peer. Because a peer is addressed only by name and command string, the stub is a complete substitute — there is nothing else about the real peer your module could have depended on. ## Installing Modules install to `lib/dpm/modules` under the install prefix (`/usr/lib/dpm/modules` on a distribution install). libdpm-core.so discovers the module on its next scan; no registration step exists beyond the file being present and valid. ## A working example The info module bundled with the `dpm` binary and libdpm-core.so, at `src/bundled-modules/info/`, tests and demonstrates full DPM system functionality, and in doing so shows the contract, command routing, and this build structure in working form.