rhalbersma / rhalbersma/cpp-ci
Design a shared cpp-build for header-only Boost.Test projects
Nobody has claimed this yet.
- Dominant language
- No language data
- Stars
- 1
- Forks
- 0
- Avg merge
- 2h 13m
- Merged PRs (30d)
- 45
Description
cpp-ci removes the drift between four copies of the workflows. The same four repositories also carry four copies of the build system, and it drifts for the same reason: there is nowhere else for it to live. This issue is the note to plan that, not to build it yet.
Working name cpp-build rather than cpp-cmake: it names the job rather than the implementation, pairs with cpp-ci, and leaves room for the pieces that are not .cmake files — CMakePresets.json, a toolchain file, the consumer scaffolding.
Scope: header-only C++ libraries tested with Boost.Test through a vcpkg manifest — xstd, tabula, bit_set, and dctl once it has a vcpkg.json.
What is actually shareable
Three tiers, judged against xstd as the seed.
Tier 1 — pure boilerplate, identical everywhere. The install and export rules are the clearest case: install(TARGETS ... FILE_SET HEADERS), install(EXPORT ... NAMESPACE), configure_package_config_file, write_basic_package_version_file(COMPATIBILITY SameMinorVersion ARCH_INDEPENDENT), and the matching install(FILES ...). That is ~35 lines of CMakeLists.txt plus a cmake/<name>Config.cmake.in, and every line of it is a function of the project name. It is also the part most likely to be subtly wrong in the repo nobody has touched in a while.
Also here: cmake_minimum_required, include(GNUInstallDirs), set(CMAKE_CXX_EXTENSIONS OFF), the PROJECT_IS_TOP_LEVEL + BUILD_TESTING guard around add_subdirectory(test), and CMakePresets.json.
Tier 2 — real logic worth writing once. Two functions carry actual thought:
- Header self-sufficiency (
test/CMakeLists.txt) — generates one TU per public header, compiles it alone, and registers it as a test. Includes the detail that the generatedmainuses a trailing return type because clang-tidy runs over the generated sources andmodernize-use-trailing-return-typewould flagint main(). That comment is exactly the kind of hard-won knowledge that does not survive being copy-pasted three times. - One test target per source — the
GLOB_RECURSEovertest/src/*.cppwith the dotted target id derived from the path.
Plus a <project>_test_options interface target for the shared warning flags.
Tier 3 — must stay per-repo. The version, the explicit header list in target_sources(FILE_SET HEADERS FILES ...), the C++ standard in target_compile_features, and anything naming the library's own types.
The constraint that shapes the whole design
The installed package must not depend on cpp-build. xstdConfig.cmake is installed and read by downstream consumers of xstd. If a shared macro leaks into it, every consumer of every one of these libraries needs cpp-build on disk to call find_package. So cpp-build is strictly build-time: it may generate the config file, but nothing it provides may appear in the generated output.
The consumption.yml tests already in cpp-ci are the right guard for this — find_package, add_subdirectory, and FetchContent all exercise the consumer side, and a leak would show up there.
First decision: how does a repo get cpp-build?
| Option | Cost |
|---|---|
git submodule at cmake/cpp-build |
Pinned by SHA, matching the pinning convention already in use, and needs no network at configure time. Costs contributors a --recursive clone. |
FetchContent at configure time |
No submodule friction, but puts a network fetch in the configure step — which breaks offline builds and adds a failure mode to the very add_subdirectory/FetchContent consumption tests above. |
| Installed CMake package | Cleanest semantics, worst ergonomics: a bootstrap install before any build. |
| Copy with a sync bot | Keeps repos self-contained, but is the current situation with extra machinery. |
Leaning submodule, for the same reason cpp-ci pins by SHA: the build should say exactly which version it used, and it should not need the network to find out.
Relationship to cpp-ci
Separate repo, separate lifecycle — same argument as the one for not putting the workflows in xstd. But note the coupling: cpp-ci's consumption.yml names test/consumer/{find_package,add_subdirectory,fetch_content} by convention, and those consumer projects are the natural thing for cpp-build to scaffold.
Sequencing
After the cpp-ci migration is complete. Two centralisations at once would mean a failure could come from either, and this one has the harder blast radius — a broken CI workflow reddens a badge, a broken install rule reaches people who depend on the library.
dctl is the natural first consumer once it has a vcpkg.json, since it has neither CI nor a modern CMake setup to preserve.
Contributor guide
No contributing guide indexed for this repository
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start by comparing xstd's CMakeLists.txt, test/CMakeLists.txt, cmake/Config.cmake.in, and CMakePresets.json, then review cpp-ci's consumption.yml and the four consumer projects named there. Produce an agreed design for cpp-build's scope, acquisition method, and sequencing; done means the plan preserves per-repository settings and keeps installed packages independent of cpp-build.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- cmake, cpp
- Domain
- build-system
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 35/100