# CMakeTemplate A C++20 project template for CMake 3.25+ that builds the same way on Linux, macOS and Windows. - **Adding code means adding a folder.** A module is a directory under `source/` whose `CMakeLists.txt` is one or two lines long. The root `CMakeLists.txt` is never edited. - **The template is one folder.** `breeze/` holds everything that makes it work: the build system, toolchains, scripts and these docs. `project/` holds this project's own settings, and `source/`, `vendor/` and `out/` are yours, at the root. Updating the template means replacing `breeze/`. - **Sixteen targets, one preset each.** Linux (x64, arm64, arm32 × GCC, Clang), macOS (arm64, x64), Windows (x64, arm64 × MSVC, clang-cl, Clang; x64 and x86 MinGW-w64). Every target has its own toolchain file and its own build directory, so switching targets never rebuilds anything. - **One setup script** checks your machine and offers to install what is missing, one tool at a time, nothing without your yes. - **A working demo** to copy from: the shared library `core_lib` (with a piece of it, `core_farewell`, in an objects module of its own), the executables `hello_exe` and `farewell_exe`, a vendored header-only dependency (`tiny_ansi`), a vendored CMake dependency (`tiny_case`) and `fmt`, fetched from Git. **Part 1, using the template:** [Requirements](#requirements) · [Quick start](#quick-start) · [Choosing a target](#choosing-a-target) · [Everyday commands](#everyday-commands) · [Adding code](#adding-code) · [Settings](#settings) · [Making it yours](#making-it-yours) · [Updating the template](#updating-the-template) · [Troubleshooting](#troubleshooting) **Part 2, how it works:** [Layout](#repository-layout) · [The settings file](#the-settings-file) · [Modules](#modules) · [Dependencies](#dependencies) · [Targets and toolchains](#targets-and-toolchains) · [Setup internals](#setup-internals) · [Script internals](#script-internals) · [Style and tooling](#style-and-tooling) · [Embedding](#embedding) · [Verification status](#verification-status) --- # Part 1: Using the template ## Requirements - **Required:** Git, CMake 3.25 or newer, and a C++20 compiler (GCC, Clang, MSVC or MinGW-w64). - **Optional, each only for what uses it:** Clang, clang-format and clang-tidy (`refactor` and the pre-commit hook), cross compilers, MinGW-w64, `qemu-user` and `wine` (to run cross-built programs), Doxygen. - **Fetched for you** into `.cache/tools/`: Ninja 1.13.2 and Doxygen 1.16.1, pinned so every machine builds with the same versions. A Ninja already on `PATH` is the fallback; the project builds without Doxygen. - Setup installs tools through apt, dnf and pacman (Linux), Homebrew (macOS) and winget (Windows). On anything else it reports what is missing and leaves the installing to you. ## Quick start ```sh breeze/scripts/setup.sh # once per clone; asks before installing anything breeze/scripts/build.sh # configure and build (Release) breeze/scripts/run.sh hello_exe World # Hello, World! (from demo_core) ``` On Windows use `breeze\scripts\setup.bat`, `breeze\scripts\build.bat` and `breeze\scripts\run.bat hello_exe World`, from `cmd` or PowerShell. To build for a specific target, name its preset: `breeze/scripts/build.sh --preset linux-x64-clang`. `run` then follows the build you made last, or takes `--preset` too. ## Choosing a target A target is a preset named `--`. `breeze/scripts/build.sh --list-presets` shows the ones your OS offers. Without `--preset` the first compiler CMake finds is used and the build goes to `out/build/default/`. | Presets | Build | Needs | |---|---|---| | `linux-x64-gcc` `linux-x64-clang` `linux-arm64-gcc` `linux-arm64-clang` `linux-arm32-gcc` `linux-arm32-clang` | Linux on x86-64, AArch64, 32-bit ARM | On matching hardware, just the compiler. Otherwise a cross compiler for that architecture; the Clang presets also need Clang. | | `macos-arm64-clang` `macos-x64-clang` | macOS on Apple Silicon, Intel | Xcode Command Line Tools. Either Mac builds both; x64 programs run on Apple Silicon under Rosetta 2. | | `windows-x64-msvc` `windows-arm64-msvc` | Windows, MSVC | Visual Studio or Build Tools with the C++ workload. ARM64 also needs the ARM64 build tools component; from an x64 machine that is a cross build (`breeze\scripts\setup.bat` offers to add it). | | `windows-x64-clang-cl` `windows-arm64-clang-cl` | Windows, clang-cl (MSVC ABI) | The above plus clang-cl: LLVM, or Visual Studio's Clang component. | | `windows-x64-clang` `windows-arm64-clang` | Windows, MSYS2 Clang (MinGW ABI) | MSYS2's CLANG64 (x64) or CLANGARM64 (ARM64) Clang package. MSYS2 has CLANGARM64 only on ARM64 Windows, so `windows-arm64-clang` needs such a machine; from x64 use the ARM64 MSVC or clang-cl presets. | | `windows-x64-mingw64` `windows-x86-mingw32` | Windows, MinGW-w64 GCC | MSYS2 with MinGW-w64 GCC. Also cross-builds from Linux. | **Cross builds.** A Linux preset for another architecture compiles with the matching `-g++` (`aarch64-linux-gnu`, `arm-linux-gnueabihf`, `x86_64-linux-gnu`); the MinGW presets also cross-build from Linux. Setup installs these where your package manager has them (see [What setup can install](#what-setup-can-install)). If `qemu-user` (Linux targets) or `wine` (Windows targets) is installed when you configure, `breeze/scripts/run` and `ctest` use it to run the result. **Windows ARM64 from an x64 machine** is a cross build too: `windows-arm64-msvc` and `windows-arm64-clang-cl` compile with Visual Studio's x64-hosted ARM64 tools (`vcvarsall x64_arm64`), which need the ARM64 build tools component, and the programs cannot run on the build machine. **Windows environments.** MSVC and clang-cl only work inside a Visual Studio developer environment, and MSYS2's compilers only with their folder on `PATH`. `breeze\scripts\build.bat` enters the right one for the preset by itself, for that run only, so no "Native Tools Command Prompt" or MSYS2 shell is needed; `PMN_NO_ENV=1` turns it off ([details](#windows-environments)). Plain `cmake --preset ...` and IDEs don't do this: start them from the matching prompt. ## Everyday commands Every script exists as `.sh` and `.bat` with the same options, answers `-h` / `--help`, and works from any directory in the repository. `--preset NAME` (or `--preset=NAME`) must come first. | Script | Usage | Does | |---|---|---| | `setup` | `[--yes] [--all] [--everything] [--check] [--skip-tools]` | Checks and installs tools, fetches pinned Ninja and Doxygen, activates the pre-commit hook. | | `build` | `[--preset NAME] [BUILD_TYPE] [CMAKE_ARGS…]`
`--list-presets` | Configures and builds into `out/build/`. `BUILD_TYPE` is `Debug`, `Release` (default), `RelWithDebInfo` or `MinSizeRel`; further arguments go to CMake as they are, e.g. `-DBUILD_TESTS=ON`. | | `run` | `[--preset NAME] TARGET [ARGS…]` | Runs an executable by CMake target name; without a target it lists them. Cross-built programs run under qemu or wine when available. | | `install` | `[--preset NAME] [BUILD_TYPE]` | Installs this project's targets to `out/install//{bin,lib}`. | | `docs` | `[--preset NAME] [MODULE]` | Builds the Doxygen docs of every module, or one, into `out/docs/`. | | `refactor` | `[PATH]` | Runs clang-format, then clang-tidy `--fix` (which needs a previous build for its compile database), over `source/` and `c-api/`, or just `PATH`. Review with `git diff`. | `run`, `install` and `docs` act on the build made last unless you pass `--preset`; `BUILD_DIR` selects an exact directory instead. The build type is not part of the directory name, so changing it within a preset reconfigures that directory. **Setup flags.** `--yes` installs the required tools without asking (for CI), `--all` the optional ones too, `--everything` also the third-party extras (today: Arch's prebuilt 32-bit ARM toolchain from the AUR), `--check` only reports and exits 1 if a required tool is missing, `--skip-tools` goes straight to the downloads. At each question: `y` installs, `n` skips that tool, `a` accepts everything left, `s` skips the remaining optional tools, `q` stops asking. With no terminal and no flag nothing is installed. **CMake directly.** ```sh cmake --preset linux-x64-gcc cmake --build --preset linux-x64-gcc ctest --test-dir out/build/linux-x64-gcc # tests need -DBUILD_TESTS=ON ``` IDEs with preset support (CLion, VS Code, Visual Studio) list the presets for your OS; personal ones go in `CMakeUserPresets.json`. A bare `-DDIST_TARGET=…` only labels a build, it does not choose a compiler, and a label that disagrees with the compiler stops the configure: pick targets with presets. ## Adding code Create a directory with a `CMakeLists.txt`; the next build finds it. ```cmake # source/MyLib/CMakeLists.txt library: sources in src/, headers in include/ add_lib_module(my_lib TYPE SHARED DEPENDS fmt::fmt) # source/MyPiece/CMakeLists.txt objects: compiled, no library of its own (a piece of one) add_objects_module(my_piece) # taken in by naming it: add_lib_module(my_lib DEPENDS my_piece) # source/MyApp/CMakeLists.txt executable: *.cpp in the module add_exe_module(my_app DEPENDS my_lib) # source/tests/my_test/CMakeLists.txt built with -DBUILD_TESTS=ON, registered with CTest add_test_module(my_test DEPENDS my_lib) # source/examples/my_example/CMakeLists.txt built with -DBUILD_EXAMPLES=ON add_example_module(my_example DEPENDS my_lib) ``` Options: `OUTPUT_NAME ` (for an objects module, which has no file, only the name `MODULE_NAME` carries), `DEPENDS `, `EXTRA_SOURCES `, `NO_DOCS`. Third-party code comes in three ways, called from the CMakeLists of the module that needs it: ```cmake add_vendor_header_only(tiny_ansi) # vendor/tiny_ansi/include, header-only add_vendor_subdirectory(tiny_case) # vendor/tiny_case/CMakeLists.txt add_dependency(fmt # an installed copy if new enough, else fetched GIT_REPOSITORY https://github.com/fmtlib/fmt.git GIT_TAG 12.1.0 FIND_PACKAGE_ARGS 12 NAMES fmt) ``` What one module declares, every module can use: `source/CoreFarewell` depends on the `fmt::fmt` that `source/Core` declares, whether fmt was fetched or an installed copy was found. Every module also gets Doxygen docs (`NO_DOCS` opts out; a `README.md` in the module's directory becomes its main page). ## Settings Pass options to `build` as CMake arguments, e.g. `breeze/scripts/build.sh -DBUILD_TESTS=ON`. | Option | Default | Effect | |---|---|---| | `BUILD_TESTS`, `BUILD_EXAMPLES` | OFF | Build the modules under `source/tests` and `source/examples`. | | `INSTALL_TESTS`, `INSTALL_EXAMPLES` | OFF | Also install them (implies the matching `BUILD_` option). | | `BUILD_SHARED_LIBS` | OFF | Library type for modules that don't say `TYPE`. | | `ENABLE_DOXYGEN` | ON | Per-module documentation; `-DDOXYGEN_=…` sets any Doxyfile tag. | | `PMN_MINGW_STATIC_RUNTIME` | OFF | MinGW: link the GCC runtime into executables instead of shipping its DLLs. Unsafe when an executable uses a shared module that passes STL types across its interface. | The project's own settings (its name, version, languages and C++ standard, `PROJECT_CODE_NAME`, `DEFAULT_BUILD_TYPE`, `OUT_DIR` and the directories under it) live in `project/Configuration.cmake`; the cache variables among them can also be overridden with `-D`. ## Making it yours 1. In `project/Configuration.cmake` set `PROJECT_TITLE`, `PROJECT_VERSION_NUMBER`, `PROJECT_SUMMARY`, `PROJECT_LANGUAGES` and `PROJECT_CXX_STANDARD` (the arguments of `project()` and the C++ standard), `PROJECT_CODE_NAME` (it becomes the `PROJECT_NAME_CODE` compile definition and the install component) and `DEFAULT_BUILD_TYPE`. 2. Delete the demo modules in `source/` and `vendor/` and add your own. 3. Replace the root `README.md` with the project's own (Doxygen uses it as the main page). 4. Run `breeze/scripts/setup`. Nothing in `breeze/` needs an edit. The root `CMakeLists.txt` and `CMakePresets.json` are yours to extend (project-wide CMake of your own, or presets that `inherit` from the template's) and need nothing to get started. ## Updating the template The template is the `breeze/` folder and nothing else; no file of the project's lives in it. To update, replace the folder with the new version's and run `breeze/scripts/setup` again (it points git's `core.hooksPath` at `breeze/githooks` and re-checks your tools). | The project's (an update never touches them) | The template's (replaced as a whole) | |---|---| | `project/`, `source/`, `vendor/`, `c-api/`; the root `CMakeLists.txt`, `CMakePresets.json` and `README.md` | `breeze/` | Four kinds of file sit at the root, outside `breeze/`, because the tools that read them look there: the dot files (`.clang-format`, `.clang-tidy` and `c-api/.clang-tidy`, `.editorconfig`, `.gitattributes`) and `.gitignore`, whose marked block every configure rewrites. A new template version may change them: compare them with the new copies. A new version may also read a setting your `project/Configuration.cmake` doesn't have yet; the configure then stops and names it, and the new version's own `project/Configuration.cmake` shows its default. **With the `breeze` CLI** the update is `breeze sdk update` (what is available), `breeze sdk upgrade` (the newest version of your major version) or `breeze sdk migrate `: it replaces `breeze/` for you (keeping the old one in `.breeze/backup/`), adds the settings the new version has, and moves a project in the old flat layout to this one. The SDK's version is `breeze/VERSION`, one line, and a release of the template is a tag (`v1.2.0`) whose `breeze/VERSION` says the same. **Moving a project that has the old flat layout** (`cmake/`, `scripts/` and `Configuration.cmake` at the root): put in `breeze/`, `project/` and the new root `CMakeLists.txt`, `CMakePresets.json` and `README.md`, and delete the old `cmake/`, `scripts/`, `.githooks/`, `StyleGuide.md` and `Configuration.cmake`. Carry your values over into `project/Configuration.cmake`: from the old `Configuration.cmake` (`PROJECT_CODE_NAME`, `DEFAULT_BUILD_TYPE`, any output path you changed) and from the old `CMakeLists.txt` (the `project()` arguments and the C++ standard). Run `breeze/scripts/setup` once, which moves `core.hooksPath` from `.githooks` to `breeze/githooks`, and delete `out/build/`: the old build directories point at files that are gone. ## Troubleshooting | Symptom | Cause and fix | |---|---| | Windows: CMake says `cl` is missing or targets the wrong architecture | CMake ran outside the right Visual Studio developer prompt. Use `breeze\scripts\build.bat`, or open the matching prompt, then delete the build directory. | | Windows: a MinGW or `-clang` preset can't find its compiler | MSYS2's toolchain folder isn't on `PATH`. `breeze\scripts\build.bat` adds it; set `MSYS2_ROOT` if MSYS2 isn't in `\msys64`. | | Windows: `cannot open msvcrt.lib`, `could not open 'msvcrtd.lib'`, or an x64 `cl` used for an ARM64 preset | The Visual Studio component for that target isn't installed. For ARM64 it is "MSVC … C++ ARM64/ARM64EC build tools (Latest)", which an x64 machine needs to cross-compile. `breeze\scripts\setup.bat` offers to add it, and `breeze\scripts\build.bat` stops with this explanation before CMake runs. | | `windows-arm64-clang` on an x64 machine says it needs MSYS2's CLANGARM64 | MSYS2 provides CLANGARM64 only on ARM64 Windows. Build ARM64 from x64 with `windows-arm64-msvc` or `windows-arm64-clang-cl`. | | `linux-arm32-*` fails with `invalid -march= option` or `Scrt1.o: file not recognized` | The cross compiler, binutils and C library come from different package sets. `breeze/scripts/setup.sh` reports this as `[part]`; on Arch x64 it replaces them with a matching prebuilt set. | | A cross build stops with `Could NOT find X11` (or OpenGL, ALSA, …), though the host has it | Cross builds look only in the target's root, which has the C/C++ runtime and nothing else; the host's libraries are for the wrong architecture. Give the build a target sysroot: a root filesystem for the target with those libraries in `.cache/sysroots/linux-`, or `-DCMAKE_SYSROOT=`. See [Cross builds and system libraries](#cross-builds-and-system-libraries). | | `breeze/scripts/run` can't run a cross-built program | No emulator was installed when the build directory was configured. Install `qemu-user` or `wine` and run `breeze/scripts/build` again. | | wine complains about its prefix | Delete `.cache/wine/`; it is rebuilt on the next run. | | "No pinned Ninja / Doxygen binary is configured for this machine" | Pinned downloads exist for x64 and arm64 on Windows, Linux and macOS (Doxygen: x64 Linux and Windows, and macOS). A 32-bit `cmake.exe` on 64-bit Windows looks the same. Use 64-bit CMake, or put Ninja / Doxygen on `PATH` (`-DDOXYGEN_EXECUTABLE=…`). | | `DIST_TARGET` mismatch error | `-DDIST_TARGET=…` disagrees with the compiler CMake chose. Use `--preset`, or drop the option. | | `linux-arm32-gcc` looks for a cross compiler on a 32-bit userland running on a 64-bit ARM kernel | `uname -m` reports aarch64 there. Pass `-DCMAKE_CXX_COMPILER=g++`. | | The pre-commit hook rejects a commit | clang-format or clang-tidy found problems in staged C/C++ files. Run `breeze/scripts/refactor` and review the diff. | --- # Part 2: How it works ## Repository layout ``` CMakeLists.txt the project's entry point: project(), then breeze/ (never edited) CMakePresets.json includes breeze/CMakePresets.json; the project's own presets go here README.md the project's own project/ Configuration.cmake the project's settings: identity, output paths. One source of truth breeze/ the template: replace this folder to update it README.md StyleGuide.md VERSION the SDK's version, one line (the `breeze` CLI reads it) CMakePresets.json 16 configure + 16 build presets, plus hidden base presets cmake/ Breeze.cmake the build system: options, output dirs, module discovery, install rules TargetSelection.cmake DIST_TARGET -> PLATFORM, ARCHITECTURE, COMPILER_ID GitignoreSync.cmake keeps the generated block of .gitignore current BuildHelpers.cmake module macros, output dirs, RPATH, runtime DLLs, manifest Dependencies.cmake add_vendor_header_only, add_vendor_subdirectory, add_dependency Documentation.cmake per-module Doxygen targets (+ DoxygenPin, FetchDoxygen) NinjaPin.cmake pinned Ninja (+ FetchNinja) HostArch.cmake machine architecture -> x64 / arm64 / arm32 PrintConfig.cmake script mode: print project/Configuration.cmake values BuildDir.cmake script mode: which build directory a script uses CopyRuntimeDlls.cmake post-build copy of runtime DLLs toolchains/ one file per target, plus _common.cmake scripts/ setup build run install docs refactor, each .sh + .bat helpers: _common.sh _tools.sh _tools.ps1 _winenv.bat githooks/pre-commit clang-format + clang-tidy on staged files source/ Core/ (core_lib) CoreFarewell/ (core_farewell, objects) HelloApp/ FarewellApp/ [tests/ examples/] vendor/ tiny_case/ (CMake project) tiny_ansi/ (header-only) c-api/ .clang-tidy for a C API surface .clang-format .clang-tidy .editorconfig .gitattributes .gitignore ``` Generated, git-ignored: `out/` (`build//`, `install//`, `docs/`, `dist/` reserved) and `.cache/` (`tools/`, `wine/`, `sysroots/`). ## The settings file `project/Configuration.cmake` defines the project's identity and every output path. The output settings are cache variables, so `-D` overrides any; the identity values are plain variables, because the root `CMakeLists.txt` passes them to `project()`. The scripts never duplicate a value: they read it through `cmake -P` helpers. | Variable | Default | Meaning | |---|---|---| | `PROJECT_TITLE` `PROJECT_VERSION_NUMBER` `PROJECT_SUMMARY` `PROJECT_LANGUAGES` | `DemoApp` `0.1.0` … `CXX` | The name, `VERSION`, `DESCRIPTION` and `LANGUAGES` of `project()`. | | `PROJECT_CXX_STANDARD` | `20` | `CMAKE_CXX_STANDARD`; the standard is required and compiler extensions are off. | | `PROJECT_CODE_NAME` | `demo` | Compile definition `PROJECT_NAME_CODE`; the `cmake --install` component. | | `DEFAULT_BUILD_TYPE` | `Release` | Used by CMake and the scripts when no type is given. | | `OUT_DIR` | `/out` | Root of everything generated. | | `BUILD_OUTPUT_DIR` `INSTALL_OUTPUT_DIR` `DOCS_OUTPUT_DIR` `DIST_OUTPUT_DIR` | `OUT_DIR/build` `/install` `/docs` `/dist` | One build directory per preset, one install directory per `DIST_TARGET`; `dist` is reserved. | | `TOOLCACHE_DIR` | `/.cache/tools` | Pinned Ninja and Doxygen. | | `RUNTIME_OUTPUT_SUBDIR` `LIBRARY_OUTPUT_SUBDIR` `ARCHIVE_OUTPUT_SUBDIR` | `bin` `bin` `lib` | Layout inside a build or install tree. | | `COMPILE_COMMANDS_DESTINATION` | `/compile_commands.json` | Copy of the compile database for clangd and clang-tidy, refreshed after each build. | `DIST_TARGET`, `PLATFORM`, `ARCHITECTURE` and `COMPILER_ID` are not settings: the template derives them (`breeze/cmake/TargetSelection.cmake`, see [Target selection](#target-selection)). - **Script mode.** `cmake "-DPRINT_VARS=A;B" -P breeze/cmake/PrintConfig.cmake` prints `-- A=value` lines. `cmake [-DPMN_NAME=] [-DPMN_RECORD=ON] -P breeze/cmake/BuildDir.cmake` prints `-- BUILD_DIR=…` (or `-- ERROR=…`) and, with `PMN_RECORD`, remembers the choice in `out/build/.last`. A plain `cmake -P` script can include the settings file because it holds settings only: nothing in it needs a compiler or writes a file. Target selection and the `.gitignore` sync are the template's, and only a real configure runs them. - **`.gitignore`.** A marked block is regenerated on every real configure from the output paths, so moving `OUT_DIR` updates it. The rest of the file is never touched. ## Modules - **Discovery.** Every `source//`, `source/tests//` and `source/examples//` that contains a `CMakeLists.txt` is added, with `CONFIGURE_DEPENDS`, so adding or removing a module reconfigures by itself. - **Macros** (`breeze/cmake/BuildHelpers.cmake`). `add_lib_module` (`add_static_lib_module` and `add_shared_lib_module` are shorthands for its `TYPE`), `add_objects_module`, `add_exe_module`, `add_test_module`, `add_example_module`. | Macro | Sources (recursive globs) | Output | Notes | |---|---|---|---| | `add_lib_module` | `src/*.cpp`, `include/*.h(pp)` | shared: `bin/`, static: `lib/` | `include/` is public. Without `TYPE`, `BUILD_SHARED_LIBS` decides. | | `add_objects_module` | `src/*.cpp`, `include/*.h(pp)` | nothing of its own: the objects go into whatever names it in `DEPENDS` | `include/` is public. See "Objects modules" below. | | `add_exe_module` | `*.cpp`, `include/*.h(pp)` | `bin/` | | | `add_test_module` | same | `bin/tests/` | Registered with CTest and `all_tests`; `EXCLUDE_FROM_ALL` unless `BUILD_TESTS`. | | `add_example_module` | same | `bin/examples/` | Same, with `BUILD_EXAMPLES`. | - **What each module target gets.** The compile definitions `MODULE_NAME` (its output name; for an objects module the `OUTPUT_NAME` it was given), `PROJECT_NAME`, `PROJECT_NAME_CODE`, `PROJECT_VER` and, in Debug, `DEBUG`; warnings (`/W4 /permissive-` on MSVC, `-Wall -Wextra -Wpedantic` elsewhere); its `DEPENDS` linked `PRIVATE` (objects modules, below, are the exception for libraries); an RPATH of `$ORIGIN` (`$ORIGIN/..` for tests and examples), which becomes `@loader_path` for Mach-O in one place, `_apply_rpath`; and a Doxygen target unless `NO_DOCS`. Project-wide: C++20 without extensions, position-independent code, `CMAKE_BUILD_WITH_INSTALL_RPATH` off Windows. - **Objects modules.** `add_objects_module` compiles a directory to object files and stops there: no archive, no DLL, nothing in `bin/` or `lib/`. Name it in the `DEPENDS` of a library, an executable, a test, an example or another objects module, and its objects become part of that target. That makes it the way to split a big library into pieces: each piece is a module of its own, with its own `include/`, and what remains of the library is a line naming them. ```cmake # source/CoreMath/CMakeLists.txt add_objects_module(core_math OUTPUT_NAME "core") # source/CoreIo/CMakeLists.txt add_objects_module(core_io OUTPUT_NAME "core" DEPENDS core_math) # source/Core/CMakeLists.txt the library: its own sources, plus the pieces add_lib_module(core_lib TYPE SHARED OUTPUT_NAME "core" DEPENDS core_io) ``` - **Name the direct pieces only.** `core_lib` above gets `core_math` too, because `core_io` needs it: CMake links an object library's objects into the target that names it and no further, so the template follows the objects modules behind the ones you name (`_resolve_object_modules`, run once all modules are discovered, so directory order doesn't matter). Cycles stop the configure. - **Headers.** A piece's `include/` is public, and a library passes its pieces' headers on: whoever depends on `core_lib` sees `core_math`'s and `core_io`'s headers. Those consumers must not name the pieces again, which would link a second copy of the objects into them (two copies of the library's state, when it is shared). An executable takes pieces privately. Keep a piece's private headers in `src/`. - **`OUTPUT_NAME`** is only what `MODULE_NAME` carries, so that the pieces of one library all identify as it. What a piece links itself, a static library for instance, is reached at the final link. - **What they are not.** Not installed, not run, not in the manifest; always position-independent, because they may end up in a shared library. Tests and examples can name a piece to test it without the rest of the library. `source/CoreFarewell` is the demo: `core_lib` takes it in, and `farewell_exe` finds `core/Farewell.hpp` through `core_lib` alone. - **Manifest.** Each configure writes `module_manifest-.txt` into the build directory, one `target=path` line per executable; `breeze/scripts/run` resolves target names through it. - **Runtime DLLs (Windows).** Shared modules an executable depends on are copied next to it after the build (`CopyRuntimeDlls.cmake`: `cmake -E copy_if_different` fails on the empty list that `$` is for most targets). For MinGW the GCC runtime (`libgcc_s_*-1.dll`, `libstdc++-6.dll`, `libwinpthread-1.dll`) is located through the compiler and copied the same way, and installed beside the executables. `PMN_MINGW_STATIC_RUNTIME=ON` links executables statically instead; that is unsafe when they use a shared module whose interface passes STL types (separate heaps and unwinders), hence off by default. - **Install.** Targets are tagged with the component `PROJECT_CODE_NAME` and go to `out/install//{bin,lib}` (tests and examples under `bin/tests`, `lib/tests`, … when `INSTALL_TESTS` / `INSTALL_EXAMPLES`). `breeze/scripts/install` always passes `--component`, which keeps the `install()` rules of fetched or vendored dependencies out; a bare `cmake --install` would run them too. - **Docs.** A `docs_` target per module plus an aggregate `docs`, built into `out/docs//html/` with `out/docs/index.html` linking them. Any Doxyfile tag can be set as `DOXYGEN_`. Without a usable Doxygen, or with `ENABLE_DOXYGEN=OFF`, `breeze/scripts/docs` does nothing and says why; `-DDOXYGEN_PINNED_VERSION=…` replaces the pinned version. ## Dependencies `breeze/cmake/Dependencies.cmake` offers `add_vendor_header_only(NAME [INCLUDE_DIR dir])` (an INTERFACE target over `vendor//include`), `add_vendor_subdirectory(SUBDIR)` (a source drop, submodule or subtree with its own `CMakeLists.txt`) and `add_dependency(NAME )` (`find_package` first when `FIND_PACKAGE_ARGS` is given, a fetch otherwise). All three keep the dependency's warnings out of your build (`SYSTEM`) and its targets out of `compile_commands.json`, and all are no-ops when repeated for the same name, so two modules may ask for the same dependency. The targets are usable from every module, wherever the call was made: an installed copy's imported targets are directory-scoped by default, so `add_dependency` makes them global for the call (`CMAKE_FIND_PACKAGE_TARGETS_GLOBAL`), and a sibling module's `DEPENDS fmt::fmt` finds them. Always give `FIND_PACKAGE_ARGS` a minimum version: without one any installed copy beats the pinned tag. A dependency's own options are set (`set(