From ba1c820433676a9fd5d636eb91fa8f48d88b9d0a Mon Sep 17 00:00:00 2001 From: Konstantin Akimov Date: Fri, 30 Oct 2026 02:17:44 +0700 Subject: [PATCH 1/2] ci: build cppcheck as a Release binary with the match compiler A bare `cmake ..` leaves cppcheck's CMAKE_BUILD_TYPE unset, which its cmake/options.cmake resolves to Debug: no optimisation, -D_GLIBCXX_DEBUG on every container, and the match compiler disabled. That binary is what the lint job has been running, and it is the reason a cold cppcheck pass takes half an hour on a 4 vCPU runner. Measured with cppcheck 2.21.0 on the full non-backported file list, cold cache, identical flags to test/lint/lint-cppcheck-dash.py, -j24: cmake .. 283 s wall, 5801 CPU s -DCMAKE_BUILD_TYPE=Release -DUSE_MATCHCOMPILER=ON 53 s wall, 1073 CPU s Both produce the same 2029 diagnostics. The match compiler is a Python script, so the builder stage gains python3. --- contrib/containers/ci/ci-slim.Dockerfile | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/contrib/containers/ci/ci-slim.Dockerfile b/contrib/containers/ci/ci-slim.Dockerfile index 74086f83cd52..a9701c81d0ff 100644 --- a/contrib/containers/ci/ci-slim.Dockerfile +++ b/contrib/containers/ci/ci-slim.Dockerfile @@ -8,13 +8,14 @@ RUN set -ex; \ cmake \ make \ g++ \ + python3 \ && rm -rf /var/lib/apt/lists/*; \ echo "Downloading Cppcheck version: ${CPPCHECK_VERSION}"; \ curl -fL "https://github.com/danmar/cppcheck/archive/${CPPCHECK_VERSION}.tar.gz" -o /tmp/cppcheck.tar.gz; \ mkdir -p /src/cppcheck && tar -xzf /tmp/cppcheck.tar.gz -C /src/cppcheck --strip-components=1; \ rm /tmp/cppcheck.tar.gz; \ cd /src/cppcheck; \ - mkdir build && cd build && cmake .. && cmake --build . -j"$(nproc)"; \ + mkdir build && cd build && cmake -DCMAKE_BUILD_TYPE=Release -DUSE_MATCHCOMPILER=ON .. && cmake --build . -j"$(nproc)"; \ strip bin/cppcheck # Main image From d9e487ea8a32114574d2ef24f73534c803749271 Mon Sep 17 00:00:00 2001 From: Konstantin Akimov Date: Fri, 30 Oct 2026 02:17:44 +0700 Subject: [PATCH 2/2] ci: run cppcheck in its own job so tests gate on lint alone Every functional-test job and the multiprocess build wait on the lint job, and the lint job spends all but about a minute of its time in cppcheck. A cold cppcheck pass, which any change to a widely included header causes, holds all six test jobs behind it for half an hour. Move cppcheck into a reusable workflow of its own, started from build.yml as a job nothing depends on, with the same cache restore and save as before. The script is renamed to test/lint/cppcheck-dash.py so that all-lint.py, which runs every lint-*.py, no longer picks it up; the lint job keeps the fast linters only. --- .github/workflows/build.yml | 9 ++++ .github/workflows/cppcheck.yml | 53 +++++++++++++++++++ .github/workflows/lint.yml | 20 ------- AGENTS.md | 5 +- test/lint/README.md | 14 +++-- ...lint-cppcheck-dash.py => cppcheck-dash.py} | 0 6 files changed, 75 insertions(+), 26 deletions(-) create mode 100644 .github/workflows/cppcheck.yml rename test/lint/{lint-cppcheck-dash.py => cppcheck-dash.py} (100%) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index ecd017fe24fa..4948b06eb3c6 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -295,6 +295,15 @@ jobs: container-path: ${{ needs.container-slim.outputs.path }} runs-on: ${{ needs.check-skip.outputs['runner-prelint'] }} + # Slow and nothing waits for it: tests gate on lint alone. + cppcheck: + name: Cppcheck + uses: ./.github/workflows/cppcheck.yml + needs: [check-skip, container-slim] + with: + container-path: ${{ needs.container-slim.outputs.path }} + runs-on: ${{ needs.check-skip.outputs['runner-prelint'] }} + src-aarch64-linux: name: aarch64-linux-build uses: ./.github/workflows/build-src.yml diff --git a/.github/workflows/cppcheck.yml b/.github/workflows/cppcheck.yml new file mode 100644 index 000000000000..e1e301511430 --- /dev/null +++ b/.github/workflows/cppcheck.yml @@ -0,0 +1,53 @@ +name: Cppcheck + +on: + workflow_call: + inputs: + container-path: + description: "Path to built container at registry" + required: true + type: string + runs-on: + description: "Runner label to use" + required: true + type: string + +jobs: + cppcheck: + name: Run cppcheck + runs-on: ${{ inputs.runs-on }} + container: + image: ${{ inputs.container-path }} # zizmor: ignore[unpinned-images] built earlier in this run by build-container.yml + options: --user root + steps: + - name: Checkout code + uses: actions/checkout@v6 + with: + ref: ${{ github.event.pull_request.head.sha }} + allow-unsafe-pr-checkout: true + persist-credentials: false + + - name: Restore cppcheck cache + uses: actions/cache/restore@v5 + with: + path: /cache/cppcheck + key: cppcheck-${{ hashFiles('contrib/containers/ci/ci-slim.Dockerfile') }}-${{ github.sha }} + restore-keys: | + cppcheck-${{ hashFiles('contrib/containers/ci/ci-slim.Dockerfile') }}- + + - name: Run cppcheck + run: | + git config --global --add safe.directory "$PWD" + export CACHE_DIR="/cache" + test/lint/cppcheck-dash.py + du -sh "${CACHE_DIR}/cppcheck" + shell: bash + + - name: Save cppcheck cache + if: | + github.event_name == 'push' && + github.ref_name == github.event.repository.default_branch + uses: actions/cache/save@v5 + with: + path: /cache/cppcheck + key: cppcheck-${{ hashFiles('contrib/containers/ci/ci-slim.Dockerfile') }}-${{ github.sha }} diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index a71e95459010..d8fc50d6f877 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -38,20 +38,10 @@ jobs: run: | git config --global --add safe.directory "$PWD" git fetch --no-tags -fu origin develop:develop - mkdir -p /cache/cppcheck shell: bash - - name: Restore cppcheck cache - uses: actions/cache/restore@v5 - with: - path: /cache/cppcheck - key: cppcheck-${{ hashFiles('contrib/containers/ci/ci-slim.Dockerfile') }}-${{ github.sha }} - restore-keys: | - cppcheck-${{ hashFiles('contrib/containers/ci/ci-slim.Dockerfile') }}- - - name: Run linters run: | - export CACHE_DIR="/cache" export BUILD_TARGET="linux64" export CHECK_DOC=1 @@ -77,18 +67,8 @@ jobs: git log --oneline ${COMMIT_RANGE} || echo "Could not show commit range" ./ci/dash/lint.sh - du -sh "${CACHE_DIR}/cppcheck" shell: bash - - name: Save cppcheck cache - if: | - github.event_name == 'push' && - github.ref_name == github.event.repository.default_branch - uses: actions/cache/save@v5 - with: - path: /cache/cppcheck - key: cppcheck-${{ hashFiles('contrib/containers/ci/ci-slim.Dockerfile') }}-${{ github.sha }} - - name: Check whether develop has moved on id: freshness if: github.event_name == 'push' && github.ref_name == github.event.repository.default_branch diff --git a/AGENTS.md b/AGENTS.md index c2efe4b12be3..329bc2361a0f 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -166,8 +166,9 @@ test/lint/lint-circular-dependencies.py Functional-test prerequisites and usage details live in `test/README.md`. Several Dash-specific tests need the `dash_hash` Python package. -To reproduce the CI lint job, including cppcheck, run `ci/dash/lint.sh` in the -`ci-slim` container as described in `test/lint/README.md`. +To reproduce the CI lint and cppcheck jobs, run `ci/dash/lint.sh` and +`test/lint/cppcheck-dash.py` in the `ci-slim` container as described in +`test/lint/README.md`. ## Backport Work diff --git a/test/lint/README.md b/test/lint/README.md index 401922cf0faa..a09b2398b540 100644 --- a/test/lint/README.md +++ b/test/lint/README.md @@ -21,9 +21,10 @@ Reproducing the CI lint job The image above is not what CI uses: its default entrypoint runs `ci/lint/06_script.sh`, which merge-bases against `master` and runs checks (`check-doc.py`, subtree checks) that the CI lint job does not, and it has no cppcheck. The CI lint job -(`.github/workflows/lint.yml`) runs `ci/dash/lint.sh` inside the `ci-slim` image. To -reproduce it, build that image and run the same script with the same environment from the -repository or worktree root: +(`.github/workflows/lint.yml`) runs `ci/dash/lint.sh` inside the `ci-slim` image, and a +separate cppcheck job (`.github/workflows/cppcheck.yml`) runs `test/lint/cppcheck-dash.py` +in the same image. To reproduce them, build that image and run the same scripts with the +same environment from the repository or worktree root: ```sh docker build -t dash-ci-slim --file ./contrib/containers/ci/ci-slim.Dockerfile ./contrib/containers/ci @@ -34,9 +35,14 @@ docker build -t dash-ci-slim --file ./contrib/containers/ci/ci-slim.Dockerfile . GIT_COMMON_DIR="$(git rev-parse --path-format=absolute --git-common-dir)" docker run --rm --user "$(id -u):$(id -g)" -e HOME=/tmp \ -v "$PWD":"$PWD" -v "$GIT_COMMON_DIR":"$GIT_COMMON_DIR" -w "$PWD" \ - -e CACHE_DIR="$PWD/ci/scratch/cache" -e BUILD_TARGET=linux64 -e CHECK_DOC=1 \ + -e BUILD_TARGET=linux64 -e CHECK_DOC=1 \ -e PULL_REQUEST=true -e COMMIT_RANGE="$(git merge-base develop HEAD)..HEAD" \ dash-ci-slim bash -c 'git config --global --add safe.directory "$PWD" && ./ci/dash/lint.sh' + +docker run --rm --user "$(id -u):$(id -g)" -e HOME=/tmp \ + -v "$PWD":"$PWD" -v "$GIT_COMMON_DIR":"$GIT_COMMON_DIR" -w "$PWD" \ + -e CACHE_DIR="$PWD/ci/scratch/cache" \ + dash-ci-slim bash -c 'git config --global --add safe.directory "$PWD" && test/lint/cppcheck-dash.py' ``` CI builds `ci-slim` for both `linux/amd64` and `linux/arm64`, so a native build is fine; diff --git a/test/lint/lint-cppcheck-dash.py b/test/lint/cppcheck-dash.py similarity index 100% rename from test/lint/lint-cppcheck-dash.py rename to test/lint/cppcheck-dash.py