Documentation generation produces consumable output in one command

Building the docs target now generates the reference and leaves finished
documents in the build tree's docs directory: pdf/ holds the compiled
PDF, html/ holds the browsable reference when that format is enabled.
Doxygen and LaTeX work under docs/tmp/, which clean removes along with
the rest of the tree.

The reference has a front page and a structure. The markdown documents
in docs/ are part of the input, OVERVIEW.md becomes the landing page,
and the rest follow as chapters ahead of the namespace, class, and file
reference. The module contract, previously a plain comment block
invisible to Doxygen, is a page in its own right.

Struct and enum members throughout the headers carry documentation, on
their own lines above what they describe.
This commit is contained in:
2026-08-15 20:19:26 -04:00
parent 17acad2b02
commit 1cd79e79f3
8 changed files with 183 additions and 114 deletions

View File

@@ -164,7 +164,10 @@ option(DPM_DOCS_PDF "Generate the code reference as PDF (requires LaTeX)" ON)
find_package(Doxygen) find_package(Doxygen)
if(DOXYGEN_FOUND) if(DOXYGEN_FOUND)
set(DOXYGEN_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/docs) # Doxygen and LaTeX both work in scratch space under docs/tmp. The
# finished document is copied up into docs/, so the directory a user
# opens holds documentation and nothing else.
set(DOXYGEN_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/docs/tmp)
set(DOXYGEN_EXTRACT_ALL YES) set(DOXYGEN_EXTRACT_ALL YES)
set(DOXYGEN_EXTRACT_STATIC YES) set(DOXYGEN_EXTRACT_STATIC YES)
set(DOXYGEN_QUIET YES) set(DOXYGEN_QUIET YES)
@@ -185,24 +188,45 @@ if(DOXYGEN_FOUND)
set(DOXYGEN_GENERATE_LATEX NO) set(DOXYGEN_GENERATE_LATEX NO)
endif() endif()
doxygen_add_docs(docs # OVERVIEW.md becomes the reference's front page, so the generated
# document opens on the project description instead of a bare index.
set(DOXYGEN_USE_MDFILE_AS_MAINPAGE ${CMAKE_CURRENT_SOURCE_DIR}/docs/OVERVIEW.md)
doxygen_add_docs(docs-generate
${CMAKE_CURRENT_SOURCE_DIR}/include ${CMAKE_CURRENT_SOURCE_DIR}/include
${CMAKE_CURRENT_SOURCE_DIR}/src ${CMAKE_CURRENT_SOURCE_DIR}/src
${CMAKE_CURRENT_SOURCE_DIR}/docs
COMMENT "Generating code reference with Doxygen" COMMENT "Generating code reference with Doxygen"
) )
# The one target a user builds. Everything below hangs off it, so
# `cmake --build <dir> --target docs` produces finished documents.
add_custom_target(docs COMMENT "Building the code reference")
add_dependencies(docs docs-generate)
if(DPM_DOCS_PDF) if(DPM_DOCS_PDF)
find_program(PDFLATEX_EXECUTABLE pdflatex) find_program(PDFLATEX_EXECUTABLE pdflatex)
if(PDFLATEX_EXECUTABLE) if(PDFLATEX_EXECUTABLE)
add_custom_target(docs-pdf add_custom_command(TARGET docs POST_BUILD
COMMAND make -C ${CMAKE_BINARY_DIR}/docs/latex COMMAND make -C ${CMAKE_BINARY_DIR}/docs/tmp/latex
COMMENT "Building PDF code reference" COMMAND ${CMAKE_COMMAND} -E copy
${CMAKE_BINARY_DIR}/docs/tmp/latex/refman.pdf
${CMAKE_BINARY_DIR}/docs/pdf/dpm-core-${PROJECT_VERSION}.pdf
COMMENT "Compiling the PDF reference"
) )
add_dependencies(docs-pdf docs)
else() else()
message(WARNING "DPM_DOCS_PDF is ON but pdflatex was not found; the docs-pdf target is unavailable") message(WARNING "DPM_DOCS_PDF is ON but pdflatex was not found; no PDF will be produced")
endif() endif()
endif() endif()
if(DPM_DOCS_HTML)
add_custom_command(TARGET docs POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_directory
${CMAKE_BINARY_DIR}/docs/tmp/html
${CMAKE_BINARY_DIR}/docs/html
COMMENT "Placing the HTML reference"
)
endif()
endif() endif()
# --------------------------------------------------------------------- # ---------------------------------------------------------------------

View File

@@ -34,14 +34,16 @@ To point a context elsewhere, pass overrides — every field is optional:
``` ```
dpm_open_overrides ov = { dpm_open_overrides ov = {
"/path/to/conf.d", /* config_dir: NULL = /etc/dpm/conf.d/ */ "/path/to/conf.d",
"/path/to/modules", /* module_path: NULL = config, then default */ "/path/to/modules",
"/path/to/root", /* root: target root for package operations */ "/path/to/root",
-1 /* log_level: -1 = from config */ -1
}; };
dpm_ctx* ctx = dpm_open(&ov); dpm_ctx* ctx = dpm_open(&ov);
``` ```
Leaving `config_dir` NULL selects `/etc/dpm/conf.d/`; leaving `module_path` NULL selects the configured value and then the built-in default; leaving `root` NULL selects `/`; a `log_level` of -1 takes the configured value.
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. 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 ## Acquiring and using modules
@@ -58,9 +60,11 @@ libdpm-core.so validates the module completely at load; a handle is returned onl
``` ```
dpm_module_info info; dpm_module_info info;
dpm_module_info_of(ctx, mod, &info); /* info.name, .version, .description */ dpm_module_info_of(ctx, mod, &info);
``` ```
`info` carries `name`, `version`, and `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. 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: **`dpm_execute`** invokes the module — a command name and arguments:
@@ -77,7 +81,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_cursor* cur = dpm_list_modules(ctx);
dpm_module_info info; dpm_module_info info;
while (dpm_cursor_next(cur, &info) == 0) { while (dpm_cursor_next(cur, &info) == 0) {
/* info.name, info.version, info.description */ /* each iteration fills info */
} }
dpm_cursor_free(cur); dpm_cursor_free(cur);
``` ```

View File

@@ -1,20 +1,28 @@
# Generating the Code Reference # Generating the Code Reference
When Doxygen is present, the build offers a `docs` target that generates the API and source reference from the documentation comments carried in the headers and sources. Two output formats are available as configure-time options: When Doxygen is present, the build offers a `docs` target that produces the reference from the documentation comments carried in the headers and sources, together with the markdown documents in `docs/`. OVERVIEW.md becomes its front page, and DESIGN.md, MODULES.md, CONSUMERS.md, BUILD.md, and this file follow as chapters ahead of the namespace, class, and file reference.
One command generates it:
```
cmake --build <build-dir> --target docs
```
The finished documents land in `<build-dir>/docs/`:
```
<build-dir>/docs/pdf/dpm-core-<version>.pdf the PDF reference
<build-dir>/docs/html/index.html the HTML reference
<build-dir>/docs/tmp/ scratch space for the generators
```
Doxygen and LaTeX both work under `docs/tmp/`, and the finished document is copied up into `docs/`. `cmake --build <build-dir> --target clean` removes the whole tree.
Two output formats are available as configure-time options:
- **`-DDPM_DOCS_PDF`** (default ON) — PDF reference, via Doxygen's native LaTeX output; requires `pdflatex` and `makeindex` - **`-DDPM_DOCS_PDF`** (default ON) — PDF reference, via Doxygen's native LaTeX output; requires `pdflatex` and `makeindex`
- **`-DDPM_DOCS_HTML`** (default OFF) — HTML reference - **`-DDPM_DOCS_HTML`** (default OFF) — HTML reference
Generate and compile the PDF reference:
```
cmake --build <build-dir> --target docs-pdf
```
The PDF lands at `<build-dir>/docs/latex/refman.pdf`.
With `DPM_DOCS_HTML` enabled at configure time, the `docs` target additionally produces the HTML reference in `<build-dir>/docs/html`:
``` ```
cmake -B <build-dir> -DDPM_DOCS_HTML=ON cmake -B <build-dir> -DDPM_DOCS_HTML=ON
cmake --build <build-dir> --target docs cmake --build <build-dir> --target docs

View File

@@ -40,9 +40,10 @@ extern "C" {
/* ------------------------------------------------------------------ */ /* ------------------------------------------------------------------ */
/** /**
* Marks the public API visible. The library is compiled with hidden * @brief Marks the public API visible
* default symbol visibility; these functions are its entire exported *
* surface. * The library is compiled with hidden default symbol visibility; the
* functions carrying this are its entire exported surface.
*/ */
#ifndef DPM_API #ifndef DPM_API
#define DPM_API __attribute__((visibility("default"))) #define DPM_API __attribute__((visibility("default")))
@@ -60,11 +61,25 @@ typedef struct dpm_cursor dpm_cursor;
/* Log levels */ /* Log levels */
/* ------------------------------------------------------------------ */ /* ------------------------------------------------------------------ */
/**
* @brief Severity levels accepted by dpm_log()
*
* A message at a level above the context's configured level is dropped.
*/
enum { enum {
/** Unrecoverable failure. */
DPM_LOG_FATAL = 0, DPM_LOG_FATAL = 0,
/** Operation failed. */
DPM_LOG_ERROR = 1, DPM_LOG_ERROR = 1,
/** Operation continued, something was wrong. */
DPM_LOG_WARN = 2, DPM_LOG_WARN = 2,
/** Normal reporting; the default level. */
DPM_LOG_INFO = 3, DPM_LOG_INFO = 3,
/** Detail for diagnosing behaviour. */
DPM_LOG_DEBUG = 4 DPM_LOG_DEBUG = 4
}; };
@@ -73,25 +88,45 @@ enum {
/* ------------------------------------------------------------------ */ /* ------------------------------------------------------------------ */
/** /**
* Overrides for dpm_open(). Any field may be left NULL (or -1 for * @brief Overrides for dpm_open()
* log_level) to accept configuration-file values and built-in defaults. *
* Every field here is exposed as a dpm CLI flag. * Any field may be left NULL (or -1 for log_level) to accept
* configuration-file values and built-in defaults. Every field here is
* exposed as a flag on the dpm binary.
*/ */
typedef struct dpm_open_overrides { typedef struct dpm_open_overrides {
const char* config_dir; /* NULL = /etc/dpm/conf.d/ */ /** NULL selects /etc/dpm/conf.d/. */
const char* module_path; /* NULL = config value, then built-in */ const char* config_dir;
const char* root; /* NULL = "/" (target root for pkg ops) */
int log_level; /* -1 = config value; else DPM_LOG_* */ /** NULL selects the configured value, then the built-in default. */
const char* module_path;
/** NULL selects "/", the target root for package operations. */
const char* root;
/** -1 selects the configured value; otherwise a DPM_LOG_* level. */
int log_level;
} dpm_open_overrides; } dpm_open_overrides;
/* ------------------------------------------------------------------ */ /* ------------------------------------------------------------------ */
/* Module information (enumeration results) */ /* Module information (enumeration results) */
/* ------------------------------------------------------------------ */ /* ------------------------------------------------------------------ */
/**
* @brief What the library read from a module at load
*
* Filled by dpm_module_info_of() and by dpm_cursor_next(). The string
* pointers remain valid until the context is closed.
*/
typedef struct dpm_module_info { typedef struct dpm_module_info {
const char* name; /* module name (filename minus .so) */ /** Module name, its filename minus .so. */
const char* version; /* module's own X.Y.Z */ const char* name;
const char* description; /* one-line description */
/** The module's own X.Y.Z. */
const char* version;
/** One-line description. */
const char* description;
} dpm_module_info; } dpm_module_info;
/* ------------------------------------------------------------------ */ /* ------------------------------------------------------------------ */
@@ -285,17 +320,28 @@ DPM_API const char* dpm_last_error(dpm_ctx* ctx);
/* Module contract (implemented by modules, called by libdpm-core) */ /* Module contract (implemented by modules, called by libdpm-core) */
/* ------------------------------------------------------------------ */ /* ------------------------------------------------------------------ */
/* /**
* Every module exports, as extern "C": * @page module_contract The module contract
* *
* A module is one .so in the module directory. It includes
* <dpm/core.h>, links -ldpm-core, and exports three reserved symbols as
* extern "C":
*
* @code
* int dpm_module_execute(dpm_ctx* ctx, const char* command, * int dpm_module_execute(dpm_ctx* ctx, const char* command,
* int argc, char** argv); * int argc, char** argv);
* const char* dpm_module_version(void); * const char* dpm_module_version(void);
* const char* dpm_module_description(void); * const char* dpm_module_description(void);
* @endcode
*
* @section module_contract_surface The functional surface
* *
* dpm_module_execute is the module's entire functional surface; its * dpm_module_execute is the module's entire functional surface; its
* capabilities are addressed by command string, so a module publishes * capabilities are addressed by command string, so a module publishes
* no headers, struct layouts, or symbols to anything that calls it. * no headers, struct layouts, or symbols to anything that calls it.
* NULL or an empty command behaves as the module's help command.
*
* @section module_contract_version Version compatibility
* *
* A module determines for itself whether it can work with the library * A module determines for itself whether it can work with the library
* it is running against: dpm_core_version() reports the running * it is running against: dpm_core_version() reports the running
@@ -303,8 +349,12 @@ DPM_API const char* dpm_last_error(dpm_ctx* ctx);
* module is built against the system-installed libdpm-core.so and is * module is built against the system-installed libdpm-core.so and is
* responsible for being correct against it. * responsible for being correct against it.
* *
* @section module_contract_validation Validation
*
* The library refuses to load any module that does not validate * The library refuses to load any module that does not validate
* completely (see the DPM specification: load-time enforcement). * completely: every reserved symbol resolves, and the version and
* description probes return well-formed values. A module that loads is
* fully valid.
*/ */
#ifdef __cplusplus #ifdef __cplusplus

View File

@@ -32,22 +32,33 @@
/** @brief A libdpm-core context: configuration, logging, module registry */ /** @brief A libdpm-core context: configuration, logging, module registry */
struct dpm_ctx { struct dpm_ctx {
/** Directory the .conf files were read from. */
std::string config_dir; std::string config_dir;
/** Directory modules are loaded from. */
std::string module_path; std::string module_path;
/** Target root for package operations. */
std::string root; std::string root;
/** Messages above this level are dropped. */
int log_level = DPM_LOG_INFO; int log_level = DPM_LOG_INFO;
/** Whether to append messages to log_file. */
bool write_to_log = false; bool write_to_log = false;
/** Path of the log file. */
std::string log_file; std::string log_file;
/* config[module][section][key] = value */ /** Configuration store, addressed as config[module][section][key]. */
std::map<std::string, std::map<std::string,
std::map<std::string, std::map<std::string,
std::map<std::string, std::string>>> config; std::map<std::string, std::string>>> config;
/* validated modules, keyed by name; loaded at most once per ctx */ /** Validated modules keyed by name; each is loaded at most once. */
std::map<std::string, std::unique_ptr<dpm_module>> modules; std::map<std::string, std::unique_ptr<dpm_module>> modules;
/** Reason for the most recent failure. */
std::string last_error; std::string last_error;
}; };

View File

@@ -30,16 +30,28 @@
/** @brief A loaded, fully validated module */ /** @brief A loaded, fully validated module */
struct dpm_module { struct dpm_module {
/** Module name, its filename minus .so. */
std::string name; std::string name;
/** The dlopen handle. */
void* handle = nullptr; void* handle = nullptr;
/** dpm_module_version() as read at load. */
std::string version; std::string version;
/** dpm_module_description() as read at load. */
std::string description; std::string description;
/** Resolved dpm_module_execute; the only path into module code. */
int (*execute)(dpm_ctx*, const char*, int, char**) = nullptr; int (*execute)(dpm_ctx*, const char*, int, char**) = nullptr;
}; };
/** @brief Enumeration cursor over validated modules */ /** @brief Enumeration cursor over validated modules */
struct dpm_cursor { struct dpm_cursor {
/** One entry per valid module. */
std::vector<dpm_module_info> infos; std::vector<dpm_module_info> infos;
/** Position of the next entry. */
size_t idx = 0; size_t idx = 0;
}; };

View File

@@ -29,11 +29,20 @@
* @brief Commands supported by the info module * @brief Commands supported by the info module
*/ */
enum Command { enum Command {
CMD_UNKNOWN, /**< Unknown or unsupported command */ /** Unknown or unsupported command. */
CMD_HELP, /**< Display help information */ CMD_UNKNOWN,
CMD_VERSION, /**< Display libdpm-core and module versions */
CMD_SYSTEM, /**< Display system information */ /** Display help information. */
CMD_CONFIG, /**< Display configuration information */ CMD_HELP,
/** Display library and module versions. */
CMD_VERSION,
/** Display system information. */
CMD_SYSTEM,
/** Display configuration information. */
CMD_CONFIG,
}; };
/** /**

View File

@@ -1,65 +1,16 @@
/* /*
* libdpm-core.map — the linker version script for libdpm-core.so * libdpm-core.map — holds libdpm-core.so's exports to the public C API,
* so <dpm/core.h> describes the binary exactly and the implementation
* behind it stays private.
* *
* Passed to the linker with --version-script; see CMakeLists.txt, which * Passed to the linker with --version-script. `dpm_*` passes the
* also lists it as a link dependency so edits force a relink. It does * functions marked DPM_API in the header, which is where membership is
* two separate jobs. * decided; `local: *` hides the rest, including the template
* * instantiations libstdc++ headers emit with default visibility.
*
* 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: global:
dpm_open; dpm_*;
dpm_close;
dpm_require;
dpm_module_info_of;
dpm_execute;
dpm_list_modules;
dpm_cursor_next;
dpm_cursor_free;
dpm_core_version;
dpm_config_get;
dpm_log;
dpm_module_path;
dpm_last_error;
local: local:
*; *;
}; };
DPM_CORE_0.1 {
};