commit f2fe6b88555fd45b90aea343e8536eca74d5a3cb Author: Acrux Development Date: Sat Oct 24 09:53:48 2026 -0300 Add project files diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..541defa --- /dev/null +++ b/.clang-format @@ -0,0 +1,117 @@ +--- +# .clang-format +# Enforces the formatting rules from the C/C++ Coding Style Guide (Part I: C++ Style). +# Applies equally to Part II C API files per §15 (Indentation and Braces) — same +# tabs/Allman/line-length rules, just different naming (naming can't be enforced +# by clang-format; see .clang-tidy). +Language: Cpp +Standard: Latest + +# --- Indentation (style guide §2) --- +UseTab: Always +TabWidth: 4 +IndentWidth: 4 +ContinuationIndentWidth: 4 +IndentCaseLabels: true +IndentPPDirectives: None +IndentExternBlock: Indent + +# Access specifiers (public:/private:) sit unindented, flush with the class +# keyword — only members are indented one level, matching every example in §1/§6. +AccessModifierOffset: -4 + +# Namespace bodies are indented like any other block (style guide §Namespaces). +NamespaceIndentation: All + +# --- Braces: Allman style everywhere (style guide §3) --- +BreakBeforeBraces: Allman +Cpp11BracedListStyle: true +# InlineOnly (not Empty or None): out-of-class function definitions always +# get full Allman expansion regardless of length, same as before -- this +# only additionally allows a function *defined inside a class body* to +# stay a compact one-liner if it's short enough to fit. That's specifically +# for the trivial-one-liner exception to "avoid inline member definitions" +# in Class Member Organization (style guide §6) -- a plain getter like +# `int getValue() const { return m_value; }` stays inline-and-compact; +# anything defined out-of-class still always gets the full brace expansion. +AllowShortFunctionsOnASingleLine: InlineOnly +AllowShortIfStatementsOnASingleLine: Never +AllowShortLoopsOnASingleLine: false +AllowShortBlocksOnASingleLine: Never +AllowShortEnumsOnASingleLine: false +AllowShortCaseLabelsOnASingleLine: false + +# No trailing "// namespace X" / "// class X" comments on closing braces +# (style guide §Namespaces / §6). +FixNamespaceComments: false + +# --- Pointers and references attach to the type (style guide §4) --- +PointerAlignment: Left +ReferenceAlignment: Pointer +DerivePointerAlignment: false + +# --- Line length (style guide §8) --- +ColumnLimit: 100 + +# --- Spacing (style guide §5) --- +SpaceBeforeParens: ControlStatements +SpacesInParentheses: false +SpacesInSquareBrackets: false +SpaceAfterCStyleCast: false +SpaceBeforeAssignmentOperators: true +SpacesInAngles: false + +# --- Include ordering (style guide §7: main header, C headers, C++ std, +# third-party, project headers) --- +SortIncludes: true +SortUsingDeclarations: true +IncludeBlocks: Regroup +IncludeIsMainRegex: '(Test)?$' +IncludeCategories: + # 1. Corresponding header is auto-detected and always sorted first. + # 2. C system headers wrapped for C++ (, , ...). + - Regex: '^<(cassert|cctype|cerrno|cfenv|cfloat|cinttypes|climits|clocale|cmath|csetjmp|csignal|cstdarg|cstddef|cstdint|cstdio|cstdlib|cstring|ctime|cuchar|cwchar|cwctype)>$' + Priority: 1 + CaseSensitive: true + # 3. C++ standard library headers (no dot, no slash — , , ...). + - Regex: '^<[a-z_]+>$' + Priority: 2 + CaseSensitive: true + # 4. Third-party / external library headers (, , ...). + - Regex: '^<.*>$' + Priority: 3 + CaseSensitive: true + # 5. Project headers, quoted ("NetworkManager.hpp", ...). + - Regex: '^".*"$' + Priority: 4 + CaseSensitive: true + +# --- Misc --- +AlignTrailingComments: true +AlignConsecutiveAssignments: false +AlignConsecutiveDeclarations: false + +# Parameters/arguments: never one-per-line (style guide §8). When a +# signature/call doesn't fit on one line, keep everything together on a +# single continuation line if it fits there (AllowAll...OnNextLine), and +# fall back to packing multiple per line (BinPack...) rather than ever +# breaking to one per line. This can't force a genuinely long signature +# to fit within ColumnLimit without wrapping at all -- doing that would +# require ColumnLimit: 0 project-wide, which would also stop comments and +# ordinary expressions from ever wrapping. See §8 for the full rule. +# +# COMPATIBILITY NOTE: clang-format 20 changed BinPackParameters (and the +# equivalent for call arguments) from a boolean to an enum, and later +# versions deprecated it again in favor of a nested PackParameters/ +# PackArguments option. The boolean form below is correct for clang-format +# through 19 (still the most common version in practice, e.g. Ubuntu +# 24.04 LTS ships 18). On clang-format 20 or newer, replace `true` with +# `BinPack` on both lines if you find these are being silently ignored. +BinPackArguments: true +BinPackParameters: true +AllowAllArgumentsOnNextLine: true +AllowAllParametersOfDeclarationOnNextLine: true + +BreakConstructorInitializers: BeforeColon +ConstructorInitializerIndentWidth: 4 +PenaltyReturnTypeOnItsOwnLine: 1000 diff --git a/.clang-tidy b/.clang-tidy new file mode 100644 index 0000000..87e69b8 --- /dev/null +++ b/.clang-tidy @@ -0,0 +1,161 @@ +--- +# .clang-tidy +# Enforces naming conventions (style guide §1) and a subset of the Language +# Feature Do's and Don'ts (style guide §10) for C++ code. Place at the repo +# root for Part I (C++) files. For Part II (C API) files, use the +# directory-scoped override in c-api/.clang-tidy instead — clang-tidy picks +# the closest .clang-tidy file up the directory tree, so nesting one there +# gives that subtree different naming rules without affecting the rest of +# the project. +# +# readability-redundant-inline-specifier is a narrow, complementary aid for +# the "avoid inline member definitions" rule in §6 (Class Member +# Organization): it only catches an explicit `inline` keyword that's +# already redundant (e.g. on a function defined in the class body, which +# is implicitly inline regardless). It does not catch — and nothing in +# this file catches — an inline definition that omits the keyword +# entirely, which is the actual, common case the style guide rule is +# about; that one stays a code-review judgment call. +Checks: > + -*, + readability-identifier-naming, + readability-redundant-inline-specifier, + modernize-use-nullptr, + modernize-use-override, + modernize-use-equals-default, + modernize-use-equals-delete, + modernize-loop-convert, + modernize-concat-nested-namespaces, + modernize-use-default-member-init, + modernize-avoid-c-arrays, + modernize-use-nodiscard, + cppcoreguidelines-special-member-functions, + cppcoreguidelines-owning-memory, + cppcoreguidelines-no-malloc, + cppcoreguidelines-pro-type-member-init, + cppcoreguidelines-pro-type-cstyle-cast, + cppcoreguidelines-macro-usage, + performance-unnecessary-value-param, + performance-unnecessary-copy-initialization, + bugprone-exception-escape, + bugprone-use-after-move + +WarningsAsErrors: '' +HeaderFilterRegex: '.*\.hpp$' +FormatStyle: file + +CheckOptions: + # --- Naming Conventions — style guide §1 --- + - key: readability-identifier-naming.NamespaceCase + value: lower_case + + - key: readability-identifier-naming.ClassCase + value: CamelCase + - key: readability-identifier-naming.StructCase + value: CamelCase + - key: readability-identifier-naming.EnumCase + value: CamelCase + - key: readability-identifier-naming.EnumConstantCase + value: CamelCase + + - key: readability-identifier-naming.FunctionCase + value: camelBack + - key: readability-identifier-naming.MethodCase + value: camelBack + + - key: readability-identifier-naming.VariableCase + value: camelBack + - key: readability-identifier-naming.LocalVariableCase + value: camelBack + - key: readability-identifier-naming.ParameterCase + value: camelBack + + # A plain local `const` (not `static`, not `constexpr`) stays under the + # Local Variables rule (camelCase) per the style guide's own note that + # only *global/static* const/constexpr values are "constants" for this + # rule's purposes -- without this, it silently inherits the general + # ConstantCase (PascalCase) fallback below instead, which would flag + # totally ordinary code like `const std::string name = ...;` inside a + # function. StaticConstantCase/GlobalConstantCase/ConstexprVariableCase + # below are unaffected and still require PascalCase, matching the guide. + - key: readability-identifier-naming.LocalConstantCase + value: camelBack + + # Private/protected member variables: camelCase with an m_ prefix. + - key: readability-identifier-naming.PrivateMemberCase + value: camelBack + - key: readability-identifier-naming.PrivateMemberPrefix + value: m_ + - key: readability-identifier-naming.ProtectedMemberCase + value: camelBack + - key: readability-identifier-naming.ProtectedMemberPrefix + value: m_ + # Public members (e.g. plain data structs) — camelCase, no prefix. + - key: readability-identifier-naming.PublicMemberCase + value: camelBack + + # Static class data members — camelCase with an s_ prefix (distinct from + # instance members above). "ClassMember" is clang-tidy's category for + # static (non-const) data members specifically. + - key: readability-identifier-naming.ClassMemberCase + value: camelBack + - key: readability-identifier-naming.ClassMemberPrefix + value: s_ + + # Global (namespace/file-scope, mutable) variables — camelCase with a g_ + # prefix, to flag genuinely global mutable state at every use site. + - key: readability-identifier-naming.GlobalVariableCase + value: camelBack + - key: readability-identifier-naming.GlobalVariablePrefix + value: g_ + + # Static variables — function-local `static` or file-scope `static` — + # camelCase with an s_ prefix, for the same reason as globals above. + - key: readability-identifier-naming.StaticVariableCase + value: camelBack + - key: readability-identifier-naming.StaticVariablePrefix + value: s_ + + # Constants and enum values — PascalCase (style guide §1 table). + - key: readability-identifier-naming.ConstantCase + value: CamelCase + - key: readability-identifier-naming.GlobalConstantCase + value: CamelCase + - key: readability-identifier-naming.StaticConstantCase + value: CamelCase + - key: readability-identifier-naming.ClassConstantCase + value: CamelCase + - key: readability-identifier-naming.ConstexprVariableCase + value: CamelCase + + # Template parameters — PascalCase (e.g. ValueType). + - key: readability-identifier-naming.TemplateParameterCase + value: CamelCase + - key: readability-identifier-naming.TypeTemplateParameterCase + value: CamelCase + + # Type aliases / typedefs — PascalCase, matching class naming. + - key: readability-identifier-naming.TypeAliasCase + value: CamelCase + - key: readability-identifier-naming.TypedefCase + value: CamelCase + + # Macros — ALL_CAPS_SNAKE_CASE (the one deliberate exception in §1). + - key: readability-identifier-naming.MacroDefinitionCase + value: UPPER_CASE + + # --- cppcoreguidelines-owning-memory — style guide §10, Memory and Ownership --- + # Flags raw new/delete and legacy C allocators used for ownership instead of + # std::unique_ptr / std::shared_ptr. + - key: cppcoreguidelines-owning-memory.LegacyResourceProducers + value: 'malloc;calloc;realloc;strdup;strndup' + - key: cppcoreguidelines-owning-memory.LegacyResourceConsumers + value: 'free;realloc' + + # --- cppcoreguidelines-macro-usage — style guide §10, prefer constexpr over #define --- + # Allow include-guard-style and project-prefixed macros; flag everything else + # that could instead be a constexpr constant. + - key: cppcoreguidelines-macro-usage.AllowedRegexp + value: '^[A-Z0-9_]+_H(PP)?$' + - key: cppcoreguidelines-macro-usage.CheckCapsOnly + value: false diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..a36a39a --- /dev/null +++ b/.editorconfig @@ -0,0 +1,34 @@ +# .editorconfig +# Baseline whitespace/indentation rules for editors that don't run +# clang-format live (or before it runs). See .clang-format for the +# authoritative, full formatting rules from the style guide. +root = true + +[*] +charset = utf-8 +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true + +# Windows scripts are CRLF (see .gitattributes). Without this, the global +# end_of_line = lf above would have editors rewrite them as LF. +[*.{bat,cmd,ps1}] +end_of_line = crlf + +# Style guide §2 / §15 — tabs everywhere, C++ and C API alike. +[*.{c,h,cpp,hpp,cc,cxx,tpp}] +indent_style = tab +indent_size = 4 +tab_width = 4 +max_line_length = 100 + +# Config/build files typically use spaces by convention — not covered by the +# C/C++ style guide, kept here only so this file has a sane fallback for +# CMakeLists.txt, YAML, etc. in the same repo. +[*.{yml,yaml,cmake,json}] +indent_style = space +indent_size = 2 + +[CMakeLists.txt] +indent_style = space +indent_size = 2 diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..f53f6af --- /dev/null +++ b/.gitattributes @@ -0,0 +1,29 @@ +# .gitattributes +# +# Forces LF line endings for source files regardless of OS or each +# contributor's core.autocrlf setting. Without this, the same file can have +# CRLF on a Windows checkout and LF on Linux/Mac, which makes clang-format +# and the pre-commit hook's diff check report false formatting differences +# that have nothing to do with actual style. +* text=auto eol=lf + +# Be explicit for the style guide's own file types, on top of the general +# rule above. +*.c text eol=lf +*.h text eol=lf +*.cpp text eol=lf +*.hpp text eol=lf +*.cc text eol=lf +*.cxx text eol=lf +*.tpp text eol=lf + +# Windows-specific scripts keep CRLF (cmd.exe is fussy about LF-only batch +# files; PowerShell scripts follow the same convention). +*.bat text eol=crlf +*.cmd text eol=crlf +*.ps1 text eol=crlf + +# Never touch binaries. +*.png binary +*.jpg binary +*.ico binary diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..de463c9 --- /dev/null +++ b/.gitignore @@ -0,0 +1,441 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. +## +## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore + +# User-specific files +*.rsuser +*.suo +*.user +*.userosscache +*.sln.docstates +*.env + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Mono auto generated files +mono_crash.* + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ + +[Dd]ebug/x64/ +[Dd]ebugPublic/x64/ +[Rr]elease/x64/ +[Rr]eleases/x64/ +bin/x64/ +obj/x64/ + +[Dd]ebug/x86/ +[Dd]ebugPublic/x86/ +[Rr]elease/x86/ +[Rr]eleases/x86/ +bin/x86/ +obj/x86/ + +[Ww][Ii][Nn]32/ +[Aa][Rr][Mm]/ +[Aa][Rr][Mm]64/ +[Aa][Rr][Mm]64[Ee][Cc]/ +bld/ +[Oo]bj/ +[Oo]ut/ +[Ll]og/ +[Ll]ogs/ + +# Build results on 'Bin' directories +**/[Bb]in/* +# Uncomment if you have tasks that rely on *.refresh files to move binaries +# (https://github.com/github/gitignore/pull/3736) +#!**/[Bb]in/*.refresh + +# Visual Studio 2015/2017 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# Visual Studio 2017 auto generated files +Generated\ Files/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* +*.trx + +# NUnit +*.VisualState.xml +TestResult.xml +nunit-*.xml + +# Approval Tests result files +*.received.* + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# Benchmark Results +BenchmarkDotNet.Artifacts/ + +# .NET Core +project.lock.json +project.fragment.lock.json +artifacts/ +.artifacts/ + +# ASP.NET Scaffolding +ScaffoldingReadMe.txt + +# StyleCop +StyleCopReport.xml + +# Files built by Visual Studio +*_i.c +*_p.c +*_h.h +*.ilk +*.meta +*.obj +*.idb +*.iobj +*.pch +*.pdb +*.ipdb +*.pgc +*.pgd +*.rsp +# but not Directory.Build.rsp, as it configures directory-level build defaults +!Directory.Build.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*_wpftmp.csproj +*.log +*.tlog +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# Visual Studio Trace Files +*.e2e + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + +# Coverlet is a free, cross platform Code Coverage Tool +coverage*.json +coverage*.xml +coverage*.info + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# Note: Comment the next line if you want to checkin your web deploy settings, +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# NuGet Symbol Packages +*.snupkg +# The packages folder can be ignored because of Package Restore +**/[Pp]ackages/* +# except build/, which is used as an MSBuild target. +!**/[Pp]ackages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/[Pp]ackages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt +*.appx +*.appxbundle +*.appxupload + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!?*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +orleans.codegen.cs + +# Including strong name files can present a security risk +# (https://github.com/github/gitignore/pull/2483#issue-259490424) +#*.snk + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm +ServiceFabricBackup/ +*.rptproj.bak + +# SQL Server files +*.mdf +*.ldf +*.ndf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings +*.rptproj.rsuser +*- [Bb]ackup.rdl +*- [Bb]ackup ([0-9]).rdl +*- [Bb]ackup ([0-9][0-9]).rdl + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat +node_modules/ + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio 6 workspace and project file (working project files containing files to include in project) +*.dsw +*.dsp + +# Visual Studio 6 technical files +*.ncb +*.aps + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +**/.paket/paket.exe +paket-files/ + +# FAKE - F# Make +**/.fake/ + +# CodeRush personal settings +**/.cr/personal + +# Python Tools for Visual Studio (PTVS) +**/__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +#tools/** +#!tools/packages.config + +# Tabs Studio +*.tss + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs + +# OpenCover UI analysis results +OpenCover/ + +# Azure Stream Analytics local run output +ASALocalRun/ + +# MSBuild Binary and Structured Log +*.binlog +MSBuild_Logs/ + +# AWS SAM Build and Temporary Artifacts folder +.aws-sam + +# NVidia Nsight GPU debugger configuration file +*.nvuser + +# MFractors (Xamarin productivity tool) working folder +**/.mfractor/ + +# Local History for Visual Studio +**/.localhistory/ + +# Visual Studio History (VSHistory) files +.vshistory/ + +# BeatPulse healthcheck temp database +healthchecksdb + +# Backup folder for Package Reference Convert tool in Visual Studio 2017 +MigrationBackup/ + +# Ionide (cross platform F# VS Code tools) working folder +**/.ionide/ + +# Fody - auto-generated XML schema +FodyWeavers.xsd + +# VS Code files for those working on multiple tools +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +!.vscode/*.code-snippets + +# Local History for Visual Studio Code +.history/ + +# Built Visual Studio Code Extensions +*.vsix + +# Windows Installer files from build outputs +*.cab +*.msi +*.msix +*.msm +*.msp + +# >>> Configuration.cmake generated paths -- BEGIN (do not edit by hand; edit Configuration.cmake and reconfigure) >>> +.cache/sysroots/ +.cache/tools/ +.cache/wine/ +compile_commands.json +out/ +out/build/ +out/dist/ +out/docs/ +out/install/ +# <<< Configuration.cmake generated paths -- END <<< diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..0f78e17 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,33 @@ +# ============================================================================= +# CMakeLists.txt -- the project's entry point +# +# Deliberately tiny, and the project's: nothing in it changes when the template +# does. Two directories hold everything else: +# +# project/ this project's settings -- its name, version, languages, C++ +# standard, output layout (project/Configuration.cmake) +# breeze/ the template: the build system, toolchains, scripts and docs. +# Replace it wholesale to update the template. +# +# source/ (modules), vendor/ (vendored code) and out/ (everything generated) +# stay at the root, as does c-api/ if you use it. +# ============================================================================= + +cmake_minimum_required(VERSION 3.25) + +# project() takes its arguments from the settings, so they are read first. The +# file holds settings only (no compiler needed), which is what lets the scripts +# read the same file without a configure. +include("${CMAKE_CURRENT_SOURCE_DIR}/project/Configuration.cmake") + +project( + ${PROJECT_TITLE} + VERSION ${PROJECT_VERSION_NUMBER} + DESCRIPTION "${PROJECT_SUMMARY}" + LANGUAGES ${PROJECT_LANGUAGES} +) + +# Everything else -- target selection, output paths, module discovery, install +# rules -- is the template's. Add project-wide CMake of your own below this line +# (or above it, if it has to come before the template's), not in breeze/. +include("${CMAKE_CURRENT_SOURCE_DIR}/breeze/cmake/Breeze.cmake") diff --git a/CMakePresets.json b/CMakePresets.json new file mode 100644 index 0000000..59818a5 --- /dev/null +++ b/CMakePresets.json @@ -0,0 +1,11 @@ +{ + "version": 6, + "cmakeMinimumRequired": { + "major": 3, + "minor": 25, + "patch": 0 + }, + "include": [ + "breeze/CMakePresets.json" + ] +} diff --git a/README.md b/README.md new file mode 100644 index 0000000..3480698 --- /dev/null +++ b/README.md @@ -0,0 +1,187 @@ +# BreezeCLI + +`breeze`: a global command line tool for projects built on the breeze SDK (the CMake template in `breeze/`). +It is written with [CLIForge](source/CLIForge) and is itself a breeze project: build it with +`breeze/scripts/build.sh` (or, once you have a `breeze`, `breeze build`). + +Three things, kept apart on purpose: + +| | What it is | Where it lives | Versioned by | +|---|---|---|---| +| **CLI** | this program; one per machine, any number of versions installed | `~/.breeze/cli//` | its own version (`breeze cli version`) | +| **SDK** | the template: `breeze/`, replaced as a whole on an update | in each project | `breeze/VERSION` (`breeze sdk version`) | +| **Tools** | extensions a project installs from a repository | `/.breeze/tools//` | the tool's tags | + +The CLI is global and the SDK is per project, so one CLI works on projects that are on different SDK versions, and on +the layouts before `breeze/` existed (see [Older SDKs](#older-sdks)). + +## Commands + +Run `breeze --help`, or `breeze --help`, for the exact options. The commands inside a project find it by +walking up from the current directory. + +### Projects and modules + +``` +breeze project new [--id] [--sdk] a new project (no demo code, one executable module to start from) +breeze project version|name|id [--set] the project's version, name (CMake project()) and id +breeze add exec|obj|test|example [--id] [--deps ...] +breeze add lib (--static|--shared) [--id] [--deps ...] +breeze delete [--yes] refuses while another module depends on it +breeze rename [--name] [--id] renames the directory and/or id, fixing the DEPENDS that name it +breeze ls [--filter] [--exec] [--obj] [--lib] [--test] [--example] [--shared] [--static] +breeze deps of [--recursive] what it depends on +breeze deps on [--recursive] what depends on it +``` + +Modules are read from, and written as, the `add_*_module(...)` calls in each `CMakeLists.txt`; the CLI changes only +what it must (a rename touches the id, the directories and the DEPENDS entries, nothing else in the files). Ids default +to `` plus `_exe`, `_lib`, `_obj`, `_test` or `_example`; `--deps` takes modules (by id or +directory) or any other CMake target (`fmt::fmt`). + +### Building and running + +``` +breeze setup [--required|--all] +breeze build [-P platform] [-A arch] [-C compiler] [-M mode] [-j jobs] [-p preset] [--cargs ...] [--bargs ...] [--no-configure] +breeze install [the same, and --no-build] +breeze run [id] [-p/-P/-A/-C/-M ...] [-x arguments ...] +breeze docs +breeze clean [--no-build] [--docs] +breeze defaults build platform|arch|compiler|preset|mode [--set ] +breeze defaults run [--set ] +``` + +These drive the SDK's own scripts (`breeze/scripts/*.sh`, `.bat` on Windows), so the environment they set up (the +pinned Ninja, the MSVC environment) is the one used. A platform, architecture and compiler name a preset +(`-P linux -A x64 -C gcc` is `linux-x64-gcc`, ignoring case and punctuation: `ClangCl` finds `clang-cl`); whatever is +missing comes from the project's defaults, then this machine. `--set none` clears a default. Defaults are kept in +`.breeze/defaults.json`, meant to be committed. + +`--cargs` and `--bargs` take everything up to the next option, dashes included: +`breeze build --cargs -DBUILD_TESTS=ON -DBUILD_EXAMPLES=ON -j 8`. `--bargs` (and `--no-configure`) run +`cmake --build` on the directory the scripts configured, which the SDK's scripts have no way to do; so there must be a +configured build first, and on Windows these two are not supported yet (the MSVC environment comes from the scripts). +`-j` reaches the build through `CMAKE_BUILD_PARALLEL_LEVEL`. + +`run` with no id runs `breeze defaults run`, or the project's only executable. `-M` is accepted for `run` and ignored: +the SDK keeps one build directory per preset. + +### The CLI itself + +``` +breeze cli version | ls +breeze cli install | pick | uninstall +``` + +Versions live in `~/.breeze/cli//` (`$BREEZE_HOME` moves `~/.breeze`); `cli/current` names the picked one. +Every other command first hands itself over to the picked version, so whichever `breeze` your shell finds is the +picked one, and `cli ...` commands always run in the version you started. Put `~/.breeze/bin` on `PATH`: the first +`cli install` puts a copy of `breeze` there. Installing this program's own version needs nothing else: + +``` +./breeze cli install $(./breeze cli version) +``` + +Other versions come from the **CLI source**: `breeze config cli-source --set `, expected to hold +`/breeze--[.exe]` (platform `linux`, `macos`, `windows`; arch `x64`, `arm64`, `arm32`, `x86`). +A URL is downloaded with `curl`. What is installed must run and say it is that version, or nothing is kept. + +### SDK versions + +``` +breeze sdk version | update | upgrade | migrate +``` + +An SDK version is a snapshot of the whole template: `breeze/` (the SDK proper) plus the files a project starts from. +Versions come from the **SDK source**: `breeze config sdk-source --set `. +A repository's tags are the versions (`v1.2.0` or `1.2.0`); a directory has one subdirectory per version +(`1.0.0/`, `1.1.0/`). The version of an SDK is the one line in `breeze/VERSION`, which the repository's tag should match. + +* `update` looks at the source and says what is there, and whether this project is behind. +* `upgrade` moves to the newest version with the same major version. +* `migrate ` moves to exactly that version, any way, across majors; `latest` is the newest release. + +Moving replaces `breeze/` (the old one is kept in `.breeze/backup/`), adds to `project/Configuration.cmake` any setting +the new SDK has and the project's file lacks, and says which dot files (`.clang-format`, ...) now differ from the +SDK's. Nothing else of the project is touched. + +### Tools + +``` +breeze tool install | uninstall | update | upgrade | migrate | ls +breeze tool +``` + +A tool is a git repository (or, while it is being written, a directory) with a `breeze-tool.json` at its root: + +```json +{ "id": "resembed", "version": "1.0.0", "description": "Embeds resources", "entry": "bin/resembed.sh" } +``` + +`entry` may be `{ "posix": "...", "windows": "..." }`. Installed, it is copied into `.breeze/tools//` and recorded in +`.breeze/tools.json` (commit both); `breeze tool ...` then runs the entry with those arguments, in the project's +directory, with `BREEZE_PROJECT_ROOT`, `BREEZE_SDK_DIR`, `BREEZE_SDK_VERSION`, `BREEZE_TOOL_ID`, `BREEZE_TOOL_DIR`, +`BREEZE_CLI`, `BREEZE_HOME` and `BREEZE_EDITOR` set. Everything after the id belongs to the tool, `--help` included, and its +exit code is the CLI's. So `breeze tool san asan enable ` is whatever the `san` tool does with +`asan enable `. `install`, `uninstall`, `update`, `upgrade`, `migrate` and `ls` can't be tool ids. + +### Configuration + +``` +breeze config editor [--set] the editor tools open files with (BREEZE_EDITOR in their environment) +breeze config sdk-source [--set] where SDK versions come from +breeze config cli-source [--set] where CLI versions come from +``` + +Kept in `~/.breeze/config.json`; `BREEZE_EDITOR`, `BREEZE_SDK_SOURCE` and `BREEZE_CLI_SOURCE` in the environment win. +`--set none` clears one. + +## Older SDKs + +The CLI asks each project's SDK where things are instead of assuming: + +* **breeze/ layout** (with or without `breeze/VERSION`): everything above. +* **The flat layout** (`scripts/`, `cmake/`, `Configuration.cmake` at the root): `ls`, `deps`, `add`, `rename`, `delete`, + `project name|version|id`, `build`, `run`, `install`, `clean` work through that layout's scripts and presets; what an + old SDK lacks is said when asked for (`add obj` on an SDK without objects modules; a preset on one without presets). + `breeze sdk migrate ` moves a flat project to the `breeze/` layout: the old `CMakeLists.txt`, + `Configuration.cmake`, `cmake/`, `scripts/` and `.githooks/` go to `.breeze/backup/`, and the project's name, version, + description, languages, C++ standard and any setting it had changed go into `project/Configuration.cmake`. The + project's own `README.md`, sources and dot files stay. + +## Building and testing + +``` +breeze/scripts/build.sh --preset linux-x64-gcc --cargs -DBUILD_TESTS=ON +out/build/linux-x64-gcc/bin/tests/core_tests # unit tests +tests/e2e.sh # drives the built program through every command group +``` + +`tests/e2e.sh` needs cmake, a compiler and git, and no network: it builds its own SDK source, tool and releases in a +temporary directory. + +Layout: `source/CLIForge` (the command line library), `source/BreezeCore` (everything the commands do; unit tested), +`source/Breeze` (the `breeze` executable: command registration only), `source/tests/CoreTests`. + +nlohmann/json is used for the files the CLI keeps: an installed 3.11 or newer is found, else the release is fetched +(pinned by hash) when you configure. + +## Notes on CLIForge + +CLIForge is used as it came, with these changes to `source/CLIForge`, to be taken back into the library: + +* `CliError::kind()` returned `kind()` (endless recursion); it now returns `m_kind`. +* `#pragma once` removed from the four `.cpp` files (a compiler warning each). + +Things to know when writing commands with it: an action's return value is ignored (so `Commands.cpp` leaves exit +codes in `breeze::cli::exitCode()` for `main()`); an exception that is not a `CliError` leaves `Engine::run`; a token +starting with `-` is always read as an option (which is why `main()` rewrites the values of `--cargs`, `--bargs` and +`-x` to the `--cargs=-DX=1` form, see `breeze/Arguments.hpp`); parameters are required, so `run [id]` is two commands. + +## Status + +Built and tested on Linux with gcc and clang (x64) and gcc for arm64 (run under qemu): the unit tests and the 94 checks +of `tests/e2e.sh` pass. Downloading a CLI release over HTTP was tried against a local web server with `curl`; git +sources were tried with local repositories only. MinGW compiles the Windows code (process creation, `cmd.exe` scripts, +paths) but nothing has been run on Windows, and nothing has been built on macOS. diff --git a/breeze/CMakePresets.json b/breeze/CMakePresets.json new file mode 100644 index 0000000..1d7033f --- /dev/null +++ b/breeze/CMakePresets.json @@ -0,0 +1,166 @@ +{ + "version": 6, + "cmakeMinimumRequired": { "major": 3, "minor": 25, "patch": 0 }, + "configurePresets": [ + { + "name": "base", + "hidden": true, + "generator": "Ninja", + "binaryDir": "${sourceDir}/out/build/${presetName}" + }, + { + "name": "base-linux", + "hidden": true, + "inherits": "base", + "condition": { "type": "equals", "lhs": "${hostSystemName}", "rhs": "Linux" } + }, + { + "name": "base-macos", + "hidden": true, + "inherits": "base", + "condition": { "type": "equals", "lhs": "${hostSystemName}", "rhs": "Darwin" } + }, + { + "name": "base-windows", + "hidden": true, + "inherits": "base", + "condition": { "type": "equals", "lhs": "${hostSystemName}", "rhs": "Windows" } + }, + { + "name": "base-mingw", + "hidden": true, + "inherits": "base", + "condition": { "type": "inList", "string": "${hostSystemName}", "list": ["Windows", "Linux"] } + }, + { + "name": "linux-x64-gcc", + "inherits": "base-linux", + "displayName": "Linux x64 (GCC)", + "description": "GCC for x64 Linux: native on x64 hardware; on an ARM64 machine a cross build with x86_64-linux-gnu-g++", + "toolchainFile": "${sourceDir}/breeze/cmake/toolchains/linux-x64-gcc.cmake" + }, + { + "name": "linux-x64-clang", + "inherits": "base-linux", + "displayName": "Linux x64 (Clang)", + "description": "Clang for x64 Linux: native on x64 hardware; on an ARM64 machine a cross build (clang++ --target=x86_64-linux-gnu)", + "toolchainFile": "${sourceDir}/breeze/cmake/toolchains/linux-x64-clang.cmake" + }, + { + "name": "linux-arm64-gcc", + "inherits": "base-linux", + "displayName": "Linux ARM64 (GCC)", + "description": "GCC for ARM64 (aarch64) Linux: native on ARM64 hardware, otherwise a cross build with aarch64-linux-gnu-g++ (Debian/Ubuntu: apt install g++-aarch64-linux-gnu; qemu-user, if installed, lets ctest run the result)", + "toolchainFile": "${sourceDir}/breeze/cmake/toolchains/linux-arm64-gcc.cmake" + }, + { + "name": "linux-arm64-clang", + "inherits": "base-linux", + "displayName": "Linux ARM64 (Clang)", + "description": "Clang for ARM64 (aarch64) Linux: native on ARM64 hardware, otherwise a cross build (clang++ --target=aarch64-linux-gnu; needs the aarch64 GCC cross package for its libraries, or -DCMAKE_SYSROOT)", + "toolchainFile": "${sourceDir}/breeze/cmake/toolchains/linux-arm64-clang.cmake" + }, + { + "name": "linux-arm32-gcc", + "inherits": "base-linux", + "displayName": "Linux ARM32 (GCC)", + "description": "GCC for 32-bit ARM (armhf) Linux: native on 32-bit ARM hardware, otherwise a cross build with arm-linux-gnueabihf-g++ (Debian/Ubuntu: apt install g++-arm-linux-gnueabihf). A 32-bit ARM userland on 64-bit ARM hardware should pass -DCMAKE_CXX_COMPILER=g++ -- see breeze/cmake/toolchains/_common.cmake", + "toolchainFile": "${sourceDir}/breeze/cmake/toolchains/linux-arm32-gcc.cmake" + }, + { + "name": "linux-arm32-clang", + "inherits": "base-linux", + "displayName": "Linux ARM32 (Clang)", + "description": "Clang for 32-bit ARM (armhf) Linux: native on 32-bit ARM hardware, otherwise a cross build (clang++ --target=) -- see linux-arm32-gcc", + "toolchainFile": "${sourceDir}/breeze/cmake/toolchains/linux-arm32-clang.cmake" + }, + { + "name": "macos-arm64-clang", + "inherits": "base-macos", + "displayName": "macOS ARM64 (Clang)", + "description": "Clang for Apple Silicon macOS -- Apple Clang from the Xcode Command Line Tools (xcode-select --install), or LLVM's own with -DCMAKE_CXX_COMPILER. The architecture is pinned with CMAKE_OSX_ARCHITECTURES, so this means arm64 even under a Rosetta-translated cmake; minimum macOS 11.0", + "toolchainFile": "${sourceDir}/breeze/cmake/toolchains/macos-arm64-clang.cmake" + }, + { + "name": "macos-x64-clang", + "inherits": "base-macos", + "displayName": "macOS x64 (Clang)", + "description": "Clang for Intel macOS -- Apple Clang from the Xcode Command Line Tools, or LLVM's own with -DCMAKE_CXX_COMPILER. Pinned to x86_64 with CMAKE_OSX_ARCHITECTURES, so on an Apple Silicon Mac it builds x64 code that runs through Rosetta 2; minimum macOS 11.0", + "toolchainFile": "${sourceDir}/breeze/cmake/toolchains/macos-x64-clang.cmake" + }, + { + "name": "windows-x64-msvc", + "inherits": "base-windows", + "displayName": "Windows x64 (MSVC)", + "description": "cl.exe for x64: run from an x64 Native Tools Command Prompt for VS -- cl.exe's architecture comes from that developer environment, not from CMake", + "toolchainFile": "${sourceDir}/breeze/cmake/toolchains/windows-x64-msvc.cmake" + }, + { + "name": "windows-arm64-msvc", + "inherits": "base-windows", + "displayName": "Windows ARM64 (MSVC)", + "description": "cl.exe for ARM64: run from a Visual Studio Developer Prompt configured for ARM64 (an ARM64 Native Tools prompt, or x64_arm64 via vcvarsall.bat to cross-build from an x64 host) -- cl.exe's architecture comes from that environment, not from CMake", + "toolchainFile": "${sourceDir}/breeze/cmake/toolchains/windows-arm64-msvc.cmake" + }, + { + "name": "windows-x64-clang-cl", + "inherits": "base-windows", + "displayName": "Windows x64 (clang-cl)", + "description": "clang-cl (Clang with the MSVC ABI), pinned to --target=x86_64-pc-windows-msvc so it means x64 whatever the machine's own architecture is", + "toolchainFile": "${sourceDir}/breeze/cmake/toolchains/windows-x64-clang-cl.cmake" + }, + { + "name": "windows-arm64-clang-cl", + "inherits": "base-windows", + "displayName": "Windows ARM64 (clang-cl)", + "description": "clang-cl (Clang with the MSVC ABI), pinned to --target=aarch64-pc-windows-msvc so it means ARM64 whatever the machine's own architecture is", + "toolchainFile": "${sourceDir}/breeze/cmake/toolchains/windows-arm64-clang-cl.cmake" + }, + { + "name": "windows-x64-clang", + "inherits": "base-windows", + "displayName": "Windows x64 (Clang, GNU-style)", + "description": "Clang with its own GNU-style driver, e.g. from an MSYS2 CLANG64 shell -- assumes that environment's clang++ is what's on PATH, distinct from clang-cl above", + "toolchainFile": "${sourceDir}/breeze/cmake/toolchains/windows-x64-clang.cmake" + }, + { + "name": "windows-arm64-clang", + "inherits": "base-windows", + "displayName": "Windows ARM64 (Clang, GNU-style)", + "description": "Clang with its own GNU-style driver, e.g. from an MSYS2 CLANGARM64 shell -- assumes that environment's clang++ is what's on PATH", + "toolchainFile": "${sourceDir}/breeze/cmake/toolchains/windows-arm64-clang.cmake" + }, + { + "name": "windows-x86-mingw32", + "inherits": "base-mingw", + "displayName": "Windows x86 (MinGW32)", + "description": "32-bit MinGW g++: i686-w64-mingw32-g++ from an MSYS2 MINGW32 shell on Windows, or a cross build from Linux (apt install g++-mingw-w64-i686; wine, if installed, lets ctest run the result)", + "toolchainFile": "${sourceDir}/breeze/cmake/toolchains/windows-x86-mingw32.cmake" + }, + { + "name": "windows-x64-mingw64", + "inherits": "base-mingw", + "displayName": "Windows x64 (MinGW-w64)", + "description": "64-bit MinGW-w64 g++: x86_64-w64-mingw32-g++ from an MSYS2 MINGW64 shell on Windows, or a cross build from Linux (apt install g++-mingw-w64-x86-64; wine, if installed, lets ctest run the result)", + "toolchainFile": "${sourceDir}/breeze/cmake/toolchains/windows-x64-mingw64.cmake" + } + ], + "buildPresets": [ + { "name": "linux-x64-gcc", "configurePreset": "linux-x64-gcc" }, + { "name": "linux-x64-clang", "configurePreset": "linux-x64-clang" }, + { "name": "linux-arm64-gcc", "configurePreset": "linux-arm64-gcc" }, + { "name": "linux-arm64-clang", "configurePreset": "linux-arm64-clang" }, + { "name": "linux-arm32-gcc", "configurePreset": "linux-arm32-gcc" }, + { "name": "linux-arm32-clang", "configurePreset": "linux-arm32-clang" }, + { "name": "macos-arm64-clang", "configurePreset": "macos-arm64-clang" }, + { "name": "macos-x64-clang", "configurePreset": "macos-x64-clang" }, + { "name": "windows-x64-msvc", "configurePreset": "windows-x64-msvc" }, + { "name": "windows-arm64-msvc", "configurePreset": "windows-arm64-msvc" }, + { "name": "windows-x64-clang-cl", "configurePreset": "windows-x64-clang-cl" }, + { "name": "windows-arm64-clang-cl", "configurePreset": "windows-arm64-clang-cl" }, + { "name": "windows-x64-clang", "configurePreset": "windows-x64-clang" }, + { "name": "windows-arm64-clang", "configurePreset": "windows-arm64-clang" }, + { "name": "windows-x86-mingw32", "configurePreset": "windows-x86-mingw32" }, + { "name": "windows-x64-mingw64", "configurePreset": "windows-x64-mingw64" } + ] +} diff --git a/breeze/README.md b/breeze/README.md new file mode 100644 index 0000000..97fc496 --- /dev/null +++ b/breeze/README.md @@ -0,0 +1,720 @@ +# 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(