From d0cf004061c3e164a6997076737f1745def68999 Mon Sep 17 00:00:00 2001 From: Roman Gilg Date: Sun, 8 Sep 2024 16:59:55 +0200 Subject: [PATCH] ci: run clang-tidy We run clang-tidy on every change. For that we introduce a separate CMake preset that is selected in the CI and activates the clang-tidy support in CMake. Additionally a Python script is provided to run clang-tidy locally when the build already exists. For now we enforce only the warning of one check as an error, but we aim to increase that over time. --- .clang-tidy | 12 ++++- .github/workflows/analysis.yml | 25 +++++++++ .github/workflows/build.yml | 65 +++++++--------------- .github/workflows/change.yml | 22 +++++++- .github/workflows/deploy.yml | 39 ++++++++++++++ CMakeLists.txt | 14 +++++ CMakePresets.json | 10 +++- Dockerfile | 1 + tooling/README.md | 7 +++ tooling/clang-tidy.py | 99 ++++++++++++++++++++++++++++++++++ 10 files changed, 245 insertions(+), 49 deletions(-) create mode 100644 .github/workflows/analysis.yml create mode 100644 .github/workflows/deploy.yml create mode 100644 tooling/README.md create mode 100755 tooling/clang-tidy.py diff --git a/.clang-tidy b/.clang-tidy index 0d50558..757fada 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -1,4 +1,14 @@ -Checks: "-*,modernize-*,cppcoreguidelines-*, -modernize-use-nodiscard, -modernize-use-trailing-return-type" +WarningsAsErrors: "readability-braces-around-statements" + +Checks: " +-*, +modernize-*, +cppcoreguidelines-*, +readability-braces-around-statements, +-modernize-use-nodiscard, +-modernize-use-trailing-return-type +" + CheckOptions: - key: modernize-use-nullptr.NullMacros value: "NULL" diff --git a/.github/workflows/analysis.yml b/.github/workflows/analysis.yml new file mode 100644 index 0000000..ffea22c --- /dev/null +++ b/.github/workflows/analysis.yml @@ -0,0 +1,25 @@ +name: Static Analysis +on: workflow_call + +jobs: + clang-tidy: + name: Check clang-tidy + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + - name: Download Docker image from artifacts + uses: actions/download-artifact@v4 + with: + name: bxt-development-image + path: distfiles + - name: Load Docker image + run: docker load -i distfiles/bxt-development.tar + - uses: addnab/docker-run-action@v3 + with: + image: anydistro/bxt-development:latest + options: -v ${{ github.workspace }}:/src + run: | + ls /src + cmake -S /src --preset clang-tidy + cmake --build /src/build/clang-tidy diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index d37a74f..5726e93 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,5 +1,15 @@ -name: Build and deploy to testing server -on: workflow_call +name: Build Docker Image +on: + workflow_call: + inputs: + tags: + description: List of tags of the resulting Docker image + required: true + type: string + target: + description: Sets the target stage to build + required: true + type: string jobs: build-docker: @@ -16,54 +26,17 @@ jobs: with: context: . push: false - target: production - tags: anydistro/bxt:latest - cache-from: type=gha - cache-to: type=gha,mode=max - outputs: type=docker,dest=./bxt-production.tar + target: ${{ inputs.target }} + tags: ${{ inputs.tags }} + cache-from: type=gha,scope=${{ inputs.target }} + cache-to: type=gha,mode=max,scope=${{ inputs.target }} + outputs: type=docker,dest=./bxt-${{ inputs.target }}.tar - name: Upload Docker image to artifacts uses: actions/upload-artifact@v4 with: - name: bxt-production-image + name: bxt-${{ inputs.target }}-image path: | - bxt-production.tar + bxt-${{ inputs.target }}.tar docker-compose.yml docker-compose.caddy.yml - - deploy-ssh: - name: Deploy to server using ssh - needs: build-docker - runs-on: ubuntu-latest - if: github.ref == 'refs/heads/master' && github.repository == 'anydistro/bxt' - - steps: - - name: Download Docker image from artifacts - uses: actions/download-artifact@v4 - with: - name: bxt-production-image - path: distfiles - - - name: Transfer Docker image to server - uses: appleboy/scp-action@v0.1.7 - with: - host: ${{ secrets.DEPLOYMENT_SERVER_HOST }} - username: ${{ secrets.DEPLOYMENT_SERVER_USER }} - key: ${{ secrets.DEPLOYMENT_SERVER_SSH_KEY }} - source: distfiles/* - target: "/tmp/bxt/" - - - name: Deploy Docker image on server - uses: appleboy/ssh-action@v1.0.3 - env: - CADDY_HOST: ${{ secrets.DEPLOYMENT_SERVER_HOST }} - with: - host: ${{ secrets.DEPLOYMENT_SERVER_HOST }} - username: ${{ secrets.DEPLOYMENT_SERVER_USER }} - key: ${{ secrets.DEPLOYMENT_SERVER_SSH_KEY }} - envs: CADDY_HOST - script: | - cd /tmp/bxt/distfiles/ - docker compose -f docker-compose.yml -f docker-compose.caddy.yml down production || true - docker load -i bxt-production.tar - docker compose -f docker-compose.yml -f docker-compose.caddy.yml -p bxt up -d production diff --git a/.github/workflows/change.yml b/.github/workflows/change.yml index 9887e66..b49e3d5 100644 --- a/.github/workflows/change.yml +++ b/.github/workflows/change.yml @@ -4,6 +4,7 @@ name: CI on: - push - pull_request + jobs: message-lint: uses: ./.github/workflows/commit-lint.yml @@ -11,6 +12,25 @@ jobs: upstream-repo: https://github.com/anydistro/bxt.git check-format: uses: ./.github/workflows/check-format.yml - build: + + prod-build: uses: ./.github/workflows/build.yml secrets: inherit + with: + tags: anydistro/bxt:latest + target: production + dev-build: + uses: ./.github/workflows/build.yml + secrets: inherit + with: + tags: anydistro/bxt-development:latest + target: development + + analysis: + uses: ./.github/workflows/analysis.yml + needs: dev-build + secrets: inherit + deploy: + uses: ./.github/workflows/deploy.yml + needs: prod-build + secrets: inherit diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml new file mode 100644 index 0000000..036f54e --- /dev/null +++ b/.github/workflows/deploy.yml @@ -0,0 +1,39 @@ +name: Deploy Docker Image to Server +on: workflow_call + +jobs: + deploy-ssh: + name: SSH to testing server + runs-on: ubuntu-latest + if: github.ref == 'refs/heads/master' && github.repository == 'anydistro/bxt' + + steps: + - name: Download Docker image from artifacts + uses: actions/download-artifact@v4 + with: + name: bxt-production-image + path: distfiles + + - name: Transfer Docker image to server + uses: appleboy/scp-action@v0.1.7 + with: + host: ${{ secrets.DEPLOYMENT_SERVER_HOST }} + username: ${{ secrets.DEPLOYMENT_SERVER_USER }} + key: ${{ secrets.DEPLOYMENT_SERVER_SSH_KEY }} + source: distfiles/* + target: "/tmp/bxt/" + + - name: Deploy Docker image on server + uses: appleboy/ssh-action@v1.0.3 + env: + CADDY_HOST: ${{ secrets.DEPLOYMENT_SERVER_HOST }} + with: + host: ${{ secrets.DEPLOYMENT_SERVER_HOST }} + username: ${{ secrets.DEPLOYMENT_SERVER_USER }} + key: ${{ secrets.DEPLOYMENT_SERVER_SSH_KEY }} + envs: CADDY_HOST + script: | + cd /tmp/bxt/distfiles/ + docker compose -f docker-compose.yml -f docker-compose.caddy.yml down production || true + docker load -i bxt-production.tar + docker compose -f docker-compose.yml -f docker-compose.caddy.yml -p bxt up -d production diff --git a/CMakeLists.txt b/CMakeLists.txt index dc83014..d5f53c4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -24,6 +24,20 @@ include("${CMAKE_SOURCE_DIR}/cmake/bundled-deps.cmake") ################################################################################ include("${CMAKE_SOURCE_DIR}/cmake/deps.cmake") +################################################################################ +# Static Analysis: Enable Clang-Tidy +################################################################################ +option(ENABLE_CLANG_TIDY "Enable Clang-Tidy checks" OFF) +if(ENABLE_CLANG_TIDY) + find_program(CLANG_TIDY_EXE NAMES clang-tidy clang-tidy-18) + + if(CLANG_TIDY_EXE) + set(CMAKE_CXX_CLANG_TIDY "${CLANG_TIDY_EXE};-header-filter=${CMAKE_SOURCE_DIR}/daemon/.*") + message(STATUS "clang-tidy found: ${CLANG_TIDY_EXE}") + else() + message(SEND_ERROR "clang-tidy not found.") + endif() +endif() ################################################################################ # Project Structure: Add subdirectories for different components diff --git a/CMakePresets.json b/CMakePresets.json index b6541cf..9b10888 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -19,7 +19,15 @@ "name": "debug", "inherits": "base", "cacheVariables": { - "CMAKE_BUILD_TYPE": "Debug" + "CMAKE_BUILD_TYPE": "Debug", + "CMAKE_EXPORT_COMPILE_COMMANDS": "ON" + } + }, + { + "name": "clang-tidy", + "inherits": "debug", + "cacheVariables": { + "ENABLE_CLANG_TIDY": "ON" } }, { diff --git a/Dockerfile b/Dockerfile index 4216e9a..0bbd2ec 100644 --- a/Dockerfile +++ b/Dockerfile @@ -21,6 +21,7 @@ apt-get install --yes \ build-essential \ clang-18 \ clang-format-18 \ + clang-tidy-18 \ clangd-18 \ cmake \ curl \ diff --git a/tooling/README.md b/tooling/README.md new file mode 100644 index 0000000..5b35d06 --- /dev/null +++ b/tooling/README.md @@ -0,0 +1,7 @@ +# Developer Tooling + +The Clang-Tidy helper script can be run on an already existing development build in a dev container with: + +``` +/bin/python3 /workspaces/source/tooling/clang-tidy.py -p /workspaces/source/build/debug -clang-version 18 -header-filter=/workspaces/source/daemon/.* +``` diff --git a/tooling/clang-tidy.py b/tooling/clang-tidy.py new file mode 100755 index 0000000..aacef12 --- /dev/null +++ b/tooling/clang-tidy.py @@ -0,0 +1,99 @@ +#!/usr/bin/env python + +# SPDX-FileCopyrightText: 2024 Roman Gilg +# SPDX-License-Identifier: MIT + +import argparse, os, subprocess, sys, tempfile +from shutil import which + +script_dir = os.path.dirname(os.path.realpath(__file__)) +source_dir = os.path.dirname(os.path.dirname(script_dir)) + + +def parse_arguments(): + parser = argparse.ArgumentParser( + description="Run clang-tidy script with additional options" + ) + parser.add_argument( + "-p", + dest="build_path", + required=True, + help="path used to read a compile command database", + ) + parser.add_argument( + "-j", + type=int, + default=0, + help="number of tidy instances to be run in parallel", + ) + parser.add_argument( + "-clang-version", + dest="clang_version", + type=int, + default=-1, + help="clang-version to use (defaults to development version)", + ) + parser.add_argument( + "-clang-tidy-binary", + dest="clang_tidy_path", + default="", + help="path to clant-tidy binary", + ) + parser.add_argument( + "-header-filter", + dest="header_filter", + default=".*", + help="regular expression matching the names of the headers to output diagnostics from", + ) + return parser.parse_args() + + +args = parse_arguments() +os.chdir(source_dir) + + +def get_binary_args(): + add_args = ["-clang-tidy-binary"] + + if args.clang_tidy_path: + return add_args + [args.clang_tidy_path] + + if args.clang_version > 0: + versioned_path = which("clang-tidy-" + str(args.clang_version)) + if versioned_path is not None: + return add_args + [versioned_path] + return [] + + +# Additional arguments for run-clang-tidy.py +additional_args = [ + "-use-color", + "-j", + str(args.j), + "-p=" + args.build_path, + "-header-filter=" + args.header_filter, +] +additional_args += get_binary_args() + +if args.clang_version > 0: + run_script_url = ( + "https://raw.githubusercontent.com/llvm/llvm-project/release/" + + str(args.clang_version) + + ".x/clang-tools-extra/clang-tidy/tool/run-clang-tidy.py" + ) +else: + run_script_url = "https://raw.githubusercontent.com/llvm/llvm-project/main/clang-tools-extra/clang-tidy/tool/run-clang-tidy.py" + +# Download the run-clang-tidy.py script and save it as a temporary file +with tempfile.NamedTemporaryFile(mode="w") as temp_file: + curl_process = subprocess.Popen(["curl", "-s", run_script_url], stdout=temp_file) + curl_process.wait() + run_clang_tidy_script_path = temp_file.name + + # Execute the modified script using Python with additional arguments + python_process = subprocess.Popen( + [sys.executable, run_clang_tidy_script_path] + additional_args + [source_dir], + stdin=subprocess.PIPE, + ) + python_process.communicate() + exit(python_process.returncode)