From 1cd79e79f36c9ec4c6e09ca8cfb8910ecb050282 Mon Sep 17 00:00:00 2001 From: "Christopher M. Punches" Date: Sat, 15 Aug 2026 20:19:26 -0400 Subject: [PATCH] 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. --- CMakeLists.txt | 38 ++++++-- docs/CONSUMERS.md | 16 ++-- docs/DOCUMENTATION.md | 30 +++--- include/dpm/core.h | 94 ++++++++++++++----- include/internal/context.hpp | 19 +++- include/internal/modules.hpp | 14 ++- src/bundled-modules/info/include/commands.hpp | 19 +++- src/libdpm-core.map | 67 ++----------- 8 files changed, 183 insertions(+), 114 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 5676b29..b28fb2f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -164,7 +164,10 @@ option(DPM_DOCS_PDF "Generate the code reference as PDF (requires LaTeX)" ON) find_package(Doxygen) 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_STATIC YES) set(DOXYGEN_QUIET YES) @@ -185,24 +188,45 @@ if(DOXYGEN_FOUND) set(DOXYGEN_GENERATE_LATEX NO) 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}/src + ${CMAKE_CURRENT_SOURCE_DIR}/docs COMMENT "Generating code reference with Doxygen" ) + # The one target a user builds. Everything below hangs off it, so + # `cmake --build --target docs` produces finished documents. + add_custom_target(docs COMMENT "Building the code reference") + add_dependencies(docs docs-generate) + if(DPM_DOCS_PDF) find_program(PDFLATEX_EXECUTABLE pdflatex) if(PDFLATEX_EXECUTABLE) - add_custom_target(docs-pdf - COMMAND make -C ${CMAKE_BINARY_DIR}/docs/latex - COMMENT "Building PDF code reference" + add_custom_command(TARGET docs POST_BUILD + COMMAND make -C ${CMAKE_BINARY_DIR}/docs/tmp/latex + 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() - 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() + + 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() # --------------------------------------------------------------------- diff --git a/docs/CONSUMERS.md b/docs/CONSUMERS.md index ecec2a7..75693b3 100644 --- a/docs/CONSUMERS.md +++ b/docs/CONSUMERS.md @@ -34,14 +34,16 @@ 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 */ + "/path/to/conf.d", + "/path/to/modules", + "/path/to/root", + -1 }; 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. ## 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_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. **`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_module_info info; while (dpm_cursor_next(cur, &info) == 0) { - /* info.name, info.version, info.description */ + /* each iteration fills info */ } dpm_cursor_free(cur); ``` diff --git a/docs/DOCUMENTATION.md b/docs/DOCUMENTATION.md index 2976da7..ca33641 100644 --- a/docs/DOCUMENTATION.md +++ b/docs/DOCUMENTATION.md @@ -1,20 +1,28 @@ # 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 --target docs +``` + +The finished documents land in `/docs/`: + +``` +/docs/pdf/dpm-core-.pdf the PDF reference +/docs/html/index.html the HTML reference +/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 --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_HTML`** (default OFF) — HTML reference -Generate and compile the PDF reference: - -``` -cmake --build --target docs-pdf -``` - -The PDF lands at `/docs/latex/refman.pdf`. - -With `DPM_DOCS_HTML` enabled at configure time, the `docs` target additionally produces the HTML reference in `/docs/html`: - ``` cmake -B -DDPM_DOCS_HTML=ON cmake --build --target docs diff --git a/include/dpm/core.h b/include/dpm/core.h index 633933d..296aa2a 100644 --- a/include/dpm/core.h +++ b/include/dpm/core.h @@ -40,9 +40,10 @@ extern "C" { /* ------------------------------------------------------------------ */ /** - * Marks the public API visible. The library is compiled with hidden - * default symbol visibility; these functions are its entire exported - * surface. + * @brief Marks the public API visible + * + * The library is compiled with hidden default symbol visibility; the + * functions carrying this are its entire exported surface. */ #ifndef DPM_API #define DPM_API __attribute__((visibility("default"))) @@ -60,11 +61,25 @@ typedef struct dpm_cursor dpm_cursor; /* Log levels */ /* ------------------------------------------------------------------ */ +/** + * @brief Severity levels accepted by dpm_log() + * + * A message at a level above the context's configured level is dropped. + */ enum { + /** Unrecoverable failure. */ DPM_LOG_FATAL = 0, + + /** Operation failed. */ DPM_LOG_ERROR = 1, - DPM_LOG_WARN = 2, - DPM_LOG_INFO = 3, + + /** Operation continued, something was wrong. */ + DPM_LOG_WARN = 2, + + /** Normal reporting; the default level. */ + DPM_LOG_INFO = 3, + + /** Detail for diagnosing behaviour. */ DPM_LOG_DEBUG = 4 }; @@ -73,25 +88,45 @@ enum { /* ------------------------------------------------------------------ */ /** - * Overrides for dpm_open(). 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 dpm CLI flag. + * @brief Overrides for dpm_open() + * + * 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 { - const char* config_dir; /* NULL = /etc/dpm/conf.d/ */ - const char* module_path; /* NULL = config value, then built-in */ - const char* root; /* NULL = "/" (target root for pkg ops) */ - int log_level; /* -1 = config value; else DPM_LOG_* */ + /** NULL selects /etc/dpm/conf.d/. */ + const char* config_dir; + + /** 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; /* ------------------------------------------------------------------ */ /* 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 { - const char* name; /* module name (filename minus .so) */ - const char* version; /* module's own X.Y.Z */ - const char* description; /* one-line description */ + /** Module name, its filename minus .so. */ + const char* name; + + /** The module's own X.Y.Z. */ + const char* version; + + /** One-line description. */ + const char* description; } 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) */ /* ------------------------------------------------------------------ */ -/* - * Every module exports, as extern "C": +/** + * @page module_contract The module contract * - * int dpm_module_execute(dpm_ctx* ctx, const char* command, - * int argc, char** argv); - * const char* dpm_module_version(void); - * const char* dpm_module_description(void); + * A module is one .so in the module directory. It includes + * , links -ldpm-core, and exports three reserved symbols as + * extern "C": + * + * @code + * int dpm_module_execute(dpm_ctx* ctx, const char* command, + * int argc, char** argv); + * const char* dpm_module_version(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 * capabilities are addressed by command string, so a module publishes * 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 * 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 * responsible for being correct against it. * + * @section module_contract_validation Validation + * * 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 diff --git a/include/internal/context.hpp b/include/internal/context.hpp index 06328e2..f2d5359 100644 --- a/include/internal/context.hpp +++ b/include/internal/context.hpp @@ -32,22 +32,33 @@ /** @brief A libdpm-core context: configuration, logging, module registry */ struct dpm_ctx { + /** Directory the .conf files were read from. */ std::string config_dir; + + /** Directory modules are loaded from. */ std::string module_path; + + /** Target root for package operations. */ std::string root; - int log_level = DPM_LOG_INFO; - bool write_to_log = false; + /** Messages above this level are dropped. */ + int log_level = DPM_LOG_INFO; + + /** Whether to append messages to log_file. */ + bool write_to_log = false; + + /** Path of the log file. */ std::string log_file; - /* config[module][section][key] = value */ + /** Configuration store, addressed as config[module][section][key]. */ std::map>> 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> modules; + /** Reason for the most recent failure. */ std::string last_error; }; diff --git a/include/internal/modules.hpp b/include/internal/modules.hpp index c783176..f5f351a 100644 --- a/include/internal/modules.hpp +++ b/include/internal/modules.hpp @@ -30,16 +30,28 @@ /** @brief A loaded, fully validated module */ struct dpm_module { + /** Module name, its filename minus .so. */ std::string name; - void* handle = nullptr; + + /** The dlopen handle. */ + void* handle = nullptr; + + /** dpm_module_version() as read at load. */ std::string version; + + /** dpm_module_description() as read at load. */ std::string description; + + /** Resolved dpm_module_execute; the only path into module code. */ int (*execute)(dpm_ctx*, const char*, int, char**) = nullptr; }; /** @brief Enumeration cursor over validated modules */ struct dpm_cursor { + /** One entry per valid module. */ std::vector infos; + + /** Position of the next entry. */ size_t idx = 0; }; diff --git a/src/bundled-modules/info/include/commands.hpp b/src/bundled-modules/info/include/commands.hpp index 89cfd64..49e90a0 100644 --- a/src/bundled-modules/info/include/commands.hpp +++ b/src/bundled-modules/info/include/commands.hpp @@ -29,11 +29,20 @@ * @brief Commands supported by the info module */ enum Command { - CMD_UNKNOWN, /**< Unknown or unsupported command */ - CMD_HELP, /**< Display help information */ - CMD_VERSION, /**< Display libdpm-core and module versions */ - CMD_SYSTEM, /**< Display system information */ - CMD_CONFIG, /**< Display configuration information */ + /** Unknown or unsupported command. */ + CMD_UNKNOWN, + + /** Display help information. */ + CMD_HELP, + + /** Display library and module versions. */ + CMD_VERSION, + + /** Display system information. */ + CMD_SYSTEM, + + /** Display configuration information. */ + CMD_CONFIG, }; /** diff --git a/src/libdpm-core.map b/src/libdpm-core.map index ba97de6..afe70ea 100644 --- a/src/libdpm-core.map +++ b/src/libdpm-core.map @@ -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 describes the binary exactly and the implementation + * behind it stays private. * - * 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. - * - * - * 1. It limits the export set. - * - * `global` names every function declared in . `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 . - * - 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. + * Passed to the linker with --version-script. `dpm_*` passes the + * functions marked DPM_API in the header, which is where membership is + * decided; `local: *` hides the rest, including the template + * instantiations libstdc++ headers emit with default visibility. */ -DPM_CORE_1.0 { +{ global: - dpm_open; - 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; + dpm_*; local: *; }; - -DPM_CORE_0.1 { -};