Route all module interaction through dispatch

A module is addressed by name and command string, and nothing else.
Typed API access handed a caller a pointer into the callee's function
table, which meant compiling against that module's struct layout — a
build-time dependency between modules that the design does not permit.
Removing it also removes the manifest, the table magic constant, and
the table size field, which existed only to describe and validate
those tables.

Load validation is now three steps: reserved contract symbols resolve,
the minimum-version handshake passes, and the version and description
probes return well-formed values. The contract is four reserved
symbols, and a module's interface is the command vocabulary it
documents.

Documentation is brought in line, and artifacts are named exactly:
libdpm-core.so for the library, <dpm/core.h> for the header, the dpm
binary for the command-line tool.
This commit is contained in:
2026-08-15 02:15:18 -04:00
parent 267529bee3
commit 97b39cac6c
17 changed files with 263 additions and 702 deletions

View File

@@ -1,10 +1,10 @@
# Consuming libdpm-core
# Consuming libdpm-core.so
Programs link libdpm-core to operate the package manager directly: build systems, installers, image builders, system tooling, and foreign-language bindings all use the same library the `dpm` CLI is built on. A program holding a default context is operating the installed package manager itself — system configuration, system module path, system tree, system locking — identically to invoking the installed `dpm` command.
Programs link libdpm-core.so as an ordinary shared library dependency — the same way they link any other library — to reach the package manager in-process: build systems, installers, image builders, system tooling, and foreign-language bindings all use the library the `dpm` binary is built on. A program holding a default context is working against system configuration, the system module path, the system tree, and system locking, the same environment the installed `dpm` binary sees.
## Compiling and linking
With libdpm-core installed, include the public header and link the library:
With the library installed, include the public header and link it:
```
#include <dpm/core.h>
@@ -16,6 +16,8 @@ g++ myprog.cpp -ldpm-core
The header installs to the standard include path and the library to the standard lib path, so no additional flags are required. The interface is a C ABI: every function is extern "C", every type crossing the boundary is a C type, and state passes through opaque handles — callable from C, C++, or any language with C FFI.
`<dpm/core.h>` names no module and carries no module-specific type. It offers two things: discovery of modules, and interaction with them.
## The context
All work happens through a context handle:
@@ -50,30 +52,24 @@ The root override is what makes chroot builds, image assembly, and sysroot manag
dpm_module* mod = dpm_require(ctx, "mymodule");
```
libdpm-core validates the module completely at load; a handle is returned only for a fully valid module. NULL means the module is absent or invalid — `dpm_last_error`(ctx) carries the precise reason. Modules load at most once per context; repeated calls return the same handle.
libdpm-core.so validates the module completely at load; a handle is returned only for a fully valid module. NULL means the module is absent or invalid — `dpm_last_error`(ctx) carries the precise reason. Modules load at most once per context; repeated calls return the same handle.
**`dpm_module_info_of`** reports what libdpm-core saw in the loaded module:
**`dpm_module_info_of`** reports what the library saw in the loaded module:
```
dpm_module_info info;
dpm_module_info_of(ctx, mod, &info); /* info.name, .version, .description, .core_min */
```
Deciding whether that version is too new or too old is yours. libdpm-core applies no version criterion of its own — a handle means the module is valid, not that it suits you.
Deciding whether that version is suitable is yours. libdpm-core.so applies no version criterion of its own — a handle means the module is valid, not that it suits you.
**`dpm_execute`** drives a module the way the CLI does — a command name and arguments:
**`dpm_execute`** invokes the module — a command name and arguments:
```
int rc = dpm_execute(ctx, mod, "command", argc, argv);
```
**`dpm_get_api`** returns a module's typed function table for direct calls:
```
const mymodule_api_v1_s* api = (const mymodule_api_v1_s*)dpm_get_api(ctx, mod, "mymodule", 1);
```
The returned table was validated at load and is usable for the life of the context. NULL means the module does not provide that API at that version.
This is the only path into module code, and it is the same path the `dpm` binary uses and the same path a module uses to reach a peer. You address a module by name and a capability by command string, so your program compiles against no module header, no struct layout, and no module symbol. What a module accepts as commands and arguments, and what its return codes mean, is documented by that module.
## Enumerating modules
@@ -90,8 +86,8 @@ The cursor covers every valid module in the module path; invalid candidates are
## Services
- **`dpm_core_version`()** — the libdpm-core version; callable without a context.
- **`dpm_module_info_of`(ctx, mod, out)** — the name, version, description, and minimum-libdpm-core version read from a loaded module.
- **`dpm_core_version`()** — the library version; callable without a context.
- **`dpm_module_info_of`(ctx, mod, out)** — the name, version, description, and minimum-library version read from a loaded module.
- **`dpm_config_get`(ctx, module, section, key)** — a value from a module's configuration namespace, or NULL if unset.
- **`dpm_log`(ctx, level, message)** — writes to the context's configured log targets; levels are `DPM_LOG_FATAL` through `DPM_LOG_DEBUG`.
- **`dpm_module_path`(ctx)** — the resolved module directory.