diff --git a/AI_GUIDELINES.md b/AI_GUIDELINES.md new file mode 100644 index 00000000..88d8911c --- /dev/null +++ b/AI_GUIDELINES.md @@ -0,0 +1,309 @@ +# AI Agent Guidelines + +This document provides guidance for AI agents working with this C++ project template. These guidelines are designed to be useful for any AI assistant, not specific to any particular system. + +**Important**: AI agents should also review the `HUMAN_GUIDELINES.md` file, which contains: +- The project's purpose and philosophy +- Information about the strict static analysis approach +- Reference to C++23 Best Practices book by Jason Turner +- Additional guidelines that both humans and AIs should follow + +AI agents should align their suggestions and implementations with the best practices and project philosophy outlined in that document. + +## Project Overview + +This is a C++ project template that enforces best practices through tooling. It is designed to help you quickly set up a new C++ project with: + +- Modern CMake (3.21+) with C++23 support +- Comprehensive warning configurations +- Sanitizers (Address, Undefined Behavior) +- Static analysis (clang-tidy, cppcheck) +- Dependency management with CPM +- Testing framework support (unit, constexpr, fuzz) +- CI integration + +## Project Structure + +- `CMakeLists.txt` - Main CMake configuration file +- `ProjectOptions.cmake` - Project-wide CMake options +- `Dependencies.cmake` - External dependency management +- `/cmake` - CMake modules and utilities +- `/configured_files` - Templates for generated files +- `/include` - Public header files +- `/src` - Source code files +- `/test` - Test files +- `/fuzz_test` - Fuzzing test files + +## Common Tasks + +### 1. Adding a New Component + +When adding a new component: + +1. Check existing components for naming conventions and structure +2. Follow the established pattern for CMakeLists.txt configuration +3. Use the project's existing target structure and namespacing +4. Ensure new code follows the project's warning and style guidelines + +### 2. Working with CMake + +- Use modern CMake practices (target-based approach) +- Set properties at the target level, not globally when possible +- Use `myproject::` namespace for targets +- Follow the established pattern for adding libraries and executables + +### 3. Managing Dependencies + +- External dependencies should be added to `Dependencies.cmake` +- Use CPM for dependency management when possible +- Always specify versions for dependencies +- Consider vendoring small dependencies that don't change often + +### 4. Testing + +The project has three primary test targets, each with specific purposes: + +1. **constexpr_tests**: + - Tests are compiled as static assertions (`STATIC_REQUIRE`) + - If the project compiles, we know the code passes these tests + - Errors are detected at compile-time + - Located in `/test/constexpr_tests.cpp` + +2. **relaxed_constexpr_tests**: + - Same tests as constexpr_tests but as runtime assertions + - Compiled with `-DCATCH_CONFIG_RUNTIME_STATIC_REQUIRE` + - When adding new tests, compile and run this target first + - Allows for debugging test failures that would otherwise be compile errors + - After tests pass in this target, they should pass in constexpr_tests + +3. **tests**: + - Contains tests that cannot be made constexpr + - Uses runtime assertions (`REQUIRE`) + - Use for tests involving I/O, runtime-only features, etc. + - Located in `/test/tests.cpp` + +**Workflow for Adding Tests:** +1. Start by adding tests to `relaxed_constexpr_tests` if they can be constexpr +2. Debug and fix any issues +3. Once passing, ensure they compile in `constexpr_tests` +4. For non-constexpr functionality, add tests to `tests.cpp` + +The project also supports fuzz testing for code that handles external inputs, located in `/fuzz_test`. + + +* ALWAYS prefer test-driven development +* ALWAYS write tests for new code +* NEVER filter tests when running test binaries - you need to know if the feature broke other tests! + +### 5. Code Coverage + +The project supports code coverage reporting using gcovr. When working with this project: + +1. **Enabling Coverage:** + - Configure with `-D_ENABLE_COVERAGE=ON` + - Note: After template instantiation, the variable prefix will change from `myproject_` to your specific project name (e.g., `fizzbuzz_ENABLE_COVERAGE=ON`) + +2. **Running Coverage:** + - Build and run the tests + - Use gcovr to generate reports: `gcovr -r . --config=gcovr.cfg` + - The HTML report will be generated at `./out/coverage/index.html` + - XML report (Cobertura format) at `out/cobertura.xml` + +3. **Coverage Goals:** + - Always work toward increasing coverage percentages + - Focus on branch coverage in addition to line coverage + - Use gcovr's branch coverage reporting (`--branch-coverage`) for detailed analysis + - Prioritize covering error handling paths and edge cases + +4. **Configuration:** + - Coverage settings are in `gcovr.cfg` + - Excludes test directories and dependencies + - Focuses on your project's source and include directories + +## Best Practices + +### Following Project Standards + +1. **Warnings**: Do not disable warnings. Fix the underlying issues instead. +2. **Sanitizers**: Do not disable sanitizers. Fix the detected problems. +3. **Static Analysis**: Address issues reported by clang-tidy and cppcheck. +4. **Code Complexity**: Keep cyclomatic complexity and function length within Lizard's limits. +5. **Modern C++**: Use modern C++ features instead of legacy approaches. +6. **Type Safety**: Prefer strong types over primitive types (e.g., create a `Velocity` type rather than using plain `int`). +7. **Compile-time Evaluation**: Mark functions as `constexpr` by default, especially in C++23 mode. +8. **Resource Management**: Follow the Rule of 0; avoid manual resource management. + +### Missing Tools + +This project is designed to fail configuration if required tools are missing. This is intentional and acts as a guardrail. + +1. **NEVER disable tools**: If build configuration fails due to missing tools, do not suggest disabling the tools +2. **ALWAYS suggest installing missing tools**: Provide instructions for installing the required tools +3. **Example missing tool message**: If you see "clang-tidy requested but executable not found", recommend installing clang-tidy, not disabling it +4. **Common required tools**: + - clang-tidy: C++ static analyzer + - cppcheck: C++ static analyzer + - lizard: Code complexity analyzer + - ccache: Compilation cache tool + - include-what-you-use: Header dependency analyzer + - bloaty: Binary size analyzer (optional, disabled by default) + +Remember, the project's guardrails exist for a reason. Help the user install and use the required tools rather than circumventing them. + +### Code Conventions + +1. Follow the existing code style for consistency +2. Use strong typing and avoid C-style casts +3. Prefer compile-time safety when possible +4. Make correct use of const-correctness +5. **Always recommend running clang-format** on modified files: + ```bash + clang-format -i path/to/changed/files/*.cpp path/to/changed/files/*.hpp + ``` + - clang-format is the source of truth for formatting + - This reduces friction in code reviews and CI pipeline failures + - Let clang-format handle all style decisions rather than manual formatting + +### Modern C++ Coding Guidelines + +1. **Format Strings**: + - Use `std::format` and `std::print` instead of iostream or printf + - NEVER use printf (this will not pass static analysis) +2. **Memory Management**: + - No raw `new`/`delete` operations + - Prefer stack allocation, then std::vector/array + - Use smart pointers if heap allocation is necessary + - ALWAYS prefer unique_ptr over shared_ptr +3. **Algorithms over Loops**: + - Use standard algorithms and ranges instead of raw loops when possible + - Use ranged-for with `auto` when algorithms aren't suitable +4. **Function Design**: + - Mark functions that return values as `[[nodiscard]]` + - Use concepts to constrain template parameters + - Return by value for small objects, avoid returning raw pointers +5. **Container Selection**: + - Use `std::array` when size is known at compile time + - Default to `std::vector` for dynamic containers + - Select other containers only when their specific properties are needed +6. **Control Flow**: + - Make case statements return values, avoid default in switch statements + - Use scoped enums instead of unscoped enums + - Use `if constexpr` for compile-time conditions + +### Building and Testing + +1. Before making significant changes, ensure you can build the project +2. Run tests after making changes to verify functionality +3. Use the provided CMake presets for consistency +4. Ensure your changes pass in all build configurations +5. **Preferred build configuration**: + - Always prefer a Debug build + - Set `_PACKAGING_MAINTAINER_MODE=OFF` + - Enable coverage with `-D_ENABLE_COVERAGE=ON` + - Example: `cmake -DCMAKE_BUILD_TYPE=Debug -Dmyproject_PACKAGING_MAINTAINER_MODE=OFF -Dmyproject_ENABLE_COVERAGE=ON ..` +6. ALWAYS commit changes after significant change has been made AND tests pass + +## Technical Implementation Details + +### CMake Configuration + +The project uses several CMake patterns: + +- Interface libraries for options and warnings +- Conditional feature enabling based on compiler support +- Presets for different build configurations + +### Complexity Analysis with Lizard + +The project uses Lizard for code complexity analysis with the following thresholds: + +- **Cyclomatic Complexity**: Functions should have CCN ≤ 15 +- **Function Length**: Functions should be ≤ 100 lines +- **Parameter Count**: Functions should have ≤ 6 parameters +- **Copy-Paste Detection**: Detects duplicated code segments + +When encountering functions that exceed these limits: +1. Consider splitting them into smaller, more focused functions +2. Extract complex logic into separate methods +3. Reduce nesting levels and simplify control flow +4. Use parameter objects for functions with many parameters +5. **For duplicated code**: Extract common functionality into shared functions or templates + +To run the analysis manually: +```bash +# Run Lizard with warning output only +cmake --build build --target lizard + +# Generate HTML report +cmake --build build --target lizard_html + +# Generate XML report for CI integration +cmake --build build --target lizard_xml +``` + +### Compiler Warning Configuration + +Each supported compiler (GCC, Clang, MSVC) has specific warning flags enabled: + +- All reasonable warnings are enabled +- Sign conversions are checked +- Shadowing is detected +- Unused code is flagged + +### Build Modes + +The project typically supports these build modes: + +- Debug: No optimization, full debug information +- Release: Optimized build with minimal debug information +- RelWithDebInfo: Optimized but with debug information + +## Common Problems and Solutions + +### Recommending Development Workflow + +When helping users with code changes, suggest setting up a quick iteration workflow: + +```bash +# Suggest this development setup for quick iteration +mkdir -p build && cd build +cmake -G Ninja -DCMAKE_BUILD_TYPE=Debug -Dmyproject_PACKAGING_MAINTAINER_MODE=OFF -Dmyproject_ENABLE_COVERAGE=ON .. +ninja +``` + +For testing code changes, recommend focusing on the non-constexpr tests first: +```bash +# For running runtime tests after changes (not constexpr tests) +cd build +ninja tests relaxed_constexpr_tests +ctest -R "unittests|relaxed_constexpr" --output-on-failure +``` + +For checking test coverage: +```bash +# For generating coverage reports +cd build +ninja +ctest +gcovr -r .. --config=../gcovr.cfg +``` + +This workflow helps the user quickly test your suggestions and allows you, as an AI assistant, to get fast feedback on proposed changes. Using Ninja as the generator speeds up compilation significantly. + +### Fixing Build Errors + +1. **Warning as Errors**: The template treats warnings as errors. Fix the warning rather than disabling it. +2. **Sanitizer Errors**: Address issues found by sanitizers at their root cause. +3. **Static Analysis**: Address the issues reported by clang-tidy and cppcheck. + +### Dependency Issues + +1. Check `Dependencies.cmake` for how dependencies are configured +2. Ensure correct version compatibility +3. Use the project's dependency management system rather than adding ad-hoc include paths + +### Cross-Platform Concerns + +1. Use conditional compilation sparingly and only when necessary +2. Consider implications of your changes on all supported platforms +3. Use the provided abstractions for platform-specific code diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 00000000..7398907d --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,26 @@ +# Claude AI Assistant Guidelines + +## IMPORTANT NOTICE + +CLAUDE, NEVER modify this file + +This file contains instructions specifically for Claude AI. For all general AI assistant guidelines, please refer to and modify the `AI_GUIDELINES.md` file instead. + +## Claude-Specific Instructions + +1. Always review `AI_GUIDELINES.md` for project standards and best practices. + +2. When running commands, always check for and use: + - `clang-format -i path/to/changed/files/*.cpp path/to/changed/files/*.hpp` + - Build commands: `cmake -G Ninja -DCMAKE_BUILD_TYPE=Debug -Dmyproject_PACKAGING_MAINTAINER_MODE=OFF -Dmyproject_ENABLE_COVERAGE=ON ..` + - Test commands: `ninja tests relaxed_constexpr_tests && ctest -R "unittests|relaxed_constexpr" --output-on-failure` + +3. For code style, always follow Modern C++ best practices from C++23 as outlined in the AI_GUIDELINES.md file. + +4. Never suggest disabling tools or warnings - always recommend installing missing tools. + +5. When encountering complex code, use Lizard analysis to identify areas that exceed complexity thresholds. + +6. Use proper CMake practices as outlined in project documentation. + +7. Follow the project's testing workflow by prioritizing relaxed_constexpr_tests first. diff --git a/CMakeLists.txt b/CMakeLists.txt index a5ac5435..84aff86d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -28,6 +28,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() diff --git a/HUMAN_GUIDELINES.md b/HUMAN_GUIDELINES.md new file mode 100644 index 00000000..feeca01d --- /dev/null +++ b/HUMAN_GUIDELINES.md @@ -0,0 +1,108 @@ +# Human Developer Guidelines + +## Project Purpose + +This project exists to provide a strong foundation for creating new C++ projects that follow industry best practices from the start. It incorporates: + +1. **Modern C++ Standards**: Built for C++23 with backward compatibility considerations +2. **Rigorous Safety Checks**: Strict warnings, sanitizers, and static analysis tools +3. **Testing Infrastructure**: Unit, constexpr, and fuzz testing frameworks +4. **Build System**: Modern CMake approach with sensible defaults +5. **CI/CD Setup**: GitHub Actions workflows for testing and validation + +## Philosophy Behind Strict Analysis Rules + +The strict static analysis rules and warnings-as-errors approach are deliberate: + +1. **Early Problem Detection**: Catching potential issues at compile time rather than runtime +2. **Consistent Code Quality**: Enforcing consistent practices across the codebase +3. **Reduced Technical Debt**: Preventing the accumulation of code smells and problematic patterns +4. **Learning Opportunity**: Helping developers learn better C++ practices through immediate feedback + +## C++ Best Practices + +This project is based on the guidelines from [C++23 Best Practices](https://leanpub.com/cpp23_best_practices/) by Jason Turner. When modifying the codebase, follow these guidelines. The book covers: + +- Using modern C++ features effectively +- Writing safer, more maintainable code +- Leveraging tools for code quality +- Performance considerations +- Testing strategies + +Here are some relevant chapter titles from that book: + + 5:Use AI Coding Assistants Judiciously + 7:Remember: C++ Is Not Object-Oriented (it's multi-paradigm) + 9:Know Your Standard Library + 10:Use The Tools + 11:Don’t Invoke Undefined Behavior + 14:Use the Tools: Automated Tests + 15:Use the Tools: Continuous Builds + 16:Use the Tools: Compiler Warnings + 17:Use the Tools: Static Analysis + 18:Use The Tools: Consider Custom Static Analysis + 19:Use the Tools: Sanitizers + 20:Use The Tools: Hardening + 21:Use the Tools: Multiple Compilers + 22:Use The Tools: Fuzzing and Mutating + 23:Use the Tools: Build Generators + 24:Use the Tools: Package Managers + 25:Make your interfaces hard to use wrong. + 27:Be Afraid of Global State + 28:Use Stronger Types (define new types, example: `velocity { int }` instead of `int`) + 29:Use [[nodiscard]] Liberally + 32:Prefer Stack Over Heap + 33:Don’t return raw pointers + 34:Be Aware of Custom Allocation And PMR + 35:Constrain Your Template Parameters With Concepts + 36:Understand consteval and constinit (and use when appropriate) + 37:Prefer Spaceships (operators) + 38:Decouple Your APIs With Views and Spans + 39:Follow the Rule of 0 + 40:If You Must Do Manual Resource Management, Follow the Rule of 5 + 41:Don’t Copy and Paste Code + 42:Prefer std::format, std::print Over iostream Or c-formatting Functions + 43:constexpr All The Things! (constexpr should be the prefered default as of C++23) + 44:Make globals in headers inline constexpr + 45:Safely Initialize Non-const Static Variables + 46:const Everything That’s Not constexpr + 47:Know Your Containers (prefer array over vector, vector over anything else) + 48:Always Initialize Your non-const, non-auto Values + 49:Prefer auto in Many Cases. + 50:Use Ranges and Views For Correctness and Readability + 51:Don’t Reuse Views + 52:Prefer Algorithms Over Loops + 53:Use Ranged-For Loops When Views and Algorithms Cannot Help + 54:Use auto in ranged for loops + 55:Make case statements return and Avoid default In switch Statements + 56:Prefer Scoped enum + 57:Use if constexpr When It Results In Better Code + 60:No More new! (always prefer stack or std containers, but use smart pointers if you must) + 61:Avoid std::bind and std::function (use lambdas and captures instead) + 62:Don’t Use initializer_list For Non-Trivial Types + 63:Consider Designated Initializers + +When making changes, refer to this resource to ensure your modifications align with the project's philosophy. + +## Code Formatting + +This project uses clang-format as the source of truth for code formatting: + +1. **Always Run clang-format**: After making code changes, run the latest version of clang-format on all modified files + ```bash + clang-format -i path/to/changed/files/*.cpp path/to/changed/files/*.hpp + ``` + +2. **Benefits of Consistent Formatting**: + - Reduces friction in code reviews + - Eliminates formatting debates + - Ensures CI pipeline success + - Maintains consistent codebase appearance + - Helps focus reviews on substantive issues rather than style + +3. **Integration Options**: + - Configure your editor to run clang-format on save + - Set up a pre-commit hook + - Use a formatting check in CI (already configured in this project) + +By letting clang-format be the final arbiter of code style, we reduce human and AI decision-making overhead on non-functional aspects of the code. diff --git a/ProjectOptions.cmake b/ProjectOptions.cmake index 3d260944..6a0eb0a0 100644 --- a/ProjectOptions.cmake +++ b/ProjectOptions.cmake @@ -78,6 +78,8 @@ 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_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() @@ -92,6 +94,8 @@ 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_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() @@ -109,6 +113,8 @@ macro(myproject_setup_options) myproject_ENABLE_UNITY_BUILD myproject_ENABLE_CLANG_TIDY myproject_ENABLE_CPPCHECK + myproject_ENABLE_LIZARD + myproject_ENABLE_BLOATY myproject_ENABLE_COVERAGE myproject_ENABLE_PCH myproject_ENABLE_CACHE) @@ -205,6 +211,14 @@ macro(myproject_local_options) myproject_enable_cppcheck(${myproject_WARNINGS_AS_ERRORS} "" # override cppcheck options ) endif() + + if(myproject_ENABLE_LIZARD) + myproject_enable_lizard(${myproject_WARNINGS_AS_ERRORS}) + endif() + + if(myproject_ENABLE_BLOATY) + myproject_enable_bloaty() + endif() if(myproject_ENABLE_COVERAGE) include(cmake/Tests.cmake) diff --git a/cmake/Bloaty.cmake b/cmake/Bloaty.cmake new file mode 100644 index 00000000..b6d01543 --- /dev/null +++ b/cmake/Bloaty.cmake @@ -0,0 +1,131 @@ +# Bloaty McBloatface - A binary size analyzer +# This module enables Bloaty for analyzing executable and library sizes + +function(myproject_setup_bloaty TARGET_NAME) + find_program(BLOATY bloaty) + if(BLOATY) + # Define output directory + set(BLOATY_OUTPUT_DIR "${CMAKE_BINARY_DIR}/bloaty_reports") + file(MAKE_DIRECTORY ${BLOATY_OUTPUT_DIR}) + + # Default report sections + set(BLOATY_SECTIONS "sections,symbols,compileunits") + + # Create custom target for basic size analysis + add_custom_target( + bloaty_${TARGET_NAME} + COMMAND ${BLOATY} $ -d ${BLOATY_SECTIONS} --domain=vm + DEPENDS ${TARGET_NAME} + WORKING_DIRECTORY ${CMAKE_BINARY_DIR} + COMMENT "Running Bloaty size analysis on ${TARGET_NAME}..." + ) + + # Create custom target for CSV report + add_custom_target( + bloaty_${TARGET_NAME}_csv + COMMAND ${BLOATY} $ -d ${BLOATY_SECTIONS} --csv > "${BLOATY_OUTPUT_DIR}/${TARGET_NAME}_size.csv" + DEPENDS ${TARGET_NAME} + WORKING_DIRECTORY ${CMAKE_BINARY_DIR} + COMMENT "Generating Bloaty CSV report for ${TARGET_NAME}..." + ) + + # Create custom target for diff against a baseline + # This target needs to be manually called with a baseline file + add_custom_target( + bloaty_${TARGET_NAME}_diff + COMMAND ${CMAKE_COMMAND} -E echo "Run with: cmake --build build --target bloaty_${TARGET_NAME}_diff -- --baseline=/path/to/baseline" + COMMENT "To compare against a baseline, provide --baseline argument" + ) + + # Add custom command to track binary size changes over time + add_custom_target( + bloaty_${TARGET_NAME}_store + COMMAND ${CMAKE_COMMAND} -E copy $ "${BLOATY_OUTPUT_DIR}/${TARGET_NAME}_baseline_$$(date +%Y%m%d%H%M%S)" + COMMAND ${CMAKE_COMMAND} -E echo "Stored baseline at ${BLOATY_OUTPUT_DIR}/${TARGET_NAME}_baseline_$$(date +%Y%m%d%H%M%S)" + DEPENDS ${TARGET_NAME} + COMMENT "Storing current binary as baseline for ${TARGET_NAME}..." + ) + + # Create custom target for template analysis (C++ specific) + add_custom_target( + bloaty_${TARGET_NAME}_templates + COMMAND ${BLOATY} $ -d template_params,symbols + DEPENDS ${TARGET_NAME} + WORKING_DIRECTORY ${CMAKE_BINARY_DIR} + COMMENT "Analyzing template usage in ${TARGET_NAME}..." + ) + + message(STATUS "Bloaty McBloatface targets created for ${TARGET_NAME}") + else() + message(${myproject_WARNING_TYPE} "Bloaty McBloatface requested but executable not found. Install with 'apt install bloaty' or from https://github.com/google/bloaty") + endif() +endfunction() + +# Function to add bloaty analysis to all executable targets +function(myproject_enable_bloaty) + find_program(BLOATY bloaty) + if(BLOATY) + # Get all executable targets + get_all_executable_targets(ALL_TARGETS) + + # Create global target that will depend on all individual targets + add_custom_target(bloaty_all) + + # Create individual bloaty targets for each executable + foreach(TARGET_NAME ${ALL_TARGETS}) + myproject_setup_bloaty(${TARGET_NAME}) + add_dependencies(bloaty_all bloaty_${TARGET_NAME}) + endforeach() + + message(STATUS "Bloaty McBloatface enabled for all executable targets") + else() + message(${myproject_WARNING_TYPE} "Bloaty McBloatface requested but executable not found. Install with 'apt install bloaty' or from https://github.com/google/bloaty") + endif() +endfunction() + +# Helper function to get all executable targets +function(get_all_executable_targets RESULT) + set(TARGETS) + + # Recursive function to get all targets + get_property(TARGETS_IN_DIR DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" PROPERTY BUILDSYSTEM_TARGETS) + + foreach(TARGET_NAME ${TARGETS_IN_DIR}) + get_target_property(TARGET_TYPE ${TARGET_NAME} TYPE) + if(TARGET_TYPE STREQUAL "EXECUTABLE") + list(APPEND TARGETS ${TARGET_NAME}) + endif() + endforeach() + + # Check subdirectories + get_property(SUBDIRS DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" PROPERTY SUBDIRECTORIES) + foreach(SUBDIR ${SUBDIRS}) + get_all_executable_targets_in_dir(${SUBDIR} SUBDIR_TARGETS) + list(APPEND TARGETS ${SUBDIR_TARGETS}) + endforeach() + + set(${RESULT} ${TARGETS} PARENT_SCOPE) +endfunction() + +# Helper function to get targets in a specific directory +function(get_all_executable_targets_in_dir DIR RESULT) + set(TARGETS) + + get_property(TARGETS_IN_DIR DIRECTORY "${DIR}" PROPERTY BUILDSYSTEM_TARGETS) + + foreach(TARGET_NAME ${TARGETS_IN_DIR}) + get_target_property(TARGET_TYPE ${TARGET_NAME} TYPE) + if(TARGET_TYPE STREQUAL "EXECUTABLE") + list(APPEND TARGETS ${TARGET_NAME}) + endif() + endforeach() + + # Check subdirectories + get_property(SUBDIRS DIRECTORY "${DIR}" PROPERTY SUBDIRECTORIES) + foreach(SUBDIR ${SUBDIRS}) + get_all_executable_targets_in_dir(${SUBDIR} SUBDIR_TARGETS) + list(APPEND TARGETS ${SUBDIR_TARGETS}) + endforeach() + + set(${RESULT} ${TARGETS} PARENT_SCOPE) +endfunction() \ No newline at end of file diff --git a/cmake/Cache.cmake b/cmake/Cache.cmake index 2164c10d..f0249e9c 100644 --- a/cmake/Cache.cmake +++ b/cmake/Cache.cmake @@ -28,6 +28,6 @@ function(myproject_enable_cache) ${CACHE_BINARY} CACHE FILEPATH "C compiler cache used") else() - message(WARNING "${CACHE_OPTION} is enabled but was not found. Not using it") + message(${myproject_WARNING_TYPE} "${CACHE_OPTION} is enabled but was not found. Not using it") endif() endfunction() diff --git a/cmake/Lizard.cmake b/cmake/Lizard.cmake new file mode 100644 index 00000000..2e6f769d --- /dev/null +++ b/cmake/Lizard.cmake @@ -0,0 +1,78 @@ +# Lizard - A cyclomatic complexity analyzer +# This module enables Lizard for analyzing code complexity + +function(myproject_setup_lizard WARNINGS_AS_ERRORS) + find_program(LIZARD lizard) + if(LIZARD) + # Define thresholds + set(LIZARD_CCN_THRESHOLD 15 CACHE STRING "Threshold for cyclomatic complexity") + set(LIZARD_LENGTH_THRESHOLD 100 CACHE STRING "Threshold for function length in lines") + set(LIZARD_PARAM_THRESHOLD 6 CACHE STRING "Threshold for number of parameters") + set(LIZARD_WARNINGS_THRESHOLD 0 CACHE STRING "Threshold for number of warnings before causing error exit") + + # Define target directories + set(LIZARD_INCLUDE_DIRS "${CMAKE_SOURCE_DIR}/include" "${CMAKE_SOURCE_DIR}/src") + + # Define common arguments + 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 "*/out/*" + -x "*/_deps/*" + -t 4 + ${LIZARD_INCLUDE_DIRS} + ) + + # Add warning as error settings if enabled + if(${WARNINGS_AS_ERRORS}) + message(STATUS "Lizard: Warnings will be treated as errors") + set(LIZARD_WARNINGS_THRESHOLD 0) + endif() + + # Create a custom target for warnings-only mode + add_custom_target( + lizard + COMMAND ${LIZARD} + ${LIZARD_COMMON_ARGS} + --warnings_only + -i ${LIZARD_WARNINGS_THRESHOLD} + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} + COMMENT "Running Lizard complexity analyzer..." + ) + + # Create HTML report target + 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..." + ) + + # Create XML report target for CI integration + 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..." + ) + + message(STATUS "Lizard complexity analyzer enabled with thresholds: \ + \n CCN: ${LIZARD_CCN_THRESHOLD} \ + \n Function Length: ${LIZARD_LENGTH_THRESHOLD} \ + \n Parameter Count: ${LIZARD_PARAM_THRESHOLD} \ + \n Warnings Threshold: ${LIZARD_WARNINGS_THRESHOLD}") + else() + message(${myproject_WARNING_TYPE} "Lizard requested but executable not found. Install with 'pip install lizard'") + endif() +endfunction() diff --git a/cmake/Sanitizers.cmake b/cmake/Sanitizers.cmake index e238fa21..41baa0bc 100644 --- a/cmake/Sanitizers.cmake +++ b/cmake/Sanitizers.cmake @@ -24,7 +24,7 @@ function( if(${ENABLE_SANITIZER_THREAD}) if("address" IN_LIST SANITIZERS OR "leak" IN_LIST SANITIZERS) - message(WARNING "Thread sanitizer does not work with Address and Leak sanitizer enabled") + message(${myproject_WARNING_TYPE} "Thread sanitizer does not work with Address and Leak sanitizer enabled") else() list(APPEND SANITIZERS "thread") endif() @@ -32,13 +32,13 @@ function( if(${ENABLE_SANITIZER_MEMORY} AND CMAKE_CXX_COMPILER_ID MATCHES ".*Clang") message( - WARNING + ${myproject_WARNING_TYPE} "Memory sanitizer requires all the code (including libc++) to be MSan-instrumented otherwise it reports false positives" ) if("address" IN_LIST SANITIZERS OR "thread" IN_LIST SANITIZERS OR "leak" IN_LIST SANITIZERS) - message(WARNING "Memory sanitizer does not work with Address, Thread or Leak sanitizer enabled") + message(${myproject_WARNING_TYPE} "Memory sanitizer does not work with Address, Thread or Leak sanitizer enabled") else() list(APPEND SANITIZERS "memory") endif() @@ -51,7 +51,7 @@ function( OR ${ENABLE_SANITIZER_UNDEFINED_BEHAVIOR} OR ${ENABLE_SANITIZER_THREAD} OR ${ENABLE_SANITIZER_MEMORY}) - message(WARNING "MSVC only supports address sanitizer") + message(${myproject_WARNING_TYPE} "MSVC only supports address sanitizer") endif() endif() diff --git a/cmake/StaticAnalyzers.cmake b/cmake/StaticAnalyzers.cmake index 652c538e..146985d2 100644 --- a/cmake/StaticAnalyzers.cmake +++ b/cmake/StaticAnalyzers.cmake @@ -1,3 +1,8 @@ +# Sets up consistent warning messages for missing static analysis tools +macro(myproject_tool_not_found_warning TOOL_NAME) + message(${myproject_WARNING_TYPE} "${TOOL_NAME} was requested but executable was not found. Please install ${TOOL_NAME} to enable this feature.") +endmacro() + macro(myproject_enable_cppcheck WARNINGS_AS_ERRORS CPPCHECK_OPTIONS) find_program(CPPCHECK cppcheck) if(CPPCHECK) @@ -47,7 +52,7 @@ macro(myproject_enable_cppcheck WARNINGS_AS_ERRORS CPPCHECK_OPTIONS) list(APPEND CMAKE_CXX_CPPCHECK --error-exitcode=2) endif() else() - message(${WARNING_MESSAGE} "cppcheck requested but executable not found") + myproject_tool_not_found_warning("cppcheck") endif() endmacro() @@ -97,10 +102,10 @@ macro(myproject_enable_clang_tidy target WARNINGS_AS_ERRORS) list(APPEND CLANG_TIDY_OPTIONS -warnings-as-errors=*) endif() - message("Also setting clang-tidy globally") + message(STATUS "Setting clang-tidy globally") set(CMAKE_CXX_CLANG_TIDY ${CLANG_TIDY_OPTIONS}) else() - message(${WARNING_MESSAGE} "clang-tidy requested but executable not found") + myproject_tool_not_found_warning("clang-tidy") endif() endmacro() @@ -109,6 +114,29 @@ macro(myproject_enable_include_what_you_use) if(INCLUDE_WHAT_YOU_USE) set(CMAKE_CXX_INCLUDE_WHAT_YOU_USE ${INCLUDE_WHAT_YOU_USE}) else() - message(${WARNING_MESSAGE} "include-what-you-use requested but executable not found") + myproject_tool_not_found_warning("include-what-you-use") + endif() +endmacro() + +macro(myproject_enable_lizard WARNINGS_AS_ERRORS) + find_program(LIZARD lizard) + if(LIZARD) + include(cmake/Lizard.cmake) + # Function defined in Lizard.cmake + myproject_setup_lizard(${WARNINGS_AS_ERRORS}) + else() + myproject_tool_not_found_warning("lizard") + endif() +endmacro() + +# Enable Bloaty McBloatface for binary size analysis +macro(myproject_enable_bloaty) + find_program(BLOATY bloaty) + if(BLOATY) + include(cmake/Bloaty.cmake) + # Function defined in Bloaty.cmake + myproject_enable_bloaty() + else() + myproject_tool_not_found_warning("bloaty") endif() endmacro()