From 6abec67daad50cac4cf13c90dccf86944e0154f3 Mon Sep 17 00:00:00 2001 From: Daniel Polo <106583643+danielPoloWork@users.noreply.github.com> Date: Sun, 14 Jun 2026 13:28:13 +0200 Subject: [PATCH] feat(cmake): install/export + pkg-config for find_package support + ADR-0028 (M7.4) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Closes ROADMAP item 7.4 — Phase 1 distribution (ADR-0004 §5). - ADR-0028 records the install/packaging layout decision. - CMake install/export: PBR_MEMORY_POOL_INSTALL option (default PROJECT_IS_TOP_LEVEL) gates install(TARGETS ... EXPORT) + install(EXPORT ... NAMESPACE pbr::), the full it/d4np/memorypool/ header tree, a relocatable package config (configure_package_config_file + SameMajorVersion version file), a pkg-config pbr-memory-pool.pc, and LICENSE under share/doc/. - EXPORT_NAME memory_pool makes the installed imported target pbr::memory_pool, identical to the in-build alias — one link line for vendored and installed consumers alike. - release.yml packages via `cmake --install` instead of hand-copying, fixing a latent bug where four of the seven public headers and the package config were missing from release tarballs. - Docs: ROADMAP 7.4, ADR index, README "Install and consume", CHANGELOG. Verified end-to-end locally (MSVC 19.51 + Ninja): configure -> build -> cmake --install to a staging prefix, then a separate consumer project did find_package(pbr_memory_pool CONFIG REQUIRED) + linked pbr::memory_pool and ran (consumer OK: got=7 block_size=64). Co-Authored-By: Claude Opus 4.8 (1M context) --- .github/workflows/release.yml | 32 +++---- CHANGELOG.md | 29 ++++++ CMakeLists.txt | 76 +++++++++++++++- README.md | 26 ++++++ ROADMAP.md | 2 +- cmake/pbr-memory-pool.pc.in | 11 +++ cmake/pbr_memory_poolConfig.cmake.in | 18 ++++ docs/adr/0028-install-and-packaging-layout.md | 91 +++++++++++++++++++ docs/adr/README.md | 1 + 9 files changed, 264 insertions(+), 22 deletions(-) create mode 100644 cmake/pbr-memory-pool.pc.in create mode 100644 cmake/pbr_memory_poolConfig.cmake.in create mode 100644 docs/adr/0028-install-and-packaging-layout.md diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index d6386c6..5ba6bd3 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -148,28 +148,20 @@ jobs: # Inner directory name embeds the platform so extractions of # multiple platforms into the same parent dir do not collide. stage="pbr-memory-pool-${version}-${platform}" - mkdir -p "${stage}/include/it/d4np/memorypool" "${stage}/lib" - # Public headers. - cp src/main/cpp/it/d4np/memorypool/memory_pool.h "${stage}/include/it/d4np/memorypool/" - cp src/main/cpp/it/d4np/memorypool/memory_pool.hpp "${stage}/include/it/d4np/memorypool/" - cp src/main/cpp/it/d4np/memorypool/version.hpp "${stage}/include/it/d4np/memorypool/" + # Use the CMake install rules (ADR-0028) so the tarball is a + # complete, find_package-ready tree rather than a hand-copied subset: + # the FULL public-header set (every *.h / *.hpp, not just the three + # core ones), the static archive under whatever name the toolchain + # produced, the CMake package config (pbr_memory_poolConfig.cmake + + # the pbr::memory_pool targets export + the version file), and the + # pkg-config .pc. This is the "installing the artifacts from a GitHub + # Release" path promised by ADR-0004 §5. The exported imported target + # is pbr::memory_pool — identical to the in-build alias (ADR-0028). + cmake --install build/release-artifact --prefix "${PWD}/${stage}" - # Static archive — file name differs by toolchain - # (libpbr_memory_pool.a on GCC/Clang/Apple Clang, - # pbr_memory_pool.lib on MSVC). M1 ships STATIC only; shared - # libraries arrive in a later milestone, see ADR-0004 §4 item 2. - if [[ -f build/release-artifact/libpbr_memory_pool.a ]]; then - cp build/release-artifact/libpbr_memory_pool.a "${stage}/lib/" - elif [[ -f build/release-artifact/pbr_memory_pool.lib ]]; then - cp build/release-artifact/pbr_memory_pool.lib "${stage}/lib/" - else - echo "::error::Could not find the built static library under build/release-artifact/" - ls -la build/release-artifact/ - exit 1 - fi - - # Top-level project docs that travel with every distribution. + # Top-level project docs that travel with every distribution (the + # install also places LICENSE under share/doc/pbr_memory_pool/). cp LICENSE "${stage}/" cp README.md "${stage}/" cp CHANGELOG.md "${stage}/" diff --git a/CHANGELOG.md b/CHANGELOG.md index 71738fb..8a88ce1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -68,6 +68,35 @@ Full release notes will live in `docs/releases/v1.0.0.md` (M7.7). new **Compatibility** section (Tier-1/Tier-2 platforms, compiler floor versions, C++17 / C89+C99 standards, thread-safety modes, zero external dependencies — [ADR-0005](docs/adr/0005-toolchain-matrix-and-supported-platforms.md)). +### Added (M7.4) + +- **CMake install / export + pkg-config — Phase 1 distribution** ([ADR-0028](docs/adr/0028-install-and-packaging-layout.md), + ROADMAP §7.4, ADR-0004 §5). A `PBR_MEMORY_POOL_INSTALL` option (default + `PROJECT_IS_TOP_LEVEL`) gates `install(TARGETS … EXPORT)` + `install(EXPORT … + NAMESPACE pbr::)`, the full `it/d4np/memorypool/` public-header tree, a + relocatable package config (`configure_package_config_file` + + `SameMajorVersion` version file), a pkg-config + [`pbr-memory-pool.pc`](cmake/pbr-memory-pool.pc.in), and `LICENSE` under + `share/doc/`. Consumers use + `find_package(pbr_memory_pool CONFIG REQUIRED)` + + `target_link_libraries(app pbr::memory_pool)`. +- The internal target carries `EXPORT_NAME memory_pool` so the **installed** + imported target is `pbr::memory_pool` — identical to the in-build alias, so a + single link line serves `add_subdirectory` / `FetchContent` and installed + packages alike. Verified end-to-end locally (MSVC 19.51 + Ninja): install to a + prefix, then a separate `find_package` consumer built, linked, and ran. +- README gains an **Install and consume** section. + +### Changed (M7.4) + +- **`release.yml` packages via `cmake --install`** instead of hand-copying. + This fixes a latent bug: the previous tarball shipped only three of the seven + public headers (`typed_pool.hpp`, `pool_allocator.hpp`, `instrumented_pool.hpp`, + `free_list_iterator.hpp` were missing) and no CMake config / `.pc`, so the + ADR-0004 §5 "install the artifacts from a Release" path was not actually + deliverable. Release tarballs are now complete `find_package`-ready install + trees. + ## [0.6.0] — 2026-06-14 **Milestone 6 — Observability & Decorators.** Optional logging / statistics / tracing diff --git a/CMakeLists.txt b/CMakeLists.txt index 0e2f079..16d3db1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -95,7 +95,14 @@ target_compile_features(pbr_memory_pool PUBLIC cxx_std_17) set_target_properties(pbr_memory_pool PROPERTIES VERSION ${PROJECT_VERSION} - SOVERSION ${PROJECT_VERSION_MAJOR}) + SOVERSION ${PROJECT_VERSION_MAJOR} + # Export under the short name so the installed imported target is + # `pbr::memory_pool` — identical to the in-build ALIAS above, so consumers + # write the same `target_link_libraries(... pbr::memory_pool)` whether they + # vendor via add_subdirectory / FetchContent or link an installed package + # (ADR-0028). Without this, NAMESPACE pbr:: + target `pbr_memory_pool` would + # export the mismatched `pbr::pbr_memory_pool`. + EXPORT_NAME memory_pool) # --------------------------------------------------------------------------- # Free-list diagnostics gate (ADR-0019). The diagnostic surface (the @@ -143,6 +150,72 @@ elseif(NOT PBR_MEMORY_POOL_THREAD_SAFETY STREQUAL "NONE") endif() # NONE leaves the macro at the memory_pool.h default — no compile definition. +# --------------------------------------------------------------------------- +# Install / packaging — Phase 1 distribution (ADR-0028, ROADMAP §7.4 / +# ADR-0004 §5). Exports a namespaced imported target so consumers can +# `find_package(pbr_memory_pool CONFIG REQUIRED)` + link `pbr::memory_pool`, +# installs the public-header tree, and emits a pkg-config `.pc`. Gated so a +# parent project that pulls this in via add_subdirectory / FetchContent does +# not leak our install rules into its own `cmake --install`. +# --------------------------------------------------------------------------- +option(PBR_MEMORY_POOL_INSTALL + "Generate install and packaging rules (config file, headers, pkg-config)" + ${PROJECT_IS_TOP_LEVEL}) +if(PBR_MEMORY_POOL_INSTALL) + include(GNUInstallDirs) + include(CMakePackageConfigHelpers) + + set(PBR_INSTALL_CMAKEDIR "${CMAKE_INSTALL_LIBDIR}/cmake/pbr_memory_pool" + CACHE STRING "Install location for the CMake package config files") + + # The library archive + the install-tree include directory. + install(TARGETS pbr_memory_pool + EXPORT pbr_memory_poolTargets + ARCHIVE DESTINATION "${CMAKE_INSTALL_LIBDIR}" + LIBRARY DESTINATION "${CMAKE_INSTALL_LIBDIR}" + RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}" + INCLUDES DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}") + + # Public headers, preserving the it/d4np/memorypool/ tree so the + # `#include ` form resolves against /include. + install(DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/src/main/cpp/it" + DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}" + FILES_MATCHING + PATTERN "*.h" + PATTERN "*.hpp") + + # The exported targets file — defines the imported pbr::memory_pool target. + install(EXPORT pbr_memory_poolTargets + FILE pbr_memory_poolTargets.cmake + NAMESPACE pbr:: + DESTINATION "${PBR_INSTALL_CMAKEDIR}") + + # The package config + version files. + configure_package_config_file( + "${CMAKE_CURRENT_SOURCE_DIR}/cmake/pbr_memory_poolConfig.cmake.in" + "${CMAKE_CURRENT_BINARY_DIR}/pbr_memory_poolConfig.cmake" + INSTALL_DESTINATION "${PBR_INSTALL_CMAKEDIR}") + write_basic_package_version_file( + "${CMAKE_CURRENT_BINARY_DIR}/pbr_memory_poolConfigVersion.cmake" + VERSION "${PROJECT_VERSION}" + COMPATIBILITY SameMajorVersion) + install(FILES + "${CMAKE_CURRENT_BINARY_DIR}/pbr_memory_poolConfig.cmake" + "${CMAKE_CURRENT_BINARY_DIR}/pbr_memory_poolConfigVersion.cmake" + DESTINATION "${PBR_INSTALL_CMAKEDIR}") + + # pkg-config for non-CMake (autotools / Make / Meson) consumers. + configure_file( + "${CMAKE_CURRENT_SOURCE_DIR}/cmake/pbr-memory-pool.pc.in" + "${CMAKE_CURRENT_BINARY_DIR}/pbr-memory-pool.pc" + @ONLY) + install(FILES "${CMAKE_CURRENT_BINARY_DIR}/pbr-memory-pool.pc" + DESTINATION "${CMAKE_INSTALL_LIBDIR}/pkgconfig") + + install(FILES "${CMAKE_CURRENT_SOURCE_DIR}/LICENSE" + DESTINATION "${CMAKE_INSTALL_DOCDIR}") +endif() + # --------------------------------------------------------------------------- # Optional sub-trees, wired in by later milestones. # --------------------------------------------------------------------------- @@ -197,4 +270,5 @@ message(STATUS " C++ standard : C++${CMAKE_CXX_STANDARD} (extensions ${C message(STATUS " Build type : ${CMAKE_BUILD_TYPE}${CMAKE_CONFIGURATION_TYPES}") message(STATUS " Tests : ${PBR_MEMORY_POOL_BUILD_TESTS}") message(STATUS " Benchmarks : ${PBR_MEMORY_POOL_BUILD_BENCHMARKS}") +message(STATUS " Install rules : ${PBR_MEMORY_POOL_INSTALL}") message(STATUS "") diff --git a/README.md b/README.md index 2b902d8..1726cc4 100644 --- a/README.md +++ b/README.md @@ -269,6 +269,32 @@ Both invocations are exercised end-to-end on every push to `master` by the [CI m For first-time setup on a fresh clone — installing CMake, Ninja, and the supported compilers per platform, plus troubleshooting and the full quality-bar workflow — see the **[Local Build Guide](docs/development/local-build.md)**. +## Install and consume + +Install to a prefix and consume with CMake's `find_package` ([ADR-0028](docs/adr/0028-install-and-packaging-layout.md) — Phase 1 distribution): + +```bash +cmake -S . -B build -DCMAKE_BUILD_TYPE=Release -DPBR_MEMORY_POOL_BUILD_TESTS=OFF +cmake --build build +cmake --install build --prefix /your/prefix +``` + +```cmake +# In the consumer's CMakeLists.txt — the imported target name is identical +# whether you install the package or vendor it via add_subdirectory / FetchContent. +find_package(pbr_memory_pool CONFIG REQUIRED) +target_link_libraries(my_app PRIVATE pbr::memory_pool) +``` + +The install tree carries every public header (`include/it/d4np/memorypool/`), the static archive, the CMake package config (`SameMajorVersion` compatibility), and a pkg-config `pbr-memory-pool.pc` for non-CMake (Make / autotools / Meson) builds. The same `cmake --install` tree is what each GitHub Release tarball contains. Vendoring without installing also works: + +```cmake +add_subdirectory(path/to/pbr-cpp-memory-pool) # or FetchContent +target_link_libraries(my_app PRIVATE pbr::memory_pool) +``` + +Package-manager distribution (vcpkg / Conan) is Phase 2, deferred post-`v1.0.0` (ROADMAP §7.8–§7.9). + ## Repository layout | Path | What lives there | diff --git a/ROADMAP.md b/ROADMAP.md index f044c5a..5a075d8 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -111,7 +111,7 @@ Goal: ship a v1.0.0 reference implementation. - [x] 7.1 Doxygen-generated API documentation published as a static site. Implemented per [ADR-0027](docs/adr/0027-doxygen-html-site-and-publication-pipeline.md), closing the rendering half that [ADR-0013](docs/adr/0013-doxygen-for-api-markdown-for-narrative.md) §4–§5 deferred to this item. A checked-in **partial** Doxyfile ([`docs/doxygen/Doxyfile`](docs/doxygen/Doxyfile)) plus a hand-written landing page ([`docs/doxygen/mainpage.md`](docs/doxygen/mainpage.md)) drive a **dependency-free** Doxygen HTML build — built-in theme with `GENERATE_TREEVIEW`, no Graphviz (`HAVE_DOT = NO`), no vendored CSS, no Python doc stack (the MkDocs/Sphinx-Breathe unified narrative site sketched in ADR-0013 §4 is explicitly **deferred post-v1.0**, consistent with spec §3.3). `INPUT` is the public-header contract surface only (`*.h` + `*.hpp` under `src/main/cpp/it/d4np/memorypool/`, `EXTRACT_PRIVATE = NO`), with `PBR_MEMORY_POOL_DIAGNOSTICS=1` predefined so the ADR-0019 free-list iterator surface renders. **The warn-as-error gate refines ADR-0013 §5**: `WARN_AS_ERROR = FAIL_ON_WARNINGS` + `WARN_IF_DOC_ERROR = YES` fail CI on the *doc-rot* class (malformed commands, unresolved cross-references, stale `@param` names) while `WARN_IF_UNDOCUMENTED` / `WARN_NO_PARAMDOC` stay **off** — gating documentation *correctness*, not ceremonial *exhaustiveness* on every operator and trait typedef (ADR-0027 §3). `PROJECT_NUMBER` is left blank and **injected at build time from `version.hpp`** so the version string stays single-sourced. A new workflow ([`.github/workflows/docs-site.yml`](.github/workflows/docs-site.yml)) builds the site as a **warn-as-error gate on every PR** (no deploy — PRs are never blocked by Pages configuration) and **publishes to GitHub Pages on push to `master`** via the official `upload-pages-artifact` / `deploy-pages` Actions (no generated HTML committed; `build/` is git-ignored). Three public-header comments gain the Doxygen `%` auto-link escape (`::%operator new` / `::%operator delete`) to silence spurious unresolved-link warnings on the global operators. Verified locally with Doxygen 1.10.0: zero warnings, `build/doxygen/html/index.html` generated. **One-time maintainer action:** enable GitHub Pages with *Source: GitHub Actions* in repository settings before the first `master` deploy (the PR gate works regardless). - [x] 7.2 README: full usage example, performance summary, compatibility matrix. The README gains a **Usage** section with six compilable examples spanning the whole surface — the C four-function core, the RAII `Pool` (ctor / `make` / `PoolBuilder`, dual-verb `allocate` vs `try_allocate`), `TypedPool` (`construct` / `destroy`), `PoolAllocator` driving a `std::list`, dynamic growth via `Pool::make_dynamic`, and `InstrumentedPool` + a `PoolObserver` — each transcribed from a single program **compiled, linked, and run against the real headers + `memory_pool.cpp` under MSVC 19.51 `/W4`** before commit (so no signature drift). The **Performance** section is consolidated into a one-paragraph summary plus three labelled regimes — fixed single-threaded (4–11×, M2.9), dynamic growth (~2×, M5.4), and threading (M4.5) — each linking its full `docs/bench/` report, with the host disclosed once up front. A new **Compatibility** section captures the [ADR-0005](docs/adr/0005-toolchain-matrix-and-supported-platforms.md) contract: Tier-1 platforms × compiler floor versions, Tier-2 best-effort targets, the C++17 / C89+C99 language-standard matrix, the `NONE`/`MUTEX`/`LOCKFREE` thread-safety knob, and the zero-external-dependency guarantee. Documentation-only — no code, ADR, or spec change. - [x] 7.3 `CHANGELOG.md` audit for the v1.0.0 entry: consolidate every Unreleased line accumulated since `v0.6.0`, verify category placement, and write the v1.0.0 summary headline (the file itself was introduced in Milestone 1.12). The `[Unreleased]` block accumulated since `v0.6.0` (the M7.1 `Added` + `Changed`, the M7.2 `Changed`) is audited for Keep-a-Changelog category placement — the published Doxygen site and the `docs-site` workflow stay under *Added* (new capabilities), the header `%`-escapes and the README expansion under *Changed* (modifications to existing surfaces) — and a **draft `v1.0.0` summary headline** is written: it frames `v1.0.0` as the first **stable** release that freezes the public C ABI + C++ surface under the SemVer 1.0 promise, seals the M0–M6 feature set, and folds in the M7 release polish (M7.1 docs site, M7.2 README, M7.4 packaging, M7.5/M7.6 audits), with the row-by-row Spec Coverage Map acceptance deferred to M7.6. Per the established convention the per-task `### Added (MX.Y)` / `### Changed (MX.Y)` subsections are retained (not flattened — `[0.6.0]` keeps three `### Added (M6.x)`); the block stays under `## [Unreleased]` and is dated to `## [1.0.0] — ` by the release PR (M7.7). Documentation-only — no code, ADR, or spec change; the bottom-of-file link references are untouched until M7.7 adds the `[1.0.0]` anchor. -- [ ] 7.4 ADR: install / packaging layout (public-header export, pkg-config, CMake `find_package` config file) — phase 1 distribution per ADR-0004 §5. +- [x] 7.4 ADR: install / packaging layout (public-header export, pkg-config, CMake `find_package` config file) — phase 1 distribution per ADR-0004 §5. Implemented as [ADR-0028](docs/adr/0028-install-and-packaging-layout.md) plus the CMake install/export machinery: a `PBR_MEMORY_POOL_INSTALL` option (default `PROJECT_IS_TOP_LEVEL`, so an embedding parent's `cmake --install` is not polluted) gates `install(TARGETS … EXPORT)` + `install(EXPORT … NAMESPACE pbr::)`, the full `it/d4np/memorypool/` public-header tree, a relocatable package config via `configure_package_config_file` with a `SameMajorVersion` version file, a pkg-config [`pbr-memory-pool.pc`](cmake/pbr-memory-pool.pc.in), and `LICENSE` under `share/doc/`. The internal target `pbr_memory_pool` carries `EXPORT_NAME memory_pool` so the **installed** imported target is `pbr::memory_pool` — identical to the in-build alias, so `target_link_libraries(app pbr::memory_pool)` is the one link line for `add_subdirectory` / `FetchContent` **and** installed-package consumers. [`release.yml`](.github/workflows/release.yml)'s `build-artifacts` job now packages via `cmake --install` (full headers + archive + config + `.pc`) instead of hand-copying — fixing a latent bug where four of the seven public headers and the package config were missing from release tarballs (the ADR-0004 §5 "install the artifacts from a Release" path). Verified end-to-end locally (MSVC 19.51 + Ninja): configure → build → `cmake --install` to a staging prefix, then a separate consumer project did `find_package(pbr_memory_pool CONFIG REQUIRED)` + linked `pbr::memory_pool` and ran (`consumer OK: got=7 block_size=64`); the exported target resolves to `pbr::memory_pool` and the `.pc` carries the correct prefix/libdir/version. Phase 2 (vcpkg / Conan) stays deferred post-v1.0 (items 7.8 / 7.9). pkg-config `.pc` `prefix=` reflects the configure-time prefix (documented limitation, ADR-0028 §6). - [ ] 7.5 Patterns catalogue audit — verify every adopted pattern has both an ADR and a code location; refresh statuses. - [ ] 7.6 **Spec compliance acceptance** — walk every row of the Spec Coverage Map (below) and confirm each requirement is satisfied by a passing test, a documented ADR, or both. Record the audit outcome in an ADR. - [ ] 7.7 **Close Milestone 7 → `v1.0.0`**: bump `version.hpp`, roll `CHANGELOG.md`, draft `docs/releases/v1.0.0.md`, open the release PR for the maintainer to tag and publish (ADR-0004 §2). diff --git a/cmake/pbr-memory-pool.pc.in b/cmake/pbr-memory-pool.pc.in new file mode 100644 index 0000000..3d080db --- /dev/null +++ b/cmake/pbr-memory-pool.pc.in @@ -0,0 +1,11 @@ +prefix=@CMAKE_INSTALL_PREFIX@ +exec_prefix=${prefix} +libdir=${exec_prefix}/@CMAKE_INSTALL_LIBDIR@ +includedir=${prefix}/@CMAKE_INSTALL_INCLUDEDIR@ + +Name: pbr-cpp-memory-pool +Description: Purpose-built reference high-performance fixed-block O(1) memory pool (C++17 / ANSI C) +URL: https://github.com/danielPoloWork/pbr-cpp-memory-pool +Version: @PROJECT_VERSION@ +Cflags: -I${includedir} +Libs: -L${libdir} -lpbr_memory_pool diff --git a/cmake/pbr_memory_poolConfig.cmake.in b/cmake/pbr_memory_poolConfig.cmake.in new file mode 100644 index 0000000..f020149 --- /dev/null +++ b/cmake/pbr_memory_poolConfig.cmake.in @@ -0,0 +1,18 @@ +@PACKAGE_INIT@ + +# CMake package config for pbr-cpp-memory-pool (ADR-0028, ROADMAP §7.4). +# +# Consumers use the library with: +# +# find_package(pbr_memory_pool CONFIG REQUIRED) +# target_link_libraries(my_app PRIVATE pbr::memory_pool) +# +# The imported target pbr::memory_pool carries its public include directory and +# its C++17 requirement, so no manual include/flag wiring is needed. The default +# (NONE / single-threaded) build has no external link dependency; a library +# built with the LOCKFREE thread-safety policy on GCC/Clang may export a plain +# `atomic` link requirement (libatomic), which the consumer's linker resolves. + +include("${CMAKE_CURRENT_LIST_DIR}/pbr_memory_poolTargets.cmake") + +check_required_components(pbr_memory_pool) diff --git a/docs/adr/0028-install-and-packaging-layout.md b/docs/adr/0028-install-and-packaging-layout.md new file mode 100644 index 0000000..0ddd82a --- /dev/null +++ b/docs/adr/0028-install-and-packaging-layout.md @@ -0,0 +1,91 @@ +# ADR-0028: Install and packaging layout — Phase 1 distribution + +- **Status:** Accepted +- **Date:** 2026-06-14 +- **Deciders:** Daniel Polo (maintainer) +- **Related:** [ADR-0004](0004-versioning-and-release-policy.md) §5 (the distribution phasing this ADR implements Phase 1 of), [ADR-0002](0002-adopt-cross-language-source-layout.md) (the `it/d4np/memorypool/` include tree the install preserves), [ADR-0005](0005-toolchain-matrix-and-supported-platforms.md) (the Tier-1 platforms the release artifacts target), [ADR-0020](0020-thread-safety-strategy-and-compile-time-knob.md) §2 (the LOCKFREE `atomic` link the exported target may carry), [ROADMAP](../../ROADMAP.md) §7.4 (the item), [`.github/workflows/release.yml`](../../.github/workflows/release.yml) (the release packaging that consumes these rules). + +## Context + +The library has been buildable and vendorable since Milestone 1, and its CMake target already declared `target_include_directories(... $)` in anticipation of an install. But there were **no `install()` rules**: a consumer could only use the pool by adding the source tree via `add_subdirectory` / `FetchContent` and linking the in-build alias `pbr::memory_pool`. There was no way to install the library to a prefix and consume it with `find_package`, and no pkg-config for non-CMake build systems. + +[ADR-0004](0004-versioning-and-release-policy.md) §5 fixes the distribution roadmap in two phases: **Phase 1 (this item, M7.4)** is CMake `find_package` support — *"consumers can `find_package(pbr_memory_pool CONFIG REQUIRED)` after vendoring via `FetchContent` or installing the artifacts from a GitHub Release"* — plus the public-header export and a pkg-config file named in ROADMAP §7.4; **Phase 2 (post-1.0)** is the vcpkg port and Conan recipe, deferred behind a stable 1.0 API. + +A latent gap reinforced the need: the release workflow ([`release.yml`](../../.github/workflows/release.yml)) hand-copied only **three of the seven** public headers (`memory_pool.h`, `memory_pool.hpp`, `version.hpp`) into its tarball — `typed_pool.hpp`, `pool_allocator.hpp`, `instrumented_pool.hpp`, and `free_list_iterator.hpp` were silently missing — and shipped neither a CMake config nor a `.pc`, so the ADR-0004 §5 "install the artifacts from a Release" claim was not actually true. + +## Decision + +We add standard CMake **install + export** rules and a **pkg-config** file, gated behind a `PBR_MEMORY_POOL_INSTALL` option, and switch the release packaging to drive them via `cmake --install`. A consumer uses the library identically whether vendored or installed: + +```cmake +find_package(pbr_memory_pool CONFIG REQUIRED) +target_link_libraries(my_app PRIVATE pbr::memory_pool) +``` + +Six sub-decisions make the layout precise: + +### 1. GNUInstallDirs layout + +Install destinations come from `GNUInstallDirs` — the archive to `${CMAKE_INSTALL_LIBDIR}`, the public-header tree to `${CMAKE_INSTALL_INCLUDEDIR}`, the package config to `${CMAKE_INSTALL_LIBDIR}/cmake/pbr_memory_pool`, the `.pc` to `${CMAKE_INSTALL_LIBDIR}/pkgconfig`, and `LICENSE` to `${CMAKE_INSTALL_DOCDIR}`. This is the convention every distro packager, Homebrew formula, and downstream build expects, so the artifacts drop into a standard prefix with no surprises. + +### 2. One exported target name: `pbr::memory_pool` + +The internal CMake target is `pbr_memory_pool`, exported under `NAMESPACE pbr::`. Left alone that would yield `pbr::pbr_memory_pool` — *different* from the in-build `ALIAS pbr::memory_pool` that `add_subdirectory` / `FetchContent` consumers already use. We set `EXPORT_NAME memory_pool` on the target so the **installed** imported target is `pbr::memory_pool` too. One link line works for every consumption mode; switching from vendoring to an installed package requires no edit. (Verified: the installed `pbr_memory_poolTargets.cmake` declares `add_library(pbr::memory_pool STATIC IMPORTED)`.) + +### 3. Relocatable CMake package config, `SameMajorVersion` compatibility + +The config is generated with `configure_package_config_file` (so `PACKAGE_PREFIX_DIR` is computed relative to the installed config file — the package is **relocatable**, works wherever it is extracted). The version file uses `write_basic_package_version_file(... COMPATIBILITY SameMajorVersion)`: under SemVer (ADR-0004) any `1.x` satisfies `find_package(pbr_memory_pool 1.0)`, and a future `2.0` does not — the version-file contract mirrors the API-stability contract. + +### 4. Header tree preserved + +`install(DIRECTORY src/main/cpp/it ... FILES_MATCHING PATTERN "*.h" PATTERN "*.hpp")` installs the whole `it/d4np/memorypool/` subtree under `include/`, so the canonical `#include ` form resolves against the install tree exactly as it does in-build. All seven public headers ship — no hand-maintained subset to fall out of date. + +### 5. `PBR_MEMORY_POOL_INSTALL` defaults to `PROJECT_IS_TOP_LEVEL` + +When the project is the top-level build, install rules are ON. When it is pulled in via `add_subdirectory` / `FetchContent`, they default OFF, so the embedding parent's own `cmake --install` is not polluted with our config/headers. A consumer who *does* want the embedded copy installed can force the option ON. + +### 6. pkg-config for non-CMake consumers + +A `pbr-memory-pool.pc` is generated from a template for autotools / Make / Meson consumers. **Known limitation:** `configure_file` bakes `CMAKE_INSTALL_PREFIX` at configure time, so the `.pc`'s `prefix=` reflects the configure-time prefix, not a later `cmake --install --prefix` or the extract location of a relocated tarball. The relocatable CMake config (§3) is the primary, fully-relocatable mechanism; the `.pc` is best-effort and correct when the package is configured with its final prefix or extracted to that prefix. This is an inherent pkg-config trait, not specific to this project. + +The release workflow's `build-artifacts` job now runs `cmake --install` into its staging tree instead of hand-copying, so every release tarball is a complete `find_package`-ready install (full headers + archive + config + `.pc`), with top-level `LICENSE` / `README` / `CHANGELOG` added for distribution convenience. + +## Alternatives Considered + +- **No export — keep vendoring-only (`add_subdirectory` / `FetchContent`).** Rejected: ADR-0004 §5 Phase 1 explicitly requires `find_package` support and installable Release artifacts; vendoring-only leaves non-CMake consumers and package managers unserved. +- **Export the default `pbr::pbr_memory_pool` name (no `EXPORT_NAME`).** Rejected: it diverges from the in-build `pbr::memory_pool` alias, so a consumer migrating between vendoring and an installed package would have to edit their `target_link_libraries`. One name for both modes is worth the one-line `EXPORT_NAME`. +- **`COMPATIBILITY ExactVersion` or `AnyNewerVersion`.** Rejected: `ExactVersion` would force consumers to pin a patch release; `AnyNewerVersion` would falsely claim a future `2.0` satisfies a `1.x` request. `SameMajorVersion` is the SemVer-correct choice. +- **Skip pkg-config — CMake config only.** Rejected: ROADMAP §7.4 names pkg-config explicitly, and Make / autotools / Meson consumers rely on it. Its prefix limitation (§6) is documented rather than used as a reason to omit it. +- **Make the `.pc` fully relocatable via a wrapper / `$ORIGIN`-style logic.** Rejected for now: pkg-config has no portable relocation primitive; the relocatable CMake config already covers the relocate-anywhere case, so the extra machinery is not worth its complexity at Phase 1. +- **Adopt CPack / generate distro packages (`.deb`, `.rpm`) now.** Rejected/deferred: out of scope for Phase 1; `release.yml` already ships per-platform `tar.gz`, and CPack/registry packaging is the Phase 2 (vcpkg/Conan) and beyond concern. +- **Install a shared library too (`BUILD_SHARED_LIBS`).** Out of scope: the library is STATIC by ADR-0004 §4 until a shared-library milestone; the install rules already handle `LIBRARY` / `RUNTIME` destinations so they need no change when that lands. + +## Consequences + +**Positive** + +- `find_package(pbr_memory_pool CONFIG REQUIRED)` + `target_link_libraries(... pbr::memory_pool)` now works against an installed package — Phase 1 of ADR-0004 §5 is delivered and was verified end-to-end (install to a staging prefix, then a separate consumer project configured, built, and ran against it). +- The release tarballs become complete, `find_package`-ready install trees, **fixing the latent bug** where four of the seven public headers and the package config were missing. +- One target name (`pbr::memory_pool`), one include form, one compatibility rule across vendoring, installed packages, and Release tarballs. +- A good citizen when embedded: `PROJECT_IS_TOP_LEVEL`-gated install rules do not leak into a parent build. + +**Negative / costs** + +- The pkg-config `.pc` carries the configure-time prefix (§6) — a documented best-effort artifact; pkg-config consumers of a relocated tarball must adjust `prefix=` or prefer the relocatable CMake config. +- Two new template files (`cmake/*.in`) and an install block to maintain; the `EXPORT_NAME` indirection is a small subtlety a future contributor must understand (documented inline). +- The `release.yml` packaging change cannot be exercised until the next tag push (M7.7); the `cmake --install` path was validated locally instead, and the local install produced exactly the expected tree (lib, full headers, config + targets + version, `.pc`, LICENSE). + +**Documentation updates landing in the same PR** + +- [`docs/adr/README.md`](README.md) — index row for ADR-0028. +- [ROADMAP](../../ROADMAP.md) §7.4 — checkbox flipped. +- [`README.md`](../../README.md) — an "Install / consume" snippet (find_package + pkg-config). +- [`CHANGELOG.md`](../../CHANGELOG.md) `Unreleased` — `Added` (install/export, pkg-config) + `Changed` (release packaging) entries. + +## References + +- [ADR-0004](0004-versioning-and-release-policy.md) §5 — the distribution phasing. +- [ROADMAP](../../ROADMAP.md) §7.4 — the item implemented here. +- [`CMakeLists.txt`](../../CMakeLists.txt), [`cmake/pbr_memory_poolConfig.cmake.in`](../../cmake/pbr_memory_poolConfig.cmake.in), [`cmake/pbr-memory-pool.pc.in`](../../cmake/pbr-memory-pool.pc.in) — the artifacts this ADR governs. +- CMake `install(EXPORT)` / `configure_package_config_file` / `write_basic_package_version_file` — [`https://cmake.org/cmake/help/latest/module/CMakePackageConfigHelpers.html`](https://cmake.org/cmake/help/latest/module/CMakePackageConfigHelpers.html). +- GNUInstallDirs — [`https://cmake.org/cmake/help/latest/module/GNUInstallDirs.html`](https://cmake.org/cmake/help/latest/module/GNUInstallDirs.html). diff --git a/docs/adr/README.md b/docs/adr/README.md index b9d8a1f..28eb238 100644 --- a/docs/adr/README.md +++ b/docs/adr/README.md @@ -60,5 +60,6 @@ Do **not** write one for purely local implementation details, formatting, or tri | 0025 | [Decorator for an instrumented pool variant](0025-decorator-for-instrumented-pool.md) | Accepted | | 0026 | [Observer for pool-lifecycle events](0026-observer-for-pool-lifecycle-events.md) | Accepted | | 0027 | [Doxygen HTML API site published to GitHub Pages](0027-doxygen-html-site-and-publication-pipeline.md) | Accepted | +| 0028 | [Install and packaging layout — Phase 1 distribution](0028-install-and-packaging-layout.md) | Accepted | When adding a new ADR, append a row to this table in the same PR.