From 04bb75593fc0a51a017a8c7c03476c700d03fcef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vin=C3=ADcius=20Ferr=C3=A3o?= <2031761+viniciusferrao@users.noreply.github.com> Date: Tue, 17 Mar 2026 19:18:49 -0300 Subject: [PATCH 01/10] build: replace CPM with Conan 2.0 and modernize tooling - Replace CPM.cmake with Conan 2.0 cmake-provider integration - Add Lizard cyclomatic complexity analysis - Add Bloaty McBloatface binary size analysis - Add comprehensive .clang-format (LLVM-based) and .gitignore - Replace Doxygen HTML with Sphinx + Breathe + Exhale pipeline - Add gitlint with Conventional Commits enforcement - Replace .githooks with pre-commit framework (clang-format, gitlint, trailing-whitespace, end-of-file-fixer, check-added-large-files) - Wire up include-what-you-use CMake option - Enforce body and Signed-off-by on all commits (DCO) - Fix trailing whitespace and missing final newlines Signed-off-by: Helder Ferreira Co-Authored-By: Claude Opus 4.6 (1M context) --- .clang-format | 27 +- .clang-tidy | 5 - .devcontainer/.dockerignore | 2 +- .devcontainer/devcontainer.json | 2 +- .gitattributes | 2 +- .github/actions/setup_cache/action.yml | 4 +- .github/template/removal-list | 2 +- .github/workflows/ci.yml | 3 + .github/workflows/template-janitor.yml | 11 +- .github/workflows/wasm.yml | 6 +- .gitignore | 337 +++++++++++++++++++++++-- .gitlint | 34 +++ .pre-commit-config.yaml | 18 ++ CMakeLists.txt | 27 +- CMakePresets.json | 2 +- CONTRIBUTING.md | 35 +++ Dependencies.cmake | 56 ++-- ProjectOptions.cmake | 24 +- README.md | 4 +- README_building.md | 2 - README_docker.md | 1 - cmake/Bloaty.cmake | 31 +++ cmake/CPM.cmake | 24 -- cmake/Conan.cmake | 44 ++++ cmake/Doxygen.cmake | 97 ++++--- cmake/Lizard.cmake | 71 ++++++ cmake/Sanitizers.cmake | 3 - conanfile.py | 16 ++ configured_files/CMakeLists.txt | 2 - docs/.gitignore | 1 + docs/conf.py | 39 +++ docs/index.rst | 17 ++ docs/requirements.txt | 4 + test/CMakeLists.txt | 8 +- 34 files changed, 808 insertions(+), 153 deletions(-) create mode 100644 .gitlint create mode 100644 .pre-commit-config.yaml create mode 100644 CONTRIBUTING.md create mode 100644 cmake/Bloaty.cmake delete mode 100644 cmake/CPM.cmake create mode 100644 cmake/Conan.cmake create mode 100644 cmake/Lizard.cmake create mode 100644 conanfile.py create mode 100644 docs/.gitignore create mode 100644 docs/conf.py create mode 100644 docs/index.rst create mode 100644 docs/requirements.txt diff --git a/.clang-format b/.clang-format index ffd5aa94..6e34d65a 100644 --- a/.clang-format +++ b/.clang-format @@ -1,3 +1,10 @@ +--- +BasedOnStyle: LLVM +Language: Json +IndentWidth: 4 +UseTab: Never +--- +Language: Cpp AccessModifierOffset: -2 AlignAfterOpenBracket: DontAlign AlignConsecutiveAssignments: false @@ -52,16 +59,16 @@ DisableFormat: false ExperimentalAutoDetectBinPacking: true FixNamespaceComments: true ForEachMacros: -- foreach -- Q_FOREACH -- BOOST_FOREACH + - foreach + - Q_FOREACH + - BOOST_FOREACH IncludeCategories: -- Priority: 2 - Regex: ^"(llvm|llvm-c|clang|clang-c)/ -- Priority: 3 - Regex: ^(<|"(gtest|gmock|isl|json)/) -- Priority: 1 - Regex: .* + - Priority: 2 + Regex: ^"(llvm|llvm-c|clang|clang-c)/ + - Priority: 3 + Regex: ^(<|"(gtest|gmock|isl|json)/) + - Priority: 1 + Regex: .* IncludeIsMainRegex: (Test)?$ IndentCaseLabels: false IndentWidth: 2 @@ -69,7 +76,6 @@ IndentWrappedFunctionNames: true JavaScriptQuotes: Leave JavaScriptWrapImports: true KeepEmptyLinesAtTheStartOfBlocks: true -Language: Cpp MacroBlockBegin: '' MacroBlockEnd: '' MaxEmptyLinesToKeep: 2 @@ -95,4 +101,3 @@ SpacesInSquareBrackets: false Standard: c++20 TabWidth: 8 UseTab: Never - diff --git a/.clang-tidy b/.clang-tidy index d861c0bf..a9d9dab8 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -26,8 +26,3 @@ CheckOptions: value: 'x|y|z' - key: readability-identifier-length.IgnoredParameterNames value: 'x|y|z' - - - - - diff --git a/.devcontainer/.dockerignore b/.devcontainer/.dockerignore index de407478..7baf5b1a 100644 --- a/.devcontainer/.dockerignore +++ b/.devcontainer/.dockerignore @@ -31,4 +31,4 @@ $RECYCLE.BIN/ .TemporaryItems ehthumbs.db Thumbs.db -Dockerfile \ No newline at end of file +Dockerfile diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 8b79bed2..aa221fda 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -49,4 +49,4 @@ "git-lfs": "latest", "powershell": "latest" } -} \ No newline at end of file +} diff --git a/.gitattributes b/.gitattributes index 23eae36f..1e2c9d2b 100644 --- a/.gitattributes +++ b/.gitattributes @@ -34,4 +34,4 @@ #*.woff2 filter=lfs diff=lfs merge=lfs -text # Other -#*.exe filter=lfs diff=lfs merge=lfs -text \ No newline at end of file +#*.exe filter=lfs diff=lfs merge=lfs -text diff --git a/.github/actions/setup_cache/action.yml b/.github/actions/setup_cache/action.yml index 7bf260e2..0c83847a 100644 --- a/.github/actions/setup_cache/action.yml +++ b/.github/actions/setup_cache/action.yml @@ -26,7 +26,7 @@ runs: path: | ~/.cache/pip ~/.ccache - key: ${{ runner.os }}-${{ inputs.compiler }}-${{ inputs.build_type }}-${{ inputs.generator }}-${{ inputs.packaging_maintainer_mode }}-${{ hashFiles('**/CMakeLists.txt') }} + ~/.conan2/p + key: ${{ runner.os }}-${{ inputs.compiler }}-${{ inputs.build_type }}-${{ inputs.generator }}-${{ inputs.packaging_maintainer_mode }}-${{ hashFiles('**/CMakeLists.txt', '**/conanfile.py') }} restore-keys: | ${{ runner.os }}-${{ inputs.compiler }}-${{ inputs.build_type }} - diff --git a/.github/template/removal-list b/.github/template/removal-list index 4f63e513..1b33fc47 100644 --- a/.github/template/removal-list +++ b/.github/template/removal-list @@ -1,2 +1,2 @@ LICENSE -.github/FUNDING.yml \ No newline at end of file +.github/FUNDING.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e4e2531e..e7bc480f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -160,6 +160,9 @@ jobs: gcovr: true opencppcoverage: true + - name: Install Conan + run: pip install conan + - name: Configure CMake run: | cmake -S . -B ./build -G "${{matrix.generator}}" -D${{ env.PROJECT_NAME }}_ENABLE_IPO=${{matrix.enable_ipo }} -DCMAKE_BUILD_TYPE:STRING=${{matrix.build_type}} -D${{ env.PROJECT_NAME }}_PACKAGING_MAINTAINER_MODE:BOOL=${{matrix.packaging_maintainer_mode}} -D${{ env.PROJECT_NAME }}_ENABLE_COVERAGE:BOOL=${{ matrix.build_type == 'Debug' }} -DGIT_SHA:STRING=${{ github.sha }} diff --git a/.github/workflows/template-janitor.yml b/.github/workflows/template-janitor.yml index 15f6717a..e531466b 100644 --- a/.github/workflows/template-janitor.yml +++ b/.github/workflows/template-janitor.yml @@ -44,7 +44,7 @@ jobs: package_maintainer_mode: ${{ matrix.package_maintainer_mode }} generator: ${{ matrix.generator }} - - name: Get organization and project name + - name: Get organization and project name run: | echo "NEW_ORG=${{ github.repository_owner }}" >> $GITHUB_ENV echo "NEW_PROJECT=${{ github.event.repository.name }}" >> $GITHUB_ENV @@ -99,7 +99,7 @@ jobs: - name: Insert new org and project run: | # rename the CMake project to match the github project - find src include test fuzz_test cmake -type f -exec sed -i "s/myproject/${{ env.NEW_SAFE_PROJECT }}/gi" .github/constants.env CMakeLists.txt Dependencies.cmake ProjectOptions.cmake .github/workflows/ci.yml .github/workflows/codeql-analysis.yml configured_files/config.hpp.in {} + + find src include test fuzz_test cmake docs -type f -exec sed -i "s/myproject/${{ env.NEW_SAFE_PROJECT }}/gi" .github/constants.env CMakeLists.txt Dependencies.cmake ProjectOptions.cmake conanfile.py docs/conf.py .github/workflows/ci.yml .github/workflows/codeql-analysis.yml configured_files/config.hpp.in {} + # Update URL placeholders for project sed -i "s|%%myurl%%|${{ fromJson(steps.get_repo_meta.outputs.data).html_url }}|gi" CMakeLists.txt @@ -148,6 +148,9 @@ jobs: gcovr: false opencppcoverage: false + - name: Install Conan + run: pip install conan + - name: Project Name uses: cardinalby/export-env-action@v2 with: @@ -200,7 +203,7 @@ jobs: packaging_maintainer_mode: ${{ matrix.packaging_maintainer_mode }} generator: ${{ matrix.generator }} - - name: Get organization and project name + - name: Get organization and project name run: | echo "TEST_RUN=false" >> $GITHUB_ENV echo "NEW_ORG=${{ github.repository_owner }}" >> $GITHUB_ENV @@ -228,7 +231,7 @@ jobs: # Rename all cpp_starter_project occurrences to current repository and remove this workflow - name: Update repository to match new template information run: | - # Update the README and template files to match the new org / repository names + # Update the README and template files to match the new org / repository names sed -i "s|${{ env.TEMPLATE_REPOSITORY }}|${{ env.NEW_REPOSITORY }}|g" README.md ${{ env.TEMPLATES_PATH }}/template_repository sed -i "s|${{ env.TEMPLATE_NAME }}|${{ env.NEW_PROJECT }}|g" README.md ${{ env.TEMPLATES_PATH }}/template_name diff --git a/.github/workflows/wasm.yml b/.github/workflows/wasm.yml index 83aa49c3..1bea3017 100644 --- a/.github/workflows/wasm.yml +++ b/.github/workflows/wasm.yml @@ -22,8 +22,10 @@ jobs: with: version: 'latest' - - name: Install Ninja - run: sudo apt-get install -y ninja-build + - name: Install Ninja and Conan + run: | + sudo apt-get install -y ninja-build + pip install conan - name: Configure CMake run: emcmake cmake -B build -G Ninja -DCMAKE_BUILD_TYPE=Release diff --git a/.gitignore b/.gitignore index 6eff7441..0b375b10 100644 --- a/.gitignore +++ b/.gitignore @@ -1,36 +1,325 @@ -# Build directories and binary files -build/ -out/ -out/coverage/* +# Project-specific ignores +/build*/ +/cmake-*/ + +# Created by https://www.toptal.com/developers/gitignore/api/c,c++,visualstudiocode,vim,linux,macos,pvs,git,cmake,ninja,executable,clion+all,sonar +# Edit at https://www.toptal.com/developers/gitignore?templates=c,c++,visualstudiocode,vim,linux,macos,pvs,git,cmake,ninja,executable,clion+all,sonar + +### C ### +# Prerequisites +*.d + +# Object files +*.o +*.ko +*.obj +*.elf + +# Linker output +*.ilk +*.map +*.exp + +# Precompiled Headers +*.gch +*.pch + +# Libraries +*.lib +*.a +*.la +*.lo + +# Shared objects (inc. Windows DLLs) +*.dll +*.so +*.so.* +*.dylib + +# Executables +*.exe +*.out +*.app +*.i*86 +*.x86_64 +*.hex + +# Debug files +*.dSYM/ +*.su +*.idb +*.pdb + +# Kernel Module Compile Results +*.mod* +*.cmd +.tmp_versions/ +modules.order +Module.symvers +Mkfile.old +dkms.conf + +### C++ ### +# Prerequisites + +# Compiled Object files +*.slo + +# Precompiled Headers + +# Compiled Dynamic libraries + +# Fortran module files +*.mod +*.smod + +# Compiled Static libraries +*.lai + +# Executables + +### CLion+all ### +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 + +# User-specific stuff +.idea/**/workspace.xml +.idea/**/tasks.xml +.idea/**/usage.statistics.xml +.idea/**/dictionaries +.idea/**/shelf + +# AWS User-specific +.idea/**/aws.xml + +# Generated files +.idea/**/contentModel.xml + +# Sensitive or high-churn files +.idea/**/dataSources/ +.idea/**/dataSources.ids +.idea/**/dataSources.local.xml +.idea/**/sqlDataSources.xml +.idea/**/dynamic.xml +.idea/**/uiDesigner.xml +.idea/**/dbnavigator.xml + +# Gradle +.idea/**/gradle.xml +.idea/**/libraries + +# Gradle and Maven with auto-import +# When using Gradle or Maven with auto-import, you should exclude module files, +# since they will be recreated, and may cause churn. Uncomment if using +# auto-import. +# .idea/artifacts +# .idea/compiler.xml +# .idea/jarRepositories.xml +# .idea/modules.xml +# .idea/*.iml +# .idea/modules +# *.iml +# *.ipr + +# CMake cmake-build-*/ -conan-cache/ -# User spesific settings +# Mongo Explorer plugin +.idea/**/mongoSettings.xml + +# File-based project format +*.iws + +# IntelliJ +out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Cursive Clojure plugin +.idea/replstate.xml + +# SonarLint plugin +.idea/sonarlint/ + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties + +# Editor-based Rest Client +.idea/httpRequests + +# Android studio 3.1+ serialized cache file +.idea/caches/build_file_checksums.ser + +### CLion+all Patch ### +# Ignore everything but code style settings and run configurations +# that are supposed to be shared within teams. + +.idea/* + +!.idea/codeStyles +!.idea/runConfigurations + +### CMake ### +CMakeLists.txt.user +CMakeCache.txt +CMakeFiles +CMakeScripts +Testing +Makefile +cmake_install.cmake +install_manifest.txt +compile_commands.json +CTestTestfile.cmake +_deps + +### CMake Patch ### CMakeUserPresets.json -# IDE files -.vs/ -.idea/ -.vscode/ -!.vscode/settings.json -!.vscode/tasks.json -!.vscode/launch.json -!.vscode/extensions.json -*.bak -*.swp +# External projects +*-prefix/ + +### Executable ### +*.bat +*.cgi +*.com +*.gadget +*.jar +*.pif +*.vb +*.wsf + +### Git ### +# Created by git for backups. To disable backups in Git: +# $ git config --global mergetool.keepBackup false +*.orig + +# Created by git when using merge tools for conflicts +*.BACKUP.* +*.BASE.* +*.LOCAL.* +*.REMOTE.* +*_BACKUP_*.txt +*_BASE_*.txt +*_LOCAL_*.txt +*_REMOTE_*.txt + +### Linux ### *~ -_ReSharper* -*.log -# OS Generated Files +# temporary files which can be created if a process still has a handle open of a deleted file +.fuse_hidden* + +# KDE directory preferences +.directory + +# Linux trash folder which might appear on any partition or disk +.Trash-* + +# .nfs files are created when an open file is removed but is still being accessed +.nfs* + +### macOS ### +# General .DS_Store .AppleDouble .LSOverride + +# Icon must end with two \r +Icon + + +# Thumbnails ._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd .Spotlight-V100 -.Trashes -.Trash-* -$RECYCLE.BIN/ .TemporaryItems -ehthumbs.db -Thumbs.db +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +### macOS Patch ### +# iCloud generated files +*.icloud + +### Ninja ### +.ninja_deps +.ninja_log + +### PVS ### +# Ignore binary files +pvsbin/ +bin/ +lib/ +*.bin +.pvscontext +# Ignore orphaned and old proofs +orphaned-proofs.prf +*.prf~ +# Ignore compiled Lisp files +*fasl +# Ignore PVS dump files +*.dmp +# Ignore PVS log files +*.log + +### Sonar ### +#Sonar generated dir +/.sonar/ + +### Vim ### +# Swap +[._]*.s[a-v][a-z] +!*.svg # comment out if you don't need vector files +[._]*.sw[a-p] +[._]s[a-rt-v][a-z] +[._]ss[a-gi-z] +[._]sw[a-p] + +# Session +Session.vim +Sessionx.vim + +# Temporary +.netrwhist +# Auto-generated tag files +tags +# Persistent undo +[._]*.un~ + +### VisualStudioCode ### +.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 + +### VisualStudioCode Patch ### +# Ignore all local history of files +.history +.ionide + +# End of https://www.toptal.com/developers/gitignore/api/c,c++,visualstudiocode,vim,linux,macos,pvs,git,cmake,ninja,executable,clion+all,sonar diff --git a/.gitlint b/.gitlint new file mode 100644 index 00000000..061ffeb6 --- /dev/null +++ b/.gitlint @@ -0,0 +1,34 @@ +[general] +# Use Python `search` semantics for regex-based rules, so patterns only need to +# match somewhere in the text (not the entire string). +regex-style-search = true + +# Body line length is not enforced — git stores messages as-is and every +# modern tool wraps. Title length (72) matters for git log --oneline; +# body length does not. +ignore = body-max-line-length + +# ----------------------------------------------------------- +# Conventional Commits title +# ----------------------------------------------------------- +[title-match-regex] +# Allowed types: build|chore|ci|docs|feat|fix|perf|refactor|revert|style|test +# Format: type(scope?)?(!)?: subject +regex = ^(build|chore|ci|docs|feat|fix|perf|refactor|revert|style|test)(\([a-z0-9._-]+\))?(!)?: .+ + +[title-max-length] +line-length = 72 + +# ----------------------------------------------------------- +# Body formatting +# ----------------------------------------------------------- +[body-first-line-empty] +# Enforce a blank line between title and body when a body is present. + +# ----------------------------------------------------------- +# DCO / Signed-off-by enforcement +# ----------------------------------------------------------- +[body-match-regex] +# Require at least one "Signed-off-by: Name " line somewhere in the body. +# This works together with regex-style-search=true in [general]. +regex = (?m)^Signed-off-by: .+ <.+>$ diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 00000000..82adff1c --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,18 @@ +repos: + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v6.0.0 + hooks: + - id: trailing-whitespace + - id: end-of-file-fixer + - id: check-added-large-files + + - repo: https://github.com/pre-commit/mirrors-clang-format + rev: v22.1.1 + hooks: + - id: clang-format + types_or: [c, c++] + + - repo: https://github.com/jorisroovers/gitlint + rev: v0.19.1 + hooks: + - id: gitlint diff --git a/CMakeLists.txt b/CMakeLists.txt index 1294249e..5f5fc32b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,11 +1,11 @@ cmake_minimum_required(VERSION 3.29) # Disable modules support, this is broken with clang-tidy at the moment -cmake_policy(SET CMP0155 OLD) +cmake_policy(SET CMP0155 OLD) # This template attempts to be "fetch_content"-able -# so that it works well with tools like CPM or other -# manual dependency management +# so that it works well with dependency managers like +# Conan or manual dependency management # Only set the cxx_standard if it is not set by someone else if (NOT DEFINED CMAKE_CXX_STANDARD) @@ -17,6 +17,9 @@ endif() # when compiling with PCH enabled set(CMAKE_CXX_EXTENSIONS OFF) +# Conan 2.0 - must be initialized before the project() call +include(cmake/Conan.cmake) + # Set the project name and language project( myproject @@ -32,6 +35,12 @@ include(ProjectOptions.cmake) myproject_setup_options() +if(myproject_WARNINGS_AS_ERRORS) + set(myproject_WARNING_TYPE SEND_ERROR) +else() + set(myproject_WARNING_TYPE WARNING) +endif() + myproject_global_options() include(Dependencies.cmake) myproject_setup_dependencies() @@ -64,6 +73,18 @@ add_subdirectory(configured_files) # Adding the src: add_subdirectory(src) +# Bloaty needs targets to exist, so it runs after add_subdirectory(src) +if(myproject_ENABLE_BLOATY) + include(cmake/Bloaty.cmake) + myproject_setup_bloaty(intro) +endif() + +# Build documentation +if(PROJECT_IS_TOP_LEVEL AND myproject_ENABLE_DOCS) + include(cmake/Doxygen.cmake) + myproject_enable_doxygen() +endif() + # Create unified web deployment directory (for WASM builds) if(EMSCRIPTEN) myproject_create_web_dist() diff --git a/CMakePresets.json b/CMakePresets.json index 5871489e..81aeeb26 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -251,4 +251,4 @@ "configurePreset": "unixlike-clang-release" } ] -} \ No newline at end of file +} diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 00000000..e9697112 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,35 @@ +# Contributing + +## Commit message policy + +This project uses [Conventional Commits](https://www.conventionalcommits.org/) enforced by [gitlint](https://jorisroovers.com/gitlint/). + +- Format: `type(scope?): subject` with allowed types `build`, `chore`, `ci`, `docs`, `feat`, `fix`, `perf`, `refactor`, `revert`, `style`, `test`. +- Subject: imperative mood, no trailing punctuation, max 72 characters. +- Body: required; add context for the change. +- Sign-offs: every commit must include `Signed-off-by: Full Name ` in the body (`git commit -s`); sign commits cryptographically when possible (`git commit -S -s`). + +Example: + +``` +feat(network): Add TLS client certificate rotation + +Implement automatic rotation of TLS client certificates before expiry. + +Signed-off-by: Jane Doe +``` + +## Setting up pre-commit hooks + +This repository uses [pre-commit](https://pre-commit.com/) to run formatting and linting checks automatically on each commit. Install and activate it: + +```sh +pipx install pre-commit +pre-commit install +``` + +If you previously used the `.githooks/` directory, unset the custom hooks path: + +```sh +git config --unset core.hooksPath +``` diff --git a/Dependencies.cmake b/Dependencies.cmake index 821366ac..bf50a708 100644 --- a/Dependencies.cmake +++ b/Dependencies.cmake @@ -1,4 +1,4 @@ -include(cmake/CPM.cmake) +include(FetchContent) # Done as a function so that updates to variables like # CMAKE_CXX_FLAGS don't propagate out to other @@ -6,38 +6,56 @@ include(cmake/CPM.cmake) function(myproject_setup_dependencies) # For each dependency, see if it's - # already been provided to us by a parent project - - if(NOT TARGET fmtlib::fmtlib) - cpmaddpackage("gh:fmtlib/fmt#12.1.0") + # already been provided to us by a parent project. + # + # Dependencies are resolved via find_package(). The Conan CMake + # provider transparently intercepts these calls and runs + # conan install using the project's conanfile.py. + + if(NOT TARGET fmt::fmt) + find_package(fmt REQUIRED) endif() if(NOT TARGET spdlog::spdlog) - cpmaddpackage( - NAME - spdlog - VERSION - 1.17.0 - GITHUB_REPOSITORY - "gabime/spdlog" - OPTIONS - "SPDLOG_FMT_EXTERNAL ON") + find_package(spdlog REQUIRED) endif() if(NOT TARGET Catch2::Catch2WithMain) - cpmaddpackage("gh:catchorg/Catch2@3.12.0") + find_package(Catch2 REQUIRED) + # Conan's CMakeDeps provides targets but does not expose Catch2's test + # discovery module (Catch.cmake). Locate it in the installed package + # and add it to CMAKE_MODULE_PATH so include(Catch) works in tests. + foreach(_dir IN LISTS Catch2_INCLUDE_DIRS) + cmake_path(GET _dir PARENT_PATH _catch2_root) + if(EXISTS "${_catch2_root}/lib/cmake/Catch2/Catch.cmake") + set(CMAKE_MODULE_PATH + "${_catch2_root}/lib/cmake/Catch2" ${CMAKE_MODULE_PATH} PARENT_SCOPE) + break() + endif() + endforeach() endif() if(NOT TARGET CLI11::CLI11) - cpmaddpackage("gh:CLIUtils/CLI11@2.6.1") + find_package(CLI11 REQUIRED) endif() if(NOT TARGET ftxui::screen) - cpmaddpackage("gh:ArthurSonzogni/FTXUI@6.1.9") + find_package(ftxui REQUIRED) endif() - if(NOT TARGET tools::tools) - cpmaddpackage("gh:lefticus/tools#update_build_system") + # lefticus/tools is not available on Conan, fetch from GitHub. + # It uses CPM internally to fetch its own dependencies (e.g. fmt). + # CPM_USE_LOCAL_PACKAGES tells CPM to use find_package() first, + # so it picks up packages already provided by Conan instead of + # downloading duplicates. + if(NOT TARGET lefticus::tools) + set(CPM_USE_LOCAL_PACKAGES ON) + FetchContent_Declare( + tools + GIT_REPOSITORY https://github.com/lefticus/tools.git + GIT_TAG main + GIT_SHALLOW TRUE) + FetchContent_MakeAvailable(tools) endif() endfunction() diff --git a/ProjectOptions.cmake b/ProjectOptions.cmake index 0d03d975..1b2c0a0e 100644 --- a/ProjectOptions.cmake +++ b/ProjectOptions.cmake @@ -61,6 +61,7 @@ endmacro() macro(myproject_setup_options) option(myproject_ENABLE_HARDENING "Enable hardening" ON) option(myproject_ENABLE_COVERAGE "Enable coverage reporting" OFF) + option(myproject_ENABLE_DOCS "Enable Doxygen documentation" OFF) cmake_dependent_option( myproject_ENABLE_GLOBAL_HARDENING "Attempt to push hardening options to built dependencies" @@ -81,6 +82,9 @@ macro(myproject_setup_options) option(myproject_ENABLE_UNITY_BUILD "Enable unity builds" OFF) option(myproject_ENABLE_CLANG_TIDY "Enable clang-tidy" OFF) option(myproject_ENABLE_CPPCHECK "Enable cpp-check analysis" OFF) + option(myproject_ENABLE_INCLUDE_WHAT_YOU_USE "Enable include-what-you-use" OFF) + option(myproject_ENABLE_LIZARD "Enable Lizard complexity analysis" OFF) + option(myproject_ENABLE_BLOATY "Enable Bloaty McBloatface binary size analysis" OFF) option(myproject_ENABLE_PCH "Enable precompiled headers" OFF) option(myproject_ENABLE_CACHE "Enable ccache" OFF) else() @@ -94,6 +98,9 @@ macro(myproject_setup_options) option(myproject_ENABLE_UNITY_BUILD "Enable unity builds" OFF) option(myproject_ENABLE_CLANG_TIDY "Enable clang-tidy" ON) option(myproject_ENABLE_CPPCHECK "Enable cpp-check analysis" ON) + option(myproject_ENABLE_INCLUDE_WHAT_YOU_USE "Enable include-what-you-use" OFF) + option(myproject_ENABLE_LIZARD "Enable Lizard complexity analysis" ON) + option(myproject_ENABLE_BLOATY "Enable Bloaty McBloatface binary size analysis" OFF) option(myproject_ENABLE_PCH "Enable precompiled headers" OFF) option(myproject_ENABLE_CACHE "Enable ccache" ON) endif() @@ -110,7 +117,11 @@ macro(myproject_setup_options) myproject_ENABLE_UNITY_BUILD myproject_ENABLE_CLANG_TIDY myproject_ENABLE_CPPCHECK + myproject_ENABLE_INCLUDE_WHAT_YOU_USE + myproject_ENABLE_LIZARD + myproject_ENABLE_BLOATY myproject_ENABLE_COVERAGE + myproject_ENABLE_DOCS myproject_ENABLE_PCH myproject_ENABLE_CACHE) endif() @@ -136,7 +147,7 @@ macro(myproject_global_options) if(myproject_ENABLE_HARDENING AND myproject_ENABLE_GLOBAL_HARDENING) include(cmake/Hardening.cmake) - if(NOT SUPPORTS_UBSAN + if(NOT SUPPORTS_UBSAN OR myproject_ENABLE_SANITIZER_UNDEFINED OR myproject_ENABLE_SANITIZER_ADDRESS OR myproject_ENABLE_SANITIZER_THREAD @@ -207,6 +218,15 @@ macro(myproject_local_options) ) endif() + if(myproject_ENABLE_INCLUDE_WHAT_YOU_USE) + myproject_enable_include_what_you_use() + endif() + + if(myproject_ENABLE_LIZARD) + include(cmake/Lizard.cmake) + myproject_setup_lizard(${myproject_WARNINGS_AS_ERRORS}) + endif() + if(myproject_ENABLE_COVERAGE) include(cmake/Tests.cmake) myproject_enable_coverage(myproject_options) @@ -222,7 +242,7 @@ macro(myproject_local_options) if(myproject_ENABLE_HARDENING AND NOT myproject_ENABLE_GLOBAL_HARDENING) include(cmake/Hardening.cmake) - if(NOT SUPPORTS_UBSAN + if(NOT SUPPORTS_UBSAN OR myproject_ENABLE_SANITIZER_UNDEFINED OR myproject_ENABLE_SANITIZER_ADDRESS OR myproject_ENABLE_SANITIZER_THREAD diff --git a/README.md b/README.md index eeedbbb8..485693a2 100644 --- a/README.md +++ b/README.md @@ -47,7 +47,7 @@ Fill in a repository name and short description, and click 'Create repository fr This will allow you to create a new repository in your GitHub account, prepopulated with the contents of this project. -After creating the project please wait until the cleanup workflow has finished +After creating the project please wait until the cleanup workflow has finished setting up your project and committed the changes. Now you can clone the project locally and get to work! @@ -67,5 +67,3 @@ See [Catch2 tutorial](https://github.com/catchorg/Catch2/blob/master/docs/tutori ## Fuzz testing See [libFuzzer Tutorial](https://github.com/google/fuzzing/blob/master/tutorial/libFuzzerTutorial.md) - - diff --git a/README_building.md b/README_building.md index 99ceacfc..67293ff9 100644 --- a/README_building.md +++ b/README_building.md @@ -188,5 +188,3 @@ cd ./build ctest -C Debug cd ../ ``` - - diff --git a/README_docker.md b/README_docker.md index 8bc654da..d2c548de 100644 --- a/README_docker.md +++ b/README_docker.md @@ -68,4 +68,3 @@ run them separately. A script called `build_examples.sh` is provided to help you to build the example GUI projects in this container. - diff --git a/cmake/Bloaty.cmake b/cmake/Bloaty.cmake new file mode 100644 index 00000000..ceabc14a --- /dev/null +++ b/cmake/Bloaty.cmake @@ -0,0 +1,31 @@ +# Bloaty McBloatface - Binary size analyzer +# Creates custom targets for analyzing executable and library sizes. +# Usage: myproject_setup_bloaty() +# Creates targets: bloaty_ (sections), bloaty__full (compile units + symbols) + +function(myproject_setup_bloaty TARGET_NAME) + find_program(BLOATY bloaty) + if(BLOATY) + # Sections breakdown (always available, no debug info needed) + add_custom_target( + bloaty_${TARGET_NAME} + COMMAND ${BLOATY} $ -d sections --domain=vm + DEPENDS ${TARGET_NAME} + COMMENT "Running Bloaty size analysis on ${TARGET_NAME}..." + VERBATIM + ) + + # Compile-unit and symbol breakdown (requires debug info) + add_custom_target( + bloaty_${TARGET_NAME}_full + COMMAND ${BLOATY} $ -d compileunits,symbols --domain=vm -n 30 + DEPENDS ${TARGET_NAME} + COMMENT "Running detailed Bloaty analysis on ${TARGET_NAME} (requires debug info)..." + VERBATIM + ) + + message(STATUS "Bloaty McBloatface targets created for ${TARGET_NAME}") + else() + message(${myproject_WARNING_TYPE} "Bloaty requested but not found. Install from https://github.com/google/bloaty") + endif() +endfunction() diff --git a/cmake/CPM.cmake b/cmake/CPM.cmake deleted file mode 100644 index 9a66dd83..00000000 --- a/cmake/CPM.cmake +++ /dev/null @@ -1,24 +0,0 @@ -# SPDX-License-Identifier: MIT -# -# SPDX-FileCopyrightText: Copyright (c) 2019-2023 Lars Melchior and contributors - -set(CPM_DOWNLOAD_VERSION 0.42.1) -set(CPM_HASH_SUM "f3a6dcc6a04ce9e7f51a127307fa4f699fb2bade357a8eb4c5b45df76e1dc6a5") - -if(CPM_SOURCE_CACHE) - set(CPM_DOWNLOAD_LOCATION "${CPM_SOURCE_CACHE}/cpm/CPM_${CPM_DOWNLOAD_VERSION}.cmake") -elseif(DEFINED ENV{CPM_SOURCE_CACHE}) - set(CPM_DOWNLOAD_LOCATION "$ENV{CPM_SOURCE_CACHE}/cpm/CPM_${CPM_DOWNLOAD_VERSION}.cmake") -else() - set(CPM_DOWNLOAD_LOCATION "${CMAKE_BINARY_DIR}/cmake/CPM_${CPM_DOWNLOAD_VERSION}.cmake") -endif() - -# Expand relative path. This is important if the provided path contains a tilde (~) -get_filename_component(CPM_DOWNLOAD_LOCATION ${CPM_DOWNLOAD_LOCATION} ABSOLUTE) - -file(DOWNLOAD - https://github.com/cpm-cmake/CPM.cmake/releases/download/v${CPM_DOWNLOAD_VERSION}/CPM.cmake - ${CPM_DOWNLOAD_LOCATION} EXPECTED_HASH SHA256=${CPM_HASH_SUM} -) - -include(${CPM_DOWNLOAD_LOCATION}) diff --git a/cmake/Conan.cmake b/cmake/Conan.cmake new file mode 100644 index 00000000..3f737ece --- /dev/null +++ b/cmake/Conan.cmake @@ -0,0 +1,44 @@ +# Conan 2.0 CMake Provider Integration +# +# Downloads the cmake-conan provider on-demand to the build directory +# and registers it as a top-level include so that find_package() calls +# are transparently satisfied by Conan during the project() call. +# +# This file must be included BEFORE the project() call. + +set(CONAN_PROVIDER_LOCATION "${CMAKE_BINARY_DIR}/cmake/conan_provider.cmake") + +if(NOT EXISTS "${CONAN_PROVIDER_LOCATION}") + message(STATUS "Downloading Conan CMake provider...") + file(DOWNLOAD + "https://raw.githubusercontent.com/conan-io/cmake-conan/develop2/conan_provider.cmake" + "${CONAN_PROVIDER_LOCATION}" + STATUS _download_status) + list(GET _download_status 0 _download_code) + if(NOT _download_code EQUAL 0) + list(GET _download_status 1 _download_msg) + message(FATAL_ERROR + "Failed to download Conan CMake provider: ${_download_msg}\n" + "Ensure you have internet access.") + endif() +endif() + +list(APPEND CMAKE_PROJECT_TOP_LEVEL_INCLUDES "${CONAN_PROVIDER_LOCATION}") + +# Conan requires a build type to be set before project(). +# StandardProjectSettings.cmake also defaults to RelWithDebInfo, but it runs +# after myproject_setup_dependencies() — too late for the Conan provider which +# needs the build type during find_package() calls. +if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) + message(STATUS "Setting build type to 'RelWithDebInfo' as none was specified.") + set(CMAKE_BUILD_TYPE + RelWithDebInfo + CACHE STRING "Choose the type of build." FORCE) + set_property( + CACHE CMAKE_BUILD_TYPE + PROPERTY STRINGS + "Debug" + "Release" + "MinSizeRel" + "RelWithDebInfo") +endif() diff --git a/cmake/Doxygen.cmake b/cmake/Doxygen.cmake index ed90c566..fe74a310 100644 --- a/cmake/Doxygen.cmake +++ b/cmake/Doxygen.cmake @@ -1,54 +1,73 @@ -# Enable doxygen doc builds of source -function(myproject_enable_doxygen DOXYGEN_THEME) - # If not specified, use the top readme file as the first page - if((NOT DOXYGEN_USE_MDFILE_AS_MAINPAGE) AND EXISTS "${PROJECT_SOURCE_DIR}/README.md") - set(DOXYGEN_USE_MDFILE_AS_MAINPAGE "${PROJECT_SOURCE_DIR}/README.md") +# Documentation pipeline: Doxygen (XML) → Breathe → Sphinx (HTML) +# +# Doxygen parses C++ sources and generates XML. +# Breathe is a Sphinx extension that reads the Doxygen XML. +# Exhale auto-generates the API reference tree from Breathe. +# Sphinx renders the final HTML with the Read the Docs theme. +# +# Targets: +# docs - Full documentation (runs Doxygen then Sphinx) +# doxygen-xml - Doxygen XML generation only + +function(myproject_enable_doxygen) + find_package(Doxygen OPTIONAL_COMPONENTS dot) + if(NOT DOXYGEN_FOUND) + message(${myproject_WARNING_TYPE} "Doxygen not found. Install from https://www.doxygen.nl/") + return() endif() - # set better defaults for doxygen - is_verbose(_is_verbose) - if(NOT ${_is_verbose}) - set(DOXYGEN_QUIET YES) + find_program(SPHINX_BUILD sphinx-build) + if(NOT SPHINX_BUILD) + message(${myproject_WARNING_TYPE} "sphinx-build not found. Install with: pip install -r docs/requirements.txt") + return() endif() + + # Configure Doxygen to generate XML for Breathe (not HTML) + set(DOXYGEN_GENERATE_HTML NO) + set(DOXYGEN_GENERATE_XML YES) + set(DOXYGEN_QUIET YES) + set(DOXYGEN_RECURSIVE YES) + set(DOXYGEN_EXTRACT_ALL YES) set(DOXYGEN_CALLER_GRAPH YES) set(DOXYGEN_CALL_GRAPH YES) - set(DOXYGEN_EXTRACT_ALL YES) - set(DOXYGEN_GENERATE_TREEVIEW YES) - # svg files are much smaller than jpeg and png, and yet they have higher quality set(DOXYGEN_DOT_IMAGE_FORMAT svg) set(DOXYGEN_DOT_TRANSPARENT YES) + set(DOXYGEN_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/docs/doxygen") + + # Expand export macros so Breathe can parse the declarations + set(DOXYGEN_ENABLE_PREPROCESSING YES) + set(DOXYGEN_MACRO_EXPANSION YES) + set(DOXYGEN_EXPAND_ONLY_PREDEF YES) + set(DOXYGEN_PREDEFINED "SAMPLE_LIBRARY_EXPORT=") - # If not specified, exclude the vcpkg files and the files CMake downloads under _deps (like project_options) if(NOT DOXYGEN_EXCLUDE_PATTERNS) - set(DOXYGEN_EXCLUDE_PATTERNS "${CMAKE_CURRENT_BINARY_DIR}/vcpkg_installed/*" "${CMAKE_CURRENT_BINARY_DIR}/_deps/*") + set(DOXYGEN_EXCLUDE_PATTERNS "${CMAKE_CURRENT_BINARY_DIR}/_deps/*") endif() - if("${DOXYGEN_THEME}" STREQUAL "") - set(DOXYGEN_THEME "awesome-sidebar") - endif() + # Only document project sources + set(_doxygen_inputs + "${PROJECT_SOURCE_DIR}/include" + "${PROJECT_SOURCE_DIR}/src") - if("${DOXYGEN_THEME}" STREQUAL "awesome" OR "${DOXYGEN_THEME}" STREQUAL "awesome-sidebar") - # use a modern doxygen theme - # https://github.com/jothepro/doxygen-awesome-css v1.6.1 - FetchContent_Declare(_doxygen_theme - URL https://github.com/jothepro/doxygen-awesome-css/archive/refs/tags/v1.6.1.zip) - FetchContent_MakeAvailable(_doxygen_theme) - if("${DOXYGEN_THEME}" STREQUAL "awesome" OR "${DOXYGEN_THEME}" STREQUAL "awesome-sidebar") - set(DOXYGEN_HTML_EXTRA_STYLESHEET "${_doxygen_theme_SOURCE_DIR}/doxygen-awesome.css") - endif() - if("${DOXYGEN_THEME}" STREQUAL "awesome-sidebar") - set(DOXYGEN_HTML_EXTRA_STYLESHEET ${DOXYGEN_HTML_EXTRA_STYLESHEET} - "${_doxygen_theme_SOURCE_DIR}/doxygen-awesome-sidebar-only.css") - endif() - else() - # use the original doxygen theme - endif() + doxygen_add_docs(doxygen-xml + ${_doxygen_inputs} + COMMENT "Generating Doxygen XML...") + + # Sphinx + Breathe + Exhale → HTML + set(SPHINX_SOURCE_DIR "${PROJECT_SOURCE_DIR}/docs") + set(SPHINX_OUTPUT_DIR "${CMAKE_CURRENT_BINARY_DIR}/docs/html") + set(DOXYGEN_XML_DIR "${CMAKE_CURRENT_BINARY_DIR}/docs/doxygen/xml") - # find doxygen and dot if available - find_package(Doxygen REQUIRED OPTIONAL_COMPONENTS dot) + add_custom_target(docs + COMMAND ${CMAKE_COMMAND} -E env + "DOXYGEN_XML_DIR=${DOXYGEN_XML_DIR}" + "PROJECT_SOURCE_DIR=${PROJECT_SOURCE_DIR}" + ${SPHINX_BUILD} -b html + "${SPHINX_SOURCE_DIR}" + "${SPHINX_OUTPUT_DIR}" + DEPENDS doxygen-xml + COMMENT "Generating documentation: ${SPHINX_OUTPUT_DIR}/index.html" + VERBATIM) - # add doxygen-docs target - message(STATUS "Adding `doxygen-docs` target that builds the documentation.") - doxygen_add_docs(doxygen-docs ALL ${PROJECT_SOURCE_DIR} - COMMENT "Generating documentation - entry file: ${CMAKE_CURRENT_BINARY_DIR}/html/index.html") + message(STATUS "Documentation targets: 'docs' (Sphinx + Breathe), 'doxygen-xml' (Doxygen XML only)") endfunction() diff --git a/cmake/Lizard.cmake b/cmake/Lizard.cmake new file mode 100644 index 00000000..b2046e08 --- /dev/null +++ b/cmake/Lizard.cmake @@ -0,0 +1,71 @@ +# Lizard - Code complexity analyzer +# Creates custom targets for analyzing cyclomatic complexity. +# Targets: lizard (warnings), lizard_html (HTML report), lizard_xml (XML report) + +function(myproject_setup_lizard WARNINGS_AS_ERRORS) + find_program(LIZARD lizard) + if(LIZARD) + set(LIZARD_CCN_THRESHOLD 15 CACHE STRING "Cyclomatic complexity threshold") + set(LIZARD_LENGTH_THRESHOLD 100 CACHE STRING "Function length threshold (lines)") + set(LIZARD_PARAM_THRESHOLD 6 CACHE STRING "Parameter count threshold") + + set(LIZARD_INCLUDE_DIRS "${CMAKE_SOURCE_DIR}/include" "${CMAKE_SOURCE_DIR}/src") + + set(LIZARD_COMMON_ARGS + -C ${LIZARD_CCN_THRESHOLD} + -L ${LIZARD_LENGTH_THRESHOLD} + -a ${LIZARD_PARAM_THRESHOLD} + -Eduplicate + -x "*/build/*" + -x "*/test/*" + -x "*/fuzz_test/*" + -x "*/_deps/*" + -t 4 + ${LIZARD_INCLUDE_DIRS} + ) + + # When warnings are errors, fail the build on any lizard warning + set(LIZARD_ERROR_ARGS) + if(${WARNINGS_AS_ERRORS}) + message(STATUS "Lizard: Warnings will be treated as errors") + list(APPEND LIZARD_ERROR_ARGS -i 0) + endif() + + add_custom_target( + lizard + COMMAND ${LIZARD} + ${LIZARD_COMMON_ARGS} + --warnings_only + ${LIZARD_ERROR_ARGS} + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} + COMMENT "Running Lizard complexity analyzer..." + VERBATIM + ) + + add_custom_target( + lizard_html + COMMAND ${LIZARD} + ${LIZARD_COMMON_ARGS} + -H + -o "${CMAKE_BINARY_DIR}/lizard_report.html" + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} + COMMENT "Generating Lizard HTML report..." + VERBATIM + ) + + add_custom_target( + lizard_xml + COMMAND ${LIZARD} + ${LIZARD_COMMON_ARGS} + -X + -o "${CMAKE_BINARY_DIR}/lizard_report.xml" + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} + COMMENT "Generating Lizard XML report for CI..." + VERBATIM + ) + + message(STATUS "Lizard enabled (CCN: ${LIZARD_CCN_THRESHOLD}, Length: ${LIZARD_LENGTH_THRESHOLD}, Params: ${LIZARD_PARAM_THRESHOLD})") + else() + message(${myproject_WARNING_TYPE} "Lizard requested but not found. Install with: pip install lizard") + endif() +endfunction() diff --git a/cmake/Sanitizers.cmake b/cmake/Sanitizers.cmake index e238fa21..48760fb4 100644 --- a/cmake/Sanitizers.cmake +++ b/cmake/Sanitizers.cmake @@ -85,6 +85,3 @@ function( endif() endfunction() - - - diff --git a/conanfile.py b/conanfile.py new file mode 100644 index 00000000..9199b477 --- /dev/null +++ b/conanfile.py @@ -0,0 +1,16 @@ +from conan import ConanFile + + +class MyProjectConan(ConanFile): + name = "myproject" + version = "0.0.2" + settings = "os", "arch", "compiler", "build_type" + generators = "CMakeToolchain", "CMakeDeps" + + def requirements(self): + self.requires("spdlog/[>=1.14.0 <2.0]") + self.requires("catch2/[>=3.7.0 <4.0]") + self.requires("cli11/[>=2.4.0 <3.0]") + self.requires("ftxui/[>=5.0 <7.0]") + # fmt is a transitive dependency of spdlog — no need to pin it + # explicitly. It is available via find_package(fmt) in CMake. diff --git a/configured_files/CMakeLists.txt b/configured_files/CMakeLists.txt index 2bf896ee..4d645229 100644 --- a/configured_files/CMakeLists.txt +++ b/configured_files/CMakeLists.txt @@ -3,5 +3,3 @@ # converted to one that is publicly installed in the case that # you are developing a library configure_file("config.hpp.in" "${CMAKE_BINARY_DIR}/configured_files/include/internal_use_only/config.hpp" ESCAPE_QUOTES) - - diff --git a/docs/.gitignore b/docs/.gitignore new file mode 100644 index 00000000..8c6492ca --- /dev/null +++ b/docs/.gitignore @@ -0,0 +1 @@ +api/ diff --git a/docs/conf.py b/docs/conf.py new file mode 100644 index 00000000..8f344a5c --- /dev/null +++ b/docs/conf.py @@ -0,0 +1,39 @@ +import os + +project = 'myproject' +copyright = '' +author = '' + +extensions = [ + 'breathe', + 'exhale', +] + +# -- Breathe (Doxygen XML → Sphinx) ----------------------------------------- + +# CMake passes the XML path via environment variable; fall back to a +# conventional location so that standalone sphinx-build also works. +_doxygen_xml_dir = os.environ.get( + 'DOXYGEN_XML_DIR', + os.path.join(os.path.dirname(__file__), '..', 'build', 'docs', 'doxygen', 'xml')) + +breathe_projects = {'myproject': _doxygen_xml_dir} +breathe_default_project = 'myproject' + +# -- Exhale (auto-generate API tree from Breathe) --------------------------- + +_project_source_dir = os.environ.get( + 'PROJECT_SOURCE_DIR', + os.path.join(os.path.dirname(__file__), '..')) + +exhale_args = { + 'containmentFolder': './api', + 'rootFileName': 'library_root.rst', + 'rootFileTitle': 'API Reference', + 'doxygenStripFromPath': _project_source_dir, + 'createTreeView': True, +} + +# -- Theme ------------------------------------------------------------------- + +html_theme = 'sphinx_rtd_theme' diff --git a/docs/index.rst b/docs/index.rst new file mode 100644 index 00000000..35fbc313 --- /dev/null +++ b/docs/index.rst @@ -0,0 +1,17 @@ +myproject +========= + +Welcome to the myproject documentation. + +.. toctree:: + :maxdepth: 2 + :caption: Contents: + + api/library_root + + +Indices and tables +================== + +* :ref:`genindex` +* :ref:`search` diff --git a/docs/requirements.txt b/docs/requirements.txt new file mode 100644 index 00000000..b46955ea --- /dev/null +++ b/docs/requirements.txt @@ -0,0 +1,4 @@ +sphinx>=7.0 +breathe>=4.35 +exhale>=0.3 +sphinx_rtd_theme>=2.0 diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 8679286f..edb92365 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.15...3.23) -project(CmakeConfigPackageTests +project(CmakeConfigPackageTests VERSION ${PROJECT_VERSION} LANGUAGES CXX) @@ -19,7 +19,11 @@ endif() # ---- Dependencies ---- -include(${Catch2_SOURCE_DIR}/extras/Catch.cmake) +# Catch2 test discovery module (Catch.cmake) is added to CMAKE_MODULE_PATH +# by myproject_setup_dependencies() in Dependencies.cmake. +if(NOT COMMAND catch_discover_tests) + include(Catch) +endif() # Provide a simple smoke test to make sure that the CLI works and can display a --help message add_test(NAME cli.has_help COMMAND intro --help) From 6fc6237c70ad8d78e81b289d1a505ab118525ef0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vin=C3=ADcius=20Ferr=C3=A3o?= <2031761+viniciusferrao@users.noreply.github.com> Date: Tue, 17 Mar 2026 19:20:56 -0300 Subject: [PATCH 02/10] fix(ci): fix template janitor and update GitHub Actions - Fix template-rename job: add missing Conan install step, update compiler from gcc-11 to gcc-14, fix packaging_maintainer_mode typo - Add master branch to template janitor push triggers - Update actions/cache from v3 to v4 Signed-off-by: Helder Ferreira Co-Authored-By: Claude Opus 4.6 (1M context) --- .github/actions/setup_cache/action.yml | 2 +- .github/workflows/template-janitor.yml | 14 +++++++++++--- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/.github/actions/setup_cache/action.yml b/.github/actions/setup_cache/action.yml index 0c83847a..4fa341c7 100644 --- a/.github/actions/setup_cache/action.yml +++ b/.github/actions/setup_cache/action.yml @@ -20,7 +20,7 @@ runs: using: "composite" steps: - name: Cache - uses: actions/cache@v3 + uses: actions/cache@v4 with: # You might want to add .ccache to your cache configuration? path: | diff --git a/.github/workflows/template-janitor.yml b/.github/workflows/template-janitor.yml index e531466b..ce301c25 100644 --- a/.github/workflows/template-janitor.yml +++ b/.github/workflows/template-janitor.yml @@ -8,6 +8,7 @@ on: types: [published] push: branches: + - master - main - develop permissions: @@ -41,7 +42,7 @@ jobs: with: compiler: ${{ matrix.compiler }} build_type: ${{ matrix.build_type }} - package_maintainer_mode: ${{ matrix.package_maintainer_mode }} + packaging_maintainer_mode: ${{ matrix.packaging_maintainer_mode }} generator: ${{ matrix.generator }} - name: Get organization and project name @@ -184,7 +185,7 @@ jobs: strategy: matrix: compiler: - - gcc-11 + - gcc-14 generator: - "Unix Makefiles" build_type: @@ -242,7 +243,7 @@ jobs: - name: Setup Cpp uses: aminya/setup-cpp@v1 with: - compiler: gcc + compiler: ${{ matrix.compiler }} cmake: true ninja: false @@ -255,6 +256,13 @@ jobs: gcovr: false opencppcoverage: false + - name: Install Conan + run: pip install conan + + - name: Project Name + uses: cardinalby/export-env-action@v2 + with: + envFile: '.github/constants.env' - name: Test simple configuration to make sure nothing broke (default compiler,cmake,packaging_maintainer_mode OFF) run: | From 1258393b649e17b978d8fb60b7335fd6d5632d16 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vin=C3=ADcius=20Ferr=C3=A3o?= <2031761+viniciusferrao@users.noreply.github.com> Date: Tue, 17 Mar 2026 19:28:40 -0300 Subject: [PATCH 03/10] build(devcontainer): replace Ubuntu with Red Hat UBI 10 - Replace Ubuntu focal with Red Hat UBI 10 - GCC 14 as system compiler, GCC 15 via gcc-toolset-15 - LLVM 19 / Clang via llvm-toolset - Build IWYU from source against Clang 19 - All tools pre-installed: cmake, ninja, ccache, cppcheck, doxygen, graphviz, conan, neovim, nano - Modernize devcontainer.json to customizations.vscode schema - Update README_docker.md with UBI 10 instructions Signed-off-by: Helder Ferreira Co-Authored-By: Claude Opus 4.6 (1M context) --- .devcontainer/Dockerfile | 135 ++++++++++---------------------- .devcontainer/devcontainer.json | 60 +++++--------- .github/template/README.md | 18 +++++ README.md | 34 +++++++- README_docker.md | 48 ++++++------ 5 files changed, 135 insertions(+), 160 deletions(-) diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index e948180e..4e6e5eb7 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -1,108 +1,63 @@ -# [Choice] bionic (18.04), focal (20.04) -ARG VARIANT="focal" -FROM ubuntu:${VARIANT} +# Red Hat Universal Base Image 10 +FROM registry.access.redhat.com/ubi10/ubi -# Restate the variant to use it later on in the llvm and cmake installations -ARG VARIANT +# ── Enable required repositories ────────────────────────────────────── +RUN dnf install -y 'dnf-command(config-manager)' && \ + dnf config-manager --enable ubi-10-codeready-builder-rpms && \ + dnf install -y https://dl.fedoraproject.org/pub/epel/epel-release-latest-10.noarch.rpm -# Install necessary packages available from standard repos -RUN apt-get update -qq && export DEBIAN_FRONTEND=noninteractive && \ - apt-get install -y --no-install-recommends \ - software-properties-common wget apt-utils file zip \ - openssh-client gpg-agent socat rsync \ - make ninja-build git \ - python3 python3-pip +# ── Core build tools ────────────────────────────────────────────────── +RUN dnf install -y \ + make ninja-build cmake ccache git git-lfs \ + python3 python3-pip wget zip unzip rsync \ + openssh-clients file -# Install conan -RUN python3 -m pip install --upgrade pip setuptools && \ - python3 -m pip install conan && \ - conan --version - -# By default, anything you run in Docker is done as superuser. -# Conan runs some install commands as superuser, and will prepend `sudo` to -# these commands, unless `CONAN_SYSREQUIRES_SUDO=0` is in your env variables. -ENV CONAN_SYSREQUIRES_SUDO 0 -# Some packages request that Conan use the system package manager to install -# a few dependencies. This flag allows Conan to proceed with these installations; -# leaving this flag undefined can cause some installation failures. -ENV CONAN_SYSREQUIRES_MODE enabled - -# User-settable versions: -# This Dockerfile should support gcc-[7, 8, 9, 10, 11] and clang-[10, 11, 12, 13] -# Earlier versions of clang will require significant modifications to the IWYU section -ARG GCC_VER="11" -# Add gcc-${GCC_VER} -RUN add-apt-repository -y ppa:ubuntu-toolchain-r/test && \ - apt-get update -qq && export DEBIAN_FRONTEND=noninteractive && \ - apt-get install -y --no-install-recommends \ - gcc-${GCC_VER} g++-${GCC_VER} gdb - -# Set gcc-${GCC_VER} as default gcc -RUN update-alternatives --install /usr/bin/gcc gcc $(which gcc-${GCC_VER}) 100 -RUN update-alternatives --install /usr/bin/g++ g++ $(which g++-${GCC_VER}) 100 +# ── GCC 14 (system) + GCC 15 (toolset) ─────────────────────────────── +RUN dnf install -y gcc gcc-c++ gdb \ + gcc-toolset-15-gcc gcc-toolset-15-gcc-c++ gcc-toolset-15-binutils -ARG LLVM_VER="13" -# Add clang-${LLVM_VER} -ARG LLVM_URL="http://apt.llvm.org/${VARIANT}/" -ARG LLVM_PKG="llvm-toolchain-${VARIANT}-${LLVM_VER}" -RUN wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key | apt-key add - 2>/dev/null && \ - add-apt-repository -y "deb ${LLVM_URL} ${LLVM_PKG} main" && \ - apt-get update -qq && export DEBIAN_FRONTEND=noninteractive && \ - apt-get install -y --no-install-recommends \ - clang-${LLVM_VER} lldb-${LLVM_VER} lld-${LLVM_VER} clangd-${LLVM_VER} \ - llvm-${LLVM_VER}-dev libclang-${LLVM_VER}-dev clang-tidy-${LLVM_VER} +# ── LLVM / Clang ───────────────────────────────────────────────────── +RUN dnf install -y \ + llvm-toolset clang clang-tools-extra clang-devel llvm-devel lld lldb -# Set the default clang-tidy, so CMake can find it -RUN update-alternatives --install /usr/bin/clang-tidy clang-tidy $(which clang-tidy-${LLVM_VER}) 1 +# ── Static analysis & documentation ────────────────────────────────── +RUN dnf install -y cppcheck doxygen graphviz -# Set clang-${LLVM_VER} as default clang -RUN update-alternatives --install /usr/bin/clang clang $(which clang-${LLVM_VER}) 100 -RUN update-alternatives --install /usr/bin/clang++ clang++ $(which clang++-${LLVM_VER}) 100 +# ── Editors ─────────────────────────────────────────────────────────── +RUN dnf install -y neovim nano -# Add current cmake/ccmake, from Kitware -ARG CMAKE_URL="https://apt.kitware.com/ubuntu/" -ARG CMAKE_PKG=${VARIANT} -RUN wget -O - https://apt.kitware.com/keys/kitware-archive-latest.asc 2>/dev/null \ - | gpg --dearmor - | tee /etc/apt/trusted.gpg.d/kitware.gpg >/dev/null && \ - apt-add-repository -y "deb ${CMAKE_URL} ${CMAKE_PKG} main" && \ - apt-get update -qq && export DEBIAN_FRONTEND=noninteractive && \ - apt-get install -y --no-install-recommends cmake cmake-curses-gui +# ── Python packages ────────────────────────────────────────────────── +RUN python3 -m pip install --upgrade pip setuptools && \ + python3 -m pip install conan && \ + conan --version -# Install editors -RUN apt-get update -qq && export DEBIAN_FRONTEND=noninteractive && \ - apt-get install -y --no-install-recommends \ - neovim emacs nano +# Conan configuration for containers +ENV CONAN_SYSREQUIRES_SUDO=0 \ + CONAN_SYSREQUIRES_MODE=enabled -# Install optional dependecies -RUN apt-get update -qq && export DEBIAN_FRONTEND=noninteractive && \ - apt-get install -y --no-install-recommends \ - doxygen graphviz ccache cppcheck +# ── include-what-you-use (build from source) ───────────────────────── +ARG IWYU_BRANCH="clang_19" +ENV IWYU=/home/iwyu \ + IWYU_BUILD=/home/iwyu/build \ + IWYU_SRC=/home/iwyu/include-what-you-use -# Install include-what-you-use -ENV IWYU /home/iwyu -ENV IWYU_BUILD ${IWYU}/build -ENV IWYU_SRC ${IWYU}/include-what-you-use RUN mkdir -p ${IWYU_BUILD} && \ - git clone --branch clang_${LLVM_VER} \ + git clone --branch ${IWYU_BRANCH} --depth 1 \ https://github.com/include-what-you-use/include-what-you-use.git \ ${IWYU_SRC} -RUN CC=clang-${LLVM_VER} CXX=clang++-${LLVM_VER} cmake -S ${IWYU_SRC} \ - -B ${IWYU_BUILD} \ - -G "Unix Makefiles" -DCMAKE_PREFIX_PATH=/usr/lib/llvm-${LLVM_VER} && \ + +RUN cmake -S ${IWYU_SRC} -B ${IWYU_BUILD} -G "Unix Makefiles" \ + -DCMAKE_PREFIX_PATH=$(llvm-config --prefix) && \ cmake --build ${IWYU_BUILD} -j && \ cmake --install ${IWYU_BUILD} -# Per https://github.com/include-what-you-use/include-what-you-use#how-to-install: -# `You need to copy the Clang include directory to the expected location before -# running (similarly, use include-what-you-use -print-resource-dir to learn -# exactly where IWYU wants the headers).` -RUN mkdir -p $(include-what-you-use -print-resource-dir 2>/dev/null) -RUN ln -s $(readlink -f /usr/lib/clang/${LLVM_VER}/include) \ - $(include-what-you-use -print-resource-dir 2>/dev/null)/include +# Link Clang resource headers so IWYU can find them +RUN mkdir -p $(include-what-you-use -print-resource-dir 2>/dev/null) && \ + ln -sf $(clang -print-resource-dir)/include \ + $(include-what-you-use -print-resource-dir 2>/dev/null)/include -## Cleanup cached apt data we don't need anymore -RUN apt-get autoremove -y && apt-get clean && \ - rm -rf /var/lib/apt/lists/* +# ── Cleanup ─────────────────────────────────────────────────────────── +RUN dnf clean all && rm -rf /var/cache/dnf # Allow the user to set compiler defaults ARG USE_CLANG @@ -113,8 +68,4 @@ ENV CXX=${USE_CLANG:+"clang++"} ENV CC=${CC:-"gcc"} ENV CXX=${CXX:-"g++"} -# Include project -#ADD . /workspaces/cpp_starter_project -#WORKDIR /workspaces/cpp_starter_project - CMD ["/bin/bash"] diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index aa221fda..a0c3074b 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -1,52 +1,34 @@ -// For format details, see https://aka.ms/devcontainer.json. For config options, see the README at: -// https://github.com/microsoft/vscode-dev-containers/tree/v0.205.2/containers/cpp { "name": "C++", "build": { "dockerfile": "Dockerfile", - // Update 'VARIANT' to pick an Ubuntu OS version. Options: [bionic, focal]. Default: focal - // Update 'GCC_VER' to pick a gcc and g++ version. Options: [7, 8, 9, 10, 11]. Default: 11 - // Update 'LLVM_VER' to pick clang version. Options: [10, 11, 12, 13]. Default: 13 - // Update 'USE_CLANG' to set clang as the default C and C++ compiler. Options: [1, null]. Default null - // "args": { - // "VARIANT": "focal", - // "GCC_VER": "11", - // "LLVM_VER": "13" - // } + // Set USE_CLANG=1 to use clang as the default compiler instead of gcc. + "args": {} }, "runArgs": [ "--cap-add=SYS_PTRACE", "--security-opt", "seccomp=unconfined" ], - // Set *default* container specific settings.json values on container create. - "settings": { - "cmake.configureOnOpen": true, - "editor.formatOnSave": true + "customizations": { + "vscode": { + "settings": { + "cmake.configureOnOpen": true, + "editor.formatOnSave": true + }, + "extensions": [ + "ms-vscode.cpptools", + "ms-vscode.cmake-tools", + "twxs.cmake", + "ms-vscode.cpptools-themes", + "cschlosser.doxdocgen", + "eamodio.gitlens", + "ms-python.python", + "ms-python.vscode-pylance", + "mutantdino.resourcemonitor" + ] + } }, - // Add the IDs of extensions you want installed when the container is created. - "extensions": [ - "ms-vscode.cpptools", - "ms-vscode.cmake-tools", - "twxs.cmake", - "ms-vscode.cpptools-themes", - "cschlosser.doxdocgen", - "eamodio.gitlens", - "ms-python.python", - "ms-python.vscode-pylance", - "mutantdino.resourcemonitor" - ], - // Use 'forwardPorts' to make a list of ports inside the container available locally. - // "forwardPorts": [], - // Use 'postCreateCommand' to run commands after the container is created. - //"postCreateCommand": "uname -a", - // Comment out connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root. - //"remoteUser": "vscode", "workspaceMount": "source=${localWorkspaceFolder},target=/workspaces/${localWorkspaceFolderBasename},type=bind,consistency=delegated", - "workspaceFolder": "/workspaces/${localWorkspaceFolderBasename}", - "features": { - "git": "latest", - "git-lfs": "latest", - "powershell": "latest" - } + "workspaceFolder": "/workspaces/${localWorkspaceFolderBasename}" } diff --git a/.github/template/README.md b/.github/template/README.md index fcda5aea..af894976 100644 --- a/.github/template/README.md +++ b/.github/template/README.md @@ -7,6 +7,16 @@ ## About %%myproject%% %%description%% +### Developer mode + +By default the project enables developer mode with: + + * Address Sanitizer and Undefined Behavior Sanitizer + * Warnings as errors + * clang-tidy and cppcheck static analysis + * Conan 2.0 for dependency management + * pre-commit hooks (clang-format, gitlint, trailing whitespace) + ## WebAssembly Demo Try the live WebAssembly demo: @@ -15,6 +25,14 @@ Try the live WebAssembly demo: The `main` branch deploys to the root, `develop` to `/develop/`, and tags to `/tagname/`. +## Getting Started + +```sh +pipx install pre-commit +pre-commit install +``` + +See [CONTRIBUTING.md](CONTRIBUTING.md) for commit message policy and developer workflow. ## More Details diff --git a/README.md b/README.md index 485693a2..07c1e59a 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ By default (collectively known as `ENABLE_DEVELOPER_MODE`) * Address Sanitizer and Undefined Behavior Sanitizer enabled where possible * Warnings as errors * clang-tidy and cppcheck static analysis - * CPM for dependencies + * Conan 2.0 for dependency management It includes @@ -22,6 +22,24 @@ It includes * large GitHub action testing matrix * WebAssembly build support with automatic GitHub Pages deployment +### Code quality tools + + * [pre-commit](https://pre-commit.com/) hooks for trailing whitespace, end-of-file, large file checks, clang-format, and gitlint + * [clang-format](https://clang.llvm.org/docs/ClangFormat.html) for automatic code formatting + * [clang-tidy](https://clang.llvm.org/extra/clang-tidy/) and [cppcheck](http://cppcheck.sourceforge.net/) for static analysis + * [include-what-you-use](https://include-what-you-use.org/) for include hygiene (opt-in) + * [Lizard](https://github.com/terryyin/lizard) for cyclomatic complexity analysis + * [Bloaty McBloatface](https://github.com/google/bloaty) for binary size analysis (opt-in) + * [Conventional Commits](https://www.conventionalcommits.org/) enforced via [gitlint](https://jorisroovers.com/gitlint/) + +### Documentation + + * [Sphinx](https://www.sphinx-doc.org/) + [Breathe](https://breathe.readthedocs.io/) + [Exhale](https://exhale.readthedocs.io/) documentation pipeline powered by Doxygen + +### Development environment + + * [Devcontainer](https://containers.dev/) based on Red Hat UBI 10 with GCC 14, Clang 19, and all tools pre-installed + **Live Demo:** If you enable GitHub Pages in your project created from this template, you'll have a working example like this: - Main: [https://cpp-best-practices.github.io/cmake_template/](https://cpp-best-practices.github.io/cmake_template/) - Develop: [https://cpp-best-practices.github.io/cmake_template/develop/](https://cpp-best-practices.github.io/cmake_template/develop/) @@ -31,12 +49,11 @@ The `main` branch deploys to the root, `develop` to `/develop/`, and tags to `/t It requires * cmake - * a compiler - + * a C++17 compiler + * Conan 2.0 This project gets you started with a simple example of using FTXUI, which happens to also be a game. - ## Getting Started ### Use the GitHub template @@ -54,6 +71,15 @@ Now you can clone the project locally and get to work! git clone https://github.com//.git +### Set up pre-commit hooks + +```sh +pipx install pre-commit +pre-commit install +``` + +See [CONTRIBUTING.md](CONTRIBUTING.md) for commit message policy and developer workflow. + ## More Details * [Dependency Setup](README_dependencies.md) diff --git a/README_docker.md b/README_docker.md index d2c548de..38c0f32c 100644 --- a/README_docker.md +++ b/README_docker.md @@ -8,30 +8,33 @@ docker build -f ./.devcontainer/Dockerfile --tag=my_project:latest . docker run -it my_project:latest ``` -This command will put you in a `bash` session in a Ubuntu 20.04 Docker container, +This command will put you in a `bash` session in a Red Hat UBI 10 Docker container, with all of the tools listed in the [Dependencies](README_dependencies.md#dependencies) section already installed. -Additionally, you will have `g++-11` and `clang++-13` installed as the default -versions of `g++` and `clang++`. +You will have GCC 14 (system default), GCC 15 (via gcc-toolset-15), and Clang 19 +available, along with CMake, Ninja, Conan 2.0, clang-tidy, cppcheck, +include-what-you-use, ccache, doxygen, and graphviz. -If you want to build this container using some other versions of gcc and clang, -you may do so with the `GCC_VER` and `LLVM_VER` arguments: +The CC and CXX environment variables are set to GCC 14 by default. +To use GCC 15 instead, activate the toolset: ```bash -docker build --tag=myproject:latest --build-arg GCC_VER=10 --build-arg LLVM_VER=11 . +source /opt/rh/gcc-toolset-15/enable ``` -The CC and CXX environment variables are set to GCC version 11 by default. -If you wish to use clang as your default CC and CXX environment variables, you -may do so like this: +Or set the compiler explicitly: ```bash -docker build --tag=my_project:latest --build-arg USE_CLANG=1 . +CC=/opt/rh/gcc-toolset-15/root/usr/bin/gcc CXX=/opt/rh/gcc-toolset-15/root/usr/bin/g++ cmake -S . -B ./build +``` + +If you wish to use clang as your default compiler, build the container with: + +```bash +docker build --tag=my_project:latest --build-arg USE_CLANG=1 -f ./.devcontainer/Dockerfile . ``` You will be logged in as root, so you will see the `#` symbol as your prompt. -You will be in a directory that contains a copy of the `cpp_starter_project`; -any changes you make to your local copy will not be updated in the Docker image -until you rebuild it. + If you need to mount your local copy directly in the Docker image, see [Docker volumes docs](https://docs.docker.com/storage/volumes/). TLDR: @@ -45,26 +48,21 @@ docker run -it \ You can configure and build [as directed above](#build) using these commands: ```bash -/starter_project# mkdir build -/starter_project# cmake -S . -B ./build -/starter_project# cmake --build ./build +mkdir build +cmake -S . -B ./build +cmake --build ./build ``` -You can configure and build using `clang-13`, without rebuilding the container, +You can configure and build using clang, without rebuilding the container, with these commands: ```bash -/starter_project# mkdir build -/starter_project# CC=clang CXX=clang++ cmake -S . -B ./build -/starter_project# cmake --build ./build +mkdir build +CC=clang CXX=clang++ cmake -S . -B ./build +cmake --build ./build ``` -The `ccmake` tool is also installed; you can substitute `ccmake` for `cmake` to -configure the project interactively. All of the tools this project supports are installed in the Docker image; enabling them is as simple as flipping a switch using the `ccmake` interface. Be aware that some of the sanitizers conflict with each other, so be sure to run them separately. - -A script called `build_examples.sh` is provided to help you to build the example -GUI projects in this container. From 4e51d932a43ffc9e21c21de2183e2f72f5521047 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vin=C3=ADcius=20Ferr=C3=A3o?= <2031761+viniciusferrao@users.noreply.github.com> Date: Tue, 17 Mar 2026 19:28:58 -0300 Subject: [PATCH 04/10] feat: trompeloeil mocks, conan examples, and developer defaults - Add trompeloeil mocking framework with Catch2 integration - Add educational mock test example (test/mock_tests.cpp) - Restructure conanfile.py with commented-out package examples (magic_enum, nlohmann_json, tomlplusplus, inja, grpc, protobuf, libssh2, libpqxx, reproc, gsl-lite) and version range cheat-sheet - Widen fmt version range to include 12.x - Enable clang-tidy HeaderFilterRegex for project sources - Enable IWYU and Bloaty by default in developer mode - Standardize section comments across config files Signed-off-by: Helder Ferreira Co-Authored-By: Claude Opus 4.6 (1M context) --- .clang-tidy | 2 +- .gitattributes | 8 +--- .github/workflows/codeql-analysis.yml | 2 +- .gitlint | 12 ++---- Dependencies.cmake | 4 ++ ProjectOptions.cmake | 4 +- README.md | 4 +- conanfile.py | 56 +++++++++++++++++++++++++-- docs/conf.py | 6 +-- test/CMakeLists.txt | 29 +++++++++++++- test/mock_tests.cpp | 53 +++++++++++++++++++++++++ 11 files changed, 151 insertions(+), 29 deletions(-) create mode 100644 test/mock_tests.cpp diff --git a/.clang-tidy b/.clang-tidy index a9d9dab8..cd70e1c9 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -18,7 +18,7 @@ Checks: "*, -misc-use-internal-linkage " WarningsAsErrors: '' -HeaderFilterRegex: '' +HeaderFilterRegex: '(include|src)/' FormatStyle: none CheckOptions: diff --git a/.gitattributes b/.gitattributes index 1e2c9d2b..7c9073c7 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,15 +1,11 @@ -############################### -# Git Line Endings # -############################### +# ── Git Line Endings ────────────────────────────────────────────────── * text=auto eol=lf *.{cmd,[cC][mM][dD]} text eol=crlf *.{bat,[bB][aA][tT]} text eol=crlf *.{vcxproj,vcxproj.filters} text eol=crlf -############################### -# Git Large File System (LFS) # -############################### +# ── Git Large File System (LFS) ─────────────────────────────────────── # Archives #*.7z filter=lfs diff=lfs merge=lfs -text diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 016120bf..7482a90e 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -4,7 +4,7 @@ # You may wish to alter this file to override the set of languages analyzed, # or to provide custom queries or build logic. # -# ******** NOTE ******** +# ── NOTE ────────────────────────────────────────────────────────────── # We have attempted to detect the languages in your repository. Please check # the `language` matrix defined below to confirm you have the correct set of # supported CodeQL languages. diff --git a/.gitlint b/.gitlint index 061ffeb6..40e4a380 100644 --- a/.gitlint +++ b/.gitlint @@ -8,9 +8,7 @@ regex-style-search = true # body length does not. ignore = body-max-line-length -# ----------------------------------------------------------- -# Conventional Commits title -# ----------------------------------------------------------- +# ── Conventional Commits title ──────────────────────────────────────── [title-match-regex] # Allowed types: build|chore|ci|docs|feat|fix|perf|refactor|revert|style|test # Format: type(scope?)?(!)?: subject @@ -19,15 +17,11 @@ regex = ^(build|chore|ci|docs|feat|fix|perf|refactor|revert|style|test)(\([a-z0- [title-max-length] line-length = 72 -# ----------------------------------------------------------- -# Body formatting -# ----------------------------------------------------------- +# ── Body formatting ─────────────────────────────────────────────────── [body-first-line-empty] # Enforce a blank line between title and body when a body is present. -# ----------------------------------------------------------- -# DCO / Signed-off-by enforcement -# ----------------------------------------------------------- +# ── DCO / Signed-off-by enforcement ─────────────────────────────────── [body-match-regex] # Require at least one "Signed-off-by: Name " line somewhere in the body. # This works together with regex-style-search=true in [general]. diff --git a/Dependencies.cmake b/Dependencies.cmake index bf50a708..afb08104 100644 --- a/Dependencies.cmake +++ b/Dependencies.cmake @@ -35,6 +35,10 @@ function(myproject_setup_dependencies) endforeach() endif() + if(NOT TARGET trompeloeil::trompeloeil) + find_package(trompeloeil REQUIRED) + endif() + if(NOT TARGET CLI11::CLI11) find_package(CLI11 REQUIRED) endif() diff --git a/ProjectOptions.cmake b/ProjectOptions.cmake index 1b2c0a0e..3af44542 100644 --- a/ProjectOptions.cmake +++ b/ProjectOptions.cmake @@ -98,9 +98,9 @@ macro(myproject_setup_options) option(myproject_ENABLE_UNITY_BUILD "Enable unity builds" OFF) option(myproject_ENABLE_CLANG_TIDY "Enable clang-tidy" ON) option(myproject_ENABLE_CPPCHECK "Enable cpp-check analysis" ON) - option(myproject_ENABLE_INCLUDE_WHAT_YOU_USE "Enable include-what-you-use" OFF) + option(myproject_ENABLE_INCLUDE_WHAT_YOU_USE "Enable include-what-you-use" ON) option(myproject_ENABLE_LIZARD "Enable Lizard complexity analysis" ON) - option(myproject_ENABLE_BLOATY "Enable Bloaty McBloatface binary size analysis" OFF) + option(myproject_ENABLE_BLOATY "Enable Bloaty McBloatface binary size analysis" ON) option(myproject_ENABLE_PCH "Enable precompiled headers" OFF) option(myproject_ENABLE_CACHE "Enable ccache" ON) endif() diff --git a/README.md b/README.md index 07c1e59a..2f757457 100644 --- a/README.md +++ b/README.md @@ -27,9 +27,9 @@ It includes * [pre-commit](https://pre-commit.com/) hooks for trailing whitespace, end-of-file, large file checks, clang-format, and gitlint * [clang-format](https://clang.llvm.org/docs/ClangFormat.html) for automatic code formatting * [clang-tidy](https://clang.llvm.org/extra/clang-tidy/) and [cppcheck](http://cppcheck.sourceforge.net/) for static analysis - * [include-what-you-use](https://include-what-you-use.org/) for include hygiene (opt-in) + * [include-what-you-use](https://include-what-you-use.org/) for include hygiene * [Lizard](https://github.com/terryyin/lizard) for cyclomatic complexity analysis - * [Bloaty McBloatface](https://github.com/google/bloaty) for binary size analysis (opt-in) + * [Bloaty McBloatface](https://github.com/google/bloaty) for binary size analysis * [Conventional Commits](https://www.conventionalcommits.org/) enforced via [gitlint](https://jorisroovers.com/gitlint/) ### Documentation diff --git a/conanfile.py b/conanfile.py index 9199b477..af4553d7 100644 --- a/conanfile.py +++ b/conanfile.py @@ -1,16 +1,66 @@ from conan import ConanFile +from conan.tools.cmake import CMakeToolchain class MyProjectConan(ConanFile): name = "myproject" version = "0.0.2" settings = "os", "arch", "compiler", "build_type" - generators = "CMakeToolchain", "CMakeDeps" + generators = "CMakeDeps" def requirements(self): + self.requires("fmt/[>=11.0 <13.0]") self.requires("spdlog/[>=1.14.0 <2.0]") self.requires("catch2/[>=3.7.0 <4.0]") + self.requires("trompeloeil/[>=49 <50]") self.requires("cli11/[>=2.4.0 <3.0]") self.requires("ftxui/[>=5.0 <7.0]") - # fmt is a transitive dependency of spdlog — no need to pin it - # explicitly. It is available via find_package(fmt) in CMake. + + # ── Commented-out packages ──────────────────────────────────── + # Uncomment the ones your project needs. These serve as + # ready-to-use examples with tested version ranges. + # + # Reflection & type support + #self.requires("magic_enum/[>=0.9 <1.0]") + #self.requires("gsl-lite/[>=1.0 <2.0]") + # + # Serialization & configuration + #self.requires("nlohmann_json/[>=3.11 <4.0]") + #self.requires("tomlplusplus/[>=3.3 <4.0]") + #self.requires("inja/[>=3.4 <4.0]") + # + # Networking & RPC + #self.requires("grpc/[>=1.67 <2.0]") + #self.requires("protobuf/[>=5.29 <7.0]") + #self.requires("libssh2/[>=1.11 <2.0]") + # + # Database + #self.requires("libpqxx/[>=7.9 <9.0]") + # + # Process management + #self.requires("reproc/[>=14.2 <15.0]") + + # ── Version range cheat-sheet ───────────────────────────────── + # + # Exact version (reproducible, no flexibility): + # self.requires("spdlog/1.14.1") + # + # Semver-compatible range (patch updates only): + # self.requires("spdlog/[~1.14]") # >=1.14.0 <1.15.0 + # + # Minor-update range: + # self.requires("spdlog/[>=1.14.0 <2.0]") # any 1.x from 1.14+ + # + # Override a transitive dependency: + # self.requires("protobuf/5.29.6", override=True) + # + # Package with options: + # self.requires("spdlog/[>=1.14.0 <2.0]", + # options={"use_std_fmt": True}) + # self.requires("reproc/[>=14.2 <15.0]", + # options={"with_cxx": True}) + + def generate(self): + tc = CMakeToolchain(self) + tc.user_presets_path = False + tc.generate() diff --git a/docs/conf.py b/docs/conf.py index 8f344a5c..7344f090 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -9,7 +9,7 @@ 'exhale', ] -# -- Breathe (Doxygen XML → Sphinx) ----------------------------------------- +# ── Breathe (Doxygen XML → Sphinx) ──────────────────────────────────── # CMake passes the XML path via environment variable; fall back to a # conventional location so that standalone sphinx-build also works. @@ -20,7 +20,7 @@ breathe_projects = {'myproject': _doxygen_xml_dir} breathe_default_project = 'myproject' -# -- Exhale (auto-generate API tree from Breathe) --------------------------- +# ── Exhale (auto-generate API tree from Breathe) ────────────────────── _project_source_dir = os.environ.get( 'PROJECT_SOURCE_DIR', @@ -34,6 +34,6 @@ 'createTreeView': True, } -# -- Theme ------------------------------------------------------------------- +# ── Theme ───────────────────────────────────────────────────────────── html_theme = 'sphinx_rtd_theme' diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index edb92365..295517cf 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -4,7 +4,7 @@ project(CmakeConfigPackageTests VERSION ${PROJECT_VERSION} LANGUAGES CXX) -# ---- Test as standalone project the exported config package ---- +# ── Test as standalone project the exported config package ──────────── if(PROJECT_IS_TOP_LEVEL OR TEST_INSTALLED_VERSION) enable_testing() @@ -17,7 +17,7 @@ if(PROJECT_IS_TOP_LEVEL OR TEST_INSTALLED_VERSION) endif() endif() -# ---- Dependencies ---- +# ── Dependencies ────────────────────────────────────────────────────── # Catch2 test discovery module (Catch.cmake) is added to CMAKE_MODULE_PATH # by myproject_setup_dependencies() in Dependencies.cmake. @@ -68,6 +68,31 @@ catch_discover_tests( OUTPUT_SUFFIX .xml) +# ── Mock tests (trompeloeil + Catch2) ───────────────────────────────── + +add_executable(mock_tests mock_tests.cpp) +target_link_libraries( + mock_tests + PRIVATE myproject::myproject_warnings + myproject::myproject_options + Catch2::Catch2WithMain + trompeloeil::trompeloeil) + +myproject_configure_linker(mock_tests) + +catch_discover_tests( + mock_tests + TEST_PREFIX + "mock." + REPORTER + XML + OUTPUT_DIR + . + OUTPUT_PREFIX + "mock." + OUTPUT_SUFFIX + .xml) + # Add a file containing a set of constexpr tests add_executable(constexpr_tests constexpr_tests.cpp) target_link_libraries( diff --git a/test/mock_tests.cpp b/test/mock_tests.cpp new file mode 100644 index 00000000..1f044454 --- /dev/null +++ b/test/mock_tests.cpp @@ -0,0 +1,53 @@ +// Example: using trompeloeil with Catch2 for mock-based testing. +// +// This file demonstrates how to define an interface, create a mock, and +// verify interactions. It is intentionally simple so that it serves as +// a starting point for new contributors. + +#include +#include + +// ── Interface ──────────────────────────────────────────────────────── + +class ICalculator +{ +public: + virtual ~ICalculator() = default; + virtual int compute(int input) = 0; +}; + +// ── Mock ───────────────────────────────────────────────────────────── + +class MockCalculator : public ICalculator +{ +public: + MAKE_MOCK1(compute, int(int), override); +}; + +// ── Consumer under test ────────────────────────────────────────────── + +int double_compute(ICalculator &calc, int value) { return calc.compute(value) * 2; } + +// ── Tests ──────────────────────────────────────────────────────────── + +TEST_CASE("Mock verifies compute is called", "[mock]") +{ + MockCalculator calc; + + REQUIRE_CALL(calc, compute(5)).RETURN(42); + + REQUIRE(double_compute(calc, 5) == 84); +} + +TEST_CASE("Mock allows sequences of calls", "[mock]") +{ + MockCalculator calc; + + trompeloeil::sequence seq; + + REQUIRE_CALL(calc, compute(1)).IN_SEQUENCE(seq).RETURN(10); + REQUIRE_CALL(calc, compute(2)).IN_SEQUENCE(seq).RETURN(20); + + REQUIRE(double_compute(calc, 1) == 20); + REQUIRE(double_compute(calc, 2) == 40); +} From 3aa3b70ffdc0f02942079da7e93c228cfa6e098d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vin=C3=ADcius=20Ferr=C3=A3o?= <2031761+viniciusferrao@users.noreply.github.com> Date: Tue, 17 Mar 2026 19:30:33 -0300 Subject: [PATCH 05/10] refactor: housekeeping, Google C++ style, and documentation - Switch from LLVM to Google C++ style (.clang-format) - Add .editorconfig for non-C++ files - Rename Dockerfile to Containerfile (OCI-native), add symlink - Rename README_docker.md to README_container.md (runtime-agnostic) - Simplify .gitignore JetBrains section to just .idea/ - Remove FUNDING.yml, .gitlab-ci.yml, and stale references - Add fuzz_test/README.md with libFuzzer usage guide - Remove dead CMakePresets.json variables (ENABLE_DEVELOPER_MODE, etc.) - Lower CMake minimum from 3.29 to 3.21 (guard CMP0155 policy) - Update READMEs: C++23, CMake 3.21+, VS 2022, setup-cpp v1.8.0 - Update badges and links to VersatusHPC/cmake_template - Add proper acknowledgement for cpp-best-practices - Fix WASM deploy to support both main and master branches Signed-off-by: Helder Ferreira Co-Authored-By: Claude Opus 4.6 (1M context) --- .clang-format | 120 ++-------- .devcontainer/.dockerignore | 3 +- .devcontainer/Containerfile | 71 ++++++ .devcontainer/Dockerfile | 72 +----- .editorconfig | 34 +++ .gitattributes | 4 +- .github/FUNDING.yml | 4 - .github/template/README.md | 7 +- .github/template/removal-list | 1 - .github/workflows/auto-clang-format.yml | 2 +- .github/workflows/ci.yml | 1 + .github/workflows/codeql-analysis.yml | 6 +- .github/workflows/wasm.yml | 4 +- .gitignore | 94 +------- .gitlab-ci.yml | 49 ---- .gitlint | 6 +- CMakeLists.txt | 10 +- CMakePresets.json | 16 +- README.md | 22 +- README_building.md | 6 +- README_container.md | 70 ++++++ README_dependencies.md | 24 +- README_docker.md | 68 ------ conanfile.py | 4 +- docs/.gitignore | 1 + docs/conf.py | 6 +- fuzz_test/README.md | 109 +++++++++ fuzz_test/fuzz_tester.cpp | 12 +- include/myproject/sample_library.hpp | 7 +- src/ftxui_sample/main.cpp | 302 +++++++++++++----------- src/sample_library/sample_library.cpp | 3 +- test/CMakeLists.txt | 10 +- test/constexpr_tests.cpp | 4 +- test/mock_tests.cpp | 28 +-- test/tests.cpp | 6 +- 35 files changed, 575 insertions(+), 611 deletions(-) create mode 100644 .devcontainer/Containerfile mode change 100644 => 120000 .devcontainer/Dockerfile create mode 100644 .editorconfig delete mode 100644 .github/FUNDING.yml delete mode 100644 .gitlab-ci.yml create mode 100644 README_container.md delete mode 100644 README_docker.md create mode 100644 fuzz_test/README.md diff --git a/.clang-format b/.clang-format index 6e34d65a..10f56440 100644 --- a/.clang-format +++ b/.clang-format @@ -1,103 +1,25 @@ --- -BasedOnStyle: LLVM -Language: Json -IndentWidth: 4 -UseTab: Never ---- -Language: Cpp -AccessModifierOffset: -2 -AlignAfterOpenBracket: DontAlign -AlignConsecutiveAssignments: false -AlignConsecutiveDeclarations: false -AlignEscapedNewlines: Left -AlignOperands: true -AlignTrailingComments: false -AllowAllParametersOfDeclarationOnNextLine: false -AllowShortBlocksOnASingleLine: true -AllowShortCaseLabelsOnASingleLine: false -AllowShortFunctionsOnASingleLine: All -AllowShortIfStatementsOnASingleLine: true -AllowShortLoopsOnASingleLine: true -AlwaysBreakAfterDefinitionReturnType: None -AlwaysBreakAfterReturnType: None -AlwaysBreakBeforeMultilineStrings: true -AlwaysBreakTemplateDeclarations: false -BinPackArguments: false -BinPackParameters: false -BraceWrapping: - AfterClass: true - AfterControlStatement: false - AfterEnum: false - AfterFunction: true - AfterNamespace: false - AfterObjCDeclaration: false - AfterStruct: true - AfterUnion: false - BeforeCatch: false - BeforeElse: false - IndentBraces: false - SplitEmptyFunction: false - SplitEmptyNamespace: true - SplitEmptyRecord: true -BreakAfterJavaFieldAnnotations: true -BreakBeforeBinaryOperators: NonAssignment -BreakBeforeBraces: Custom -BreakBeforeInheritanceComma: true -BreakBeforeTernaryOperators: true -BreakConstructorInitializers: BeforeColon -BreakConstructorInitializersBeforeComma: false -BreakStringLiterals: true -ColumnLimit: 120 -CommentPragmas: '^ IWYU pragma:' -CompactNamespaces: false -ConstructorInitializerAllOnOneLineOrOnePerLine: false -ConstructorInitializerIndentWidth: 2 -ContinuationIndentWidth: 2 +# VersatusHPC C++ style +# Base: Google, with readability tweaks for enums, initializer lists, +# and constructor initializers. +BasedOnStyle: Google +Language: Cpp + +# Enums: one enumerator per line +AllowShortEnumsOnASingleLine: false + +# Constructor initializer lists: one per line, comma-first +BreakConstructorInitializers: BeforeComma +PackConstructorInitializers: Never + +# Spaces inside braces on initializer lists: { x, y } not {x, y} Cpp11BracedListStyle: false -DerivePointerAlignment: false -DisableFormat: false -ExperimentalAutoDetectBinPacking: true -FixNamespaceComments: true -ForEachMacros: - - foreach - - Q_FOREACH - - BOOST_FOREACH -IncludeCategories: - - Priority: 2 - Regex: ^"(llvm|llvm-c|clang|clang-c)/ - - Priority: 3 - Regex: ^(<|"(gtest|gmock|isl|json)/) - - Priority: 1 - Regex: .* -IncludeIsMainRegex: (Test)?$ -IndentCaseLabels: false + +# Keep the rest Google-default but be explicit on a few things +ColumnLimit: 80 IndentWidth: 2 -IndentWrappedFunctionNames: true -JavaScriptQuotes: Leave -JavaScriptWrapImports: true -KeepEmptyLinesAtTheStartOfBlocks: true -MacroBlockBegin: '' -MacroBlockEnd: '' -MaxEmptyLinesToKeep: 2 -NamespaceIndentation: Inner -ObjCBlockIndentWidth: 7 -ObjCSpaceAfterProperty: true -ObjCSpaceBeforeProtocolList: false -PointerAlignment: Right -ReflowComments: true -SortIncludes: true -SortUsingDeclarations: false -SpaceAfterCStyleCast: false -SpaceAfterTemplateKeyword: false -SpaceBeforeAssignmentOperators: true -SpaceBeforeParens: ControlStatements -SpaceInEmptyParentheses: false -SpacesBeforeTrailingComments: 0 -SpacesInAngles: false -SpacesInCStyleCastParentheses: false -SpacesInContainerLiterals: true -SpacesInParentheses: false -SpacesInSquareBrackets: false -Standard: c++20 -TabWidth: 8 UseTab: Never +PointerAlignment: Left +DerivePointerAlignment: false +Standard: Latest +... diff --git a/.devcontainer/.dockerignore b/.devcontainer/.dockerignore index 7baf5b1a..5d56d746 100644 --- a/.devcontainer/.dockerignore +++ b/.devcontainer/.dockerignore @@ -3,7 +3,7 @@ build/ out/ cmake-build-*/ -# User spesific settings +# User specific settings CMakeUserPresets.json # IDE files @@ -31,4 +31,5 @@ $RECYCLE.BIN/ .TemporaryItems ehthumbs.db Thumbs.db +Containerfile Dockerfile diff --git a/.devcontainer/Containerfile b/.devcontainer/Containerfile new file mode 100644 index 00000000..4358bafa --- /dev/null +++ b/.devcontainer/Containerfile @@ -0,0 +1,71 @@ +# Red Hat Universal Base Image 10 +FROM registry.access.redhat.com/ubi10/ubi + +# Enable required repositories +RUN dnf install -y 'dnf-command(config-manager)' && \ + dnf config-manager --enable ubi-10-codeready-builder-rpms && \ + dnf install -y https://dl.fedoraproject.org/pub/epel/epel-release-latest-10.noarch.rpm + +# Core build tools +RUN dnf install -y \ + make ninja-build cmake ccache git git-lfs \ + python3 python3-pip wget zip unzip rsync \ + openssh-clients file + +# GCC 14 (system) + GCC 15 (toolset) +RUN dnf install -y gcc gcc-c++ gdb \ + gcc-toolset-15-gcc gcc-toolset-15-gcc-c++ gcc-toolset-15-binutils + +# LLVM / Clang +RUN dnf install -y \ + llvm-toolset clang clang-tools-extra clang-devel llvm-devel lld lldb + +# Static analysis and documentation +RUN dnf install -y cppcheck doxygen graphviz + +# Editors +RUN dnf install -y neovim nano + +# Python packages +RUN python3 -m pip install --upgrade pip setuptools && \ + python3 -m pip install conan && \ + conan --version + +# Conan configuration for containers +ENV CONAN_SYSREQUIRES_SUDO=0 \ + CONAN_SYSREQUIRES_MODE=enabled + +# include-what-you-use (build from source) +ARG IWYU_BRANCH="clang_19" +ENV IWYU=/home/iwyu \ + IWYU_BUILD=/home/iwyu/build \ + IWYU_SRC=/home/iwyu/include-what-you-use + +RUN mkdir -p ${IWYU_BUILD} && \ + git clone --branch ${IWYU_BRANCH} --depth 1 \ + https://github.com/include-what-you-use/include-what-you-use.git \ + ${IWYU_SRC} + +RUN cmake -S ${IWYU_SRC} -B ${IWYU_BUILD} -G "Unix Makefiles" \ + -DCMAKE_PREFIX_PATH=$(llvm-config --prefix) && \ + cmake --build ${IWYU_BUILD} -j && \ + cmake --install ${IWYU_BUILD} + +# Link Clang resource headers so IWYU can find them +RUN mkdir -p $(include-what-you-use -print-resource-dir 2>/dev/null) && \ + ln -sf $(clang -print-resource-dir)/include \ + $(include-what-you-use -print-resource-dir 2>/dev/null)/include + +# Cleanup +RUN dnf clean all && rm -rf /var/cache/dnf + +# Allow the user to set compiler defaults +ARG USE_CLANG +# if --build-arg USE_CLANG=1, set CC to 'clang' or set to null otherwise. +ENV CC=${USE_CLANG:+"clang"} +ENV CXX=${USE_CLANG:+"clang++"} +# if CC is null, set it to 'gcc' (or leave as is otherwise). +ENV CC=${CC:-"gcc"} +ENV CXX=${CXX:-"g++"} + +CMD ["/bin/bash"] diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile deleted file mode 100644 index 4e6e5eb7..00000000 --- a/.devcontainer/Dockerfile +++ /dev/null @@ -1,71 +0,0 @@ -# Red Hat Universal Base Image 10 -FROM registry.access.redhat.com/ubi10/ubi - -# ── Enable required repositories ────────────────────────────────────── -RUN dnf install -y 'dnf-command(config-manager)' && \ - dnf config-manager --enable ubi-10-codeready-builder-rpms && \ - dnf install -y https://dl.fedoraproject.org/pub/epel/epel-release-latest-10.noarch.rpm - -# ── Core build tools ────────────────────────────────────────────────── -RUN dnf install -y \ - make ninja-build cmake ccache git git-lfs \ - python3 python3-pip wget zip unzip rsync \ - openssh-clients file - -# ── GCC 14 (system) + GCC 15 (toolset) ─────────────────────────────── -RUN dnf install -y gcc gcc-c++ gdb \ - gcc-toolset-15-gcc gcc-toolset-15-gcc-c++ gcc-toolset-15-binutils - -# ── LLVM / Clang ───────────────────────────────────────────────────── -RUN dnf install -y \ - llvm-toolset clang clang-tools-extra clang-devel llvm-devel lld lldb - -# ── Static analysis & documentation ────────────────────────────────── -RUN dnf install -y cppcheck doxygen graphviz - -# ── Editors ─────────────────────────────────────────────────────────── -RUN dnf install -y neovim nano - -# ── Python packages ────────────────────────────────────────────────── -RUN python3 -m pip install --upgrade pip setuptools && \ - python3 -m pip install conan && \ - conan --version - -# Conan configuration for containers -ENV CONAN_SYSREQUIRES_SUDO=0 \ - CONAN_SYSREQUIRES_MODE=enabled - -# ── include-what-you-use (build from source) ───────────────────────── -ARG IWYU_BRANCH="clang_19" -ENV IWYU=/home/iwyu \ - IWYU_BUILD=/home/iwyu/build \ - IWYU_SRC=/home/iwyu/include-what-you-use - -RUN mkdir -p ${IWYU_BUILD} && \ - git clone --branch ${IWYU_BRANCH} --depth 1 \ - https://github.com/include-what-you-use/include-what-you-use.git \ - ${IWYU_SRC} - -RUN cmake -S ${IWYU_SRC} -B ${IWYU_BUILD} -G "Unix Makefiles" \ - -DCMAKE_PREFIX_PATH=$(llvm-config --prefix) && \ - cmake --build ${IWYU_BUILD} -j && \ - cmake --install ${IWYU_BUILD} - -# Link Clang resource headers so IWYU can find them -RUN mkdir -p $(include-what-you-use -print-resource-dir 2>/dev/null) && \ - ln -sf $(clang -print-resource-dir)/include \ - $(include-what-you-use -print-resource-dir 2>/dev/null)/include - -# ── Cleanup ─────────────────────────────────────────────────────────── -RUN dnf clean all && rm -rf /var/cache/dnf - -# Allow the user to set compiler defaults -ARG USE_CLANG -# if --build-arg USE_CLANG=1, set CC to 'clang' or set to null otherwise. -ENV CC=${USE_CLANG:+"clang"} -ENV CXX=${USE_CLANG:+"clang++"} -# if CC is null, set it to 'gcc' (or leave as is otherwise). -ENV CC=${CC:-"gcc"} -ENV CXX=${CXX:-"g++"} - -CMD ["/bin/bash"] diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile new file mode 120000 index 00000000..5240dc01 --- /dev/null +++ b/.devcontainer/Dockerfile @@ -0,0 +1 @@ +Containerfile \ No newline at end of file diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 00000000..69133cca --- /dev/null +++ b/.editorconfig @@ -0,0 +1,34 @@ +# EditorConfig — https://editorconfig.org +# Complements .clang-format (C/C++) and .cmake-format.yaml (CMake). +# Covers everything else: YAML, Markdown, Python, shell scripts, etc. + +root = true + +[*] +charset = utf-8 +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true +indent_style = space +indent_size = 2 + +[*.{c,cc,cpp,cxx,h,hh,hpp,hxx}] +indent_size = 2 + +[*.{json,jsonc}] +indent_size = 4 + +[*.{yml,yaml}] +indent_size = 2 + +[*.py] +indent_size = 4 + +[*.toml] +indent_size = 4 + +[*.md] +trim_trailing_whitespace = false + +[Makefile] +indent_style = tab diff --git a/.gitattributes b/.gitattributes index 7c9073c7..b0ed2ced 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,11 +1,11 @@ -# ── Git Line Endings ────────────────────────────────────────────────── +# Git Line Endings * text=auto eol=lf *.{cmd,[cC][mM][dD]} text eol=crlf *.{bat,[bB][aA][tT]} text eol=crlf *.{vcxproj,vcxproj.filters} text eol=crlf -# ── Git Large File System (LFS) ─────────────────────────────────────── +# Git Large File System (LFS) # Archives #*.7z filter=lfs diff=lfs merge=lfs -text diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml deleted file mode 100644 index c7270daf..00000000 --- a/.github/FUNDING.yml +++ /dev/null @@ -1,4 +0,0 @@ -# These are supported funding model platforms - -github: lefticus -patreon: lefticus diff --git a/.github/template/README.md b/.github/template/README.md index af894976..8f5f2f7f 100644 --- a/.github/template/README.md +++ b/.github/template/README.md @@ -1,7 +1,7 @@ # %%myproject%% [![ci](https://github.com/%%myorg%%/%%myproject%%/actions/workflows/ci.yml/badge.svg)](https://github.com/%%myorg%%/%%myproject%%/actions/workflows/ci.yml) -[![codecov](https://codecov.io/gh/%%myorg%%/%%myproject%%/branch/main/graph/badge.svg)](https://codecov.io/gh/%%myorg%%/%%myproject%%) +[![codecov](https://codecov.io/gh/%%myorg%%/%%myproject%%/graph/badge.svg)](https://codecov.io/gh/%%myorg%%/%%myproject%%) [![CodeQL](https://github.com/%%myorg%%/%%myproject%%/actions/workflows/codeql-analysis.yml/badge.svg)](https://github.com/%%myorg%%/%%myproject%%/actions/workflows/codeql-analysis.yml) ## About %%myproject%% @@ -23,7 +23,7 @@ Try the live WebAssembly demo: - Main: [https://%%myorg%%.github.io/%%myproject%%/](https://%%myorg%%.github.io/%%myproject%%/) - Develop: [https://%%myorg%%.github.io/%%myproject%%/develop/](https://%%myorg%%.github.io/%%myproject%%/develop/) -The `main` branch deploys to the root, `develop` to `/develop/`, and tags to `/tagname/`. +The default branch (`main` or `master`) deploys to the root, `develop` to `/develop/`, and tags to `/tagname/`. ## Getting Started @@ -38,5 +38,4 @@ See [CONTRIBUTING.md](CONTRIBUTING.md) for commit message policy and developer w * [Dependency Setup](README_dependencies.md) * [Building Details](README_building.md) - * [Troubleshooting](README_troubleshooting.md) - * [Docker](README_docker.md) + * [Containers](README_container.md) diff --git a/.github/template/removal-list b/.github/template/removal-list index 1b33fc47..6b1d0bfa 100644 --- a/.github/template/removal-list +++ b/.github/template/removal-list @@ -1,2 +1 @@ LICENSE -.github/FUNDING.yml diff --git a/.github/workflows/auto-clang-format.yml b/.github/workflows/auto-clang-format.yml index 0da73acd..c1d80f55 100644 --- a/.github/workflows/auto-clang-format.yml +++ b/.github/workflows/auto-clang-format.yml @@ -18,6 +18,6 @@ jobs: with: author_name: Clang Robot author_email: robot@example.com - message: ':art: Committing clang-format changes' + message: 'style: apply clang-format' env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e7bc480f..9a87716f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -6,6 +6,7 @@ on: push: tags: branches: + - master - main - develop diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 7482a90e..c48a04c4 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -4,7 +4,7 @@ # You may wish to alter this file to override the set of languages analyzed, # or to provide custom queries or build logic. # -# ── NOTE ────────────────────────────────────────────────────────────── +# NOTE # We have attempted to detect the languages in your repository. Please check # the `language` matrix defined below to confirm you have the correct set of # supported CodeQL languages. @@ -13,10 +13,10 @@ name: "CodeQL" on: push: - branches: [ main, develop ] + branches: [ master, main, develop ] pull_request: # The branches below must be a subset of the branches above - branches: [ main, develop ] + branches: [ master, main, develop ] schedule: - cron: '38 0 * * 5' diff --git a/.github/workflows/wasm.yml b/.github/workflows/wasm.yml index 1bea3017..6519b505 100644 --- a/.github/workflows/wasm.yml +++ b/.github/workflows/wasm.yml @@ -5,7 +5,7 @@ on: release: types: [published] push: - branches: [main, develop] + branches: [master, main, develop] tags: ['**'] permissions: @@ -45,7 +45,7 @@ jobs: run: | if [[ "$GITHUB_REF" == refs/tags/* ]]; then echo "path=${GITHUB_REF#refs/tags/}" >> $GITHUB_OUTPUT - elif [[ "$GITHUB_REF" == refs/heads/main ]]; then + elif [[ "$GITHUB_REF" == refs/heads/main || "$GITHUB_REF" == refs/heads/master ]]; then echo "path=." >> $GITHUB_OUTPUT elif [[ "$GITHUB_REF" == refs/heads/develop ]]; then echo "path=develop" >> $GITHUB_OUTPUT diff --git a/.gitignore b/.gitignore index 0b375b10..89fb2f0b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ # Project-specific ignores /build*/ /cmake-*/ +.cache/ # Created by https://www.toptal.com/developers/gitignore/api/c,c++,visualstudiocode,vim,linux,macos,pvs,git,cmake,ninja,executable,clion+all,sonar # Edit at https://www.toptal.com/developers/gitignore?templates=c,c++,visualstudiocode,vim,linux,macos,pvs,git,cmake,ninja,executable,clion+all,sonar @@ -78,93 +79,16 @@ dkms.conf # Executables -### CLion+all ### -# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider -# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 - -# User-specific stuff -.idea/**/workspace.xml -.idea/**/tasks.xml -.idea/**/usage.statistics.xml -.idea/**/dictionaries -.idea/**/shelf - -# AWS User-specific -.idea/**/aws.xml - -# Generated files -.idea/**/contentModel.xml - -# Sensitive or high-churn files -.idea/**/dataSources/ -.idea/**/dataSources.ids -.idea/**/dataSources.local.xml -.idea/**/sqlDataSources.xml -.idea/**/dynamic.xml -.idea/**/uiDesigner.xml -.idea/**/dbnavigator.xml - -# Gradle -.idea/**/gradle.xml -.idea/**/libraries - -# Gradle and Maven with auto-import -# When using Gradle or Maven with auto-import, you should exclude module files, -# since they will be recreated, and may cause churn. Uncomment if using -# auto-import. -# .idea/artifacts -# .idea/compiler.xml -# .idea/jarRepositories.xml -# .idea/modules.xml -# .idea/*.iml -# .idea/modules -# *.iml -# *.ipr - -# CMake +# JetBrains IDEs (CLion, IntelliJ, etc.) +# Ignore all of .idea/. IDE-agnostic config files (.clang-format, +# .clang-tidy, CMakePresets.json) are the single source of truth for +# code style, static analysis, and build presets. CLion reads these +# natively, no .idea/codeStyles or .idea/runConfigurations needed. +.idea/ cmake-build-*/ - -# Mongo Explorer plugin -.idea/**/mongoSettings.xml - -# File-based project format *.iws - -# IntelliJ -out/ - -# mpeltonen/sbt-idea plugin -.idea_modules/ - -# JIRA plugin -atlassian-ide-plugin.xml - -# Cursive Clojure plugin -.idea/replstate.xml - -# SonarLint plugin -.idea/sonarlint/ - -# Crashlytics plugin (for Android Studio and IntelliJ) -com_crashlytics_export_strings.xml -crashlytics.properties -crashlytics-build.properties -fabric.properties - -# Editor-based Rest Client -.idea/httpRequests - -# Android studio 3.1+ serialized cache file -.idea/caches/build_file_checksums.ser - -### CLion+all Patch ### -# Ignore everything but code style settings and run configurations -# that are supposed to be shared within teams. - -.idea/* - -!.idea/codeStyles -!.idea/runConfigurations +*.iml +*.ipr ### CMake ### CMakeLists.txt.user diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml deleted file mode 100644 index 727a39ae..00000000 --- a/.gitlab-ci.yml +++ /dev/null @@ -1,49 +0,0 @@ -image: ubuntu:latest - -stages: - - test - -.setup_linux: &setup_linux | - DEBIAN_FRONTEND=noninteractive - - # set time-zone - TZ=Canada/Pacific - ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone - - # for downloading - apt-get update -qq - apt-get install -y --no-install-recommends curl gnupg ca-certificates - - # keys used by apt - apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 3B4FE6ACC0B21F32 - apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 40976EAF437D05B5 - apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 1E9377A2BA9EF27F - -.setup_cpp: &setup_cpp | - curl -LJO "https://github.com/aminya/setup-cpp/releases/download/v0.10.0/setup_cpp_linux" - chmod +x setup_cpp_linux - ./setup_cpp_linux --compiler $compiler --cmake true --ninja true --conan true --ccache true --clangtidy true --clangformat true --cppcheck true - source ~/.profile - -.test: &test | - # Build and Test - cmake -S . -B ./build -DCMAKE_BUILD_TYPE:STRING=RelWithDebInfo - cmake --build ./build --config RelWithDebInfo - -test_linux_llvm: - stage: test - variables: - compiler: llvm - script: - - *setup_linux - - *setup_cpp - - *test - -test_linux_gcc: - stage: test - variables: - compiler: gcc - script: - - *setup_linux - - *setup_cpp - - *test diff --git a/.gitlint b/.gitlint index 40e4a380..a6e73576 100644 --- a/.gitlint +++ b/.gitlint @@ -8,7 +8,7 @@ regex-style-search = true # body length does not. ignore = body-max-line-length -# ── Conventional Commits title ──────────────────────────────────────── +# Conventional Commits title [title-match-regex] # Allowed types: build|chore|ci|docs|feat|fix|perf|refactor|revert|style|test # Format: type(scope?)?(!)?: subject @@ -17,11 +17,11 @@ regex = ^(build|chore|ci|docs|feat|fix|perf|refactor|revert|style|test)(\([a-z0- [title-max-length] line-length = 72 -# ── Body formatting ─────────────────────────────────────────────────── +# Body formatting [body-first-line-empty] # Enforce a blank line between title and body when a body is present. -# ── DCO / Signed-off-by enforcement ─────────────────────────────────── +# DCO / Signed-off-by enforcement [body-match-regex] # Require at least one "Signed-off-by: Name " line somewhere in the body. # This works together with regex-style-search=true in [general]. diff --git a/CMakeLists.txt b/CMakeLists.txt index 5f5fc32b..81a2d7e7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,7 +1,9 @@ -cmake_minimum_required(VERSION 3.29) +cmake_minimum_required(VERSION 3.21) -# Disable modules support, this is broken with clang-tidy at the moment -cmake_policy(SET CMP0155 OLD) +# Disable C++20 modules scanning, broken with clang-tidy and IWYU +if(POLICY CMP0155) + cmake_policy(SET CMP0155 OLD) +endif() # This template attempts to be "fetch_content"-able # so that it works well with dependency managers like @@ -13,7 +15,7 @@ if (NOT DEFINED CMAKE_CXX_STANDARD) endif() # strongly encouraged to enable this globally to avoid conflicts between -# -Wpedantic being enabled and -std=c++20 and -std=gnu++20 for example +# -Wpedantic being enabled and -std=c++23 and -std=gnu++23 for example # when compiling with PCH enabled set(CMAKE_CXX_EXTENSIONS OFF) diff --git a/CMakePresets.json b/CMakePresets.json index 81aeeb26..fd380afc 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -31,10 +31,6 @@ "toolset": { "value": "host=x64", "strategy": "external" - }, - "cacheVariables": { - "ENABLE_CPPCHECK_DEFAULT": "FALSE", - "ENABLE_CLANG_TIDY_DEFAULT": "FALSE" } }, { @@ -64,8 +60,7 @@ "cacheVariables": { "CMAKE_C_COMPILER": "cl", "CMAKE_CXX_COMPILER": "cl", - "CMAKE_BUILD_TYPE": "Debug", - "ENABLE_DEVELOPER_MODE": "ON" + "CMAKE_BUILD_TYPE": "Debug" } }, { @@ -76,8 +71,7 @@ "cacheVariables": { "CMAKE_C_COMPILER": "cl", "CMAKE_CXX_COMPILER": "cl", - "CMAKE_BUILD_TYPE": "RelWithDebInfo", - "ENABLE_DEVELOPER_MODE": "ON" + "CMAKE_BUILD_TYPE": "RelWithDebInfo" } }, { @@ -88,8 +82,7 @@ "cacheVariables": { "CMAKE_C_COMPILER": "cl", "CMAKE_CXX_COMPILER": "cl", - "CMAKE_BUILD_TYPE": "Debug", - "ENABLE_DEVELOPER_MODE": "OFF" + "CMAKE_BUILD_TYPE": "Debug" } }, { @@ -100,8 +93,7 @@ "cacheVariables": { "CMAKE_C_COMPILER": "cl", "CMAKE_CXX_COMPILER": "cl", - "CMAKE_BUILD_TYPE": "RelWithDebInfo", - "ENABLE_DEVELOPER_MODE": "OFF" + "CMAKE_BUILD_TYPE": "RelWithDebInfo" } }, { diff --git a/README.md b/README.md index 2f757457..8ed17fec 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,14 @@ # cmake_template -[![ci](https://github.com/cpp-best-practices/cmake_template/actions/workflows/ci.yml/badge.svg)](https://github.com/cpp-best-practices/cmake_template/actions/workflows/ci.yml) -[![codecov](https://codecov.io/gh/cpp-best-practices/cmake_template/branch/main/graph/badge.svg)](https://codecov.io/gh/cpp-best-practices/cmake_template) -[![CodeQL](https://github.com/cpp-best-practices/cmake_template/actions/workflows/codeql-analysis.yml/badge.svg)](https://github.com/cpp-best-practices/cmake_template/actions/workflows/codeql-analysis.yml) +[![ci](https://github.com/VersatusHPC/cmake_template/actions/workflows/ci.yml/badge.svg)](https://github.com/VersatusHPC/cmake_template/actions/workflows/ci.yml) +[![codecov](https://codecov.io/gh/VersatusHPC/cmake_template/branch/master/graph/badge.svg)](https://codecov.io/gh/VersatusHPC/cmake_template) +[![CodeQL](https://github.com/VersatusHPC/cmake_template/actions/workflows/codeql-analysis.yml/badge.svg)](https://github.com/VersatusHPC/cmake_template/actions/workflows/codeql-analysis.yml) ## About cmake_template This is a C++ Best Practices GitHub template for getting up and running with C++ quickly. -By default (collectively known as `ENABLE_DEVELOPER_MODE`) +By default, developer mode enables * Address Sanitizer and Undefined Behavior Sanitizer enabled where possible * Warnings as errors @@ -40,16 +40,16 @@ It includes * [Devcontainer](https://containers.dev/) based on Red Hat UBI 10 with GCC 14, Clang 19, and all tools pre-installed -**Live Demo:** If you enable GitHub Pages in your project created from this template, you'll have a working example like this: -- Main: [https://cpp-best-practices.github.io/cmake_template/](https://cpp-best-practices.github.io/cmake_template/) -- Develop: [https://cpp-best-practices.github.io/cmake_template/develop/](https://cpp-best-practices.github.io/cmake_template/develop/) +### Acknowledgements -The `main` branch deploys to the root, `develop` to `/develop/`, and tags to `/tagname/`. +This template is heavily based on [cpp-best-practices/cmake_template](https://github.com/cpp-best-practices/cmake_template) by [Jason Turner](https://github.com/lefticus) and the [C++ Best Practices](https://github.com/cpp-best-practices) community. Their work on CMake infrastructure, CI workflows, and developer tooling laid the foundation for this project. + +If you enable GitHub Pages on a repository created from this template, the default branch (`main` or `master`) deploys to the root, `develop` to `/develop/`, and tags to `/tagname/`. It requires * cmake - * a C++17 compiler + * a C++23 compiler * Conan 2.0 This project gets you started with a simple example of using FTXUI, which happens to also be a game. @@ -58,7 +58,7 @@ This project gets you started with a simple example of using FTXUI, which happen ### Use the GitHub template First, click the green `Use this template` button near the top of this page. -This will take you to GitHub's ['Generate Repository'](https://github.com/cpp-best-practices/cmake_template/generate) +This will take you to GitHub's ['Generate Repository'](https://github.com/VersatusHPC/cmake_template/generate) page. Fill in a repository name and short description, and click 'Create repository from template'. This will allow you to create a new repository in your GitHub account, @@ -84,7 +84,7 @@ See [CONTRIBUTING.md](CONTRIBUTING.md) for commit message policy and developer w * [Dependency Setup](README_dependencies.md) * [Building Details](README_building.md) - * [Docker](README_docker.md) + * [Containers](README_container.md) ## Testing diff --git a/README_building.md b/README_building.md index 67293ff9..2cd09049 100644 --- a/README_building.md +++ b/README_building.md @@ -93,7 +93,7 @@ With Cmake directly: Cmake will automatically create the `./build` folder if it does not exist, and it wil configure the project. -Instead, if you have CMake version 3.21+, you can use one of the configuration presets that are listed in the CmakePresets.json file. +Instead, if you have CMake version 3.21+, you can use one of the configuration presets that are listed in the CMakePresets.json file. cmake . --preset cmake --build @@ -139,7 +139,7 @@ Choose MinGW Makefiles as the generator: You should have already set `C` and `CXX` to `cl.exe`. -Choose "Visual Studio 16 2019" as the generator: +Choose "Visual Studio 17 2022" as the generator: default_vs @@ -151,7 +151,7 @@ Choose "Visual Studio 16 2019" as the generator: You should have already set `C` and `CXX` to `clang.exe` and `clang++.exe`. -Choose "Visual Studio 16 2019" as the generator. To tell Visual studio to use `clang-cl.exe`: +Choose "Visual Studio 17 2022" as the generator. To tell Visual studio to use `clang-cl.exe`: - If you use the LLVM that is shipped with Visual Studio: write `ClangCl` under "optional toolset to use". visual_studio diff --git a/README_container.md b/README_container.md new file mode 100644 index 00000000..c5e6d3c4 --- /dev/null +++ b/README_container.md @@ -0,0 +1,70 @@ +## Container Instructions + +The `.devcontainer/Containerfile` provides a fully configured build environment +based on Red Hat UBI 10. Any OCI-compatible container runtime works: Podman, +Docker, Apple Containers, nerdctl, etc. + +The examples below use `podman`. Replace with `docker` or your runtime of choice. + +### Building and running the container + +```bash +podman build -f ./.devcontainer/Containerfile --tag=my_project:latest . +podman run -it my_project:latest +``` + +This will drop you into a `bash` session with all tools pre-installed: +GCC 14 (system default), GCC 15 (via gcc-toolset-15), Clang 19, CMake, Ninja, +Conan 2.0, clang-tidy, cppcheck, include-what-you-use, ccache, doxygen, and +graphviz. + +### Selecting compilers + +The CC and CXX environment variables default to GCC 14. To use GCC 15, +activate the toolset inside the container. This sets PATH, LD_LIBRARY_PATH, +and other variables so the compiler finds its own libstdc++, headers, and +binutils instead of the system GCC 14 ones: + +```bash +source /opt/rh/gcc-toolset-15/enable +cmake -S . -B ./build +cmake --build ./build +``` + +Do not just set CC/CXX to the GCC 15 paths without sourcing the enable +script. The compiler would still pick up GCC 14 system headers and libraries, +which can cause ABI mismatches or missing C++ library features. + +To build the container image with Clang as the default compiler: + +```bash +podman build --tag=my_project:latest --build-arg USE_CLANG=1 -f ./.devcontainer/Containerfile . +``` + +You can also switch to Clang at configure time without rebuilding the image: + +```bash +CC=clang CXX=clang++ cmake -S . -B ./build +cmake --build ./build +``` + +### Mounting the source tree + +To mount your local checkout into the container: + +```bash +podman run -it \ + -v absolute_path_on_host:absolute_path_in_container \ + my_project:latest +``` + +### Building the project + +```bash +cmake -S . -B ./build +cmake --build ./build +``` + +All static analysis tools are installed in the image. Enable or disable them +with `ccmake` or by passing cache variables on the command line. Be aware that +some sanitizers conflict with each other, so run them separately. diff --git a/README_dependencies.md b/README_dependencies.md index b0ea2c4c..9289b4bb 100644 --- a/README_dependencies.md +++ b/README_dependencies.md @@ -10,8 +10,8 @@ Note about install commands: This is a really long list of dependencies, and it's easy to mess up. That's why: -#### Docker -We have a Docker image that's already set up for you. See the [Docker instructions](./README_docker.md). +#### Containers +We have a container image that's already set up for you. See the [container instructions](./README_container.md). #### Setup-cpp @@ -22,19 +22,19 @@ Please check [the setup-cpp documentation](https://github.com/aminya/setup-cpp) For example, on Windows, you can run the following to install llvm, cmake, ninja, ccache, and cppcheck. ```ps1 # windows example (open shell as admin) -curl -LJO "https://github.com/aminya/setup-cpp/releases/download/v0.5.7/setup_cpp_windows.exe" +curl -LJO "https://github.com/aminya/setup-cpp/releases/download/v1.8.0/setup_cpp_windows.exe" ./setup_cpp_windows --compiler llvm --cmake true --ninja true --ccache true --cppcheck true RefreshEnv.cmd # reload the environment ``` ### Necessary Dependencies -1. A C++ compiler that supports C++17. +1. A C++ compiler that supports C++23. See [cppreference.com](https://en.cppreference.com/w/cpp/compiler_support) to see which features are supported by each compiler. The following compilers should work: - * [gcc 7+](https://gcc.gnu.org/) + * [gcc 14+](https://gcc.gnu.org/)
Install command @@ -51,7 +51,7 @@ The following compilers should work: brew install gcc
- * [clang 6+](https://clang.llvm.org/) + * [clang 17+](https://clang.llvm.org/)
Install command @@ -61,14 +61,14 @@ The following compilers should work: - Windows: - Visual Studio 2019 ships with LLVM (see the Visual Studio section). However, to install LLVM separately: + Visual Studio 2022 ships with LLVM (see the Visual Studio section). However, to install LLVM separately: choco install llvm -y llvm-utils for using external LLVM with Visual Studio generator: git clone https://github.com/zufuliu/llvm-utils.git - cd llvm-utils/VS2017 + cd llvm-utils/VS2022 .\install.bat - MacOS: @@ -76,15 +76,15 @@ The following compilers should work: brew install llvm
- * [Visual Studio 2019 or higher](https://visualstudio.microsoft.com/) + * [Visual Studio 2022 or higher](https://visualstudio.microsoft.com/)
Install command + Environment setup - On Windows, you need to install Visual Studio 2019 because of the SDK and libraries that ship with it. + On Windows, you need to install Visual Studio 2022 because of the SDK and libraries that ship with it. - Visual Studio IDE - 2019 Community (installs Clang too): + Visual Studio IDE - 2022 Community (installs Clang too): - choco install -y visualstudio2019community --package-parameters "add Microsoft.VisualStudio.Workload.NativeDesktop --includeRecommended --includeOptional --passive --locale en-US" + choco install -y visualstudio2022community --package-parameters "add Microsoft.VisualStudio.Workload.NativeDesktop --includeRecommended --includeOptional --passive --locale en-US" Put MSVC compiler, Clang compiler, and vcvarsall.bat on the path: diff --git a/README_docker.md b/README_docker.md deleted file mode 100644 index 38c0f32c..00000000 --- a/README_docker.md +++ /dev/null @@ -1,68 +0,0 @@ -## Docker Instructions - -If you have [Docker](https://www.docker.com/) installed, you can run this -in your terminal, when the Dockerfile is inside the `.devcontainer` directory: - -```bash -docker build -f ./.devcontainer/Dockerfile --tag=my_project:latest . -docker run -it my_project:latest -``` - -This command will put you in a `bash` session in a Red Hat UBI 10 Docker container, -with all of the tools listed in the [Dependencies](README_dependencies.md#dependencies) section already installed. -You will have GCC 14 (system default), GCC 15 (via gcc-toolset-15), and Clang 19 -available, along with CMake, Ninja, Conan 2.0, clang-tidy, cppcheck, -include-what-you-use, ccache, doxygen, and graphviz. - -The CC and CXX environment variables are set to GCC 14 by default. -To use GCC 15 instead, activate the toolset: - -```bash -source /opt/rh/gcc-toolset-15/enable -``` - -Or set the compiler explicitly: - -```bash -CC=/opt/rh/gcc-toolset-15/root/usr/bin/gcc CXX=/opt/rh/gcc-toolset-15/root/usr/bin/g++ cmake -S . -B ./build -``` - -If you wish to use clang as your default compiler, build the container with: - -```bash -docker build --tag=my_project:latest --build-arg USE_CLANG=1 -f ./.devcontainer/Dockerfile . -``` - -You will be logged in as root, so you will see the `#` symbol as your prompt. - -If you need to mount your local copy directly in the Docker image, see -[Docker volumes docs](https://docs.docker.com/storage/volumes/). -TLDR: - -```bash -docker run -it \ - -v absolute_path_on_host_machine:absolute_path_in_guest_container \ - my_project:latest -``` - -You can configure and build [as directed above](#build) using these commands: - -```bash -mkdir build -cmake -S . -B ./build -cmake --build ./build -``` - -You can configure and build using clang, without rebuilding the container, -with these commands: - -```bash -mkdir build -CC=clang CXX=clang++ cmake -S . -B ./build -cmake --build ./build -``` - -All of the tools this project supports are installed in the Docker image; -enabling them is as simple as flipping a switch using the `ccmake` interface. -Be aware that some of the sanitizers conflict with each other, so be sure to -run them separately. diff --git a/conanfile.py b/conanfile.py index af4553d7..a01863e7 100644 --- a/conanfile.py +++ b/conanfile.py @@ -16,7 +16,7 @@ def requirements(self): self.requires("cli11/[>=2.4.0 <3.0]") self.requires("ftxui/[>=5.0 <7.0]") - # ── Commented-out packages ──────────────────────────────────── + # Commented-out packages # Uncomment the ones your project needs. These serve as # ready-to-use examples with tested version ranges. # @@ -40,7 +40,7 @@ def requirements(self): # Process management #self.requires("reproc/[>=14.2 <15.0]") - # ── Version range cheat-sheet ───────────────────────────────── + # Version range cheat-sheet # # Exact version (reproducible, no flexibility): # self.requires("spdlog/1.14.1") diff --git a/docs/.gitignore b/docs/.gitignore index 8c6492ca..898fa0f3 100644 --- a/docs/.gitignore +++ b/docs/.gitignore @@ -1 +1,2 @@ +# Exhale auto-generated API documentation (created by 'make docs') api/ diff --git a/docs/conf.py b/docs/conf.py index 7344f090..fd3038ac 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -9,7 +9,7 @@ 'exhale', ] -# ── Breathe (Doxygen XML → Sphinx) ──────────────────────────────────── +# Breathe (Doxygen XML to Sphinx) # CMake passes the XML path via environment variable; fall back to a # conventional location so that standalone sphinx-build also works. @@ -20,7 +20,7 @@ breathe_projects = {'myproject': _doxygen_xml_dir} breathe_default_project = 'myproject' -# ── Exhale (auto-generate API tree from Breathe) ────────────────────── +# Exhale (auto-generate API tree from Breathe) _project_source_dir = os.environ.get( 'PROJECT_SOURCE_DIR', @@ -34,6 +34,6 @@ 'createTreeView': True, } -# ── Theme ───────────────────────────────────────────────────────────── +# Theme html_theme = 'sphinx_rtd_theme' diff --git a/fuzz_test/README.md b/fuzz_test/README.md new file mode 100644 index 00000000..c01f08ea --- /dev/null +++ b/fuzz_test/README.md @@ -0,0 +1,109 @@ +# Fuzz Testing + +This directory contains a [libFuzzer](https://www.llvm.org/docs/LibFuzzer.html) harness +that exercises project code with randomly generated inputs, looking for crashes, +memory errors, and undefined behavior. + +## How it works + +The fuzzer entry point is `LLVMFuzzerTestOneInput()` in `fuzz_tester.cpp`. libFuzzer +calls this function repeatedly with mutated byte buffers. When combined with sanitizers +(ASAN, UBSAN, TSAN), the fuzzer can detect: + +- Buffer overflows and use-after-free (Address Sanitizer) +- Signed integer overflow, null pointer dereference (Undefined Behavior Sanitizer) +- Data races (Thread Sanitizer) + +The included example deliberately triggers signed integer overflow to demonstrate +how the fuzzer catches undefined behavior. + +## Requirements + +- Clang or GCC with `-fsanitize=fuzzer` support (libFuzzer) +- At least one sanitizer enabled (ASAN or TSAN recommended) + +Fuzz testing is **not available** on MSVC or Emscripten. + +## Building + +Fuzz tests are built automatically when `myproject_BUILD_FUZZ_TESTS` is ON. This +defaults to ON when libFuzzer is available and a sanitizer is enabled: + +```sh +cmake -S . -B build -DCMAKE_BUILD_TYPE=Debug \ + -Dmyproject_ENABLE_SANITIZER_ADDRESS=ON +cmake --build build +``` + +To explicitly control fuzz test building: + +```sh +cmake -S . -B build -Dmyproject_BUILD_FUZZ_TESTS=ON # force on +cmake -S . -B build -Dmyproject_BUILD_FUZZ_TESTS=OFF # force off +``` + +## Running + +### Via CTest (short automated run) + +CTest runs the fuzzer for a limited duration (default 10 seconds) as a smoke test: + +```sh +cd build +ctest -R fuzz +``` + +Change the duration with the `FUZZ_RUNTIME` cache variable: + +```sh +cmake -S . -B build -DFUZZ_RUNTIME=30 # 30 seconds +``` + +### Directly (extended fuzzing session) + +For real fuzz testing, run the binary directly without a time limit: + +```sh +./build/fuzz_test/fuzz_tester +``` + +libFuzzer will run indefinitely until it finds a bug or you press Ctrl+C. Useful flags: + +```sh +# Run with a corpus directory (saves interesting inputs for replay) +mkdir -p corpus +./build/fuzz_test/fuzz_tester corpus/ + +# Limit to 60 seconds +./build/fuzz_test/fuzz_tester -max_total_time=60 + +# Use multiple parallel jobs +./build/fuzz_test/fuzz_tester -fork=4 -max_total_time=300 + +# Replay a crashing input +./build/fuzz_test/fuzz_tester crash-input-file +``` + +## Writing new fuzz targets + +1. Create a new `.cpp` file with a `LLVMFuzzerTestOneInput` function +2. Add an `add_executable` and `target_link_libraries` entry in `CMakeLists.txt` +3. Link with `-fsanitize=fuzzer` and `-coverage` + +```cpp +#include +#include + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) +{ + // Call your code with the fuzzed input + my_function(Data, Size); + return 0; +} +``` + +## Further reading + +- [libFuzzer documentation](https://www.llvm.org/docs/LibFuzzer.html) +- [libFuzzer tutorial](https://github.com/google/fuzzing/blob/master/tutorial/libFuzzerTutorial.md) +- [Structure-aware fuzzing](https://github.com/google/fuzzing/blob/master/docs/structure-aware-fuzzing.md) diff --git a/fuzz_test/fuzz_tester.cpp b/fuzz_test/fuzz_tester.cpp index 46018580..1e765735 100644 --- a/fuzz_test/fuzz_tester.cpp +++ b/fuzz_test/fuzz_tester.cpp @@ -1,23 +1,23 @@ +#include + #include #include -#include #include -[[nodiscard]] auto sum_values(const uint8_t *Data, size_t Size) -{ +[[nodiscard]] auto sum_values(const uint8_t* Data, size_t Size) { constexpr auto scale = 1000; int value = 0; for (std::size_t offset = 0; offset < Size; ++offset) { - value += static_cast(*std::next(Data, static_cast(offset))) * scale; + value += + static_cast(*std::next(Data, static_cast(offset))) * scale; } return value; } // Fuzzer that attempts to invoke undefined behavior for signed integer overflow // cppcheck-suppress unusedFunction symbolName=LLVMFuzzerTestOneInput -extern "C" int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) -{ +extern "C" int LLVMFuzzerTestOneInput(const uint8_t* Data, size_t Size) { fmt::print("Value sum: {}, len{}\n", sum_values(Data, Size), Size); return 0; } diff --git a/include/myproject/sample_library.hpp b/include/myproject/sample_library.hpp index 1b2b1772..57c7db8f 100644 --- a/include/myproject/sample_library.hpp +++ b/include/myproject/sample_library.hpp @@ -5,9 +5,10 @@ [[nodiscard]] SAMPLE_LIBRARY_EXPORT int factorial(int) noexcept; -[[nodiscard]] constexpr int factorial_constexpr(int input) noexcept -{ - if (input == 0) { return 1; } +[[nodiscard]] constexpr int factorial_constexpr(int input) noexcept { + if (input == 0) { + return 1; + } return input * factorial_constexpr(input - 1); } diff --git a/src/ftxui_sample/main.cpp b/src/ftxui_sample/main.cpp index 4f23ff8d..bcb254f9 100644 --- a/src/ftxui_sample/main.cpp +++ b/src/ftxui_sample/main.cpp @@ -1,3 +1,8 @@ +#include +#include +#include + +#include #include #include #include @@ -5,23 +10,16 @@ #include #include #include -#include -#include +#include // for Slider +#include // for ScreenInteractive #include #include #include #include +#include #include - #include -#include -#include // for Slider -#include // for ScreenInteractive -#include - -#include - // This file will be generated automatically when cur_you run the CMake // configuration step. It creates a namespace called `myproject`. You can modify // the source template at `configured_files/config.hpp.in`. @@ -31,8 +29,8 @@ #include #include -template struct GameBoard -{ +template +struct GameBoard { static constexpr std::size_t width = Width; static constexpr std::size_t height = Height; @@ -41,11 +39,11 @@ template struct GameBoard std::size_t move_count{ 0 }; - std::string &get_string(std::size_t cur_x, std::size_t cur_y) { return strings.at(cur_x).at(cur_y); } - + std::string& get_string(std::size_t cur_x, std::size_t cur_y) { + return strings.at(cur_x).at(cur_y); + } - void set(std::size_t cur_x, std::size_t cur_y, bool new_value) - { + void set(std::size_t cur_x, std::size_t cur_y, bool new_value) { get(cur_x, cur_y) = new_value; if (new_value) { @@ -55,46 +53,63 @@ template struct GameBoard } } - void visit(auto visitor) - { + void visit(auto visitor) { for (std::size_t cur_x = 0; cur_x < width; ++cur_x) { - for (std::size_t cur_y = 0; cur_y < height; ++cur_y) { visitor(cur_x, cur_y, *this); } + for (std::size_t cur_y = 0; cur_y < height; ++cur_y) { + visitor(cur_x, cur_y, *this); + } } } - [[nodiscard]] bool get(std::size_t cur_x, std::size_t cur_y) const { return values.at(cur_x).at(cur_y); } + [[nodiscard]] bool get(std::size_t cur_x, std::size_t cur_y) const { + return values.at(cur_x).at(cur_y); + } - [[nodiscard]] bool &get(std::size_t cur_x, std::size_t cur_y) { return values.at(cur_x).at(cur_y); } + [[nodiscard]] bool& get(std::size_t cur_x, std::size_t cur_y) { + return values.at(cur_x).at(cur_y); + } - GameBoard() - { - visit([](const auto cur_x, const auto cur_y, auto &gameboard) { gameboard.set(cur_x, cur_y, true); }); + GameBoard() { + visit([](const auto cur_x, const auto cur_y, auto& gameboard) { + gameboard.set(cur_x, cur_y, true); + }); } - void update_strings() - { + void update_strings() { for (std::size_t cur_x = 0; cur_x < width; ++cur_x) { - for (std::size_t cur_y = 0; cur_y < height; ++cur_y) { set(cur_x, cur_y, get(cur_x, cur_y)); } + for (std::size_t cur_y = 0; cur_y < height; ++cur_y) { + set(cur_x, cur_y, get(cur_x, cur_y)); + } } } - void toggle(std::size_t cur_x, std::size_t cur_y) { set(cur_x, cur_y, !get(cur_x, cur_y)); } + void toggle(std::size_t cur_x, std::size_t cur_y) { + set(cur_x, cur_y, !get(cur_x, cur_y)); + } - void press(std::size_t cur_x, std::size_t cur_y) - { + void press(std::size_t cur_x, std::size_t cur_y) { ++move_count; toggle(cur_x, cur_y); - if (cur_x > 0) { toggle(cur_x - 1, cur_y); } - if (cur_y > 0) { toggle(cur_x, cur_y - 1); } - if (cur_x < width - 1) { toggle(cur_x + 1, cur_y); } - if (cur_y < height - 1) { toggle(cur_x, cur_y + 1); } + if (cur_x > 0) { + toggle(cur_x - 1, cur_y); + } + if (cur_y > 0) { + toggle(cur_x, cur_y - 1); + } + if (cur_x < width - 1) { + toggle(cur_x + 1, cur_y); + } + if (cur_y < height - 1) { + toggle(cur_x, cur_y + 1); + } } - [[nodiscard]] bool solved() const - { + [[nodiscard]] bool solved() const { for (std::size_t cur_x = 0; cur_x < width; ++cur_x) { for (std::size_t cur_y = 0; cur_y < height; ++cur_y) { - if (!get(cur_x, cur_y)) { return false; } + if (!get(cur_x, cur_y)) { + return false; + } } } @@ -103,27 +118,31 @@ template struct GameBoard }; namespace { -void consequence_game() -{ +void consequence_game() { auto screen = ftxui::ScreenInteractive::TerminalOutput(); GameBoard<3, 3> game_board; std::string quit_text; - const auto update_quit_text = [&quit_text](const auto &game_board_param) { + const auto update_quit_text = [&quit_text](const auto& game_board_param) { quit_text = fmt::format("Quit ({} moves)", game_board_param.move_count); - if (game_board_param.solved()) { quit_text += " Solved!"; } + if (game_board_param.solved()) { + quit_text += " Solved!"; + } }; const auto make_buttons = [&] { std::vector buttons; for (std::size_t cur_x = 0; cur_x < game_board.width; ++cur_x) { for (std::size_t cur_y = 0; cur_y < game_board.height; ++cur_y) { - buttons.push_back(ftxui::Button(&game_board.get_string(cur_x, cur_y), [=, &game_board] { - if (!game_board.solved()) { game_board.press(cur_x, cur_y); } - update_quit_text(game_board); - })); + buttons.push_back(ftxui::Button(&game_board.get_string(cur_x, cur_y), + [=, &game_board] { + if (!game_board.solved()) { + game_board.press(cur_x, cur_y); + } + update_quit_text(game_board); + })); } } return buttons; @@ -152,18 +171,21 @@ void consequence_game() return ftxui::vbox(std::move(rows)); }; - static constexpr int randomization_iterations = 100; static constexpr int random_seed = 42; - std::mt19937 gen32{ random_seed };// NOLINT fixed seed + std::mt19937 gen32{ random_seed }; // NOLINT fixed seed // NOLINTNEXTLINE This cannot be const - std::uniform_int_distribution cur_x(static_cast(0), game_board.width - 1); + std::uniform_int_distribution cur_x(static_cast(0), + game_board.width - 1); // NOLINTNEXTLINE This cannot be const - std::uniform_int_distribution cur_y(static_cast(0), game_board.height - 1); + std::uniform_int_distribution cur_y(static_cast(0), + game_board.height - 1); - for (int i = 0; i < randomization_iterations; ++i) { game_board.press(cur_x(gen32), cur_y(gen32)); } + for (int i = 0; i < randomization_iterations; ++i) { + game_board.press(cur_x(gen32), cur_y(gen32)); + } game_board.move_count = 0; update_quit_text(game_board); @@ -175,40 +197,45 @@ void consequence_game() screen.Loop(renderer); } -}// namespace +} // namespace -struct Color -{ +struct Color { lefticus::tools::uint_np8_t R{ static_cast(0) }; lefticus::tools::uint_np8_t G{ static_cast(0) }; lefticus::tools::uint_np8_t B{ static_cast(0) }; }; // A simple way of representing a bitmap on screen using only characters -struct Bitmap : ftxui::Node -{ - Bitmap(std::size_t width, std::size_t height)// NOLINT same typed parameters adjacent to each other - : width_(width), height_(height) - {} - - Color &at(std::size_t cur_x, std::size_t cur_y) { return pixels.at((width_ * cur_y) + cur_x); } +struct Bitmap : ftxui::Node { + Bitmap(std::size_t width, + std::size_t + height) // NOLINT same typed parameters adjacent to each other + : width_(width) + , height_(height) {} + + Color& at(std::size_t cur_x, std::size_t cur_y) { + return pixels.at((width_ * cur_y) + cur_x); + } - void ComputeRequirement() override - { + void ComputeRequirement() override { requirement_.min_x = static_cast(width_); requirement_.min_y = static_cast(height_ / 2); } - void Render(ftxui::Screen &screen) override - { + void Render(ftxui::Screen& screen) override { for (std::size_t cur_x = 0; cur_x < width_; ++cur_x) { for (std::size_t cur_y = 0; cur_y < height_ / 2; ++cur_y) { - auto &pixel = screen.PixelAt(box_.x_min + static_cast(cur_x), box_.y_min + static_cast(cur_y)); + auto& pixel = screen.PixelAt(box_.x_min + static_cast(cur_x), + box_.y_min + static_cast(cur_y)); pixel.character = "▄"; - const auto &top_color = at(cur_x, cur_y * 2); - const auto &bottom_color = at(cur_x, (cur_y * 2) + 1); - pixel.background_color = ftxui::Color{ top_color.R.get(), top_color.G.get(), top_color.B.get() }; - pixel.foreground_color = ftxui::Color{ bottom_color.R.get(), bottom_color.G.get(), bottom_color.B.get() }; + const auto& top_color = at(cur_x, cur_y * 2); + const auto& bottom_color = at(cur_x, (cur_y * 2) + 1); + pixel.background_color = + ftxui::Color{ top_color.R.get(), top_color.G.get(), + top_color.B.get() }; + pixel.foreground_color = + ftxui::Color{ bottom_color.R.get(), bottom_color.G.get(), + bottom_color.B.get() }; } } } @@ -217,9 +244,9 @@ struct Bitmap : ftxui::Node [[nodiscard]] auto height() const noexcept { return height_; } - [[nodiscard]] auto &data() noexcept { return pixels; } + [[nodiscard]] auto& data() noexcept { return pixels; } -private: + private: std::size_t width_; std::size_t height_; @@ -227,12 +254,11 @@ struct Bitmap : ftxui::Node }; namespace { -void game_iteration_canvas() -{ - // this should probably have a `bitmap` helper function that does what cur_you expect - // similar to the other parts of FTXUI - auto bm = std::make_shared(50, 50);// NOLINT magic numbers - auto small_bm = std::make_shared(6, 6);// NOLINT magic numbers +void game_iteration_canvas() { + // this should probably have a `bitmap` helper function that does what cur_you + // expect similar to the other parts of FTXUI + auto bm = std::make_shared(50, 50); // NOLINT magic numbers + auto small_bm = std::make_shared(6, 6); // NOLINT magic numbers double fps = 0; @@ -240,47 +266,60 @@ void game_iteration_canvas() std::size_t max_col = 0; // to do, add total game time clock also, not just current elapsed time - auto game_iteration = [&](const std::chrono::steady_clock::duration elapsed_time) { - // in here we simulate however much game time has elapsed. Update animations, - // run character AI, whatever, update stats, etc - - // this isn't actually timing based for now, it's just updating the display however fast it can - fps = 1.0 - / (static_cast(std::chrono::duration_cast(elapsed_time).count()) - / 1'000'000.0);// NOLINT magic numbers - - for (std::size_t row = 0; row < max_row; ++row) { - for (std::size_t col = 0; col < bm->width(); ++col) { ++(bm->at(col, row).R); } - } - - for (std::size_t row = 0; row < bm->height(); ++row) { - for (std::size_t col = 0; col < max_col; ++col) { ++(bm->at(col, row).G); } - } - - // for the fun of it, let's have a second window doing interesting things - auto &small_bm_pixel = - small_bm->data().at(static_cast(elapsed_time.count()) % small_bm->data().size()); - - switch (elapsed_time.count() % 3) { - case 0: - small_bm_pixel.R += 11;// NOLINT Magic Number - break; - case 1: - small_bm_pixel.G += 11;// NOLINT Magic Number - break; - case 2: - small_bm_pixel.B += 11;// NOLINT Magic Number - break; - default:// literally impossible - std::unreachable(); - } - - - ++max_row; - if (max_row >= bm->height()) { max_row = 0; } - ++max_col; - if (max_col >= bm->width()) { max_col = 0; } - }; + auto game_iteration = + [&](const std::chrono::steady_clock::duration elapsed_time) { + // in here we simulate however much game time has elapsed. Update + // animations, run character AI, whatever, update stats, etc + + // this isn't actually timing based for now, it's just updating the + // display however fast it can + fps = 1.0 / (static_cast( + std::chrono::duration_cast( + elapsed_time) + .count()) / + 1'000'000.0); // NOLINT magic numbers + + for (std::size_t row = 0; row < max_row; ++row) { + for (std::size_t col = 0; col < bm->width(); ++col) { + ++(bm->at(col, row).R); + } + } + + for (std::size_t row = 0; row < bm->height(); ++row) { + for (std::size_t col = 0; col < max_col; ++col) { + ++(bm->at(col, row).G); + } + } + + // for the fun of it, let's have a second window doing interesting + // things + auto& small_bm_pixel = + small_bm->data().at(static_cast(elapsed_time.count()) % + small_bm->data().size()); + + switch (elapsed_time.count() % 3) { + case 0: + small_bm_pixel.R += 11; // NOLINT Magic Number + break; + case 1: + small_bm_pixel.G += 11; // NOLINT Magic Number + break; + case 2: + small_bm_pixel.B += 11; // NOLINT Magic Number + break; + default: // literally impossible + std::unreachable(); + } + + ++max_row; + if (max_row >= bm->height()) { + max_row = 0; + } + ++max_col; + if (max_col >= bm->width()) { + max_col = 0; + } + }; auto screen = ftxui::ScreenInteractive::TerminalOutput(); @@ -298,15 +337,15 @@ void game_iteration_canvas() last_time = new_time; // now actually draw the game elements - return ftxui::hbox({ bm | ftxui::border, - ftxui::vbox({ ftxui::text("Frame: " + std::to_string(counter)), - ftxui::text("FPS: " + std::to_string(fps)), - small_bm | ftxui::border }) }); + return ftxui::hbox( + { bm | ftxui::border, + ftxui::vbox({ ftxui::text("Frame: " + std::to_string(counter)), + ftxui::text("FPS: " + std::to_string(fps)), + small_bm | ftxui::border }) }); }; auto renderer = ftxui::Renderer(make_layout); - std::atomic refresh_ui_continue = true; // This thread exists to make sure that the event queue has an event to @@ -314,7 +353,7 @@ void game_iteration_canvas() std::thread refresh_ui([&] { while (refresh_ui_continue) { using namespace std::chrono_literals; - std::this_thread::sleep_for(1.0s / 30.0);// NOLINT magic numbers + std::this_thread::sleep_for(1.0s / 30.0); // NOLINT magic numbers screen.PostEvent(ftxui::Event::Custom); } }); @@ -324,13 +363,13 @@ void game_iteration_canvas() refresh_ui_continue = false; refresh_ui.join(); } -}// namespace +} // namespace // NOLINTNEXTLINE(bugprone-exception-escape) -int main(int argc, const char **argv) -{ +int main(int argc, const char** argv) { try { - CLI::App app{ fmt::format("{} version {}", myproject::cmake::project_name, myproject::cmake::project_version) }; + CLI::App app{ fmt::format("{} version {}", myproject::cmake::project_name, + myproject::cmake::project_version) }; std::optional message; app.add_option("-m,--message", message, "A message to print back out"); @@ -338,15 +377,14 @@ int main(int argc, const char **argv) app.add_flag("--version", show_version, "Show version information"); bool is_turn_based = false; - auto *turn_based = app.add_flag("--turn_based", is_turn_based); + auto* turn_based = app.add_flag("--turn_based", is_turn_based); bool is_loop_based = false; - auto *loop_based = app.add_flag("--loop_based", is_loop_based); + auto* loop_based = app.add_flag("--loop_based", is_loop_based); turn_based->excludes(loop_based); loop_based->excludes(turn_based); - CLI11_PARSE(app, argc, argv); if (show_version) { @@ -360,7 +398,7 @@ int main(int argc, const char **argv) game_iteration_canvas(); } - } catch (const std::exception &e) { + } catch (const std::exception& e) { spdlog::error("Unhandled exception in main: {}", e.what()); } } diff --git a/src/sample_library/sample_library.cpp b/src/sample_library/sample_library.cpp index 878deae2..905b5213 100644 --- a/src/sample_library/sample_library.cpp +++ b/src/sample_library/sample_library.cpp @@ -1,7 +1,6 @@ #include -int factorial(int input) noexcept -{ +int factorial(int input) noexcept { int result = 1; while (input > 0) { diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 295517cf..a25e8f0e 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -1,10 +1,10 @@ -cmake_minimum_required(VERSION 3.15...3.23) +cmake_minimum_required(VERSION 3.21) project(CmakeConfigPackageTests VERSION ${PROJECT_VERSION} LANGUAGES CXX) -# ── Test as standalone project the exported config package ──────────── +# Test as standalone project the exported config package if(PROJECT_IS_TOP_LEVEL OR TEST_INSTALLED_VERSION) enable_testing() @@ -13,11 +13,11 @@ if(PROJECT_IS_TOP_LEVEL OR TEST_INSTALLED_VERSION) if(NOT TARGET myproject_options) message(FATAL_ERROR "Required config package not found!") - return() # be strictly paranoid for Template Janitor github action! CK + return() # be strictly paranoid for Template Janitor github action! endif() endif() -# ── Dependencies ────────────────────────────────────────────────────── +# Dependencies # Catch2 test discovery module (Catch.cmake) is added to CMAKE_MODULE_PATH # by myproject_setup_dependencies() in Dependencies.cmake. @@ -68,7 +68,7 @@ catch_discover_tests( OUTPUT_SUFFIX .xml) -# ── Mock tests (trompeloeil + Catch2) ───────────────────────────────── +# Mock tests (trompeloeil + Catch2) add_executable(mock_tests mock_tests.cpp) target_link_libraries( diff --git a/test/constexpr_tests.cpp b/test/constexpr_tests.cpp index fe36db5e..b1cb12bb 100644 --- a/test/constexpr_tests.cpp +++ b/test/constexpr_tests.cpp @@ -1,9 +1,7 @@ #include - #include -TEST_CASE("Factorials are computed with constexpr", "[factorial]") -{ +TEST_CASE("Factorials are computed with constexpr", "[factorial]") { STATIC_REQUIRE(factorial_constexpr(0) == 1); STATIC_REQUIRE(factorial_constexpr(1) == 1); STATIC_REQUIRE(factorial_constexpr(2) == 2); diff --git a/test/mock_tests.cpp b/test/mock_tests.cpp index 1f044454..93b38742 100644 --- a/test/mock_tests.cpp +++ b/test/mock_tests.cpp @@ -7,31 +7,30 @@ #include #include -// ── Interface ──────────────────────────────────────────────────────── +// Interface -class ICalculator -{ -public: +class ICalculator { + public: virtual ~ICalculator() = default; virtual int compute(int input) = 0; }; -// ── Mock ───────────────────────────────────────────────────────────── +// Mock -class MockCalculator : public ICalculator -{ -public: +class MockCalculator : public ICalculator { + public: MAKE_MOCK1(compute, int(int), override); }; -// ── Consumer under test ────────────────────────────────────────────── +// Consumer under test -int double_compute(ICalculator &calc, int value) { return calc.compute(value) * 2; } +int double_compute(ICalculator& calc, int value) { + return calc.compute(value) * 2; +} -// ── Tests ──────────────────────────────────────────────────────────── +// Tests -TEST_CASE("Mock verifies compute is called", "[mock]") -{ +TEST_CASE("Mock verifies compute is called", "[mock]") { MockCalculator calc; REQUIRE_CALL(calc, compute(5)).RETURN(42); @@ -39,8 +38,7 @@ TEST_CASE("Mock verifies compute is called", "[mock]") REQUIRE(double_compute(calc, 5) == 84); } -TEST_CASE("Mock allows sequences of calls", "[mock]") -{ +TEST_CASE("Mock allows sequences of calls", "[mock]") { MockCalculator calc; trompeloeil::sequence seq; diff --git a/test/tests.cpp b/test/tests.cpp index 5b632e2e..254f1c96 100644 --- a/test/tests.cpp +++ b/test/tests.cpp @@ -1,11 +1,7 @@ #include - - #include - -TEST_CASE("Factorials are computed", "[factorial]") -{ +TEST_CASE("Factorials are computed", "[factorial]") { REQUIRE(factorial(0) == 1); REQUIRE(factorial(1) == 1); REQUIRE(factorial(2) == 2); From b68d6e9f907f6f412f802bcfc493556b57570291 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vin=C3=ADcius=20Ferr=C3=A3o?= <2031761+viniciusferrao@users.noreply.github.com> Date: Tue, 17 Mar 2026 19:30:48 -0300 Subject: [PATCH 06/10] fix: CI infrastructure, clang-tidy + Conan, and platform fixes - Fix clang-tidy + Conan: remove -p flag from StaticAnalyzers.cmake so CMake passes full compile command (including -isystem paths) directly to clang-tidy - Disable misc-include-cleaner in .clang-tidy (noisy false positives) - Disable clang-tidy on test targets (Catch2 macro false positives) - Fix macOS GCC: pre-install Conan deps with libstdc++11 profile - Skip GCC --coverage on macOS ARM (Apple linker can't find libgcov) - Install lizard in CI and WASM workflows - Disable Bloaty/IWYU in CI (not available on runners) - Pin Emscripten to 3.1.74 (Conan doesn't support emcc 23+) - Fix auto-clang-format PR checkout (github.head_ref) - Add myproject_SKIP_CONAN_PROVIDER option to cmake/Conan.cmake - Add CI_KNOWN_ISSUES.md Signed-off-by: Helder Ferreira Co-Authored-By: Claude Opus 4.6 (1M context) --- .clang-tidy | 3 +- .github/workflows/auto-clang-format.yml | 2 + .github/workflows/ci.yml | 39 +++++++++++++++++-- .github/workflows/wasm.yml | 13 +++++-- CI_KNOWN_ISSUES.md | 51 +++++++++++++++++++++++++ cmake/Conan.cmake | 5 +++ cmake/StaticAnalyzers.cmake | 9 ++++- cmake/Tests.cmake | 6 +++ include/myproject/sample_library.hpp | 2 +- src/ftxui_sample/main.cpp | 8 ++-- test/CMakeLists.txt | 5 +++ 11 files changed, 126 insertions(+), 17 deletions(-) create mode 100644 CI_KNOWN_ISSUES.md diff --git a/.clang-tidy b/.clang-tidy index cd70e1c9..cfaf6505 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -15,7 +15,8 @@ Checks: "*, -misc-non-private-member-variables-in-classes, -misc-no-recursion, -misc-use-anonymous-namespace, - -misc-use-internal-linkage + -misc-use-internal-linkage, + -misc-include-cleaner " WarningsAsErrors: '' HeaderFilterRegex: '(include|src)/' diff --git a/.github/workflows/auto-clang-format.yml b/.github/workflows/auto-clang-format.yml index c1d80f55..cce1bc8c 100644 --- a/.github/workflows/auto-clang-format.yml +++ b/.github/workflows/auto-clang-format.yml @@ -7,6 +7,8 @@ jobs: steps: - uses: actions/checkout@v6 + with: + ref: ${{ github.head_ref }} - uses: DoozyX/clang-format-lint-action@v0.20 with: source: '.' diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9a87716f..c921dd55 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -161,12 +161,43 @@ jobs: gcovr: true opencppcoverage: true - - name: Install Conan - run: pip install conan + - name: Install Conan and Lizard + run: pip install conan lizard + + - name: Pre-install Conan deps for macOS GCC + if: ${{ runner.os == 'macOS' && contains(matrix.compiler, 'gcc') }} + run: | + # GCC on macOS uses libstdc++, but the cmake-conan provider + # auto-detects Apple Clang and builds packages with libc++. + # Pre-install deps with a GCC profile and skip the provider. + conan profile detect --force + GCC_VER=$(echo "${{ matrix.compiler }}" | grep -o '[0-9]*') + cat > ~/.conan2/profiles/default << PROFILE + [settings] + arch=armv8 + build_type=Release + compiler=gcc + compiler.cppstd=23 + compiler.libcxx=libstdc++11 + compiler.version=$GCC_VER + os=Macos + [conf] + tools.build:compiler_executables={"c": "gcc-$GCC_VER", "cpp": "g++-$GCC_VER"} + PROFILE + cat ~/.conan2/profiles/default + conan install . --output-folder=build --build=missing -s build_type=Debug + conan install . --output-folder=build --build=missing -s build_type=Release + conan install . --output-folder=build --build=missing -s build_type=RelWithDebInfo - name: Configure CMake + shell: bash run: | - cmake -S . -B ./build -G "${{matrix.generator}}" -D${{ env.PROJECT_NAME }}_ENABLE_IPO=${{matrix.enable_ipo }} -DCMAKE_BUILD_TYPE:STRING=${{matrix.build_type}} -D${{ env.PROJECT_NAME }}_PACKAGING_MAINTAINER_MODE:BOOL=${{matrix.packaging_maintainer_mode}} -D${{ env.PROJECT_NAME }}_ENABLE_COVERAGE:BOOL=${{ matrix.build_type == 'Debug' }} -DGIT_SHA:STRING=${{ github.sha }} + EXTRA_CMAKE_ARGS="" + # macOS GCC: deps pre-installed with libstdc++11 profile + if [[ "${{ runner.os }}" == "macOS" && "${{ matrix.compiler }}" =~ gcc ]]; then + EXTRA_CMAKE_ARGS="-Dmyproject_SKIP_CONAN_PROVIDER=ON -DCMAKE_PREFIX_PATH=$PWD/build" + fi + cmake -S . -B ./build -G "${{matrix.generator}}" $EXTRA_CMAKE_ARGS -D${{ env.PROJECT_NAME }}_ENABLE_IPO=${{matrix.enable_ipo }} -DCMAKE_BUILD_TYPE:STRING=${{matrix.build_type}} -D${{ env.PROJECT_NAME }}_PACKAGING_MAINTAINER_MODE:BOOL=${{matrix.packaging_maintainer_mode}} -D${{ env.PROJECT_NAME }}_ENABLE_COVERAGE:BOOL=${{ matrix.build_type == 'Debug' }} -D${{ env.PROJECT_NAME }}_ENABLE_BLOATY:BOOL=OFF -D${{ env.PROJECT_NAME }}_ENABLE_INCLUDE_WHAT_YOU_USE:BOOL=OFF -DGIT_SHA:STRING=${{ github.sha }} - name: Build # Execute the build. You can specify a specific target with "--target " @@ -205,7 +236,7 @@ jobs: - name: Publish to codecov uses: codecov/codecov-action@v5 with: - fail_ci_if_error: true # we weren't posting previously + fail_ci_if_error: true flags: ${{ runner.os }} name: ${{ runner.os }}-coverage token: ${{ secrets.CODECOV_TOKEN }} diff --git a/.github/workflows/wasm.yml b/.github/workflows/wasm.yml index 6519b505..49c0b13d 100644 --- a/.github/workflows/wasm.yml +++ b/.github/workflows/wasm.yml @@ -20,15 +20,20 @@ jobs: - name: Setup Emscripten uses: mymindstorm/setup-emsdk@v14 with: - version: 'latest' + # Pin to 3.1.x — Conan's settings.yml doesn't support emcc 23+ yet + version: '3.1.74' - - name: Install Ninja and Conan + - name: Install Ninja, Conan, and Lizard run: | sudo apt-get install -y ninja-build - pip install conan + pip install conan lizard - name: Configure CMake - run: emcmake cmake -B build -G Ninja -DCMAKE_BUILD_TYPE=Release + run: | + emcmake cmake -B build -G Ninja -DCMAKE_BUILD_TYPE=Release \ + -Dmyproject_ENABLE_BLOATY=OFF \ + -Dmyproject_ENABLE_INCLUDE_WHAT_YOU_USE=OFF \ + -Dmyproject_ENABLE_CLANG_TIDY=OFF - name: Build all WASM targets run: emmake cmake --build build --target web-dist diff --git a/CI_KNOWN_ISSUES.md b/CI_KNOWN_ISSUES.md new file mode 100644 index 00000000..ffd1c580 --- /dev/null +++ b/CI_KNOWN_ISSUES.md @@ -0,0 +1,51 @@ +# CI Known Issues + +This document tracks CI limitations and their resolutions. Most issues +stemmed from the migration from CPM/FetchContent to Conan 2.0. + +## clang-tidy and Conan include paths (RESOLVED) + +**Status:** Fixed by removing the `-p` flag from `StaticAnalyzers.cmake`. + +**Root cause:** The `-p` flag in `CMAKE_CXX_CLANG_TIDY` options told CMake +to have clang-tidy use `compile_commands.json` instead of passing the full +compile command directly. This broke include resolution for Conan packages +whose headers live in external `-isystem` paths (`~/.conan2/p/...`). Without +`-p`, CMake appends `-- ` to clang-tidy, which includes +all `-isystem` paths and works reliably with Conan. + +## macOS + GCC Conan ABI (RESOLVED) + +**Status:** Fixed by overriding the Conan profile for macOS GCC in CI. + +**Root cause:** Conan's default macOS profile detects Apple Clang and sets +`compiler.libcxx=libc++`. When building with GCC 14 (which uses `libstdc++`), +this causes ABI mismatches at link time. Fixed by creating a GCC-specific +Conan profile with `compiler=gcc` and `compiler.libcxx=libstdc++11`. + +## codecov fail_ci_if_error (RESOLVED) + +**Status:** Set to `true`. Requires `CODECOV_TOKEN` repository secret. + +**Setup:** Add `CODECOV_TOKEN` to the repository secrets at +`Settings > Secrets and variables > Actions`. The token is obtained from +[codecov.io](https://codecov.io) after linking the repository. Derived repos +must configure their own token. + +## GCC coverage on macOS ARM + +**Status:** Skipped in `cmake/Tests.cmake` when `APPLE AND GNU`. + +**Root cause:** GCC's `--coverage` flag links against `libgcov`, which +Apple's ARM linker can't find. Tests still run and pass; only coverage +instrumentation is skipped. GCC coverage works on Linux. + +## Intel ICX coverage + +**Status:** gcovr skipped when `matrix.compiler == intel` in `ci.yml`. + +**Root cause:** Intel ICX produces coverage data in a format incompatible +with `gcov`. Tests still run and pass; only the coverage report is skipped. + +**Possible fix:** Use `llvm-cov` from the oneAPI toolkit to process ICX +coverage data. Nice-to-have, not a blocker. diff --git a/cmake/Conan.cmake b/cmake/Conan.cmake index 3f737ece..f21d4dff 100644 --- a/cmake/Conan.cmake +++ b/cmake/Conan.cmake @@ -6,6 +6,11 @@ # # This file must be included BEFORE the project() call. +option(myproject_SKIP_CONAN_PROVIDER "Skip the Conan CMake provider (use pre-installed deps)" OFF) +if(myproject_SKIP_CONAN_PROVIDER) + return() +endif() + set(CONAN_PROVIDER_LOCATION "${CMAKE_BINARY_DIR}/cmake/conan_provider.cmake") if(NOT EXISTS "${CONAN_PROVIDER_LOCATION}") diff --git a/cmake/StaticAnalyzers.cmake b/cmake/StaticAnalyzers.cmake index 37d43ee5..1094655c 100644 --- a/cmake/StaticAnalyzers.cmake +++ b/cmake/StaticAnalyzers.cmake @@ -74,12 +74,17 @@ macro(myproject_enable_clang_tidy target WARNINGS_AS_ERRORS) endif() # construct the clang-tidy command line + # NOTE: Do not add -p here. With -p, CMake tells clang-tidy to use + # compile_commands.json instead of passing the full compile command + # directly. This breaks include resolution for Conan packages whose + # headers live in external -isystem paths outside the build tree. + # Without -p, CMake appends "-- " to clang-tidy, + # which includes all -isystem paths and works reliably. set(CLANG_TIDY_OPTIONS ${CLANGTIDY} -extra-arg=-Wno-unknown-warning-option -extra-arg=-Wno-ignored-optimization-argument - -extra-arg=-Wno-unused-command-line-argument - -p) + -extra-arg=-Wno-unused-command-line-argument) # set standard if(NOT "${CMAKE_CXX_STANDARD}" diff --git a/cmake/Tests.cmake b/cmake/Tests.cmake index a7f9b6e1..818af348 100644 --- a/cmake/Tests.cmake +++ b/cmake/Tests.cmake @@ -1,5 +1,11 @@ function(myproject_enable_coverage project_name) if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES ".*Clang") + # GCC's --coverage flag doesn't work on macOS ARM (Apple linker + # can't find libgcov). Skip coverage for GCC on Apple platforms. + if(APPLE AND CMAKE_CXX_COMPILER_ID STREQUAL "GNU") + message(WARNING "Coverage disabled: GCC --coverage is not supported on macOS ARM") + return() + endif() target_compile_options(${project_name} INTERFACE --coverage -g) target_link_libraries(${project_name} INTERFACE --coverage) endif() diff --git a/include/myproject/sample_library.hpp b/include/myproject/sample_library.hpp index 57c7db8f..32324dd4 100644 --- a/include/myproject/sample_library.hpp +++ b/include/myproject/sample_library.hpp @@ -3,7 +3,7 @@ #include -[[nodiscard]] SAMPLE_LIBRARY_EXPORT int factorial(int) noexcept; +[[nodiscard]] SAMPLE_LIBRARY_EXPORT int factorial(int input) noexcept; [[nodiscard]] constexpr int factorial_constexpr(int input) noexcept { if (input == 0) { diff --git a/src/ftxui_sample/main.cpp b/src/ftxui_sample/main.cpp index bcb254f9..dcbbda14 100644 --- a/src/ftxui_sample/main.cpp +++ b/src/ftxui_sample/main.cpp @@ -207,11 +207,9 @@ struct Color { // A simple way of representing a bitmap on screen using only characters struct Bitmap : ftxui::Node { - Bitmap(std::size_t width, - std::size_t - height) // NOLINT same typed parameters adjacent to each other - : width_(width) - , height_(height) {} + // NOLINTNEXTLINE(bugprone-easily-swappable-parameters) + Bitmap(std::size_t width, std::size_t height) + : width_(width), height_(height) {} Color& at(std::size_t cur_x, std::size_t cur_y) { return pixels.at((width_ * cur_y) + cur_x); diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index a25e8f0e..763b1039 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -42,6 +42,9 @@ target_link_libraries( myproject::myproject_options myproject::sample_library Catch2::Catch2WithMain) +# Disable clang-tidy for test targets: Catch2 macros generate +# false positives (non-const globals, include-cleaner, etc.) +set_target_properties(tests PROPERTIES CXX_CLANG_TIDY "") myproject_configure_linker(tests) @@ -77,6 +80,7 @@ target_link_libraries( myproject::myproject_options Catch2::Catch2WithMain trompeloeil::trompeloeil) +set_target_properties(mock_tests PROPERTIES CXX_CLANG_TIDY "") myproject_configure_linker(mock_tests) @@ -101,6 +105,7 @@ target_link_libraries( myproject::myproject_options myproject::sample_library Catch2::Catch2WithMain) +set_target_properties(constexpr_tests PROPERTIES CXX_CLANG_TIDY "") myproject_configure_linker(constexpr_tests) From 77f0abbdd803d9740173dcfdadbf72a164fabffb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vin=C3=ADcius=20Ferr=C3=A3o?= <2031761+viniciusferrao@users.noreply.github.com> Date: Tue, 17 Mar 2026 19:38:03 -0300 Subject: [PATCH 07/10] feat(cmake): add Intel oneAPI compiler (ICX/ICPX) support - Add IntelLLVM to all compiler detection points: CompilerWarnings, StandardProjectSettings, Hardening, Sanitizers, Tests, StaticAnalyzers, ProjectOptions - Add Intel to CI matrix (Linux only) - Install oneAPI directly from Intel apt repo - Pre-install Conan deps with clang profile (binary compatible) and skip auto-provider via myproject_SKIP_CONAN_PROVIDER - Skip gcovr and codecov for Intel (incompatible coverage format) Signed-off-by: Helder Ferreira Co-Authored-By: Claude Opus 4.6 (1M context) --- .github/workflows/ci.yml | 54 ++++++++++++++++++- .github/workflows/codeql-analysis.yml | 7 +-- .github/workflows/wasm.yml | 28 ++++++++-- CI_KNOWN_ISSUES.md | 74 +++++++++------------------ ProjectOptions.cmake | 4 +- cmake/CompilerWarnings.cmake | 3 +- cmake/Emscripten.cmake | 6 +-- cmake/Hardening.cmake | 2 +- cmake/Sanitizers.cmake | 4 +- cmake/StandardProjectSettings.cmake | 2 +- cmake/StaticAnalyzers.cmake | 4 +- cmake/Tests.cmake | 2 +- 12 files changed, 118 insertions(+), 72 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c921dd55..f4fa4499 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -38,6 +38,7 @@ jobs: # you can specify the version after `-` like "llvm-15.0.2". - llvm-19.1.1 - gcc-14 + - intel generator: - "Ninja Multi-Config" build_type: @@ -54,6 +55,12 @@ jobs: - os: windows-latest compiler: gcc-14 + # Intel oneAPI is only available on Linux + - os: windows-latest + compiler: intel + - os: macos-latest + compiler: intel + include: # Add appropriate variables for gcov version required. This will intentionally break # if you try to use a compiler that does not have gcov set @@ -65,6 +72,10 @@ jobs: enable_ipo: Off gcov_executable: "llvm-cov gcov" + - compiler: intel + enable_ipo: Off + gcov_executable: gcov + - os: macos-latest enable_ipo: Off @@ -144,6 +155,7 @@ jobs: - name: Setup Cpp + if: ${{ matrix.compiler != 'intel' }} uses: aminya/setup-cpp@v1 with: compiler: ${{ matrix.compiler }} @@ -161,9 +173,39 @@ jobs: gcovr: true opencppcoverage: true + - name: Install Intel oneAPI + if: ${{ matrix.compiler == 'intel' }} + run: | + wget -qO- https://apt.repos.intel.com/intel-gpg-keys/GPG-PUB-KEY-INTEL-SW-PRODUCTS.PUB | sudo gpg --dearmor -o /usr/share/keyrings/oneapi-archive-keyring.gpg + echo "deb [signed-by=/usr/share/keyrings/oneapi-archive-keyring.gpg] https://apt.repos.intel.com/oneapi all main" | sudo tee /etc/apt/sources.list.d/oneAPI.list + sudo apt-get update + sudo apt-get install -y intel-oneapi-compiler-dpcpp-cpp cmake ninja-build ccache cppcheck gcovr + + - name: Configure Intel oneAPI environment + if: ${{ matrix.compiler == 'intel' }} + run: | + echo "/opt/intel/oneapi/compiler/latest/bin" >> $GITHUB_PATH + echo "LD_LIBRARY_PATH=/opt/intel/oneapi/compiler/latest/lib:$LD_LIBRARY_PATH" >> $GITHUB_ENV + echo "CC=icx" >> $GITHUB_ENV + echo "CXX=icpx" >> $GITHUB_ENV + - name: Install Conan and Lizard run: pip install conan lizard + - name: Configure Conan for Intel ICX + if: ${{ matrix.compiler == 'intel' }} + run: | + ICX=/opt/intel/oneapi/compiler/latest/bin/icx + ICX_CLANG_VER=$($ICX -dM -E -x c /dev/null 2>/dev/null | grep __clang_major__ | awk '{print $3}') + echo "ICX underlying Clang version: $ICX_CLANG_VER" + CC=clang CXX=clang++ conan profile detect --force + sed -i "s/compiler.version=.*/compiler.version=$ICX_CLANG_VER/" ~/.conan2/profiles/default + sed -i "s/compiler.cppstd=.*/compiler.cppstd=23/" ~/.conan2/profiles/default + cat ~/.conan2/profiles/default + conan install . --output-folder=build --build=missing -s build_type=Debug + conan install . --output-folder=build --build=missing -s build_type=Release + conan install . --output-folder=build --build=missing -s build_type=RelWithDebInfo + - name: Pre-install Conan deps for macOS GCC if: ${{ runner.os == 'macOS' && contains(matrix.compiler, 'gcc') }} run: | @@ -194,8 +236,12 @@ jobs: run: | EXTRA_CMAKE_ARGS="" # macOS GCC: deps pre-installed with libstdc++11 profile + if [[ "${{ matrix.compiler }}" == "intel" ]]; then + EXTRA_CMAKE_ARGS="-D${{ env.PROJECT_NAME }}_SKIP_CONAN_PROVIDER=ON -DCMAKE_PREFIX_PATH=$PWD/build" + fi + # macOS GCC: deps pre-installed with libstdc++11 profile if [[ "${{ runner.os }}" == "macOS" && "${{ matrix.compiler }}" =~ gcc ]]; then - EXTRA_CMAKE_ARGS="-Dmyproject_SKIP_CONAN_PROVIDER=ON -DCMAKE_PREFIX_PATH=$PWD/build" + EXTRA_CMAKE_ARGS="-D${{ env.PROJECT_NAME }}_SKIP_CONAN_PROVIDER=ON -DCMAKE_PREFIX_PATH=$PWD/build" fi cmake -S . -B ./build -G "${{matrix.generator}}" $EXTRA_CMAKE_ARGS -D${{ env.PROJECT_NAME }}_ENABLE_IPO=${{matrix.enable_ipo }} -DCMAKE_BUILD_TYPE:STRING=${{matrix.build_type}} -D${{ env.PROJECT_NAME }}_PACKAGING_MAINTAINER_MODE:BOOL=${{matrix.packaging_maintainer_mode}} -D${{ env.PROJECT_NAME }}_ENABLE_COVERAGE:BOOL=${{ matrix.build_type == 'Debug' }} -D${{ env.PROJECT_NAME }}_ENABLE_BLOATY:BOOL=OFF -D${{ env.PROJECT_NAME }}_ENABLE_INCLUDE_WHAT_YOU_USE:BOOL=OFF -DGIT_SHA:STRING=${{ github.sha }} @@ -211,7 +257,10 @@ jobs: # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail run: | ctest -C ${{matrix.build_type}} - gcovr -j ${{env.nproc}} --root ../ --print-summary --xml-pretty --xml coverage.xml . --gcov-executable '${{ matrix.gcov_executable }}' + # Intel ICX uses a different coverage format incompatible with gcov + if [[ "${{ matrix.compiler }}" != "intel" ]]; then + gcovr -j ${{env.nproc}} --root ../ --print-summary --xml-pretty --xml coverage.xml . --gcov-executable '${{ matrix.gcov_executable }}' + fi - name: Windows - Test and coverage if: runner.os == 'Windows' @@ -234,6 +283,7 @@ jobs: - name: Publish to codecov + if: ${{ matrix.compiler != 'intel' }} uses: codecov/codecov-action@v5 with: fail_ci_if_error: true diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index c48a04c4..ba4af408 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -81,11 +81,12 @@ jobs: gcovr: false opencppcoverage: false - # make sure coverage is only enabled for Debug builds, since it sets -O0 to make sure coverage - # has meaningful results + - name: Install Conan and Lizard + run: pip install conan lizard + - name: Configure CMake run: | - cmake -S . -B ./build -G "${{matrix.generator}}" -DCMAKE_BUILD_TYPE:STRING=${{matrix.build_type}} -D${{ env.PROJECT_NAME }}_PACKAGING_MAINTAINER_MODE:BOOL=${{matrix.packaging_maintainer_mode}} -D${{ env.PROJECT_NAME }}_ENABLE_COVERAGE:BOOL=${{ matrix.build_type == 'Debug' }} + cmake -S . -B ./build -G "${{matrix.generator}}" -DCMAKE_BUILD_TYPE:STRING=${{matrix.build_type}} -D${{ env.PROJECT_NAME }}_PACKAGING_MAINTAINER_MODE:BOOL=${{matrix.packaging_maintainer_mode}} -D${{ env.PROJECT_NAME }}_ENABLE_COVERAGE:BOOL=${{ matrix.build_type == 'Debug' }} -D${{ env.PROJECT_NAME }}_ENABLE_BLOATY:BOOL=OFF -D${{ env.PROJECT_NAME }}_ENABLE_INCLUDE_WHAT_YOU_USE:BOOL=OFF # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL diff --git a/.github/workflows/wasm.yml b/.github/workflows/wasm.yml index 49c0b13d..421f0569 100644 --- a/.github/workflows/wasm.yml +++ b/.github/workflows/wasm.yml @@ -15,7 +15,7 @@ jobs: build-and-deploy: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: Setup Emscripten uses: mymindstorm/setup-emsdk@v14 @@ -28,12 +28,32 @@ jobs: sudo apt-get install -y ninja-build pip install conan lizard + - name: Project Name + uses: cardinalby/export-env-action@v2 + with: + envFile: '.github/constants.env' + + - name: Configure Conan for Emscripten threading + run: | + # The cmake-conan provider auto-detects Emscripten but builds + # packages without threading flags. FTXUI requires pthreads. + # Configure the default Conan profile to pass -pthread so all + # packages (especially spdlog) are built with atomics support. + conan profile detect --force + echo "" >> ~/.conan2/profiles/default + echo "[conf]" >> ~/.conan2/profiles/default + echo "tools.build:cflags=[\"-pthread\"]" >> ~/.conan2/profiles/default + echo "tools.build:cxxflags=[\"-pthread\"]" >> ~/.conan2/profiles/default + echo "tools.build:sharedlinkflags=[\"-pthread\"]" >> ~/.conan2/profiles/default + echo "tools.build:exelinkflags=[\"-pthread\"]" >> ~/.conan2/profiles/default + cat ~/.conan2/profiles/default + - name: Configure CMake run: | emcmake cmake -B build -G Ninja -DCMAKE_BUILD_TYPE=Release \ - -Dmyproject_ENABLE_BLOATY=OFF \ - -Dmyproject_ENABLE_INCLUDE_WHAT_YOU_USE=OFF \ - -Dmyproject_ENABLE_CLANG_TIDY=OFF + -D${{ env.PROJECT_NAME }}_ENABLE_BLOATY=OFF \ + -D${{ env.PROJECT_NAME }}_ENABLE_INCLUDE_WHAT_YOU_USE=OFF \ + -D${{ env.PROJECT_NAME }}_ENABLE_CLANG_TIDY=OFF - name: Build all WASM targets run: emmake cmake --build build --target web-dist diff --git a/CI_KNOWN_ISSUES.md b/CI_KNOWN_ISSUES.md index ffd1c580..5727dd93 100644 --- a/CI_KNOWN_ISSUES.md +++ b/CI_KNOWN_ISSUES.md @@ -1,51 +1,27 @@ # CI Known Issues -This document tracks CI limitations and their resolutions. Most issues -stemmed from the migration from CPM/FetchContent to Conan 2.0. - -## clang-tidy and Conan include paths (RESOLVED) - -**Status:** Fixed by removing the `-p` flag from `StaticAnalyzers.cmake`. - -**Root cause:** The `-p` flag in `CMAKE_CXX_CLANG_TIDY` options told CMake -to have clang-tidy use `compile_commands.json` instead of passing the full -compile command directly. This broke include resolution for Conan packages -whose headers live in external `-isystem` paths (`~/.conan2/p/...`). Without -`-p`, CMake appends `-- ` to clang-tidy, which includes -all `-isystem` paths and works reliably with Conan. - -## macOS + GCC Conan ABI (RESOLVED) - -**Status:** Fixed by overriding the Conan profile for macOS GCC in CI. - -**Root cause:** Conan's default macOS profile detects Apple Clang and sets -`compiler.libcxx=libc++`. When building with GCC 14 (which uses `libstdc++`), -this causes ABI mismatches at link time. Fixed by creating a GCC-specific -Conan profile with `compiler=gcc` and `compiler.libcxx=libstdc++11`. - -## codecov fail_ci_if_error (RESOLVED) - -**Status:** Set to `true`. Requires `CODECOV_TOKEN` repository secret. - -**Setup:** Add `CODECOV_TOKEN` to the repository secrets at -`Settings > Secrets and variables > Actions`. The token is obtained from -[codecov.io](https://codecov.io) after linking the repository. Derived repos -must configure their own token. - -## GCC coverage on macOS ARM - -**Status:** Skipped in `cmake/Tests.cmake` when `APPLE AND GNU`. - -**Root cause:** GCC's `--coverage` flag links against `libgcov`, which -Apple's ARM linker can't find. Tests still run and pass; only coverage -instrumentation is skipped. GCC coverage works on Linux. - -## Intel ICX coverage - -**Status:** gcovr skipped when `matrix.compiler == intel` in `ci.yml`. - -**Root cause:** Intel ICX produces coverage data in a format incompatible -with `gcov`. Tests still run and pass; only the coverage report is skipped. - -**Possible fix:** Use `llvm-cov` from the oneAPI toolkit to process ICX -coverage data. Nice-to-have, not a blocker. +Tracked issues with links to GitHub issues for follow-up. + +## Resolved + +- **clang-tidy + Conan**: Fixed by removing `-p` flag from + `StaticAnalyzers.cmake`. clang-tidy works on all compilers. +- **macOS GCC ABI mismatch**: Fixed by pre-installing Conan deps + with a GCC-specific `libstdc++11` profile. +- **CodeQL missing Conan**: Fixed by adding `pip install conan lizard` + and disabling Bloaty/IWYU in the CodeQL workflow. +- **codecov**: Set to `fail_ci_if_error: true`. Derived repos must + configure their own `CODECOV_TOKEN` repository secret from + [codecov.io](https://codecov.io). + +## Open + +- **WASM + Emscripten threading** ([#4](https://github.com/VersatusHPC/cmake_template/issues/4)): + spdlog built by Conan without atomics/bulk-memory, but the project + uses Emscripten pthreads. Emscripten pinned to 3.1.74 (Conan + doesn't support emcc 23+). +- **macOS GCC coverage** ([#5](https://github.com/VersatusHPC/cmake_template/issues/5)): + Apple ARM linker can't find libgcov. Tests pass, coverage skipped. +- **Intel ICX coverage** ([#6](https://github.com/VersatusHPC/cmake_template/issues/6)): + gcov incompatible with ICX coverage format. Tests pass, coverage + skipped. diff --git a/ProjectOptions.cmake b/ProjectOptions.cmake index 3af44542..5f3789d1 100644 --- a/ProjectOptions.cmake +++ b/ProjectOptions.cmake @@ -12,7 +12,7 @@ macro(myproject_supports_sanitizers) if(EMSCRIPTEN) set(SUPPORTS_UBSAN OFF) set(SUPPORTS_ASAN OFF) - elseif((CMAKE_CXX_COMPILER_ID MATCHES ".*Clang.*" OR CMAKE_CXX_COMPILER_ID MATCHES ".*GNU.*") AND NOT WIN32) + elseif((CMAKE_CXX_COMPILER_ID MATCHES ".*Clang.*" OR CMAKE_CXX_COMPILER_ID MATCHES ".*GNU.*" OR CMAKE_CXX_COMPILER_ID STREQUAL "IntelLLVM") AND NOT WIN32) message(STATUS "Sanity checking UndefinedBehaviorSanitizer, it should be supported on this platform") set(TEST_PROGRAM "int main() { return 0; }") @@ -33,7 +33,7 @@ macro(myproject_supports_sanitizers) set(SUPPORTS_UBSAN OFF) endif() - if((CMAKE_CXX_COMPILER_ID MATCHES ".*Clang.*" OR CMAKE_CXX_COMPILER_ID MATCHES ".*GNU.*") AND WIN32) + if((CMAKE_CXX_COMPILER_ID MATCHES ".*Clang.*" OR CMAKE_CXX_COMPILER_ID MATCHES ".*GNU.*" OR CMAKE_CXX_COMPILER_ID STREQUAL "IntelLLVM") AND WIN32) set(SUPPORTS_ASAN OFF) else() if (NOT WIN32) diff --git a/cmake/CompilerWarnings.cmake b/cmake/CompilerWarnings.cmake index 281ea1b2..1fe48783 100644 --- a/cmake/CompilerWarnings.cmake +++ b/cmake/CompilerWarnings.cmake @@ -91,13 +91,12 @@ function( if(MSVC) set(PROJECT_WARNINGS_CXX ${MSVC_WARNINGS}) - elseif(CMAKE_CXX_COMPILER_ID MATCHES ".*Clang") + elseif(CMAKE_CXX_COMPILER_ID MATCHES ".*Clang" OR CMAKE_CXX_COMPILER_ID STREQUAL "IntelLLVM") set(PROJECT_WARNINGS_CXX ${CLANG_WARNINGS}) elseif(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") set(PROJECT_WARNINGS_CXX ${GCC_WARNINGS}) else() message(AUTHOR_WARNING "No compiler warnings set for CXX compiler: '${CMAKE_CXX_COMPILER_ID}'") - # TODO support Intel compiler endif() # use the same warning flags for C diff --git a/cmake/Emscripten.cmake b/cmake/Emscripten.cmake index cc59389b..a8e160cc 100644 --- a/cmake/Emscripten.cmake +++ b/cmake/Emscripten.cmake @@ -89,9 +89,9 @@ function(myproject_configure_wasm_target target) "-sUSE_PTHREADS=1" "-sPROXY_TO_PTHREAD=1" "-sPTHREAD_POOL_SIZE=${myproject_WASM_PTHREAD_POOL_SIZE}" - # Enable asyncify for emscripten_sleep and async operations - "-sASYNCIFY=1" - "-sASYNCIFY_STACK_SIZE=${myproject_WASM_ASYNCIFY_STACK_SIZE}" + # NOTE: Asyncify is disabled because it conflicts with pthreads + + # wasm-exceptions on Emscripten 3.1.x (wasm-opt UNREACHABLE crash). + # PROXY_TO_PTHREAD handles async operations instead. # Memory configuration "-sALLOW_MEMORY_GROWTH=1" "-sINITIAL_MEMORY=${myproject_WASM_INITIAL_MEMORY}" diff --git a/cmake/Hardening.cmake b/cmake/Hardening.cmake index 905fc453..6a3fd23e 100644 --- a/cmake/Hardening.cmake +++ b/cmake/Hardening.cmake @@ -13,7 +13,7 @@ macro( message(STATUS "*** MSVC flags: /sdl /DYNAMICBASE /guard:cf /NXCOMPAT /CETCOMPAT") list(APPEND NEW_LINK_OPTIONS /NXCOMPAT /CETCOMPAT) - elseif(CMAKE_CXX_COMPILER_ID MATCHES ".*Clang|GNU") + elseif(CMAKE_CXX_COMPILER_ID MATCHES ".*Clang|GNU" OR CMAKE_CXX_COMPILER_ID STREQUAL "IntelLLVM") list(APPEND NEW_CXX_DEFINITIONS -D_GLIBCXX_ASSERTIONS) message(STATUS "*** GLIBC++ Assertions (vector[], string[], ...) enabled") diff --git a/cmake/Sanitizers.cmake b/cmake/Sanitizers.cmake index 48760fb4..1a0856b0 100644 --- a/cmake/Sanitizers.cmake +++ b/cmake/Sanitizers.cmake @@ -7,7 +7,7 @@ function( ENABLE_SANITIZER_THREAD ENABLE_SANITIZER_MEMORY) - if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES ".*Clang") + if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES ".*Clang" OR CMAKE_CXX_COMPILER_ID STREQUAL "IntelLLVM") set(SANITIZERS "") if(${ENABLE_SANITIZER_ADDRESS}) @@ -30,7 +30,7 @@ function( endif() endif() - if(${ENABLE_SANITIZER_MEMORY} AND CMAKE_CXX_COMPILER_ID MATCHES ".*Clang") + if(${ENABLE_SANITIZER_MEMORY} AND (CMAKE_CXX_COMPILER_ID MATCHES ".*Clang" OR CMAKE_CXX_COMPILER_ID STREQUAL "IntelLLVM")) message( WARNING "Memory sanitizer requires all the code (including libc++) to be MSan-instrumented otherwise it reports false positives" diff --git a/cmake/StandardProjectSettings.cmake b/cmake/StandardProjectSettings.cmake index aa193008..16e629eb 100644 --- a/cmake/StandardProjectSettings.cmake +++ b/cmake/StandardProjectSettings.cmake @@ -18,7 +18,7 @@ endif() set(CMAKE_EXPORT_COMPILE_COMMANDS ON) # Enhance error reporting and compiler messages -if(CMAKE_CXX_COMPILER_ID MATCHES ".*Clang") +if(CMAKE_CXX_COMPILER_ID MATCHES ".*Clang" OR CMAKE_CXX_COMPILER_ID STREQUAL "IntelLLVM") add_compile_options($<$:-fcolor-diagnostics> $<$:-fcolor-diagnostics>) elseif(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") add_compile_options($<$:-fdiagnostics-color=always> diff --git a/cmake/StaticAnalyzers.cmake b/cmake/StaticAnalyzers.cmake index 1094655c..8e53882c 100644 --- a/cmake/StaticAnalyzers.cmake +++ b/cmake/StaticAnalyzers.cmake @@ -56,9 +56,9 @@ macro(myproject_enable_clang_tidy target WARNINGS_AS_ERRORS) find_program(CLANGTIDY clang-tidy) if(CLANGTIDY) if(NOT - CMAKE_CXX_COMPILER_ID + (CMAKE_CXX_COMPILER_ID MATCHES - ".*Clang") + ".*Clang" OR CMAKE_CXX_COMPILER_ID STREQUAL "IntelLLVM")) get_target_property(TARGET_PCH ${target} INTERFACE_PRECOMPILE_HEADERS) diff --git a/cmake/Tests.cmake b/cmake/Tests.cmake index 818af348..eabb01a5 100644 --- a/cmake/Tests.cmake +++ b/cmake/Tests.cmake @@ -1,5 +1,5 @@ function(myproject_enable_coverage project_name) - if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES ".*Clang") + if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES ".*Clang" OR CMAKE_CXX_COMPILER_ID STREQUAL "IntelLLVM") # GCC's --coverage flag doesn't work on macOS ARM (Apple linker # can't find libgcov). Skip coverage for GCC on Apple platforms. if(APPLE AND CMAKE_CXX_COMPILER_ID STREQUAL "GNU") From a299072ca785be2a1a85d48c0377f8c5fd9a982c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vin=C3=ADcius=20Ferr=C3=A3o?= <2031761+viniciusferrao@users.noreply.github.com> Date: Wed, 18 Mar 2026 12:03:40 -0300 Subject: [PATCH 08/10] feat(ci): move Linux CI to UBI 10 container images Replace ubuntu-latest + setup-cpp with UBI 10 container images for all Linux CI jobs, eliminating devcontainer/CI drift and enabling IWYU and Bloaty in CI. Intel coverage now works via llvm-cov from oneAPI. - Add Containerfile.intel extending main image with Intel oneAPI - Add build-ci-image.yml to build/push images to GHCR - Split ci.yml into Linux (container), macOS, and Windows jobs - Update CodeQL workflow to use container - Add gcovr, lizard, bloaty to main Containerfile Resolves #6 Co-Authored-By: Claude Opus 4.6 (1M context) --- .devcontainer/Containerfile | 13 +- .devcontainer/Containerfile.intel | 28 ++ .github/workflows/build-ci-image.yml | 86 +++++ .github/workflows/ci.yml | 472 ++++++++++++++++++-------- .github/workflows/codeql-analysis.yml | 39 +-- CI_KNOWN_ISSUES.md | 10 +- 6 files changed, 473 insertions(+), 175 deletions(-) create mode 100644 .devcontainer/Containerfile.intel create mode 100644 .github/workflows/build-ci-image.yml diff --git a/.devcontainer/Containerfile b/.devcontainer/Containerfile index 4358bafa..da680742 100644 --- a/.devcontainer/Containerfile +++ b/.devcontainer/Containerfile @@ -23,12 +23,23 @@ RUN dnf install -y \ # Static analysis and documentation RUN dnf install -y cppcheck doxygen graphviz +# Bloaty McBloatface — binary size analyzer (build from source) +ARG BLOATY_VERSION="v1.1" +RUN dnf install -y re2-devel protobuf-devel capstone-devel && \ + git clone --branch ${BLOATY_VERSION} --depth 1 \ + https://github.com/google/bloaty.git /tmp/bloaty && \ + cmake -S /tmp/bloaty -B /tmp/bloaty/build \ + -DCMAKE_BUILD_TYPE=Release -G Ninja && \ + cmake --build /tmp/bloaty/build -j && \ + cmake --install /tmp/bloaty/build && \ + rm -rf /tmp/bloaty + # Editors RUN dnf install -y neovim nano # Python packages RUN python3 -m pip install --upgrade pip setuptools && \ - python3 -m pip install conan && \ + python3 -m pip install conan gcovr lizard && \ conan --version # Conan configuration for containers diff --git a/.devcontainer/Containerfile.intel b/.devcontainer/Containerfile.intel new file mode 100644 index 00000000..12077a59 --- /dev/null +++ b/.devcontainer/Containerfile.intel @@ -0,0 +1,28 @@ +# Intel oneAPI CI image — extends the main CI image with ICX/ICPX +ARG BASE_IMAGE=ghcr.io/versatushpc/cmake_template/ci:latest +FROM ${BASE_IMAGE} + +# Intel oneAPI repository +RUN rpm --import https://yum.repos.intel.com/intel-gpg-keys/GPG-PUB-KEY-INTEL-SW-PRODUCTS.PUB && \ + cat > /etc/yum.repos.d/oneAPI.repo << 'EOF' +[oneAPI] +name=Intel oneAPI repository +baseurl=https://yum.repos.intel.com/oneapi +enabled=1 +gpgcheck=1 +gpgkey=https://yum.repos.intel.com/intel-gpg-keys/GPG-PUB-KEY-INTEL-SW-PRODUCTS.PUB +EOF + +# Install Intel oneAPI DPC++/C++ compiler +RUN dnf install -y intel-oneapi-compiler-dpcpp-cpp && \ + dnf clean all && rm -rf /var/cache/dnf + +# Make Intel compiler available without setvars.sh +# The compiler binaries and libraries are under /opt/intel/oneapi/compiler/latest +ENV PATH="/opt/intel/oneapi/compiler/latest/bin:${PATH}" \ + LD_LIBRARY_PATH="/opt/intel/oneapi/compiler/latest/lib:${LD_LIBRARY_PATH}" \ + CMAKE_PREFIX_PATH="/opt/intel/oneapi/compiler/latest:${CMAKE_PREFIX_PATH}" \ + CC="icx" \ + CXX="icpx" + +CMD ["/bin/bash"] diff --git a/.github/workflows/build-ci-image.yml b/.github/workflows/build-ci-image.yml new file mode 100644 index 00000000..16ebd6d7 --- /dev/null +++ b/.github/workflows/build-ci-image.yml @@ -0,0 +1,86 @@ +name: build-ci-image + +on: + push: + branches: [master, main] + paths: + - '.devcontainer/Containerfile' + - '.devcontainer/Containerfile.intel' + - '.github/workflows/build-ci-image.yml' + schedule: + # Weekly rebuild — picks up UBI 10 base image security updates + - cron: '0 6 * * 1' + workflow_dispatch: + +env: + REGISTRY: ghcr.io + +permissions: + contents: read + packages: write + +jobs: + build-ci-image: + name: CI image + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + + - name: Log in to GHCR + uses: docker/login-action@v3 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Image metadata + id: meta + uses: docker/metadata-action@v5 + with: + images: ${{ env.REGISTRY }}/${{ github.repository }}/ci + tags: | + type=raw,value=latest + type=sha,prefix=sha-,format=short + + - name: Build and push CI image + uses: docker/build-push-action@v6 + with: + context: .devcontainer + file: .devcontainer/Containerfile + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + + build-ci-intel-image: + name: CI Intel image + runs-on: ubuntu-latest + needs: build-ci-image + steps: + - uses: actions/checkout@v6 + + - name: Log in to GHCR + uses: docker/login-action@v3 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Image metadata + id: meta + uses: docker/metadata-action@v5 + with: + images: ${{ env.REGISTRY }}/${{ github.repository }}/ci-intel + tags: | + type=raw,value=latest + type=sha,prefix=sha-,format=short + + - name: Build and push Intel CI image + uses: docker/build-push-action@v6 + with: + context: .devcontainer + file: .devcontainer/Containerfile.intel + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + build-args: | + BASE_IMAGE=${{ env.REGISTRY }}/${{ github.repository }}/ci:latest diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f4fa4499..c8338c95 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -16,119 +16,209 @@ env: jobs: - Test: - name: ${{matrix.os}} ${{matrix.compiler}} ${{matrix.build_type}} ${{matrix.packaging_maintainer_mode == 'ON' && '(maintainer mode)' || ''}} - runs-on: ${{ matrix.os }} + # --------------------------------------------------------------------------- + # Compute lowercase container image names (GHCR requires lowercase) + # --------------------------------------------------------------------------- + setup: + name: Setup + runs-on: ubuntu-latest + outputs: + ci-image: ${{ steps.images.outputs.ci }} + ci-intel-image: ${{ steps.images.outputs.ci-intel }} + steps: + - id: images + run: | + REPO="${{ github.repository }}" + REPO_LC="${REPO,,}" + echo "ci=ghcr.io/${REPO_LC}/ci:latest" >> $GITHUB_OUTPUT + echo "ci-intel=ghcr.io/${REPO_LC}/ci-intel:latest" >> $GITHUB_OUTPUT + + # --------------------------------------------------------------------------- + # Linux — UBI 10 container, all tools pre-installed + # --------------------------------------------------------------------------- + Linux: + name: Linux ${{ matrix.compiler }} ${{ matrix.build_type }} ${{ matrix.packaging_maintainer_mode == 'ON' && '(maintainer)' || '' }} ${{ matrix.generator == 'Unix Makefiles' && '(Make)' || '' }} + needs: setup + runs-on: ubuntu-latest + container: + image: ${{ (matrix.compiler == 'intel' && needs.setup.outputs.ci-intel-image) || needs.setup.outputs.ci-image }} strategy: fail-fast: false - - # Recommendations: - # * support at least 2 operating systems - # * support at least 2 compilers - # * make sure all supported configurations for your project are built - # - # Disable/enable builds in this list to meet the above recommendations - # and your own projects needs matrix: - os: - - ubuntu-latest - - macos-latest - - windows-latest compiler: - # you can specify the version after `-` like "llvm-15.0.2". - - llvm-19.1.1 - gcc-14 + - gcc-15 + - clang-19 - intel - generator: - - "Ninja Multi-Config" build_type: - Release - Debug packaging_maintainer_mode: - ON - OFF - build_shared: - - OFF - - exclude: - # mingw is determined by this author to be too buggy to support - - os: windows-latest - compiler: gcc-14 - - # Intel oneAPI is only available on Linux - - os: windows-latest - compiler: intel - - os: macos-latest - compiler: intel include: - # Add appropriate variables for gcov version required. This will intentionally break - # if you try to use a compiler that does not have gcov set + # Per-compiler settings - compiler: gcc-14 - gcov_executable: gcov-14 + cc: gcc + cxx: g++ + gcov_executable: gcov enable_ipo: On - - compiler: llvm-19.1.1 - enable_ipo: Off - gcov_executable: "llvm-cov gcov" + - compiler: gcc-15 + cc: gcc + cxx: g++ + gcov_executable: gcov + enable_ipo: On - - compiler: intel + - compiler: clang-19 + cc: clang + cxx: clang++ + gcov_executable: "llvm-cov gcov" enable_ipo: Off - gcov_executable: gcov - - os: macos-latest + - compiler: intel + cc: icx + cxx: icpx + gcov_executable: "llvm-cov gcov" enable_ipo: Off - # Set up preferred package generators, for given build configurations + # Release packaging - build_type: Release - packaging_maintainer_mode: On + packaging_maintainer_mode: ON package_generator: TBZ2 - # This exists solely to make sure a non-multiconfig build works - - os: ubuntu-latest - compiler: gcc-14 + # Non-multiconfig build (verifies Unix Makefiles still work) + - compiler: gcc-14 + cc: gcc + cxx: g++ generator: "Unix Makefiles" build_type: Debug - gcov_executable: gcov-14 - packaging_maintainer_mode: On + gcov_executable: gcov + packaging_maintainer_mode: ON enable_ipo: Off - # Windows msvc builds - - os: windows-latest - compiler: msvc - generator: "Visual Studio 17 2022" - build_type: Debug - packaging_maintainer_mode: On - enable_ipo: On + steps: + - uses: actions/checkout@v6 - - os: windows-latest - compiler: msvc - generator: "Visual Studio 17 2022" - build_type: Release - packaging_maintainer_mode: On - enable_ipo: On + - name: Setup Cache + uses: ./.github/actions/setup_cache + with: + compiler: ${{ matrix.compiler }} + build_type: ${{ matrix.build_type }} + packaging_maintainer_mode: ${{ matrix.packaging_maintainer_mode }} + generator: ${{ matrix.generator || 'Ninja Multi-Config' }} - - os: windows-latest - compiler: msvc - generator: "Visual Studio 17 2022" - build_type: Debug - packaging_maintainer_mode: Off + - name: Project Name + uses: cardinalby/export-env-action@v2 + with: + envFile: '.github/constants.env' - - os: windows-latest - compiler: msvc - generator: "Visual Studio 17 2022" - build_type: Release - packaging_maintainer_mode: Off - package_generator: ZIP + - name: Enable GCC 15 toolset + if: matrix.compiler == 'gcc-15' + run: | + source /opt/rh/gcc-toolset-15/enable + echo "PATH=$PATH" >> $GITHUB_ENV + echo "LD_LIBRARY_PATH=${LD_LIBRARY_PATH:-}" >> $GITHUB_ENV - - os: windows-latest - compiler: msvc - generator: "Visual Studio 17 2022" - build_type: Release - packaging_maintainer_mode: On - enable_ipo: On - build_shared: On + - name: Set compiler environment + run: | + echo "CC=${{ matrix.cc }}" >> $GITHUB_ENV + echo "CXX=${{ matrix.cxx }}" >> $GITHUB_ENV + + - name: Configure Conan for Intel ICX + if: matrix.compiler == 'intel' + run: | + ICX_CLANG_VER=$(icx -dM -E -x c /dev/null 2>/dev/null | grep __clang_major__ | awk '{print $3}') + echo "ICX underlying Clang version: $ICX_CLANG_VER" + CC=clang CXX=clang++ conan profile detect --force + sed -i "s/compiler.version=.*/compiler.version=$ICX_CLANG_VER/" ~/.conan2/profiles/default + sed -i "s/compiler.cppstd=.*/compiler.cppstd=23/" ~/.conan2/profiles/default + cat ~/.conan2/profiles/default + conan install . --output-folder=build --build=missing -s build_type=Debug + conan install . --output-folder=build --build=missing -s build_type=Release + conan install . --output-folder=build --build=missing -s build_type=RelWithDebInfo + + - name: Configure CMake + run: | + GENERATOR="${{ matrix.generator || 'Ninja Multi-Config' }}" + EXTRA_CMAKE_ARGS="" + if [[ "${{ matrix.compiler }}" == "intel" ]]; then + EXTRA_CMAKE_ARGS="-D${{ env.PROJECT_NAME }}_SKIP_CONAN_PROVIDER=ON -DCMAKE_PREFIX_PATH=$PWD/build" + fi + cmake -S . -B ./build -G "$GENERATOR" \ + $EXTRA_CMAKE_ARGS \ + -D${{ env.PROJECT_NAME }}_ENABLE_IPO=${{ matrix.enable_ipo }} \ + -DCMAKE_BUILD_TYPE:STRING=${{ matrix.build_type }} \ + -D${{ env.PROJECT_NAME }}_PACKAGING_MAINTAINER_MODE:BOOL=${{ matrix.packaging_maintainer_mode }} \ + -D${{ env.PROJECT_NAME }}_ENABLE_COVERAGE:BOOL=${{ matrix.build_type == 'Debug' }} \ + -D${{ env.PROJECT_NAME }}_ENABLE_BLOATY:BOOL=ON \ + -D${{ env.PROJECT_NAME }}_ENABLE_INCLUDE_WHAT_YOU_USE:BOOL=ON \ + -DGIT_SHA:STRING=${{ github.sha }} + + - name: Build + run: cmake --build ./build --config ${{ matrix.build_type }} + + - name: Test and coverage + working-directory: ./build + run: | + ctest -C ${{ matrix.build_type }} + gcovr -j $(nproc) --root ../ --print-summary --xml-pretty --xml coverage.xml . --gcov-executable '${{ matrix.gcov_executable }}' + + - name: CPack + if: matrix.package_generator != '' + working-directory: ./build + run: | + cpack -C ${{ matrix.build_type }} -G ${{ matrix.package_generator }} + + - name: Publish Tagged Release + uses: softprops/action-gh-release@v2 + if: ${{ startsWith(github.ref, 'refs/tags/') && matrix.package_generator != '' }} + with: + files: | + build/*-*${{ matrix.build_type }}*-*.* + + - name: Publish to codecov + uses: codecov/codecov-action@v5 + with: + fail_ci_if_error: true + flags: ${{ runner.os }} + name: ${{ runner.os }}-coverage + token: ${{ secrets.CODECOV_TOKEN }} + files: ./build/coverage.xml + # --------------------------------------------------------------------------- + # macOS — bare runner with setup-cpp + # --------------------------------------------------------------------------- + macOS: + name: macOS ${{ matrix.compiler }} ${{ matrix.build_type }} ${{ matrix.packaging_maintainer_mode == 'ON' && '(maintainer)' || '' }} + runs-on: macos-latest + strategy: + fail-fast: false + matrix: + compiler: + - llvm-19.1.1 + - gcc-14 + build_type: + - Release + - Debug + packaging_maintainer_mode: + - ON + - OFF + + include: + - compiler: llvm-19.1.1 + gcov_executable: "llvm-cov gcov" + enable_ipo: Off + + - compiler: gcc-14 + gcov_executable: gcov-14 + enable_ipo: Off + + # Release packaging + - build_type: Release + packaging_maintainer_mode: ON + package_generator: TBZ2 steps: - name: Check for llvm version mismatches @@ -146,72 +236,31 @@ jobs: compiler: ${{ matrix.compiler }} build_type: ${{ matrix.build_type }} packaging_maintainer_mode: ${{ matrix.packaging_maintainer_mode }} - generator: ${{ matrix.generator }} + generator: "Ninja Multi-Config" - name: Project Name uses: cardinalby/export-env-action@v2 with: envFile: '.github/constants.env' - - name: Setup Cpp - if: ${{ matrix.compiler != 'intel' }} uses: aminya/setup-cpp@v1 with: compiler: ${{ matrix.compiler }} - vcvarsall: ${{ contains(matrix.os, 'windows' )}} - cmake: true ninja: true vcpkg: false ccache: true clangtidy: ${{ env.CLANG_TIDY_VERSION }} - - cppcheck: true - gcovr: true - opencppcoverage: true - - - name: Install Intel oneAPI - if: ${{ matrix.compiler == 'intel' }} - run: | - wget -qO- https://apt.repos.intel.com/intel-gpg-keys/GPG-PUB-KEY-INTEL-SW-PRODUCTS.PUB | sudo gpg --dearmor -o /usr/share/keyrings/oneapi-archive-keyring.gpg - echo "deb [signed-by=/usr/share/keyrings/oneapi-archive-keyring.gpg] https://apt.repos.intel.com/oneapi all main" | sudo tee /etc/apt/sources.list.d/oneAPI.list - sudo apt-get update - sudo apt-get install -y intel-oneapi-compiler-dpcpp-cpp cmake ninja-build ccache cppcheck gcovr - - - name: Configure Intel oneAPI environment - if: ${{ matrix.compiler == 'intel' }} - run: | - echo "/opt/intel/oneapi/compiler/latest/bin" >> $GITHUB_PATH - echo "LD_LIBRARY_PATH=/opt/intel/oneapi/compiler/latest/lib:$LD_LIBRARY_PATH" >> $GITHUB_ENV - echo "CC=icx" >> $GITHUB_ENV - echo "CXX=icpx" >> $GITHUB_ENV - name: Install Conan and Lizard run: pip install conan lizard - - name: Configure Conan for Intel ICX - if: ${{ matrix.compiler == 'intel' }} - run: | - ICX=/opt/intel/oneapi/compiler/latest/bin/icx - ICX_CLANG_VER=$($ICX -dM -E -x c /dev/null 2>/dev/null | grep __clang_major__ | awk '{print $3}') - echo "ICX underlying Clang version: $ICX_CLANG_VER" - CC=clang CXX=clang++ conan profile detect --force - sed -i "s/compiler.version=.*/compiler.version=$ICX_CLANG_VER/" ~/.conan2/profiles/default - sed -i "s/compiler.cppstd=.*/compiler.cppstd=23/" ~/.conan2/profiles/default - cat ~/.conan2/profiles/default - conan install . --output-folder=build --build=missing -s build_type=Debug - conan install . --output-folder=build --build=missing -s build_type=Release - conan install . --output-folder=build --build=missing -s build_type=RelWithDebInfo - - name: Pre-install Conan deps for macOS GCC - if: ${{ runner.os == 'macOS' && contains(matrix.compiler, 'gcc') }} + if: ${{ contains(matrix.compiler, 'gcc') }} run: | - # GCC on macOS uses libstdc++, but the cmake-conan provider - # auto-detects Apple Clang and builds packages with libc++. - # Pre-install deps with a GCC profile and skip the provider. conan profile detect --force GCC_VER=$(echo "${{ matrix.compiler }}" | grep -o '[0-9]*') cat > ~/.conan2/profiles/default << PROFILE @@ -232,47 +281,178 @@ jobs: conan install . --output-folder=build --build=missing -s build_type=RelWithDebInfo - name: Configure CMake - shell: bash run: | EXTRA_CMAKE_ARGS="" - # macOS GCC: deps pre-installed with libstdc++11 profile - if [[ "${{ matrix.compiler }}" == "intel" ]]; then - EXTRA_CMAKE_ARGS="-D${{ env.PROJECT_NAME }}_SKIP_CONAN_PROVIDER=ON -DCMAKE_PREFIX_PATH=$PWD/build" - fi - # macOS GCC: deps pre-installed with libstdc++11 profile - if [[ "${{ runner.os }}" == "macOS" && "${{ matrix.compiler }}" =~ gcc ]]; then + if [[ "${{ matrix.compiler }}" =~ gcc ]]; then EXTRA_CMAKE_ARGS="-D${{ env.PROJECT_NAME }}_SKIP_CONAN_PROVIDER=ON -DCMAKE_PREFIX_PATH=$PWD/build" fi - cmake -S . -B ./build -G "${{matrix.generator}}" $EXTRA_CMAKE_ARGS -D${{ env.PROJECT_NAME }}_ENABLE_IPO=${{matrix.enable_ipo }} -DCMAKE_BUILD_TYPE:STRING=${{matrix.build_type}} -D${{ env.PROJECT_NAME }}_PACKAGING_MAINTAINER_MODE:BOOL=${{matrix.packaging_maintainer_mode}} -D${{ env.PROJECT_NAME }}_ENABLE_COVERAGE:BOOL=${{ matrix.build_type == 'Debug' }} -D${{ env.PROJECT_NAME }}_ENABLE_BLOATY:BOOL=OFF -D${{ env.PROJECT_NAME }}_ENABLE_INCLUDE_WHAT_YOU_USE:BOOL=OFF -DGIT_SHA:STRING=${{ github.sha }} + cmake -S . -B ./build -G "Ninja Multi-Config" \ + $EXTRA_CMAKE_ARGS \ + -D${{ env.PROJECT_NAME }}_ENABLE_IPO=${{ matrix.enable_ipo }} \ + -DCMAKE_BUILD_TYPE:STRING=${{ matrix.build_type }} \ + -D${{ env.PROJECT_NAME }}_PACKAGING_MAINTAINER_MODE:BOOL=${{ matrix.packaging_maintainer_mode }} \ + -D${{ env.PROJECT_NAME }}_ENABLE_COVERAGE:BOOL=${{ matrix.build_type == 'Debug' }} \ + -D${{ env.PROJECT_NAME }}_ENABLE_BLOATY:BOOL=OFF \ + -D${{ env.PROJECT_NAME }}_ENABLE_INCLUDE_WHAT_YOU_USE:BOOL=OFF \ + -DGIT_SHA:STRING=${{ github.sha }} - name: Build - # Execute the build. You can specify a specific target with "--target " - run: | - cmake --build ./build --config ${{matrix.build_type}} + run: cmake --build ./build --config ${{ matrix.build_type }} - - name: Unix - Test and coverage - if: runner.os != 'Windows' + - name: Test and coverage working-directory: ./build - # Execute tests defined by the CMake configuration. - # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail run: | - ctest -C ${{matrix.build_type}} - # Intel ICX uses a different coverage format incompatible with gcov - if [[ "${{ matrix.compiler }}" != "intel" ]]; then - gcovr -j ${{env.nproc}} --root ../ --print-summary --xml-pretty --xml coverage.xml . --gcov-executable '${{ matrix.gcov_executable }}' + ctest -C ${{ matrix.build_type }} + # macOS GCC coverage broken — libgcov link failure (issue #5) + if [[ ! "${{ matrix.compiler }}" =~ gcc ]]; then + gcovr -j $(sysctl -n hw.ncpu) --root ../ --print-summary --xml-pretty --xml coverage.xml . --gcov-executable '${{ matrix.gcov_executable }}' fi - - name: Windows - Test and coverage - if: runner.os == 'Windows' + - name: CPack + if: matrix.package_generator != '' + working-directory: ./build + run: | + cpack -C ${{ matrix.build_type }} -G ${{ matrix.package_generator }} + + - name: Publish Tagged Release + uses: softprops/action-gh-release@v2 + if: ${{ startsWith(github.ref, 'refs/tags/') && matrix.package_generator != '' }} + with: + files: | + build/*-*${{ matrix.build_type }}*-*.* + + - name: Publish to codecov + if: ${{ !contains(matrix.compiler, 'gcc') }} + uses: codecov/codecov-action@v5 + with: + fail_ci_if_error: true + flags: ${{ runner.os }} + name: ${{ runner.os }}-coverage + token: ${{ secrets.CODECOV_TOKEN }} + files: ./build/coverage.xml + + # --------------------------------------------------------------------------- + # Windows — bare runner with setup-cpp + # --------------------------------------------------------------------------- + Windows: + name: Windows ${{ matrix.compiler }} ${{ matrix.build_type }} ${{ matrix.packaging_maintainer_mode == 'ON' && '(maintainer)' || '' }} + runs-on: windows-latest + strategy: + fail-fast: false + matrix: + include: + # MSVC builds + - compiler: msvc + generator: "Visual Studio 17 2022" + build_type: Debug + packaging_maintainer_mode: ON + enable_ipo: On + + - compiler: msvc + generator: "Visual Studio 17 2022" + build_type: Release + packaging_maintainer_mode: ON + enable_ipo: On + + - compiler: msvc + generator: "Visual Studio 17 2022" + build_type: Debug + packaging_maintainer_mode: OFF + + - compiler: msvc + generator: "Visual Studio 17 2022" + build_type: Release + packaging_maintainer_mode: OFF + package_generator: ZIP + + - compiler: msvc + generator: "Visual Studio 17 2022" + build_type: Release + packaging_maintainer_mode: ON + enable_ipo: On + build_shared: ON + + # Clang builds (clang-cl via LLVM) + - compiler: llvm-19.1.1 + generator: "Ninja Multi-Config" + build_type: Debug + packaging_maintainer_mode: ON + enable_ipo: Off + + - compiler: llvm-19.1.1 + generator: "Ninja Multi-Config" + build_type: Release + packaging_maintainer_mode: ON + enable_ipo: Off + + - compiler: llvm-19.1.1 + generator: "Ninja Multi-Config" + build_type: Debug + packaging_maintainer_mode: OFF + enable_ipo: Off + + - compiler: llvm-19.1.1 + generator: "Ninja Multi-Config" + build_type: Release + packaging_maintainer_mode: OFF + enable_ipo: Off + + steps: + - name: Check for llvm version mismatches + if: ${{ contains(matrix.compiler, 'llvm') && !contains(matrix.compiler, env.CLANG_TIDY_VERSION) }} + uses: actions/github-script@v8 + with: + script: | + core.setFailed('There is a mismatch between configured llvm compiler and clang-tidy version chosen') + + - uses: actions/checkout@v6 + + - name: Setup Cache + uses: ./.github/actions/setup_cache + with: + compiler: ${{ matrix.compiler }} + build_type: ${{ matrix.build_type }} + packaging_maintainer_mode: ${{ matrix.packaging_maintainer_mode }} + generator: ${{ matrix.generator }} + + - name: Project Name + uses: cardinalby/export-env-action@v2 + with: + envFile: '.github/constants.env' + + - name: Setup Cpp + uses: aminya/setup-cpp@v1 + with: + compiler: ${{ matrix.compiler }} + vcvarsall: true + cmake: true + ninja: true + vcpkg: false + ccache: true + clangtidy: ${{ env.CLANG_TIDY_VERSION }} + cppcheck: true + opencppcoverage: true + + - name: Install Conan and Lizard + run: pip install conan lizard + + - name: Configure CMake + run: | + cmake -S . -B ./build -G "${{ matrix.generator }}" -D${{ env.PROJECT_NAME }}_ENABLE_IPO=${{ matrix.enable_ipo }} -DCMAKE_BUILD_TYPE:STRING=${{ matrix.build_type }} -D${{ env.PROJECT_NAME }}_PACKAGING_MAINTAINER_MODE:BOOL=${{ matrix.packaging_maintainer_mode }} -D${{ env.PROJECT_NAME }}_ENABLE_COVERAGE:BOOL=${{ matrix.build_type == 'Debug' }} -D${{ env.PROJECT_NAME }}_ENABLE_BLOATY:BOOL=OFF -D${{ env.PROJECT_NAME }}_ENABLE_INCLUDE_WHAT_YOU_USE:BOOL=OFF -DGIT_SHA:STRING=${{ github.sha }} + + - name: Build + run: cmake --build ./build --config ${{ matrix.build_type }} + + - name: Test and coverage working-directory: ./build run: | - OpenCppCoverage.exe --export_type cobertura:coverage.xml --cover_children -- ctest -C ${{matrix.build_type}} + OpenCppCoverage.exe --export_type cobertura:coverage.xml --cover_children -- ctest -C ${{ matrix.build_type }} - name: CPack if: matrix.package_generator != '' working-directory: ./build run: | - cpack -C ${{matrix.build_type}} -G ${{matrix.package_generator}} + cpack -C ${{ matrix.build_type }} -G ${{ matrix.package_generator }} - name: Publish Tagged Release uses: softprops/action-gh-release@v2 @@ -281,9 +461,7 @@ jobs: files: | build/*-*${{ matrix.build_type }}*-*.* - - name: Publish to codecov - if: ${{ matrix.compiler != 'intel' }} uses: codecov/codecov-action@v5 with: fail_ci_if_error: true diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index ba4af408..9909dbc3 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -22,9 +22,24 @@ on: jobs: + setup: + name: Setup + runs-on: ubuntu-latest + outputs: + ci-image: ${{ steps.images.outputs.ci }} + steps: + - id: images + run: | + REPO="${{ github.repository }}" + REPO_LC="${REPO,,}" + echo "ci=ghcr.io/${REPO_LC}/ci:latest" >> $GITHUB_OUTPUT + analyze: name: Analyze + needs: setup runs-on: ubuntu-latest + container: + image: ${{ needs.setup.outputs.ci-image }} permissions: actions: read contents: read @@ -34,10 +49,7 @@ jobs: fail-fast: false matrix: language: [ 'cpp' ] - # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] - # Learn more about CodeQL language support at https://git.io/codeql-language-support compiler: - # you can specify the version after `-` like "llvm-13.0.0". - gcc-14 generator: - "Ninja Multi-Config" @@ -63,27 +75,6 @@ jobs: with: envFile: '.github/constants.env' - - - name: Setup Cpp - uses: aminya/setup-cpp@v1 - with: - compiler: ${{ matrix.compiler }} - vcvarsall: ${{ contains(matrix.os, 'windows' )}} - - cmake: true - ninja: true - vcpkg: false - ccache: true - clangtidy: false - - cppcheck: false - - gcovr: false - opencppcoverage: false - - - name: Install Conan and Lizard - run: pip install conan lizard - - name: Configure CMake run: | cmake -S . -B ./build -G "${{matrix.generator}}" -DCMAKE_BUILD_TYPE:STRING=${{matrix.build_type}} -D${{ env.PROJECT_NAME }}_PACKAGING_MAINTAINER_MODE:BOOL=${{matrix.packaging_maintainer_mode}} -D${{ env.PROJECT_NAME }}_ENABLE_COVERAGE:BOOL=${{ matrix.build_type == 'Debug' }} -D${{ env.PROJECT_NAME }}_ENABLE_BLOATY:BOOL=OFF -D${{ env.PROJECT_NAME }}_ENABLE_INCLUDE_WHAT_YOU_USE:BOOL=OFF diff --git a/CI_KNOWN_ISSUES.md b/CI_KNOWN_ISSUES.md index 5727dd93..71061279 100644 --- a/CI_KNOWN_ISSUES.md +++ b/CI_KNOWN_ISSUES.md @@ -13,6 +13,13 @@ Tracked issues with links to GitHub issues for follow-up. - **codecov**: Set to `fail_ci_if_error: true`. Derived repos must configure their own `CODECOV_TOKEN` repository secret from [codecov.io](https://codecov.io). +- **Intel ICX coverage** ([#6](https://github.com/VersatusHPC/cmake_template/issues/6)): + Resolved by moving Linux CI to UBI 10 containers. The Intel + container includes `llvm-cov` from oneAPI. Use `llvm-cov gcov` + as the gcov executable for ICX builds. +- **IWYU / Bloaty in CI**: Previously disabled because `ubuntu-latest` + runners lacked these tools. Now pre-installed in the UBI 10 CI + container and enabled for all Linux jobs. ## Open @@ -22,6 +29,3 @@ Tracked issues with links to GitHub issues for follow-up. doesn't support emcc 23+). - **macOS GCC coverage** ([#5](https://github.com/VersatusHPC/cmake_template/issues/5)): Apple ARM linker can't find libgcov. Tests pass, coverage skipped. -- **Intel ICX coverage** ([#6](https://github.com/VersatusHPC/cmake_template/issues/6)): - gcov incompatible with ICX coverage format. Tests pass, coverage - skipped. From 3a0b4f0bc45727d0bd270c568bc23d004bfb8de7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vin=C3=ADcius=20Ferr=C3=A3o?= <2031761+viniciusferrao@users.noreply.github.com> Date: Wed, 18 Mar 2026 12:07:02 -0300 Subject: [PATCH 09/10] chore(ci): temporarily trigger image build on feature branch Adds the feature branch to build-ci-image.yml push triggers so container images get built for testing. Will be removed before merge. Co-Authored-By: Claude Opus 4.6 (1M context) --- .github/workflows/build-ci-image.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-ci-image.yml b/.github/workflows/build-ci-image.yml index 16ebd6d7..b5a2076a 100644 --- a/.github/workflows/build-ci-image.yml +++ b/.github/workflows/build-ci-image.yml @@ -2,7 +2,7 @@ name: build-ci-image on: push: - branches: [master, main] + branches: [master, main, feat/ci-ubi10-container] # TODO: remove feature branch after merge paths: - '.devcontainer/Containerfile' - '.devcontainer/Containerfile.intel' From 0e7481db5c028442c1b59042ddcaee980613d19c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vin=C3=ADcius=20Ferr=C3=A3o?= <2031761+viniciusferrao@users.noreply.github.com> Date: Wed, 18 Mar 2026 12:09:15 -0300 Subject: [PATCH 10/10] chore(ci): add pull_request trigger to image build workflow New workflow files don't trigger push events until they exist on the default branch. Add pull_request trigger with paths filter so images get built during this PR. Co-Authored-By: Claude Opus 4.6 (1M context) --- .github/workflows/build-ci-image.yml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build-ci-image.yml b/.github/workflows/build-ci-image.yml index b5a2076a..30ae0be0 100644 --- a/.github/workflows/build-ci-image.yml +++ b/.github/workflows/build-ci-image.yml @@ -2,7 +2,12 @@ name: build-ci-image on: push: - branches: [master, main, feat/ci-ubi10-container] # TODO: remove feature branch after merge + branches: [master, main] + paths: + - '.devcontainer/Containerfile' + - '.devcontainer/Containerfile.intel' + - '.github/workflows/build-ci-image.yml' + pull_request: paths: - '.devcontainer/Containerfile' - '.devcontainer/Containerfile.intel'