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.
This commit is contained in:
@@ -82,7 +82,7 @@ set_target_properties(info PROPERTIES
|
||||
add_dependencies(dpm info)
|
||||
|
||||
# ---------------------------------------------------------------------
|
||||
# Test fixture modules: one known-good stub, three deliberately broken
|
||||
# Test fixture modules: one known-good stub, two deliberately broken
|
||||
# ---------------------------------------------------------------------
|
||||
enable_testing()
|
||||
|
||||
@@ -95,7 +95,7 @@ file(WRITE ${CMAKE_BINARY_DIR}/DartConfiguration.tcl
|
||||
|
||||
set(FIXTURE_MODULE_DIR ${CMAKE_BINARY_DIR}/tests/fixtures)
|
||||
|
||||
foreach(fixture good missing_symbols core_too_new bad_version)
|
||||
foreach(fixture good missing_symbols bad_version)
|
||||
add_library(fixture_${fixture} MODULE tests/fixtures/src/${fixture}.cpp)
|
||||
set_target_properties(fixture_${fixture} PROPERTIES
|
||||
PREFIX ""
|
||||
@@ -123,7 +123,7 @@ set_target_properties(test_core PROPERTIES
|
||||
BUILD_RPATH "$ORIGIN/../lib"
|
||||
)
|
||||
|
||||
foreach(fixture good missing_symbols core_too_new bad_version)
|
||||
foreach(fixture good missing_symbols bad_version)
|
||||
add_dependencies(test_core fixture_${fixture})
|
||||
endforeach()
|
||||
|
||||
|
||||
@@ -58,7 +58,7 @@ libdpm-core.so validates the module completely at load; a handle is returned onl
|
||||
|
||||
```
|
||||
dpm_module_info info;
|
||||
dpm_module_info_of(ctx, mod, &info); /* info.name, .version, .description, .core_min */
|
||||
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.
|
||||
@@ -77,7 +77,7 @@ This is the only path into module code, and it is the same path the `dpm` binary
|
||||
dpm_cursor* cur = dpm_list_modules(ctx);
|
||||
dpm_module_info info;
|
||||
while (dpm_cursor_next(cur, &info) == 0) {
|
||||
/* info.name, info.version, info.description, info.core_min */
|
||||
/* info.name, info.version, info.description */
|
||||
}
|
||||
dpm_cursor_free(cur);
|
||||
```
|
||||
@@ -87,7 +87,7 @@ The cursor covers every valid module in the module path; invalid candidates are
|
||||
## Services
|
||||
|
||||
- **`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_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.
|
||||
|
||||
@@ -28,10 +28,10 @@ Modules are loaded with `RTLD_LOCAL`, so a module's symbols never enter the glob
|
||||
|
||||
## Versioning model
|
||||
|
||||
Compatibility is directional, and the module is the one that declares it:
|
||||
Every version question is settled by the party that has to live with the answer. libdpm-core.so reports; it rules on nothing.
|
||||
|
||||
- **Each module reports the minimum library version it supports** (a reserved contract symbol). At load, the running library compares its own version against that minimum: if it is older → refuse with an explicit "library too old for this module" report; otherwise load.
|
||||
- **Consuming modules judge module versions; libdpm-core.so does not.** The library reports the version it saw at load and draws no conclusion from it. A module that depends on another requires it by name, reads the reported version, and decides for itself whether that version is suitable for the commands it intends to issue. A load is a statement that the module is valid, never that it is compatible with a particular caller.
|
||||
- **A module determines its own compatibility with the library.** It is built against the system-installed `libdpm-core.so` and is responsible for being correct against it. `dpm_core_version()` reports the running version to any module that needs to act on it, and that module proceeds or fails on its own judgement.
|
||||
- **Consuming modules judge module versions.** A module that depends on another requires it by name, reads the reported version, and decides for itself whether that version is suitable for the commands it intends to issue. A load is a statement that the module is valid, never that it is compatible with a particular caller.
|
||||
|
||||
## libdpm-core.so
|
||||
|
||||
@@ -63,7 +63,7 @@ Releases the context: unloads every module handle it issued, closes log targets,
|
||||
Resolves the module `name` in the module path and runs the full load-time validation sequence (see Load-time enforcement) if the module is not already loaded in this context. On success returns a module handle owned by the context (repeated calls return the same handle — modules are loaded at most once per context). On failure returns NULL and records the precise reason: not found, or validation step failed with the step and detail. No version criterion is applied here; compatibility is the caller's to determine from the reported version.
|
||||
|
||||
**`int dpm_module_info_of(dpm_ctx* ctx, dpm_module* mod, dpm_module_info* out)`**
|
||||
Fills `out` with the loaded module's name, version, description, and minimum-library version exactly as they were read at load (string pointers valid until context close). This is how a consumer obtains the version it will judge. The library attaches no meaning to the values. Returns 0 on success, nonzero if the module cannot be reported on.
|
||||
Fills `out` with the loaded module's name, version, and description exactly as they were read at load (string pointers valid until context close). This is how a consumer obtains the version it will judge. The library attaches no meaning to the values. Returns 0 on success, nonzero if the module cannot be reported on.
|
||||
|
||||
**`int dpm_execute(dpm_ctx* ctx, dpm_module* mod, const char* command, int argc, char** argv)`**
|
||||
Dispatch: invokes the module's `dpm_module_execute` with the context, `command`, and the argument vector. Returns the module's return value verbatim (0 = success). The library adds nothing to the call besides delivery; argument semantics beyond "argv[0] is the command" are the module's to define.
|
||||
@@ -76,7 +76,7 @@ This is the entire path into module code. A caller addresses a module by name an
|
||||
Scans the module path and returns a cursor over all *valid* modules (each candidate .so is validated on first scan; failures are logged and excluded). Returns NULL on an unreadable module path.
|
||||
|
||||
**`int dpm_cursor_next(dpm_cursor* cur, dpm_module_info* out)`**
|
||||
Advances the cursor. Fills `out` with the next module's name, version, description, and minimum-library version (string pointers valid until context close). Returns 0 and fills `out` while entries remain; returns nonzero at end.
|
||||
Advances the cursor. Fills `out` with the next module's name, version, and description (string pointers valid until context close). Returns 0 and fills `out` while entries remain; returns nonzero at end.
|
||||
|
||||
**`void dpm_cursor_free(dpm_cursor* cur)`**
|
||||
Releases the cursor. NULL is a no-op.
|
||||
@@ -111,8 +111,7 @@ Returns the module's own version as an X.Y.Z string. Must be constant for the li
|
||||
**`const char* dpm_module_description(void)`**
|
||||
Returns a one-line human-readable description, used in module listings.
|
||||
|
||||
**`const char* dpm_module_core_min(void)`**
|
||||
Returns the minimum library version (X.Y.Z) this module supports — the oldest one whose contract and services the module was written against. A library older than that refuses to load the module, and says so.
|
||||
**Version compatibility with the library is the module's own to determine.** 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 what it is running under, `dpm_core_version()` reports the running version and the module decides what to do with that.
|
||||
|
||||
**Symbol naming**: functional exports are prefixed with the module's name (raw_\*, pkg_\*); the dpm_ prefix is reserved for the contract and for libdpm-core.so.
|
||||
|
||||
@@ -123,8 +122,7 @@ Returns the minimum library version (X.Y.Z) this module supports — the oldest
|
||||
libdpm-core.so is the sole authority on module validity; the contract above is enforced by its validator, not by any SDK. Validation is all-or-nothing; a module is registered only after passing every step:
|
||||
|
||||
1. **Resolve all reserved contract symbols.** Any missing → refuse, log the exact list, `dlclose`.
|
||||
2. **Minimum-version handshake.** `dpm_module_core_min`() must be ≤ the running library version. If the library is too old, refuse and say so — the remedy is updating the library, and the message names it.
|
||||
3. **Probe the cheap calls.** `dpm_module_version`() and `dpm_module_description`() are invoked immediately; NULL or malformed returns → refuse.
|
||||
2. **Probe the cheap calls.** `dpm_module_version`() and `dpm_module_description`() are invoked immediately; NULL or malformed returns → refuse.
|
||||
|
||||
Failures happen at install/load time, loudly and itemized. Consumers never receive a partially valid module: if a handle was handed out, the contract already validated.
|
||||
|
||||
@@ -232,7 +230,7 @@ Peers are reached at runtime by name and command string. Building pkg does not r
|
||||
|
||||
- Working on **pkg**: edit, run unit tests (instant, zero environment), harness run with a raw stub before merge. A real raw is never needed, or even possessed, until integration.
|
||||
- Working on **raw**: same, except its tests need only fixture package files and a scratch tree.
|
||||
- Working on **libdpm-core.so**: its test fixtures are deliberately broken modules — missing symbols, a too-new minimum-version declaration, a malformed version — plus one known-good stub. Development never needs any real package module.
|
||||
- Working on **libdpm-core.so**: its test fixtures are deliberately broken modules — missing symbols, a malformed version — plus one known-good stub. Development never needs any real package module.
|
||||
- **Debugging** is the layer-2 harness under a debugger — it is the "run the module without the system" mechanism, so no separate standalone build exists or is maintained.
|
||||
|
||||
The discipline that keeps this honest: stubs are written to the command vocabulary the real peer documents, and layer-2 validation plus the layer-3 bootstrap run in CI, so a stub that drifts from reality is caught by the first integration pass rather than shipped.
|
||||
@@ -256,7 +254,7 @@ The config-dir override matters most: once the context reads config from the loc
|
||||
|
||||
- **A module's interface is its command vocabulary.** Retiring or changing the meaning of a command is a version change in the module that owns it, judged by every consumer that dispatches to it.
|
||||
- **Breaking the library's exported ABI means a new symbol version generation**: the export set is what consumers link against, so a break is a deliberate, versioned event rather than an incidental one.
|
||||
- **Compatibility is decided by the consumer**: modules declare the minimum library version they support, and libdpm-core.so enforces that one handshake because it is the host. Everything else is reported, not enforced — the library hands a consumer the version it saw and the consumer decides whether to proceed.
|
||||
- **Compatibility is decided by the party that has to live with it**: a module determines whether it works with the library it is running against, and a consuming module determines whether a peer's version suits it. libdpm-core.so reports the versions it saw and enforces nothing beyond validity.
|
||||
|
||||
## Invariants
|
||||
|
||||
|
||||
@@ -15,15 +15,22 @@ 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_core_min(void)`**
|
||||
The minimum library version (X.Y.Z) the module supports — the oldest one whose contract and services it was written against. A libdpm-core.so older than that refuses to load the module, and the refusal message says so.
|
||||
|
||||
The `dpm_ctx` type and the service declarations all come from the installed public header:
|
||||
|
||||
```
|
||||
#include <dpm/core.h>
|
||||
```
|
||||
|
||||
## You determine your own compatibility with the library
|
||||
|
||||
Your module is built against the system-installed `libdpm-core.so` and is responsible for being correct against it. Where you need to act on what you are running under, `dpm_core_version()` reports the running version and you decide what to do:
|
||||
|
||||
```
|
||||
const char* running = dpm_core_version();
|
||||
```
|
||||
|
||||
Check it, proceed or fail on your own terms, and report through `dpm_log` and your return code.
|
||||
|
||||
## Your interface is your command vocabulary
|
||||
|
||||
A module publishes no headers, no struct layouts, and no symbols to anything that calls it. Everything it offers is reached through `dpm_module_execute`, addressed by command string, with arguments passed as an argument vector and a status returned as an int.
|
||||
@@ -57,7 +64,7 @@ A module that depends on a peer is the party that judges the peer's version. Req
|
||||
|
||||
## Validation at load
|
||||
|
||||
libdpm-core.so is the sole authority on module validity, and validation is all-or-nothing. Before a module is offered to anyone, it verifies, in order: every reserved contract symbol resolves; the minimum-version handshake passes; and the version and description probes return well-formed values. 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.
|
||||
libdpm-core.so is the sole authority on module validity, and validation is all-or-nothing. Before a module is offered to anyone, it verifies that every reserved contract symbol resolves and that the version and description probes return well-formed values. A module failing either 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
|
||||
|
||||
@@ -99,7 +106,7 @@ dpm --module-path <build-dir> mymodule <command>
|
||||
|
||||
libdpm-core.so 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.
|
||||
|
||||
Where your module calls a peer, put a **stub module** in the fixture module path: a small .so exporting the four reserved symbols and answering the commands your module issues. libdpm-core.so validates and dispatches to it exactly as it would the real peer. Because a peer is addressed only by name and command string, the stub is a complete substitute — there is nothing else about the real peer your module could have depended on.
|
||||
Where your module calls a peer, put a **stub module** in the fixture module path: a small .so exporting the three reserved symbols and answering the commands your module issues. libdpm-core.so validates and dispatches to it exactly as it would the real peer. Because a peer is addressed only by name and command string, the stub is a complete substitute — there is nothing else about the real peer your module could have depended on.
|
||||
|
||||
## Installing
|
||||
|
||||
|
||||
@@ -53,7 +53,7 @@ The context owns everything it hands out. Every string a caller receives stays v
|
||||
|
||||
**Acquiring a module.** `dpm_require` resolves a module by name, validates it completely, and returns a handle — or returns NULL, with `dpm_last_error` carrying the precise reason. Modules load at most once per context, and repeated calls return the same handle.
|
||||
|
||||
**Reading what the library saw.** `dpm_module_info_of` fills in a module's name, version, description, and minimum-library version. Those values are reported, and no conclusion is drawn from them.
|
||||
**Reading what the library saw.** `dpm_module_info_of` fills in a module's name, version, and description. Those values are reported, and no conclusion is drawn from them.
|
||||
|
||||
**Calling into a module.** `dpm_execute` passes a command name and an argument vector to the module's entry point and returns its result. This is the only path into module code, and it is the same one the `dpm` binary takes.
|
||||
|
||||
@@ -104,17 +104,18 @@ The `ctx` a module needs is the one handed to it in its own entry point, so it r
|
||||
|
||||
*What a module author implements.*
|
||||
|
||||
A module is one `.so` in the module directory exporting four reserved symbols: a command entry point, its own version, a one-line description, and the minimum library version it supports. It includes `<dpm/core.h>` for those declarations and links `-ldpm-core`, and that is its entire build dependency.
|
||||
A module is one `.so` in the module directory exporting three reserved symbols: a command entry point, its own version, and a one-line description. It includes `<dpm/core.h>` for those declarations and links `-ldpm-core`, and that is its entire build dependency.
|
||||
|
||||
The entry point receives the context that dispatched the call, the command name, and an argument vector, and returns an int. Everything a module offers the rest of the system is reachable through that one function, addressed by command name — which is what keeps a caller free of any compile-time knowledge of the module it is calling.
|
||||
|
||||
A module is built against the system-installed `libdpm-core.so` and is responsible for being correct against it. Where it needs to know what it is running on, `dpm_core_version()` reports the running version and the module acts on that itself.
|
||||
|
||||
## Load-time enforcement
|
||||
|
||||
libdpm-core.so validates a module completely before offering it to anyone:
|
||||
|
||||
1. Every reserved contract symbol resolves.
|
||||
2. The module's minimum library version is not newer than the running library's own.
|
||||
3. The version and description probes return well-formed values.
|
||||
2. The version and description probes return well-formed values.
|
||||
|
||||
A module failing any step is refused with an itemized reason and its library handle closed. Validation is all-or-nothing: if a handle comes back, the contract already passed. Consumers never defend against partially valid modules, because they cannot receive one.
|
||||
|
||||
@@ -122,9 +123,11 @@ From the command line this shows up as a module missing from `--list-modules` wi
|
||||
|
||||
## Versioning
|
||||
|
||||
libdpm-core.so enforces exactly one version rule, the one where it is the host: each module states the oldest library version it supports, and a library older than that refuses to load the module, with a message naming the remedy.
|
||||
Every version question belongs to the party that has to live with the answer.
|
||||
|
||||
Every other version question belongs to the consumer. A module that depends on another requires it, reads the version reported back, and decides for itself whether that version is suitable for the commands it intends to issue. libdpm-core.so reports what it saw and draws no conclusion from it, so a handle means the module is valid rather than that it suits any particular caller.
|
||||
A module determines its own compatibility with the library it is running against. `dpm_core_version()` reports the running version, and the module proceeds or fails on its own judgement. A module is built against the system-installed `libdpm-core.so` and is responsible for being correct against it.
|
||||
|
||||
A module that depends on another module requires it, reads the version reported back, and decides for itself whether that version is suitable for the commands it intends to issue. libdpm-core.so reports what it saw and draws no conclusion from it, so a handle means the module is valid rather than that it suits any particular caller.
|
||||
|
||||
## Why it works this way
|
||||
|
||||
@@ -157,7 +160,7 @@ One repository per module, plus the libdpm-core.so repository. A module links li
|
||||
- A harness that links the real libdpm-core.so and points the module path at build output plus fixture stubs exercises the real validation on a bare builder.
|
||||
- Only final integration needs the whole system, and because alternate roots are first-class, it needs a directory rather than a virtual machine.
|
||||
|
||||
This repository's own fixtures are deliberately broken stub modules — missing symbols, a too-new minimum version, a malformed version — plus one known-good stub. Developing libdpm-core.so never requires a real package module to exist.
|
||||
This repository's own fixtures are deliberately broken stub modules — missing symbols, a malformed version — plus one known-good stub. Developing libdpm-core.so never requires a real package module to exist.
|
||||
|
||||
## What this repository produces
|
||||
|
||||
|
||||
@@ -92,7 +92,6 @@ typedef struct dpm_module_info {
|
||||
const char* name; /* module name (filename minus .so) */
|
||||
const char* version; /* module's own X.Y.Z */
|
||||
const char* description; /* one-line description */
|
||||
const char* core_min; /* minimum libdpm-core version it supports */
|
||||
} dpm_module_info;
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
@@ -139,9 +138,9 @@ DPM_API void dpm_close(dpm_ctx* ctx);
|
||||
* context. Modules are loaded at most once per context; repeated
|
||||
* calls return the same handle.
|
||||
*
|
||||
* Version compatibility is the caller's judgement, not this library's:
|
||||
* read the loaded module's version with dpm_module_info_of() and
|
||||
* decide whether it is acceptable.
|
||||
* Version compatibility is the caller's judgement: read the loaded
|
||||
* module's version with dpm_module_info_of() and decide whether it is
|
||||
* acceptable.
|
||||
*
|
||||
* @param ctx The libdpm-core context
|
||||
* @param name The module name (its filename minus .so)
|
||||
@@ -151,13 +150,11 @@ DPM_API void dpm_close(dpm_ctx* ctx);
|
||||
DPM_API dpm_module* dpm_require(dpm_ctx* ctx, const char* name);
|
||||
|
||||
/**
|
||||
* @brief Reports what libdpm-core sees in a loaded module
|
||||
* @brief Reports what the library sees in a loaded module
|
||||
*
|
||||
* Fills `out` with the module's name, version, description, and
|
||||
* minimum-libdpm-core version, exactly as they were read at load. No
|
||||
* compatibility conclusion is drawn from these values; the caller
|
||||
* decides whether the version it is looking at is too new or too
|
||||
* old for its purposes.
|
||||
* Fills `out` with the module's name, version, and description,
|
||||
* exactly as they were read at load. The caller decides whether the
|
||||
* version it is looking at suits its purposes.
|
||||
*
|
||||
* @param ctx The libdpm-core context
|
||||
* @param mod A module handle from dpm_require()
|
||||
@@ -210,9 +207,8 @@ DPM_API dpm_cursor* dpm_list_modules(dpm_ctx* ctx);
|
||||
/**
|
||||
* @brief Advances an enumeration cursor
|
||||
*
|
||||
* Fills `out` with the next module's name, version, description, and
|
||||
* minimum-libdpm-core version; the string pointers remain valid until
|
||||
* context close.
|
||||
* Fills `out` with the next module's name, version, and description;
|
||||
* the string pointers remain valid until context close.
|
||||
*
|
||||
* @param cur The cursor from dpm_list_modules()
|
||||
* @param out Receives the next module's information
|
||||
@@ -296,11 +292,17 @@ DPM_API const char* dpm_last_error(dpm_ctx* ctx);
|
||||
* int argc, char** argv);
|
||||
* const char* dpm_module_version(void);
|
||||
* const char* dpm_module_description(void);
|
||||
* const char* dpm_module_core_min(void);
|
||||
*
|
||||
* dpm_module_execute is the module's entire functional surface; its
|
||||
* capabilities are addressed by command string, so a module publishes
|
||||
* no headers, struct layouts, or symbols to anything that calls it.
|
||||
*
|
||||
* A module determines for itself whether it can work with the library
|
||||
* it is running against: dpm_core_version() reports the running
|
||||
* version, and the module proceeds or fails on its own judgement. A
|
||||
* module is built against the system-installed libdpm-core.so and is
|
||||
* responsible for being correct against it.
|
||||
*
|
||||
* The library refuses to load any module that does not validate
|
||||
* completely (see the DPM specification: load-time enforcement).
|
||||
*/
|
||||
|
||||
@@ -34,7 +34,6 @@ struct dpm_module {
|
||||
void* handle = nullptr;
|
||||
std::string version;
|
||||
std::string description;
|
||||
std::string core_min;
|
||||
int (*execute)(dpm_ctx*, const char*, int, char**) = nullptr;
|
||||
};
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* @file version.hpp
|
||||
* @brief X.Y.Z version parsing and comparison
|
||||
* @brief X.Y.Z version parsing
|
||||
*
|
||||
* @copyright Copyright (c) 2026 SILO GROUP LLC
|
||||
* @author Chris Punches <chris.punches@silogroup.org>
|
||||
@@ -33,13 +33,4 @@ namespace dpmcore {
|
||||
*/
|
||||
bool parse_version(const char* s, long out[3]);
|
||||
|
||||
/**
|
||||
* @brief Compares two valid X.Y.Z version strings
|
||||
*
|
||||
* @param a First version
|
||||
* @param b Second version
|
||||
* @return -1 if a < b, 0 if equal, 1 if a > b
|
||||
*/
|
||||
int compare_versions(const char* a, const char* b);
|
||||
|
||||
} // namespace dpmcore
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
*
|
||||
* Bundles with the dpm binary and libdpm-core.so; used for testing and
|
||||
* reporting functionality of libdpm-core.so. Implements the full DPM
|
||||
* module contract: the four reserved symbols, with every capability
|
||||
* module contract: the three reserved symbols, with every capability
|
||||
* reached by command string through the entry point.
|
||||
*
|
||||
* @copyright Copyright (c) 2026 SILO GROUP LLC
|
||||
@@ -28,7 +28,6 @@
|
||||
#include "commands.hpp"
|
||||
|
||||
#define INFO_MODULE_VERSION "0.1.0"
|
||||
#define INFO_CORE_MIN "0.1.0"
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Reserved contract symbols */
|
||||
@@ -50,14 +49,6 @@ extern "C" const char* dpm_module_description(void)
|
||||
return "Reports and tests libdpm-core functionality.";
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns the minimum libdpm-core version this module supports
|
||||
*/
|
||||
extern "C" const char* dpm_module_core_min(void)
|
||||
{
|
||||
return INFO_CORE_MIN;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Command entry point
|
||||
*
|
||||
|
||||
@@ -1,18 +1,46 @@
|
||||
/*
|
||||
* libdpm-core.map — the export list for libdpm-core.so
|
||||
* libdpm-core.map — the linker version script for libdpm-core.so
|
||||
*
|
||||
* Passed to the linker with --version-script. Names under `global` are
|
||||
* exported; `local: *` hides everything else, so the public C API is the
|
||||
* only surface a consumer or a loaded module can bind to.
|
||||
* Passed to the linker with --version-script; see CMakeLists.txt, which
|
||||
* also lists it as a link dependency so edits force a relink. It does
|
||||
* two separate jobs.
|
||||
*
|
||||
* `global` is every function in include/dpm/core.h and nothing else. A
|
||||
* new public function is added here in the commit that adds it to the
|
||||
* header.
|
||||
*
|
||||
* The node name stamps each symbol (dpm_open@@DPM_CORE_1.0), so a break
|
||||
* can ship as a new node while already-linked binaries keep resolving
|
||||
* the old one. Newest node first. DPM_CORE_0.1 was never released; it is
|
||||
* the shape a retired node takes.
|
||||
* 1. It limits the export set.
|
||||
*
|
||||
* `global` names every function declared in <dpm/core.h>. `local: *`
|
||||
* hides everything else, including the template instantiations that
|
||||
* libstdc++ headers emit with default visibility.
|
||||
*
|
||||
*
|
||||
* 2. It versions the exports.
|
||||
*
|
||||
* Every symbol here is stamped with the node name: dpm_open becomes
|
||||
* dpm_open@@DPM_CORE_1.0. The linker reads that node off the library a
|
||||
* consumer links against and records it in the consumer's own binary,
|
||||
* and at startup the dynamic linker verifies the library still provides
|
||||
* it.
|
||||
*
|
||||
* That keeps an already-installed module working after the library
|
||||
* changes underneath it, which is what makes updating libdpm-core.so
|
||||
* safe mid-bootstrap.
|
||||
*
|
||||
* A breaking change ships as a new node while the old node keeps its
|
||||
* original definitions, so binaries built against the old node keep
|
||||
* resolving them. Both live in one .so under one soname, preserving the
|
||||
* invariant the routing model depends on: exactly one instance of the
|
||||
* library mapped per process.
|
||||
*
|
||||
*
|
||||
* Changing this file:
|
||||
*
|
||||
* - A new public function goes into `global` in the same commit that
|
||||
* adds it to <dpm/core.h>.
|
||||
* - A breaking change to an existing function adds a new node above
|
||||
* DPM_CORE_1.0, leaving this node and its definitions intact.
|
||||
*
|
||||
* Newest node first. DPM_CORE_0.1 was never released; it is here as the
|
||||
* shape a retired node takes.
|
||||
*/
|
||||
DPM_CORE_1.0 {
|
||||
global:
|
||||
|
||||
@@ -96,7 +96,6 @@ std::unique_ptr<dpm_module> validate_and_load(dpm_ctx* ctx,
|
||||
"dpm_module_execute",
|
||||
"dpm_module_version",
|
||||
"dpm_module_description",
|
||||
"dpm_module_core_min",
|
||||
};
|
||||
|
||||
std::string missing;
|
||||
@@ -117,25 +116,9 @@ std::unique_ptr<dpm_module> validate_and_load(dpm_ctx* ctx,
|
||||
auto exec_f = reinterpret_cast<execute_fn>(resolve(handle, "dpm_module_execute"));
|
||||
auto version_f = reinterpret_cast<string_fn>(resolve(handle, "dpm_module_version"));
|
||||
auto desc_f = reinterpret_cast<string_fn>(resolve(handle, "dpm_module_description"));
|
||||
auto core_min_f = reinterpret_cast<string_fn>(resolve(handle, "dpm_module_core_min"));
|
||||
|
||||
/* Step 2: minimum-version handshake. */
|
||||
const char* core_min = core_min_f();
|
||||
/* Step 2: probe the cheap calls. */
|
||||
long parsed[3];
|
||||
if (!core_min || !parse_version(core_min, parsed)) {
|
||||
reason = "dpm_module_core_min() returned a malformed version";
|
||||
dlclose(handle);
|
||||
return nullptr;
|
||||
}
|
||||
if (compare_versions(core_min, DPM_CORE_VERSION_STR) > 0) {
|
||||
reason = std::string("requires libdpm-core >= ") + core_min +
|
||||
", running libdpm-core is " DPM_CORE_VERSION_STR
|
||||
" — update libdpm-core";
|
||||
dlclose(handle);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
/* Step 3: probe the cheap calls. */
|
||||
const char* version = version_f();
|
||||
if (!version || !parse_version(version, parsed)) {
|
||||
reason = "dpm_module_version() returned a malformed version";
|
||||
@@ -154,7 +137,6 @@ std::unique_ptr<dpm_module> validate_and_load(dpm_ctx* ctx,
|
||||
mod->handle = handle;
|
||||
mod->version = version;
|
||||
mod->description = description;
|
||||
mod->core_min = core_min;
|
||||
mod->execute = exec_f;
|
||||
return mod;
|
||||
}
|
||||
@@ -196,7 +178,6 @@ int dpm_module_info_of(dpm_ctx* ctx, dpm_module* mod, dpm_module_info* out)
|
||||
out->name = mod->name.c_str();
|
||||
out->version = mod->version.c_str();
|
||||
out->description = mod->description.c_str();
|
||||
out->core_min = mod->core_min.c_str();
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -264,7 +245,6 @@ dpm_cursor* dpm_list_modules(dpm_ctx* ctx)
|
||||
info.name = mod->name.c_str();
|
||||
info.version = mod->version.c_str();
|
||||
info.description = mod->description.c_str();
|
||||
info.core_min = mod->core_min.c_str();
|
||||
cur->infos.push_back(info);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* @file version.cpp
|
||||
* @brief X.Y.Z version parsing and comparison
|
||||
* @brief X.Y.Z version parsing
|
||||
*
|
||||
* @copyright Copyright (c) 2026 SILO GROUP LLC
|
||||
* @author Chris Punches <chris.punches@silogroup.org>
|
||||
@@ -60,24 +60,4 @@ bool parse_version(const char* s, long out[3])
|
||||
return true;
|
||||
}
|
||||
|
||||
int compare_versions(const char* a, const char* b)
|
||||
{
|
||||
long va[3] = {0, 0, 0};
|
||||
long vb[3] = {0, 0, 0};
|
||||
|
||||
parse_version(a, va);
|
||||
parse_version(b, vb);
|
||||
|
||||
for (int i = 0; i < 3; i++) {
|
||||
if (va[i] < vb[i]) {
|
||||
return -1;
|
||||
}
|
||||
if (va[i] > vb[i]) {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
} // namespace dpmcore
|
||||
|
||||
5
tests/fixtures/src/bad_version.cpp
vendored
5
tests/fixtures/src/bad_version.cpp
vendored
@@ -30,11 +30,6 @@ extern "C" const char* dpm_module_description(void)
|
||||
return "Fixture with a malformed version.";
|
||||
}
|
||||
|
||||
extern "C" const char* dpm_module_core_min(void)
|
||||
{
|
||||
return "0.1.0";
|
||||
}
|
||||
|
||||
extern "C" int dpm_module_execute(void* ctx, const char* command,
|
||||
int argc, char** argv)
|
||||
{
|
||||
|
||||
47
tests/fixtures/src/core_too_new.cpp
vendored
47
tests/fixtures/src/core_too_new.cpp
vendored
@@ -1,47 +0,0 @@
|
||||
/**
|
||||
* @file core_too_new.cpp
|
||||
* @brief Broken fixture: demands a libdpm-core newer than any that exists
|
||||
*
|
||||
* Contract-complete, but dpm_module_core_min() reports 99.0.0.
|
||||
* libdpm-core must refuse it at validation step 2 with a message
|
||||
* naming the remedy (update libdpm-core).
|
||||
*
|
||||
* Part of the Dark Horse Linux Package Manager (DPM)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
extern "C" const char* dpm_module_version(void)
|
||||
{
|
||||
return "1.0.0";
|
||||
}
|
||||
|
||||
extern "C" const char* dpm_module_description(void)
|
||||
{
|
||||
return "Fixture demanding a future libdpm-core.";
|
||||
}
|
||||
|
||||
extern "C" const char* dpm_module_core_min(void)
|
||||
{
|
||||
return "99.0.0";
|
||||
}
|
||||
|
||||
extern "C" int dpm_module_execute(void* ctx, const char* command,
|
||||
int argc, char** argv)
|
||||
{
|
||||
(void)ctx;
|
||||
(void)command;
|
||||
(void)argc;
|
||||
(void)argv;
|
||||
return 0;
|
||||
}
|
||||
5
tests/fixtures/src/good.cpp
vendored
5
tests/fixtures/src/good.cpp
vendored
@@ -34,11 +34,6 @@ extern "C" const char* dpm_module_description(void)
|
||||
return "Known-good stub module.";
|
||||
}
|
||||
|
||||
extern "C" const char* dpm_module_core_min(void)
|
||||
{
|
||||
return "0.1.0";
|
||||
}
|
||||
|
||||
/* Answers "ping" with 42 so a dispatch round trip is observable, and
|
||||
0 for anything else. */
|
||||
extern "C" int dpm_module_execute(void* ctx, const char* command,
|
||||
|
||||
5
tests/fixtures/src/missing_symbols.cpp
vendored
5
tests/fixtures/src/missing_symbols.cpp
vendored
@@ -2,9 +2,8 @@
|
||||
* @file missing_symbols.cpp
|
||||
* @brief Broken fixture: exports only part of the module contract
|
||||
*
|
||||
* Missing dpm_module_execute, dpm_module_core_min, and
|
||||
* dpm_module_manifest. libdpm-core must refuse it at step 1 and
|
||||
* name the missing symbols.
|
||||
* Exports the version and description probes and nothing else.
|
||||
* libdpm-core.so refuses it at step 1 and names dpm_module_execute.
|
||||
*
|
||||
* Part of the Dark Horse Linux Package Manager (DPM)
|
||||
*
|
||||
|
||||
@@ -102,9 +102,6 @@ int main(void)
|
||||
CHECK(error_contains(ctx, "missing required contract symbols"));
|
||||
CHECK(error_contains(ctx, "dpm_module_execute"));
|
||||
|
||||
CHECK(dpm_require(ctx, "core_too_new") == nullptr);
|
||||
CHECK(error_contains(ctx, "update libdpm-core"));
|
||||
|
||||
CHECK(dpm_require(ctx, "bad_version") == nullptr);
|
||||
CHECK(error_contains(ctx, "malformed version"));
|
||||
|
||||
@@ -125,7 +122,6 @@ int main(void)
|
||||
CHECK(dpm_module_info_of(ctx, good, &seen) == 0);
|
||||
CHECK(std::strcmp(seen.name, "good") == 0);
|
||||
CHECK(std::strcmp(seen.version, "1.2.3") == 0);
|
||||
CHECK(std::strcmp(seen.core_min, "0.1.0") == 0);
|
||||
CHECK(seen.description != nullptr && *seen.description);
|
||||
|
||||
CHECK(dpm_module_info_of(ctx, nullptr, &seen) != 0);
|
||||
@@ -150,7 +146,6 @@ int main(void)
|
||||
count++;
|
||||
CHECK(std::strcmp(info.name, "good") == 0);
|
||||
CHECK(std::strcmp(info.version, "1.2.3") == 0);
|
||||
CHECK(std::strcmp(info.core_min, "0.1.0") == 0);
|
||||
CHECK(info.description != nullptr && *info.description);
|
||||
}
|
||||
CHECK(count == 1);
|
||||
|
||||
Reference in New Issue
Block a user