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

5.1 KiB

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;
}