Files
BreezeSDK/breeze/README.md
T
2026-10-24 08:05:45 -03:00

721 lines
52 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 `<os>-<arch>-<compiler>`. `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
`<triplet>-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…]`<br>`--list-presets` | Configures and builds into `out/build/<preset>`. `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/<preset>/{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 <file name>` (for an objects module, which has no file, only the name `MODULE_NAME`
carries), `DEPENDS <targets>`, `EXTRA_SOURCES <files>`, `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_<TAG>=…` 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 <version>`: 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 `<system drive>\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-<arch>`, or `-DCMAKE_SYSROOT=<dir>`. 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/<preset>/`, `install/<target>/`, `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` | `<source>/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` | `<source>/.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` | `<source>/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=<preset|default>] [-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/<dir>/`, `source/tests/<dir>/` and `source/examples/<dir>/` 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-<CONFIG>.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
`$<TARGET_RUNTIME_DLLS:…>` 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/<DIST_TARGET>/{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>` target per module plus an aggregate `docs`, built into
`out/docs/<target>/html/` with `out/docs/index.html` linking them. Any Doxyfile tag can be set
as `DOXYGEN_<TAG>`. 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/<name>/include`), `add_vendor_subdirectory(SUBDIR)` (a source drop, submodule or
subtree with its own `CMakeLists.txt`) and `add_dependency(NAME <FetchContent_Declare args>)`
(`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(<OPTION> … CACHE …)`) before the call;
`source/Core/CMakeLists.txt` is the commented example. In cross builds `find_package` only looks in
the target's root, so a host's `libfmt` is never picked up -- and neither is anything the target's
root lacks, which is what a target sysroot is for (see
[Cross builds and system libraries](#cross-builds-and-system-libraries)).
## Targets and toolchains
### Target selection
`DIST_TARGET` is the one label of a build, `<platform>-<arch>-<compiler>`; `PLATFORM`,
`ARCHITECTURE` (`x64`, `arm64`, `arm32`, `x86`) and `COMPILER_ID` (`gcc`, `clang`, `msvc`,
`clang-cl`, `mingw64`, `mingw32`) follow from it.
- A **toolchain file** (`breeze/cmake/toolchains/<target>.cmake`, three lines each) declares it with
`pmn_toolchain_target(<target>)` and calls one family macro: `pmn_linux_toolchain(arch gcc|clang)`,
`pmn_macos_toolchain(arch)`, `pmn_msvc_toolchain(arch)`, `pmn_clang_cl_toolchain(arch)`,
`pmn_msys_clang_toolchain()`, `pmn_mingw_toolchain(arch)`. An explicit `-DDIST_TARGET` must agree
with the toolchain's.
- **Without a toolchain**, `TargetSelection.cmake` detects the target from the compiler CMake found.
Linux: the machine's architecture (`HostArch.cmake`) and the compiler ID. macOS:
`CMAKE_OSX_ARCHITECTURES`, else `CMAKE_SYSTEM_PROCESSOR`; several architectures (a universal
binary) is an error. Windows: MSVC and clang-cl by `CMAKE_CXX_COMPILER_ARCHITECTURE_ID`, GNU-style
Clang by the host architecture, GCC as MinGW by pointer size (there is no ARM MinGW target:
MinGW-w64's GCC has no ARM64 Windows backend).
- An explicit `-DDIST_TARGET` is **cross-checked** against that detection, and a disagreement stops
the configure with the toolchain file or preset to use. An undetectable architecture also stops
it, with the list of valid values.
- **Presets** (`breeze/CMakePresets.json`, version 6, included by the root `CMakePresets.json`). A hidden `base` (Ninja, `out/build/${presetName}`)
and host-OS bases with `condition`s (`base-linux`, `base-macos`, `base-windows`, `base-mingw` for
Windows or Linux hosts) sit under 16 visible configure presets and 16 build presets with the same
names. There are no test presets.
### Linux
`pmn_linux_toolchain` decides by comparing the requested architecture with the machine's.
- **Native** uses the ordinary `g++` / `clang++`. **Cross** uses `<triplet>-g++` /
`<triplet>-gcc`, resolved to absolute paths so every `try_compile` agrees; a missing compiler stops
with the install hint. A compiler passed with `-DCMAKE_CXX_COMPILER` beats the toolchain's; a stray
`CXX` environment variable does not. A 32-bit userland on a 64-bit ARM kernel reports `aarch64`, so
it needs `-DCMAKE_CXX_COMPILER=g++`.
- **Cross mechanics** (`pmn_cross_target`): `CMAKE_SYSTEM_NAME` / `PROCESSOR` are set, programs come
from the host, and libraries, headers and packages only from the target sysroot (if there is one, see below) and
`/usr/<triplet>`.
- **Emulator** (`pmn_cross_emulator`): `qemu-<arch> -L <root>` for Linux targets, `wine64` /
`wine` for MinGW. It is stored as the cache entry `CMAKE_CROSSCOMPILING_EMULATOR` once found, so an
emulator installed later is picked up by the next configure and a stored choice stays until you
change the entry or delete the build directory. `breeze/scripts/run` and CTest read it from there.
- **Clang** cross builds use `clang++ --target=<triple>` with the cross g++'s libstdc++ and linker.
When that g++ reports a real sysroot (dnf's COPR toolchains, ARM's own), Clang also gets
`--sysroot` and `--gcc-toolchain=/usr`; flat layouts (Debian's `/usr/<triplet>`, pacman's) need
neither.
- **Checks.** The toolchain asks the installed cross g++ about itself instead of trusting a
convention, because a cross compiler is only as good as the binutils and C library installed
beside it. Each check is a warning with the likely cause, printed once, and configure carries on:
- a sysroot that `-print-sysroot` reports but that isn't on disk is dropped (Clang would otherwise
fall back to the host's crt files: `Scrt1.o: file not recognized`);
- Clang's `--target` is the triple `-dumpmachine` reports when it is the same architecture:
ARM's own GCC is `arm-none-linux-gnueabihf`, and Clang finds its libraries only under that
name;
- `-print-prog-name=as` returning a bare `as` means GCC found no assembler for its target and will
run the host's (`invalid -march= option`).
- **Idempotence.** CMake reads a toolchain file again for every scope that needs it (`project()`,
each enabled language, `try_compile`), so everything here must be cheap and leave the same result
when repeated; flags appended to `*_FLAGS_INIT` are added only if absent.
#### Cross builds and system libraries
A cross compiler package ships the target's C/C++ runtime and nothing else, and a cross build looks only
in the target's own root, never at the host's libraries (they are for the wrong architecture). So a
dependency that needs another system library -- raylib's GLFW wants X11, others OpenGL or ALSA -- stops
with `Could NOT find X11` even though the host has it. The fix is a **target sysroot**: a root filesystem
for the target (`usr/include`, `usr/lib`, …) with those libraries installed.
- **Where it is looked for.** `-DCMAKE_SYSROOT=<dir>` (also in a preset), else
`.cache/sysroots/linux-<arch>` (`linux-arm64`, `linux-arm32`, `linux-x64`) if that directory exists:
one per architecture, shared by the gcc and clang presets, so several cross presets build one after
another with no options. The configure prints `Target sysroot: …` or, when there is none,
`Cross build without a target sysroot …`; `breeze/scripts/build.sh` repeats the explanation when such a
configure fails on something not found. `.cache/sysroots/` is git-ignored.
- **What it changes.** `--sysroot` for GCC and Clang; `find_package`/`find_library` search it before the
compiler's own root; qemu runs the result against it (`-L`) when it holds the target's dynamic loader;
pkg-config answers from its `.pc` files only (`PKG_CONFIG_SYSROOT_DIR`, `PKG_CONFIG_LIBDIR`, and
`PKG_CONFIG_PATH` dropped); a Debian-style `usr/lib/<triplet>` layout is found too.
- **Before the first configure, or a fresh build directory after.** A build directory keeps what CMake
detected about the compiler the first time it was configured, so adding, changing or removing a
sysroot afterwards stops the configure and names the directory to delete
(`rm -rf out/build/<preset>`); `breeze/scripts/build.sh` says the same when a configure fails on a
missing library.
- **Getting one.** Whatever gives you the target's own libraries and headers:
- *Copy it from the target*, with its `-dev` packages installed:
`rsync -aR pi@raspberrypi:/lib pi@raspberrypi:/usr/include pi@raspberrypi:/usr/lib .cache/sysroots/linux-arm64/`
(absolute symlinks that come along can be made relative with `symlinks -rc`).
- *Arch host:* the Arch Linux ARM root filesystem (archlinuxarm.org/about/downloads; check the
signature listed there), with the packages installed through qemu. For 32-bit ARM use the `armv7`
tarball and `linux-arm32`.
```bash
sudo pacman -S --needed qemu-user-static qemu-user-static-binfmt arch-install-scripts libarchive
sudo systemctl restart systemd-binfmt
mkdir -p .cache/sysroots/linux-arm64
sudo bsdtar -xpf ArchLinuxARM-aarch64-latest.tar.gz -C .cache/sysroots/linux-arm64
sudo arch-chroot .cache/sysroots/linux-arm64 /bin/bash -c \
'pacman-key --init && pacman-key --populate archlinuxarm && pacman -Syu --noconfirm libx11 libxrandr libxinerama libxcursor libxi mesa'
```
- *Debian or Ubuntu host:* `debootstrap --arch=arm64 --include=libx11-dev,libxrandr-dev,libxinerama-dev,libxcursor-dev,libxi-dev,libgl-dev <suite> .cache/sysroots/linux-arm64 <mirror>`
with `qemu-user-static` installed.
- **Or build natively.** Run the build inside that same root filesystem under qemu (`arch-chroot`,
`chroot`) or on the device itself: an aarch64 machine takes the `linux-arm64-*` presets as native
builds, with no sysroot involved.
- **If linking picks the wrong C library.** The sysroot's glibc is used, not the cross package's. A
sysroot much older than the cross compiler's own glibc can fail to link; update the sysroot.
### macOS
Apple's clang is one multi-architecture compiler, so building x86_64 on Apple Silicon, or the
reverse, is an `-arch` flag, not a cross build: no sysroot, no emulator, `CMAKE_CROSSCOMPILING` stays
off. `pmn_macos_toolchain` pins `CMAKE_OSX_ARCHITECTURES` explicitly, even when it equals the
machine's (an x86_64 `cmake` under Rosetta would otherwise quietly build x86_64 for an arm64 target),
and sets `CMAKE_OSX_DEPLOYMENT_TARGET` to 11.0 unless you pass another; Apple's libc++ marks newer
library features (`std::format`, `std::pmr`, …) unavailable below the macOS that introduced them.
Universal binaries are not a target: build both presets and `lipo -create` the results. Apple's
clang (Command Line Tools) and LLVM's own, e.g. Homebrew's, both count as Clang; Homebrew's llvm is
keg-only and is where `clang-tidy` comes from, so `breeze/scripts/refactor.sh` and the hook look in
`/opt/homebrew/opt/llvm/bin` and `/usr/local/opt/llvm/bin` after `PATH`.
### Windows
- **MSVC** (`pmn_msvc_toolchain`) picks `cl`; its architecture comes from the developer environment it
runs in, not from CMake, so the architecture check is what notices the wrong prompt (a 32-bit-
targeting `cl` is reported with the fix). **clang-cl** picks `clang-cl` and pins
`--target=<arch>-pc-windows-msvc`. **MSYS2 Clang** (`pmn_msys_clang_toolchain`) picks `clang++`;
CLANG64 / CLANGARM64 decides its target. **MinGW** (`pmn_mingw_toolchain`) uses
`<triplet>-g++` (`x86_64-w64-mingw32`, `i686-w64-mingw32`), natively on Windows or as a cross build
from Linux, where `wine` runs the result.
- **Checks.** For the presets that link against MSVC's runtime libraries (msvc and clang-cl),
`pmn_check_msvc_target` warns once, before CMake's compiler test fails on it, when the prompt's
`VSCMD_ARG_TGT_ARCH` names another target or `VCToolsInstallDir\lib\<arch>\msvcrt.lib` does not
exist, i.e. the target's Visual Studio component is not installed.
- **Runtime.** MinGW executables use a dynamic GCC runtime whose DLLs are copied beside them (see
[Modules](#modules)).
- **`wine` hosts.** On x64 Debian/Ubuntu `wine` needs `wine64` and `wine32:i386` (an i386 multiarch
that setup offers to enable); on Arch it lives in `multilib`, which setup offers to enable. Wine is
not offered on ARM64 hosts.
#### Windows environments
`breeze\scripts\build.bat` calls `breeze\scripts\_winenv.bat <preset>` right before configuring. The environment
lives only in that run (`setlocal`); the user's own `cmake` is put back in front of `PATH`
afterwards, because Visual Studio's environment adds its own, often older, one.
| Presets | Environment entered |
|---|---|
| `windows-x64-msvc` `windows-arm64-msvc` | `vcvarsall.bat` of the newest Visual Studio or Build Tools that has the target's component, found with `vswhere` (the x86/x64 tools, plus the ARM64 tools for arm64, so an older Visual Studio that has them beats a newer one that doesn't), with `x64`, `x64_arm64`, `arm64` or `arm64_x64` according to the target and the machine (`x86_x64` and `x86_arm64` on 32-bit Windows). It then checks that `lib\<target>\msvcrt.lib` exists under `VCToolsInstallDir` and stops with the component to add if not: with the target's component missing, `vcvarsall` still reports the target and leaves the host's x64 `cl` on `PATH`, so neither its exit status nor `VSCMD_ARG_TGT_ARCH` is proof. |
| `windows-x64-clang-cl` `windows-arm64-clang-cl` | The same, then `clang-cl` on `PATH` if absent: `Program Files\LLVM\bin`, else Visual Studio's own copy (`VC\Tools\Llvm\x64\bin`, `…\ARM64\bin`, `…\bin`). |
| `windows-x64-mingw64` `windows-x86-mingw32` | `<MSYS2>\mingw64\bin` or `mingw32\bin` first on `PATH`, unless the preset's compiler is already on it. |
| `windows-x64-clang` `windows-arm64-clang` | `<MSYS2>\clang64\bin` or `clangarm64\bin` first on `PATH`, always: a `clang++` from elsewhere targets another ABI. `clangarm64` exists only on ARM64 Windows; on x64 the helper says so and points at the cross-building presets. |
`<MSYS2>` is `MSYS2_ROOT`, else `%SystemDrive%\msys64`. Left alone: a shell that already is a
developer prompt for the right target (`VSCMD_ARG_TGT_ARCH` and `cl` on `PATH`), presets it doesn't
know, and everything when `PMN_NO_ENV` is set. `vcvarsall` is judged by what it leaves behind, not
by its exit status; a failure prints its output and the likely fix. The helper is flat batch on
purpose (single-line `if` and `goto`, no parenthesized blocks), because the paths it expands contain
`(x86)`.
## Setup internals
`breeze/scripts/setup.sh` and `setup.bat` run four steps: check and offer the tools, fetch the pinned
Ninja, fetch the pinned Doxygen, activate the pre-commit hook (`git config core.hooksPath
breeze/githooks`). A step that cannot finish (no pinned binary for this machine, no network) says so and
the others still run.
- **Catalog.** `_tools.sh` holds one row per tool: id, required or optional, label, what it is for
and the package for each manager. `_tools.ps1` holds the Windows equivalent. For each missing tool
setup says what it is for, shows the exact command and waits; required tools default to yes,
optional ones to no.
- **Cross compilers must work, not merely exist.** An AArch64, 32-bit ARM or x86-64 cross compiler
counts as installed only if it compiles *and links* a small C++ program (which needs the target's
assembler, headers, libstdc++, crt1.o and libc). One that can't is reported as `[part]` with the
compiler's first error line and offered for repair. After an install the test runs again and, on a
failure, prints `-dumpmachine`, `-print-sysroot`, `-print-prog-name=as` and the installed
`<triplet>-*` packages.
- **Consent.** Third-party extras (today only the AUR toolchain below) are never answered by `--all`
or by an earlier `a`: only `--everything` or a `y` typed at their own question.
### What setup can install
| Tool | apt | dnf | pacman | Homebrew |
|---|---|---|---|---|
| Git, CMake | `git` `cmake` | `git` `cmake` | `git` `cmake` | `git` `cmake` |
| C++ compiler | `g++` | `gcc-c++` | `gcc` | Xcode Command Line Tools |
| Clang | `clang` | `clang` | `clang` | Apple's, from the tools |
| clang-format | `clang-format` | `clang-tools-extra` | `clang` | `clang-format` |
| clang-tidy | `clang-tidy` | `clang-tools-extra` | `clang` | `llvm` |
| AArch64 cross | `g++-aarch64-linux-gnu` | `aarch64-linux-gnu-{binutils,gcc,glibc}` (COPR) | `aarch64-linux-gnu-gcc` (x64 hosts) | — |
| 32-bit ARM cross | `g++-arm-linux-gnueabihf` | `arm-linux-gnueabihf-{binutils,gcc,glibc}` (COPR) | AUR, below (x64 hosts) | — |
| x86-64 cross | `g++-x86-64-linux-gnu` | — | — | — |
| MinGW-w64 | `g++-mingw-w64-x86-64` `g++-mingw-w64-i686` | `mingw64-gcc-c++` `mingw32-gcc-c++` | `mingw-w64-gcc` | — |
| qemu-user | `qemu-user` | `qemu-user` | `qemu-user` | — |
| wine | `wine` `wine64` `wine32:i386` | `wine` | `wine` (multilib) | — |
| Ninja | `ninja-build` | `ninja-build` | `ninja` | `ninja` |
Ninja is only offered where no pinned download exists (32-bit ARM Linux). Fedora's own cross GCC
packages ship no libstdc++, so on dnf the ARM compilers come from lantw44's COPR, enabled only after
a separate confirmation. On Windows, setup uses winget for Git, CMake, Visual Studio 2022 Build
Tools (C++ workload), LLVM and MSYS2; adds the "MSVC ARM64 build tools" component
(`Microsoft.VisualStudio.Component.VC.Tools.ARM64`) to an existing Visual Studio through the
Visual Studio Installer's `modify` command, which asks for administrator rights; and uses MSYS2's
`pacman` for `mingw-w64-x86_64-gcc`,
`mingw-w64-i686-gcc` and `mingw-w64-clang-x86_64-clang`; it can add the tools' folders to the user
`PATH`. On macOS it runs `xcode-select --install` and, if you agree, Homebrew's installer.
**32-bit ARM on Arch** has no official package. The AUR carries ARM's own prebuilt GNU toolchain
(x86-64 hosts only) as four packages, `arm-linux-gnueabihf-{binutils,glibc-headers,glibc,gcc}-bin`.
Setup builds each from its AUR repository with `makepkg` (sources are downloaded once into
`~/.cache/pmn-aur-arm32/`) and installs all four in one transaction,
`pacman -U --noconfirm --needed --ask 4`. `--ask 4` pre-answers yes to "X and Y are in conflict.
Remove Y?" (pacman's default, and `--noconfirm`'s, is no); that is how a source-built binutils or
glibc is replaced, and it applies to any conflicting package in the transaction, which the prompt says.
Leftover `gcc-stage1` / `gcc-stage2` packages are removed first, and nothing is removed until all
four have built. Installing `arm-linux-gnueabihf-gcc-bin` alone through an AUR helper does not work:
its dependencies are generic names (`binutils`, `glibc`, `glibc-headers`), which a helper resolves
to the source-built packages (hours of compiling), leaving ARM's prebuilt GCC beside a binutils and
glibc that don't fit it.
## Script internals
- **Same options, two languages.** Each script is `.sh` (bash 3.2-compatible, macOS's default shell:
no `mapfile`, no bash-4 expansions, empty arrays guarded for `set -u`, BSD `sed` and `mktemp`) and
`.bat`. `_common.sh` holds what the `.sh` scripts share: reading config, resolving the build
directory, the Homebrew llvm lookup. `build.bat` additionally enters the compiler environment
([Windows environments](#windows-environments)); the `.sh` scripts assume you are in the one you
need, which on Linux and macOS is every shell.
- **Which build.** `--preset NAME` selects `out/build/NAME`, otherwise `BuildDir.cmake` picks the
directory recorded in `out/build/.last`, falling back to `default`. `build` records the directory
it used.
- **`build`** prefers the pinned Ninja, then one on `PATH`, then CMake's default generator with a
warning. `cmd.exe` splits `%1`, `%2`, … on `=`, `,` and `;`, which would mangle `-DNAME=value`,
so `build.bat` takes the preset and build type off the front and forwards the rest from the
unsplit `%*`.
- **`run`** finds the executable in `module_manifest-<CONFIG>.txt`, reads
`CMAKE_CROSSCOMPILING_EMULATOR` from the build's `CMakeCache.txt` and runs through it. Under
wine it sets `XDG_RUNTIME_DIR` and a private `WINEPREFIX` in `.cache/wine/` (`WINEARCH` for the
x86 and x64 targets); `run.bat` runs natively.
- **`install`** runs `cmake --install --component <PROJECT_CODE_NAME>` into
`out/install/<DIST_TARGET>`.
- **`refactor`** runs clang-format, then clang-tidy with `--fix`, over `source/` and `c-api/`.
## Style and tooling
`StyleGuide.md` has two parts: C++ (PascalCase and camelCase naming) and a C API surface
(snake_case). Formatting (tabs, Allman braces, line length) comes from the single root
`.clang-format`; naming from the root `.clang-tidy`, and `c-api/.clang-tidy` swaps in the C rules for
files under `c-api/`. `.editorconfig` and `.gitattributes` keep editors and line endings in line
(`.bat` and `.ps1` stay CRLF). The pre-commit hook runs clang-format and clang-tidy on staged C/C++
files and skips with a warning when the tools are missing.
## Embedding
Used through `add_subdirectory`, the root project decides: `OUT_DIR` derives from the outermost
`CMAKE_SOURCE_DIR`, and its toolchain and presets apply.
## Verification status
- **Linux and Windows presets, Arch x64:** the 32-bit ARM presets build with the prebuilt AUR
toolchain (reported by the maintainer). The Arch install and repair logic was also exercised
against stand-in `pacman`, `git`, `makepkg` and cross-compiler programs: broken compiler,
declined, `--all`, `--everything`, a failing build leaving the machine untouched.
- **`_winenv.bat`:** exercised with an emulator of the batch subset it uses against a fake machine
(every preset, 32-bit, x64 and ARM64 hosts, already-correct prompts, an x64 machine without the
ARM64 tools, damaged installs, and each failure path). Its first run on a real `cmd.exe` is its
first test there.
- **Toolchain checks in `_common.cmake`** (including `pmn_check_msvc_target`): written without a
CMake available to run them; the Arch 32-bit ARM configure was their first real run, and the
Windows ones are untested.
- **Setup's ARM64 component row** (`Get-MsvcArm64Status`, `Install-MsvcComponent` in
`_tools.ps1`): written without PowerShell available; only bracket and quote balance were
checked.
- **Target sysroots** (`pmn_find_sysroot`, `pmn_use_sysroot`, the explanation `breeze/scripts/build.sh` adds to a
failed cross configure): exercised on Linux x64 with CMake 3.28 and Ubuntu's cross compilers, against a
*stand-in* sysroot (the cross libc, real X11 headers, and empty stub libraries compiled for the target),
not against Arch Linux ARM or Raspberry Pi OS packages. arm64 and arm32, gcc and clang, found by
directory and by `-DCMAKE_SYSROOT`, flat and Debian-style multiarch layouts, pkg-config kept to the
sysroot, a sysroot without a loader, a missing one, a wrong one; a raylib 5.5 game built for arm64 stops
with `Could NOT find X11` without a sysroot and builds with one. The native x64 and MinGW presets were
rebuilt unchanged. The recipes above for *making* a sysroot (`rsync`, Arch Linux ARM, `debootstrap`) have
not been run, and neither a Mac nor Windows has run any of this; the new code is reached only by Linux
cross builds.
- **The `breeze/` and `project/` layout:** the Linux side was exercised from the finished tree and
from the zip: the presets through the root file's `include` (and a project preset that inherits one),
native x64 gcc, arm64 gcc and clang and arm32 gcc with and without a sysroot, MinGW from Linux, a raylib
game for arm64, and `setup`, `build`, `run`, `install`, `docs`, `refactor`, the `cmake -P` queries and
the pre-commit hook; also replacing `breeze/` with a pristine copy, a `project/Configuration.cmake` that
lacks a setting, and a sysroot added or removed over an existing build directory. The changes to the
`.bat` and `.ps1` scripts are the moved paths and the `cd` to the root, nothing else (each changed line
was compared with the old file; CRLF endings are intact); nothing was run on Windows or on a Mac.
- **Objects modules:** built and run on Linux with gcc and clang for x64, arm64 and arm32 (the ARM ones
under qemu), and with MinGW from Linux (built and linked; the DLL exports the pieces' symbols, nothing
was run). A scratch tree covered pieces three deep, pieces behind a static library, a static and a
shared library, an executable, a test and an example taking pieces, consumers discovered before their
pieces, a raw library name in `DEPENDS`, and a cycle. Each piece appears once in the archive and on the
link line. Not run: MSVC and macOS. For MSVC, CMake's own generator source shows the absorbed objects
feeding the `.def` file that `WINDOWS_EXPORT_ALL_SYMBOLS` produces, so the DLL should export them; that
is a reading, not a run.
- **A dependency declared by one module and used by another** (`fmt::fmt`, declared in `source/Core`,
used by `source/CoreFarewell`): built with fmt 12.1.0 installed (static and shared) and not installed,
with CMake 3.25.2, 3.27.9, 3.28.3, 3.31.6 and 4.1.2, plus a module that CMake reaches before the one
that declares it. Cross builds ignore the host's copy and fetch one for the target.
- **macOS:** the toolchain, `TargetSelection.cmake`'s detection and the RPATH translation were
exercised by cross-compiling to Mach-O from Linux and reading the load commands (`LC_RPATH
@loader_path`, `@rpath/libdemo_core.dylib`, arm64 and x86_64); the scripts ran under a real bash
3.2. No Mac has run Apple's own clang and SDK yet, so look there first if something is off.