diff --git a/.github/workflows/static-analysis.yml b/.github/workflows/static-analysis.yml new file mode 100644 index 000000000..8e3a59e9d --- /dev/null +++ b/.github/workflows/static-analysis.yml @@ -0,0 +1,108 @@ +name: Static Code Analysis + +on: + push: + branches: [ trunk ] + pull_request: + branches: [ trunk ] + +env: + BUILD_TYPE: Debug + +jobs: + cppcheck: + runs-on: ubuntu-latest + if: ${{ contains(github.event.head_commit.message, 'PERFORM_CA') || contains(github.event.pull_request.body, 'PERFORM_CA') }} + permissions: + contents: read + + steps: + - uses: actions/checkout@v3 + + - name: Install cppcheck + run: sudo apt-get update && sudo apt-get install -y cppcheck + + - name: Install dependencies + run: | + sudo apt-get install -y g++ libcurl4-gnutls-dev libfreetype6-dev libgif-dev libgtest-dev libjpeg-dev libpixman-1-dev libpng-dev libsdl2-dev libsdl2-image-dev libtinyxml2-dev libwebp-dev libx11-dev libxcursor-dev ninja-build libnode-dev zlib1g-dev libarchive-dev + pip install cmake + + - name: Git submodules + run: git submodule update --init --recursive + + - name: Configure CMake + run: mkdir build && cd build && cmake -G Ninja -DCMAKE_BUILD_TYPE=$BUILD_TYPE .. + + - name: Build the project (required to generate *.xml.h files) + run: cd build && ninja besprited + + - name: Run cppcheck + run: cd build && cmake --build . --target cppcheck + continue-on-error: true + + clang-tidy: + runs-on: ubuntu-latest + if: ${{ contains(github.event.head_commit.message, 'PERFORM_CA') || contains(github.event.pull_request.body, 'PERFORM_CA') }} + permissions: + contents: read + + steps: + - uses: actions/checkout@v3 + + - name: Install clang-tidy + run: sudo apt-get update && sudo apt-get install -y clang-tidy + + - name: Install dependencies + run: | + sudo apt-get install -y g++ libcurl4-gnutls-dev libfreetype6-dev libgif-dev libgtest-dev libjpeg-dev libpixman-1-dev libpng-dev libsdl2-dev libsdl2-image-dev libtinyxml2-dev libwebp-dev libx11-dev libxcursor-dev ninja-build libnode-dev zlib1g-dev libarchive-dev + pip install cmake + + - name: Git submodules + run: git submodule update --init --recursive + + - name: Configure CMake + run: mkdir build && cd build && cmake -G Ninja -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DCMAKE_EXPORT_COMPILE_COMMANDS=ON .. + + - name: Build the project (required to generate *.xml.h files) + run: cd build && ninja besprited + + - name: Run clang-tidy + run: cd build && cmake --build . --target clang-tidy + continue-on-error: true + + gcc-analyzer: + runs-on: ubuntu-latest + if: ${{ contains(github.event.head_commit.message, 'PERFORM_CA') || contains(github.event.pull_request.body, 'PERFORM_CA') }} + permissions: + contents: read + + steps: + - uses: actions/checkout@v3 + + - name: Install GCC (ensure version with -fanalyzer support) + run: | + sudo apt-get update + sudo apt-get install -y gcc-10 g++-10 + sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-10 100 + sudo update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-10 100 + + - name: Verify GCC version + run: gcc --version && g++ --version + + - name: Install dependencies + run: | + sudo apt-get install -y libcurl4-gnutls-dev libfreetype6-dev libgif-dev libgtest-dev libjpeg-dev libpixman-1-dev libpng-dev libsdl2-dev libsdl2-image-dev libtinyxml2-dev libwebp-dev libx11-dev libxcursor-dev ninja-build libnode-dev zlib1g-dev libarchive-dev + pip install cmake + + - name: Git submodules + run: git submodule update --init --recursive + + - name: Configure CMake + run: mkdir build && cd build && cmake -G Ninja -DCMAKE_BUILD_TYPE=$BUILD_TYPE .. + + - name: Build the project (required to generate *.xml.h files) + run: cd build && ninja besprited + + - name: Run GCC with -fanalyzer + run: cd build && cmake --build . --target gcc-analyzer + continue-on-error: true diff --git a/CMakeLists.txt b/CMakeLists.txt index 3d5a5610b..1eab987d5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -371,3 +371,74 @@ add_subdirectory(src) # Third party libraries add_subdirectory(third_party) + +###################################################################### +# Static code analysis targets + +# cppcheck target +find_program(CPPCHECK_EXECUTABLE cppcheck) +if(CPPCHECK_EXECUTABLE) + add_custom_target(cppcheck + COMMAND ${CPPCHECK_EXECUTABLE} + --enable=all + --suppress=missingIncludeSystem + --suppress=unmatchedSuppression + --inline-suppr + --error-exitcode=1 + --std=c++20 + -I ${CMAKE_SOURCE_DIR}/src + -I ${CMAKE_SOURCE_DIR}/third_party + ${CMAKE_SOURCE_DIR}/src + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} + COMMENT "Running cppcheck static analysis" + VERBATIM + ) + message(STATUS "cppcheck found: ${CPPCHECK_EXECUTABLE}") +else() + message(STATUS "cppcheck not found - target 'cppcheck' will not be available") +endif() + +# clang-tidy target +find_program(CLANG_TIDY_EXECUTABLE clang-tidy) +if(CLANG_TIDY_EXECUTABLE) + # Get list of source files + file(GLOB_RECURSE ALL_SOURCE_FILES + ${CMAKE_SOURCE_DIR}/src/*.cpp + ${CMAKE_SOURCE_DIR}/src/*.cc + ${CMAKE_SOURCE_DIR}/src/*.c + ) + + add_custom_target(clang-tidy + COMMAND ${CLANG_TIDY_EXECUTABLE} + -p=${CMAKE_BINARY_DIR} + ${ALL_SOURCE_FILES} + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} + COMMENT "Running clang-tidy static analysis" + VERBATIM + ) + message(STATUS "clang-tidy found: ${CLANG_TIDY_EXECUTABLE}") +else() + message(STATUS "clang-tidy not found - target 'clang-tidy' will not be available") +endif() + +# gcc-analyzer target +# Note: This target builds the 'besprited' executable with GCC's -fanalyzer flag. +# If analyzing different targets is needed, modify the --target argument below. +if(CMAKE_COMPILER_IS_GNUCXX) + add_custom_target(gcc-analyzer + COMMAND ${CMAKE_COMMAND} -E echo "Building with GCC -fanalyzer..." + COMMAND ${CMAKE_COMMAND} + -DCMAKE_BUILD_TYPE=Debug + -DCMAKE_CXX_FLAGS="-fanalyzer" + -DCMAKE_C_FLAGS="-fanalyzer" + -B${CMAKE_BINARY_DIR}/analyzer_build + -S${CMAKE_SOURCE_DIR} + COMMAND ${CMAKE_COMMAND} --build ${CMAKE_BINARY_DIR}/analyzer_build --target besprited + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} + COMMENT "Running GCC with -fanalyzer flag" + VERBATIM + ) + message(STATUS "GCC compiler found: ${CMAKE_CXX_COMPILER}") +else() + message(STATUS "GCC compiler not found - target 'gcc-analyzer' will not be available") +endif() diff --git a/INSTALL.md b/INSTALL.md index bf21cc9c0..9464ab03f 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -14,6 +14,7 @@ * [MacOS details](#macos-details) * [Android details](#android-details) * [Installing](#installing) +* [Static Code Analysis](#static-code-analysis) ## Platforms @@ -126,3 +127,47 @@ Once you've finished compiling, you can install Besprited by running the following command from the `build` directory: ninja install + +## Static Code Analysis + +Besprited includes build targets for running static code analysis tools. These targets help maintain code quality by detecting potential bugs, security vulnerabilities, and code style issues. + +### Available Analysis Targets + +From the `build` directory, you can run the following analysis targets: + +**cppcheck** - Runs cppcheck static analysis: + + cmake --build . --target cppcheck + +Requires: [cppcheck](http://cppcheck.sourceforge.net/) to be installed on your system. + +**clang-tidy** - Runs clang-tidy static analysis: + + cmake --build . --target clang-tidy + +Requires: clang-tidy to be installed on your system. + +**gcc-analyzer** - Builds with GCC's `-fanalyzer` flag: + + cmake --build . --target gcc-analyzer + +Requires: GCC 10 or higher (which includes the `-fanalyzer` feature). + +### Installing Analysis Tools + +**Linux (Debian/Ubuntu):** + + sudo apt-get install cppcheck clang-tidy gcc-10 g++-10 + +**Linux (Fedora):** + + sudo dnf install cppcheck clang-tools-extra gcc + +**MacOS:** + + brew install cppcheck llvm gcc + +**Windows (MSYS2):** + + pacman -S mingw-w64-i686-cppcheck mingw-w64-i686-clang-tools-extra mingw-w64-i686-gcc