Files
dpm-core-ng/docs/CONSUMERS.md
Christopher M. Punches 17acad2b02 Modules determine their own compatibility with the library
A module is built against the system-installed libdpm-core.so and is
responsible for being correct against it. Where it needs to act on the
version it is running under, dpm_core_version() reports that and the
module decides for itself.

dpm_module_core_min() is removed. It was a declaration handed to the
library to enforce on the module's behalf, and enforcement of that kind
belongs nowhere in a library that routes and hosts. The contract is now
three reserved symbols and load validation is two steps: the reserved
symbols resolve, and the version and description probes return
well-formed values.

compare_versions had no remaining caller and is removed; parse_version
stays for the well-formedness probe. The core_too_new fixture went with
the handshake it existed to exercise.
2026-08-15 04:21:58 -04:00

126 lines
5.1 KiB
Markdown

# Consuming libdpm-core.so
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 the library installed, include the public header and link it:
```
#include <dpm/core.h>
```
```
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:
```
dpm_ctx* ctx = dpm_open(NULL);
...
dpm_close(ctx);
```
`dpm_open`(NULL) reads the system configuration (`/etc/dpm/conf.d/`), resolves the system module path, and targets the root filesystem. `dpm_close` releases every handle the context issued; all pointers obtained through the context are invalid after it.
To point a context elsewhere, pass overrides — every field is optional:
```
dpm_open_overrides ov = {
"/path/to/conf.d", /* config_dir: NULL = /etc/dpm/conf.d/ */
"/path/to/modules", /* module_path: NULL = config, then default */
"/path/to/root", /* root: target root for package operations */
-1 /* log_level: -1 = from config */
};
dpm_ctx* ctx = dpm_open(&ov);
```
The root override is what makes chroot builds, image assembly, and sysroot management work: package operations act on the given tree instead of the running system. Multiple simultaneous contexts with different roots are legal.
## Acquiring and using modules
**`dpm_require`** loads a module by name, on demand:
```
dpm_module* mod = dpm_require(ctx, "mymodule");
```
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 the library saw in the loaded module:
```
dpm_module_info info;
dpm_module_info_of(ctx, mod, &info); /* info.name, .version, .description */
```
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`** invokes the module — a command name and arguments:
```
int rc = dpm_execute(ctx, mod, "command", argc, argv);
```
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
```
dpm_cursor* cur = dpm_list_modules(ctx);
dpm_module_info info;
while (dpm_cursor_next(cur, &info) == 0) {
/* info.name, info.version, info.description */
}
dpm_cursor_free(cur);
```
The cursor covers every valid module in the module path; invalid candidates are excluded and logged.
## Services
- **`dpm_core_version`()** — the library version; callable without a context.
- **`dpm_module_info_of`(ctx, mod, out)** — the name, version, and description 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.
- **`dpm_last_error`(ctx)** — a human-readable description of the most recent failure on the context, or NULL.
## Ownership and errors
Strings returned by the library are owned by the context (or by the module that produced them) and remain valid until `dpm_close`; callers never free them. Functions returning int use 0 for success. Functions returning pointers use NULL for failure, with detail available from `dpm_last_error`.
## Complete example
```
#include <dpm/core.h>
#include <stdio.h>
int main(void) {
dpm_ctx* ctx = dpm_open(NULL);
if (!ctx) {
fprintf(stderr, "failed to initialize\n");
return 1;
}
dpm_module* mod = dpm_require(ctx, "info");
if (!mod) {
fprintf(stderr, "%s\n", dpm_last_error(ctx));
dpm_close(ctx);
return 1;
}
int rc = dpm_execute(ctx, mod, "version", 0, NULL);
dpm_close(ctx);
return rc;
}
```