Module records and aliases

Installation records what a module reports about itself. dpm_install_module
opens a module once, writes its version and description to a .meta file in
/var/lib/dpm/metadata/, and records the alternate names it declared in
modules.aliases beside it. dpm_uninstall_module removes both, leaving the
module file in place.

dpm_list_modules reads those records and opens no module. A module with no
record lists with a version of <uninstalled> and still loads when a caller
names it. An unreadable or absent metadata directory costs the listing its
detail and costs nothing else.

Aliases give a module alternate names, declared through the new
dpm_module_aliases contract symbol or added with dpm_add_module_alias. A
name is recorded once: one already serving as an alias, or belonging to an
installed module, is refused rather than repointed. dpm_require matches a
name against the installed modules, then the alias table, then the module
path.

The metadata directory is a fifth override field and the -M flag, and
[modules] metadata in core.conf.

The test suite is four binaries covering context, modules, records, and
aliases, each a ctest case of its own, alongside the CLI cases.
This commit is contained in:
2026-08-24 03:51:39 -04:00
parent 71d409b5c7
commit 0291e61fd8
32 changed files with 2147 additions and 290 deletions

View File

@@ -4,7 +4,7 @@ A DPM module is one shared object in the module directory. libdpm-core.so loads
## The Module Contract
A module includes `<dpm/core.h>`, 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.
A module includes `<dpm/core.h>`, links `-ldpm-core`, and exports the following four 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 only entry through which the module performs work. `ctx` is the host context that dispatched the call; the module reaches every service (`dpm_log`, `dpm_config_get`, `dpm_get_resolved_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. Where a nonzero return needs explaining, record the reason with `dpm_set_last_error` immediately before returning, and the caller reads it back with `dpm_get_last_error`. It must be callable immediately after load with no other setup.
@@ -15,6 +15,18 @@ The module's own version as an X.Y.Z string. libdpm-core.so reports this value t
`const char* dpm_module_description(void)`
A one-line human-readable description, shown in module listings.
`const char* dpm_module_aliases(void)`
A comma-separated list of alternate names your module answers to, or NULL for none:
```
extern "C" const char* dpm_module_aliases(void)
{
return "installer, files";
}
```
NULL is a complete answer. The symbol itself is required, and a module that does not export it is refused at load with the other contract failures.
The `dpm_ctx` type and the service declarations all come from the installed public header:
```
@@ -110,7 +122,35 @@ Where your module calls a peer, put a **stub module** in the fixture module path
## 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.
Modules install to `lib/dpm/modules` under the install prefix (`/usr/lib/dpm/modules` on a distribution install), and the package's install step then records the module:
```
dpm --install-module mymodule
```
That loads the module once, writes its version and description into `/var/lib/dpm/metadata/mymodule.meta`, and records each alias it declared whose name is free. From then on `dpm --list-modules` reports your module from that record and opens nothing, which is what keeps listing the modules on a system from executing them.
Removal is the counterpart:
```
dpm --uninstall-module mymodule
```
which deletes the record and every alias resolving to the module, leaving the `.so` where it is.
A module present in the module path with no record is reported in listings with a version of `<uninstalled>`. It still loads and runs when a caller requires it by name, with a warning, so a module put in place by hand works before anyone has installed it.
## Aliases
An alias is a second name a module answers to. Your declared aliases are recorded at installation; an operator adds more with:
```
dpm --add-alias mymodule mm
dpm --remove-alias mm
dpm --list-aliases [mymodule]
```
A name is recorded once. Adding a name that is already an alias, or that is an installed module's own name, is refused rather than repointed, so an existing route to a module is never taken over silently. Module names resolve before aliases, so a module's own name always reaches that module.
## A Working Example