89 lines
5.1 KiB
Markdown
89 lines
5.1 KiB
Markdown
# Developing DPM Modules
|
|
|
|
A DPM module is one shared object in the module directory. Core loads it, validates it completely, routes CLI commands to it, and serves its functions to other modules and to programs linked against libdpm-core. This document covers writing, building, testing, and installing a module.
|
|
|
|
## The module contract
|
|
|
|
Every module 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 generic command entry point. `ctx` is the host context that dispatched the call; the module reaches every core service (dpm_log, dpm_config_get, dpm_module_path, ...) through it. `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. Consumers state minimum-version requirements against this value.
|
|
|
|
**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 core version (X.Y.Z) the module supports — the oldest core whose contract and services it was written against. Core refuses to load the module if its own version is lower, and the refusal message says so.
|
|
|
|
**const dpm_manifest\* dpm_module_manifest(void)**
|
|
A static table declaring the module's entire functional surface: an entry count and, per entry, the API name, its table version, and the exact exported symbol carrying the table — for example { "mymodule", 1, "mymodule_api_v1" }. Only manifest-declared tables are ever handed to consumers; an API absent from the manifest does not exist, even if its symbol does. A module providing no API tables returns a manifest with a count of zero.
|
|
|
|
The dpm_manifest and dpm_manifest_entry types, the table header, and the core service declarations all come from the installed public header:
|
|
|
|
```
|
|
#include <dpm/core.h>
|
|
```
|
|
|
|
## API tables
|
|
|
|
A module's functions are published to consumers as API tables: one exported symbol per API version (mymodule_api_v1), pointing to a plain C struct of function pointers. Every table opens with a dpm_api_table_header — the DPM_API_TABLE_MAGIC constant, then the table struct's size in bytes as the module compiled it. Later revisions of the same version may append fields at the tail; the size field lets consumers detect what is present.
|
|
|
|
All parameters and return values crossing a table are C types only. State passes through opaque handles; errors are int codes.
|
|
|
|
**Symbol naming**: every functional export is prefixed with the module's name (mymodule_\*). The dpm_ prefix is reserved for the contract symbols and core.
|
|
|
|
## Validation at load
|
|
|
|
Core is the sole authority on module validity, and validation is all-or-nothing. Before a module is offered to anyone, core verifies, in order: every reserved contract symbol resolves; the core-minimum handshake passes; the version and description probes return well-formed values; every manifest-declared symbol resolves; and every declared table carries the correct magic and a sane size. 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 <build-dir>
|
|
cmake --build <build-dir>
|
|
```
|
|
|
|
libdpm-core is the only DPM link dependency a module ever has. Dependencies on other modules are runtime concerns, resolved through core's require/get_api — a peer module is never linked and never needs to be present to build.
|
|
|
|
## Running and testing locally
|
|
|
|
Load the freshly built module through a locally run dpm without installing anything:
|
|
|
|
```
|
|
dpm --module-path <build-dir> mymodule <command>
|
|
```
|
|
|
|
Core 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.
|
|
|
|
## Installing
|
|
|
|
Modules install to lib/dpm/modules under the install prefix (/usr/lib/dpm/modules on a distribution install). Core discovers the module on its next scan; no registration step exists beyond the file being present and valid.
|
|
|
|
## Reference implementation
|
|
|
|
The info module bundled with the core repository at src/bundled-modules/info/ is a complete working example of the contract, an API table, and this build structure.
|