From 193997428147be80eabcae178ab85d82aefcdd5a Mon Sep 17 00:00:00 2001 From: komalmahale Date: Fri, 8 May 2026 14:07:16 +0200 Subject: [PATCH 1/8] Add S-CORE quality proposal and hybrid CI runtime tracking demo --- .github/workflows/clang_tidy_analysis.yml | 76 +++++++ .github/workflows/codeql_analysis.yml | 89 ++++++++ .github/workflows/coverity_analysis.yml | 78 +++++++ .github/workflows/hybrid_quality_demo.yml | 130 ++++++++++++ tools/ci/generate_hybrid_quality_dashboard.py | 192 ++++++++++++++++++ 5 files changed, 565 insertions(+) create mode 100644 .github/workflows/clang_tidy_analysis.yml create mode 100644 .github/workflows/codeql_analysis.yml create mode 100644 .github/workflows/coverity_analysis.yml create mode 100644 .github/workflows/hybrid_quality_demo.yml create mode 100644 tools/ci/generate_hybrid_quality_dashboard.py diff --git a/.github/workflows/clang_tidy_analysis.yml b/.github/workflows/clang_tidy_analysis.yml new file mode 100644 index 000000000..1b0cfeb69 --- /dev/null +++ b/.github/workflows/clang_tidy_analysis.yml @@ -0,0 +1,76 @@ +# ******************************************************************************* +# Copyright (c) 2026 Contributors to the Eclipse Foundation +# +# See the NOTICE file(s) distributed with this work for additional +# information regarding copyright ownership. +# +# This program and the accompanying materials are made available under the +# terms of the Apache License Version 2.0 which is available at +# https://www.apache.org/licenses/LICENSE-2.0 +# +# SPDX-License-Identifier: Apache-2.0 +# ******************************************************************************* + +name: Clang-Tidy Analysis + +on: + workflow_call: + outputs: + duration-seconds: + description: Runtime of the clang-tidy check in seconds. + value: ${{ jobs.clang_tidy.outputs.duration-seconds }} + workflow_dispatch: + +permissions: + contents: read + +concurrency: + group: clang_tidy_analysis-${{ github.event.pull_request.number || github.run_id }} + cancel-in-progress: ${{ github.event_name == 'pull_request' }} + +env: + ANDROID_HOME: "" + ANDROID_SDK_ROOT: "" + +jobs: + clang_tidy: + runs-on: ubuntu-24.04 + outputs: + duration-seconds: ${{ steps.timer.outputs.duration-seconds }} + steps: + - name: Start timer + id: start_time + run: echo "start=$(date +%s)" >> "$GITHUB_OUTPUT" + + - name: Checkout repository + uses: actions/checkout@v6.0.2 + + - name: Free Disk Space (Ubuntu) + uses: eclipse-score/more-disk-space@v1 + with: + level: 4 + + - uses: castler/setup-bazel@8818d35864b4088fb3a12e7a3191777dc418fd69 + with: + bazelisk-cache: true + disk-cache: "clang_tidy_analysis" + disk-cache-key: "main" + repository-cache: true + cache-save: ${{ github.ref == 'refs/heads/main' }} + + - name: Allow linux-sandbox + uses: ./actions/unblock_user_namespace_for_linux_sandbox + + - name: Run clang-tidy analysis + run: | + bazel build //... --aspects //tools/lint:clang_tidy_aspect + + - name: End timer + if: ${{ always() }} + id: timer + run: | + end=$(date +%s) + start=${{ steps.start_time.outputs.start }} + duration=$((end - start)) + echo "duration-seconds=$duration" >> "$GITHUB_OUTPUT" + echo "clang-tidy duration: ${duration}s" >> "$GITHUB_STEP_SUMMARY" diff --git a/.github/workflows/codeql_analysis.yml b/.github/workflows/codeql_analysis.yml new file mode 100644 index 000000000..62d1f0b49 --- /dev/null +++ b/.github/workflows/codeql_analysis.yml @@ -0,0 +1,89 @@ +# ******************************************************************************* +# Copyright (c) 2026 Contributors to the Eclipse Foundation +# +# See the NOTICE file(s) distributed with this work for additional +# information regarding copyright ownership. +# +# This program and the accompanying materials are made available under the +# terms of the Apache License Version 2.0 which is available at +# https://www.apache.org/licenses/LICENSE-2.0 +# +# SPDX-License-Identifier: Apache-2.0 +# ******************************************************************************* + +name: CodeQL Analysis + +on: + workflow_call: + outputs: + duration-seconds: + description: Runtime of the CodeQL check in seconds. + value: ${{ jobs.codeql.outputs.duration-seconds }} + workflow_dispatch: + +permissions: + actions: read + contents: read + security-events: write + +concurrency: + group: codeql_analysis-${{ github.event.pull_request.number || github.run_id }} + cancel-in-progress: ${{ github.event_name == 'pull_request' }} + +env: + ANDROID_HOME: "" + ANDROID_SDK_ROOT: "" + +jobs: + codeql: + runs-on: ubuntu-24.04 + outputs: + duration-seconds: ${{ steps.timer.outputs.duration-seconds }} + steps: + - name: Start timer + id: start_time + run: echo "start=$(date +%s)" >> "$GITHUB_OUTPUT" + + - name: Checkout repository + uses: actions/checkout@v6.0.2 + + - name: Free Disk Space (Ubuntu) + uses: eclipse-score/more-disk-space@v1 + with: + level: 4 + + - name: Initialize CodeQL + uses: github/codeql-action/init@v3 + with: + languages: c-cpp + build-mode: manual + + - uses: castler/setup-bazel@8818d35864b4088fb3a12e7a3191777dc418fd69 + with: + bazelisk-cache: true + disk-cache: "codeql_analysis" + disk-cache-key: "main" + repository-cache: true + cache-save: ${{ github.ref == 'refs/heads/main' }} + + - name: Allow linux-sandbox + uses: ./actions/unblock_user_namespace_for_linux_sandbox + + - name: Build for CodeQL extraction + run: | + bazel build //... + + - name: Analyze with CodeQL + uses: github/codeql-action/analyze@v3 + with: + category: /language:c-cpp + + - name: End timer + if: ${{ always() }} + id: timer + run: | + end=$(date +%s) + start=${{ steps.start_time.outputs.start }} + duration=$((end - start)) + echo "duration-seconds=$duration" >> "$GITHUB_OUTPUT" + echo "CodeQL duration: ${duration}s" >> "$GITHUB_STEP_SUMMARY" diff --git a/.github/workflows/coverity_analysis.yml b/.github/workflows/coverity_analysis.yml new file mode 100644 index 000000000..9675470ad --- /dev/null +++ b/.github/workflows/coverity_analysis.yml @@ -0,0 +1,78 @@ +# ******************************************************************************* +# Copyright (c) 2026 Contributors to the Eclipse Foundation +# +# See the NOTICE file(s) distributed with this work for additional +# information regarding copyright ownership. +# +# This program and the accompanying materials are made available under the +# terms of the Apache License Version 2.0 which is available at +# https://www.apache.org/licenses/LICENSE-2.0 +# +# SPDX-License-Identifier: Apache-2.0 +# ******************************************************************************* + +name: Coverity Analysis + +on: + workflow_call: + outputs: + duration-seconds: + description: Runtime of the Coverity check in seconds. + value: ${{ jobs.coverity.outputs.duration-seconds }} + workflow_dispatch: + +permissions: + contents: read + +concurrency: + group: coverity_analysis-${{ github.event.pull_request.number || github.run_id }} + cancel-in-progress: ${{ github.event_name == 'pull_request' }} + +env: + ANDROID_HOME: "" + ANDROID_SDK_ROOT: "" + +jobs: + coverity: + runs-on: ubuntu-24.04 + outputs: + duration-seconds: ${{ steps.timer.outputs.duration-seconds }} + steps: + - name: Start timer + id: start_time + run: echo "start=$(date +%s)" >> "$GITHUB_OUTPUT" + + - name: Checkout repository + uses: actions/checkout@v6.0.2 + + - name: Free Disk Space (Ubuntu) + uses: eclipse-score/more-disk-space@v1 + with: + level: 4 + + - uses: castler/setup-bazel@8818d35864b4088fb3a12e7a3191777dc418fd69 + with: + bazelisk-cache: true + disk-cache: "coverity_analysis" + disk-cache-key: "main" + repository-cache: true + cache-save: ${{ github.ref == 'refs/heads/main' }} + + - name: Allow linux-sandbox + uses: ./actions/unblock_user_namespace_for_linux_sandbox + + - name: Run Coverity scan + run: | + echo "Coverity integration placeholder" + echo "To enable: configure Coverity account and API token in secrets" + bazel build //... + + - name: End timer + if: ${{ always() }} + id: timer + run: | + end=$(date +%s) + start=${{ steps.start_time.outputs.start }} + duration=$((end - start)) + echo "duration-seconds=$duration" >> "$GITHUB_OUTPUT" + echo "Coverity duration: ${duration}s" >> "$GITHUB_STEP_SUMMARY" diff --git a/.github/workflows/hybrid_quality_demo.yml b/.github/workflows/hybrid_quality_demo.yml new file mode 100644 index 000000000..de5b75021 --- /dev/null +++ b/.github/workflows/hybrid_quality_demo.yml @@ -0,0 +1,130 @@ +# ******************************************************************************* +# Copyright (c) 2026 Contributors to the Eclipse Foundation +# +# See the NOTICE file(s) distributed with this work for additional +# information regarding copyright ownership. +# +# This program and the accompanying materials are made available under the +# terms of the Apache License Version 2.0 which is available at +# https://www.apache.org/licenses/LICENSE-2.0 +# +# SPDX-License-Identifier: Apache-2.0 +# ******************************************************************************* + +name: Hybrid Quality Demo + +on: + workflow_dispatch: + inputs: + run_nightly_checks: + description: Run nightly quality checks in addition to fast PR checks. + required: false + type: boolean + default: true + schedule: + - cron: '0 2 * * *' + +permissions: + actions: read + contents: read + security-events: write + +concurrency: + group: hybrid_quality_demo-${{ github.ref }}-${{ github.event_name }} + cancel-in-progress: false + +jobs: + pr_checks: + name: Fast PR checks + uses: ./.github/workflows/build_and_test_host.yml + with: + run_all_configurations: false + + coverage: + name: Coverage report + if: ${{ github.event_name == 'schedule' || inputs.run_nightly_checks }} + needs: pr_checks + uses: ./.github/workflows/coverage_report.yml + + thread_sanitizer: + name: Thread sanitizer + if: ${{ github.event_name == 'schedule' || inputs.run_nightly_checks }} + needs: pr_checks + uses: ./.github/workflows/thread_sanitizer.yml + + address_sanitizer: + name: Address/UB/leak sanitizer + if: ${{ github.event_name == 'schedule' || inputs.run_nightly_checks }} + needs: pr_checks + uses: ./.github/workflows/address_undefined_behavior_leak_sanitizer.yml + + codeql: + name: CodeQL analysis + if: ${{ github.event_name == 'schedule' || inputs.run_nightly_checks }} + needs: pr_checks + uses: ./.github/workflows/codeql_analysis.yml + + clang_tidy: + name: Clang-Tidy analysis + if: ${{ github.event_name == 'schedule' || inputs.run_nightly_checks }} + needs: pr_checks + uses: ./.github/workflows/clang_tidy_analysis.yml + + coverity: + name: Coverity analysis + if: ${{ github.event_name == 'schedule' || inputs.run_nightly_checks }} + needs: pr_checks + uses: ./.github/workflows/coverity_analysis.yml + + dashboard: + name: Generate quality dashboard with timing + if: ${{ always() }} + needs: + - pr_checks + - coverage + - thread_sanitizer + - address_sanitizer + - codeql + - clang_tidy + - coverity + runs-on: ubuntu-24.04 + permissions: + contents: read + steps: + - name: Checkout repository + uses: actions/checkout@v6.0.2 + + - name: Generate dashboard files with timing + env: + PR_CHECKS_RESULT: ${{ needs.pr_checks.result }} + COVERAGE_RESULT: ${{ needs.coverage.result || 'skipped' }} + THREAD_SANITIZER_RESULT: ${{ needs.thread_sanitizer.result || 'skipped' }} + ADDRESS_SANITIZER_RESULT: ${{ needs.address_sanitizer.result || 'skipped' }} + CODEQL_RESULT: ${{ needs.codeql.result || 'skipped' }} + CLANG_TIDY_RESULT: ${{ needs.clang_tidy.result || 'skipped' }} + COVERITY_RESULT: ${{ needs.coverity.result || 'skipped' }} + CODEQL_DURATION_SECONDS: ${{ needs.codeql.outputs.duration-seconds || '' }} + CLANG_TIDY_DURATION_SECONDS: ${{ needs.clang_tidy.outputs.duration-seconds || '' }} + COVERITY_DURATION_SECONDS: ${{ needs.coverity.outputs.duration-seconds || '' }} + COVERAGE_ARTIFACT_NAME: ${{ needs.coverage.outputs.artifact-name }} + REPOSITORY_NAME: ${{ github.repository }} + RUN_ID: ${{ github.run_id }} + REF_NAME: ${{ github.ref_name }} + EVENT_NAME: ${{ github.event_name }} + run: | + python3 tools/ci/generate_hybrid_quality_dashboard.py dashboard + + - name: Publish workflow summary + run: cat dashboard/summary.md >> "$GITHUB_STEP_SUMMARY" + + - name: Show timing report + if: ${{ always() }} + run: | + echo "## Quality Check Timing Report" >> "$GITHUB_STEP_SUMMARY" + cat dashboard/timing.txt >> "$GITHUB_STEP_SUMMARY" 2>/dev/null || echo "Timing data generated." >> "$GITHUB_STEP_SUMMARY" + + - name: Upload dashboard artifact + uses: actions/upload-artifact@v4 + with: + name: hybrid_quality_dashboard_${{ github.run_id }} + path: dashboard/ diff --git a/tools/ci/generate_hybrid_quality_dashboard.py b/tools/ci/generate_hybrid_quality_dashboard.py new file mode 100644 index 000000000..e5c9c1e27 --- /dev/null +++ b/tools/ci/generate_hybrid_quality_dashboard.py @@ -0,0 +1,192 @@ +#!/usr/bin/env python3 + +import html +import os +import pathlib +import sys + +STATUS_LABELS = { + "success": "Passed", + "failure": "Failed", + "cancelled": "Cancelled", + "skipped": "Skipped", +} + +STATUS_COLORS = { + "success": "#1a7f37", + "failure": "#cf222e", + "cancelled": "#9a6700", + "skipped": "#6e7781", +} + + +def normalize_status(value: str) -> str: + if value in STATUS_LABELS: + return value + return "skipped" + + +def format_duration(duration_value: str) -> str: + if not duration_value: + return "-" + try: + seconds = int(duration_value) + except ValueError: + return "-" + minutes = seconds // 60 + remaining_seconds = seconds % 60 + return f"{minutes}m {remaining_seconds}s ({seconds}s)" + + +def render_markdown_row(name: str, status: str, duration: str, notes: str) -> str: + return f"| {name} | {STATUS_LABELS[status]} | {duration} | {notes} |" + + +def render_html_row(name: str, status: str, duration: str, notes: str) -> str: + safe_name = html.escape(name) + safe_notes = html.escape(notes) + safe_duration = html.escape(duration) + label = html.escape(STATUS_LABELS[status]) + color = STATUS_COLORS[status] + return ( + "" + f"{safe_name}" + f"{label}" + f"{safe_duration}" + f"{safe_notes}" + "" + ) + + +def main() -> int: + output_dir = pathlib.Path(sys.argv[1]) if len(sys.argv) > 1 else pathlib.Path("dashboard") + output_dir.mkdir(parents=True, exist_ok=True) + + run_id = os.environ.get("RUN_ID", "unknown") + repository_name = os.environ.get("REPOSITORY_NAME", "unknown") + ref_name = os.environ.get("REF_NAME", "unknown") + event_name = os.environ.get("EVENT_NAME", "unknown") + coverage_artifact_name = os.environ.get("COVERAGE_ARTIFACT_NAME", "") + + checks = [ + ( + "Fast PR checks", + normalize_status(os.environ.get("PR_CHECKS_RESULT", "skipped")), + "-", + "Build and unit tests on the default host configuration.", + ), + ( + "CodeQL analysis", + normalize_status(os.environ.get("CODEQL_RESULT", "skipped")), + format_duration(os.environ.get("CODEQL_DURATION_SECONDS", "")), + "Nightly static security analysis.", + ), + ( + "Clang-Tidy analysis", + normalize_status(os.environ.get("CLANG_TIDY_RESULT", "skipped")), + format_duration(os.environ.get("CLANG_TIDY_DURATION_SECONDS", "")), + "Clang static code analysis and linting.", + ), + ( + "Coverity analysis", + normalize_status(os.environ.get("COVERITY_RESULT", "skipped")), + format_duration(os.environ.get("COVERITY_DURATION_SECONDS", "")), + "Coverity static code analysis.", + ), + ( + "Coverage report", + normalize_status(os.environ.get("COVERAGE_RESULT", "skipped")), + "-", + "Nightly-style coverage generation." + + (f" Artifact: {coverage_artifact_name}." if coverage_artifact_name else ""), + ), + ( + "Thread sanitizer", + normalize_status(os.environ.get("THREAD_SANITIZER_RESULT", "skipped")), + "-", + "Nightly thread sanitizer run.", + ), + ( + "Address/UB/leak sanitizer", + normalize_status(os.environ.get("ADDRESS_SANITIZER_RESULT", "skipped")), + "-", + "Nightly address, undefined behavior, and leak sanitizer run.", + ), + ] + + markdown_lines = [ + "## Hybrid Quality Demo", + "", + f"- Repository: `{repository_name}`", + f"- Ref: `{ref_name}`", + f"- Event: `{event_name}`", + f"- Run: `{run_id}`", + "", + "| Check | Status | Runtime | Notes |", + "| --- | --- | --- | --- |", + ] + markdown_lines.extend( + render_markdown_row(name, status, duration, notes) for name, status, duration, notes in checks + ) + markdown_lines.extend( + [ + "", + "This demo shows the hybrid model: fast PR checks run first, and heavier quality checks (CodeQL, clang-tidy, Coverity, coverage, and sanitizers) run separately for nightly-style visibility.", + ] + ) + (output_dir / "summary.md").write_text("\n".join(markdown_lines) + "\n", encoding="utf-8") + + timing_lines = [ + "# Quality Check Timing Report", + "", + "## Measured Runtime", + "", + "| Check | Status | Runtime |", + "| --- | --- | --- |", + ] + for name, status, duration, _ in checks: + timing_lines.append(f"| {name} | {STATUS_LABELS[status]} | {duration} |") + (output_dir / "timing.txt").write_text("\n".join(timing_lines) + "\n", encoding="utf-8") + + html_rows = "\n".join( + render_html_row(name, status, duration, notes) for name, status, duration, notes in checks + ) + html_document = f""" + + + + + Hybrid Quality Demo with Timing + + + +
+
+

Hybrid Quality Demo with Timing

+

This dashboard shows measured runtime for quality checks.

+
+ + + {html_rows} +
CheckStatusRuntimeNotes
+
+
+
+ + +""" + (output_dir / "index.html").write_text(html_document, encoding="utf-8") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) From dc1c480401c167cfc8017614daa881b7bea049d3 Mon Sep 17 00:00:00 2001 From: komalmahale Date: Sun, 10 May 2026 20:36:56 +0200 Subject: [PATCH 2/8] Run hybrid quality timing on pull requests --- .github/workflows/hybrid_quality_demo.yml | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/.github/workflows/hybrid_quality_demo.yml b/.github/workflows/hybrid_quality_demo.yml index de5b75021..5a03e5289 100644 --- a/.github/workflows/hybrid_quality_demo.yml +++ b/.github/workflows/hybrid_quality_demo.yml @@ -14,6 +14,8 @@ name: Hybrid Quality Demo on: + pull_request: + types: [opened, reopened, synchronize] workflow_dispatch: inputs: run_nightly_checks: @@ -42,37 +44,37 @@ jobs: coverage: name: Coverage report - if: ${{ github.event_name == 'schedule' || inputs.run_nightly_checks }} + if: ${{ github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' && inputs.run_nightly_checks }} needs: pr_checks uses: ./.github/workflows/coverage_report.yml thread_sanitizer: name: Thread sanitizer - if: ${{ github.event_name == 'schedule' || inputs.run_nightly_checks }} + if: ${{ github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' && inputs.run_nightly_checks }} needs: pr_checks uses: ./.github/workflows/thread_sanitizer.yml address_sanitizer: name: Address/UB/leak sanitizer - if: ${{ github.event_name == 'schedule' || inputs.run_nightly_checks }} + if: ${{ github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' && inputs.run_nightly_checks }} needs: pr_checks uses: ./.github/workflows/address_undefined_behavior_leak_sanitizer.yml codeql: name: CodeQL analysis - if: ${{ github.event_name == 'schedule' || inputs.run_nightly_checks }} + if: ${{ github.event_name == 'pull_request' || github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' && inputs.run_nightly_checks }} needs: pr_checks uses: ./.github/workflows/codeql_analysis.yml clang_tidy: name: Clang-Tidy analysis - if: ${{ github.event_name == 'schedule' || inputs.run_nightly_checks }} + if: ${{ github.event_name == 'pull_request' || github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' && inputs.run_nightly_checks }} needs: pr_checks uses: ./.github/workflows/clang_tidy_analysis.yml coverity: name: Coverity analysis - if: ${{ github.event_name == 'schedule' || inputs.run_nightly_checks }} + if: ${{ github.event_name == 'pull_request' || github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' && inputs.run_nightly_checks }} needs: pr_checks uses: ./.github/workflows/coverity_analysis.yml From 974e660aa4d4d5b4b20cbf5c469b5f2045cb9a53 Mon Sep 17 00:00:00 2001 From: komalmahale Date: Mon, 11 May 2026 06:31:40 +0200 Subject: [PATCH 3/8] Fix hybrid workflow startup conditions on pull_request --- .github/workflows/hybrid_quality_demo.yml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/hybrid_quality_demo.yml b/.github/workflows/hybrid_quality_demo.yml index 5a03e5289..96dab67b0 100644 --- a/.github/workflows/hybrid_quality_demo.yml +++ b/.github/workflows/hybrid_quality_demo.yml @@ -44,37 +44,37 @@ jobs: coverage: name: Coverage report - if: ${{ github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' && inputs.run_nightly_checks }} + if: ${{ github.event_name == 'schedule' || (github.event_name == 'workflow_dispatch' && github.event.inputs.run_nightly_checks == 'true') }} needs: pr_checks uses: ./.github/workflows/coverage_report.yml thread_sanitizer: name: Thread sanitizer - if: ${{ github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' && inputs.run_nightly_checks }} + if: ${{ github.event_name == 'schedule' || (github.event_name == 'workflow_dispatch' && github.event.inputs.run_nightly_checks == 'true') }} needs: pr_checks uses: ./.github/workflows/thread_sanitizer.yml address_sanitizer: name: Address/UB/leak sanitizer - if: ${{ github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' && inputs.run_nightly_checks }} + if: ${{ github.event_name == 'schedule' || (github.event_name == 'workflow_dispatch' && github.event.inputs.run_nightly_checks == 'true') }} needs: pr_checks uses: ./.github/workflows/address_undefined_behavior_leak_sanitizer.yml codeql: name: CodeQL analysis - if: ${{ github.event_name == 'pull_request' || github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' && inputs.run_nightly_checks }} + if: ${{ github.event_name == 'pull_request' || github.event_name == 'schedule' || (github.event_name == 'workflow_dispatch' && github.event.inputs.run_nightly_checks == 'true') }} needs: pr_checks uses: ./.github/workflows/codeql_analysis.yml clang_tidy: name: Clang-Tidy analysis - if: ${{ github.event_name == 'pull_request' || github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' && inputs.run_nightly_checks }} + if: ${{ github.event_name == 'pull_request' || github.event_name == 'schedule' || (github.event_name == 'workflow_dispatch' && github.event.inputs.run_nightly_checks == 'true') }} needs: pr_checks uses: ./.github/workflows/clang_tidy_analysis.yml coverity: name: Coverity analysis - if: ${{ github.event_name == 'pull_request' || github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' && inputs.run_nightly_checks }} + if: ${{ github.event_name == 'pull_request' || github.event_name == 'schedule' || (github.event_name == 'workflow_dispatch' && github.event.inputs.run_nightly_checks == 'true') }} needs: pr_checks uses: ./.github/workflows/coverity_analysis.yml From ce379c309dd4dd39a585506aef26047aa7367987 Mon Sep 17 00:00:00 2001 From: komalmahale Date: Mon, 11 May 2026 06:32:29 +0200 Subject: [PATCH 4/8] Fix hybrid workflow permissions for reusable jobs --- .github/workflows/hybrid_quality_demo.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/hybrid_quality_demo.yml b/.github/workflows/hybrid_quality_demo.yml index 96dab67b0..b20599690 100644 --- a/.github/workflows/hybrid_quality_demo.yml +++ b/.github/workflows/hybrid_quality_demo.yml @@ -27,7 +27,7 @@ on: - cron: '0 2 * * *' permissions: - actions: read + actions: write contents: read security-events: write From fd8ae95fe774bf9a9acd2c45c00974cd8448aa21 Mon Sep 17 00:00:00 2001 From: komalmahale Date: Mon, 11 May 2026 07:06:25 +0200 Subject: [PATCH 5/8] changes --- .github/workflows/clang_tidy_analysis.yml | 2 +- .github/workflows/codeql_analysis.yml | 55 ++++++++++++++----- .github/workflows/coverage_report.yml | 18 ++++++ .github/workflows/hybrid_quality_demo.yml | 12 +--- tools/ci/generate_hybrid_quality_dashboard.py | 12 +--- 5 files changed, 65 insertions(+), 34 deletions(-) diff --git a/.github/workflows/clang_tidy_analysis.yml b/.github/workflows/clang_tidy_analysis.yml index 1b0cfeb69..2b9274005 100644 --- a/.github/workflows/clang_tidy_analysis.yml +++ b/.github/workflows/clang_tidy_analysis.yml @@ -63,7 +63,7 @@ jobs: - name: Run clang-tidy analysis run: | - bazel build //... --aspects //tools/lint:clang_tidy_aspect + bazel build //... --aspects=//:tools/lint/linters.bzl%clang_tidy_aspect - name: End timer if: ${{ always() }} diff --git a/.github/workflows/codeql_analysis.yml b/.github/workflows/codeql_analysis.yml index 62d1f0b49..ae2d9371c 100644 --- a/.github/workflows/codeql_analysis.yml +++ b/.github/workflows/codeql_analysis.yml @@ -52,31 +52,58 @@ jobs: with: level: 4 - - name: Initialize CodeQL - uses: github/codeql-action/init@v3 - with: - languages: c-cpp - build-mode: manual - - - uses: castler/setup-bazel@8818d35864b4088fb3a12e7a3191777dc418fd69 + - name: Setup Bazel with shared caching + uses: bazel-contrib/setup-bazel@0.18.0 with: bazelisk-cache: true disk-cache: "codeql_analysis" - disk-cache-key: "main" repository-cache: true - cache-save: ${{ github.ref == 'refs/heads/main' }} + # --config=codeql sets --disk_cache= (disabled), so saving the disk cache + # would always write an empty entry. Bazelisk cache and repo cache are still useful. + cache-save: false - name: Allow linux-sandbox uses: ./actions/unblock_user_namespace_for_linux_sandbox - - name: Build for CodeQL extraction + - name: Run CodeQL via Bazel + run: | + bazel run //quality/static_analysis:codeql_lint -- --target=//... + + - name: Locate SARIF output + if: always() + id: sarif_path run: | - bazel build //... + OUTPUT_PATH="$(bazel info output_path)" + echo "sarif=${OUTPUT_PATH}/codeql.sarif" >> "$GITHUB_OUTPUT" + echo "csv=${OUTPUT_PATH}/codeql.csv" >> "$GITHUB_OUTPUT" + + - name: Check SARIF file exists + if: always() + id: sarif_check + run: | + if [ -f "${{ steps.sarif_path.outputs.sarif }}" ]; then + echo "exists=true" >> "$GITHUB_OUTPUT" + else + echo "exists=false" >> "$GITHUB_OUTPUT" + echo "No SARIF file found — skipping upload." + fi + + - name: Upload SARIF to GitHub Security tab + if: always() && steps.sarif_check.outputs.exists == 'true' + uses: github/codeql-action/upload-sarif@v3 + with: + sarif_file: ${{ steps.sarif_path.outputs.sarif }} + category: codeql-misra-cpp - - name: Analyze with CodeQL - uses: github/codeql-action/analyze@v3 + - name: Upload CodeQL artifacts + if: always() && steps.sarif_check.outputs.exists == 'true' + uses: actions/upload-artifact@v4 with: - category: /language:c-cpp + name: codeql-results-${{ github.sha }} + path: | + ${{ steps.sarif_path.outputs.sarif }} + ${{ steps.sarif_path.outputs.csv }} + if-no-files-found: ignore - name: End timer if: ${{ always() }} diff --git a/.github/workflows/coverage_report.yml b/.github/workflows/coverage_report.yml index 9d36e5cfd..fbd3f7282 100644 --- a/.github/workflows/coverage_report.yml +++ b/.github/workflows/coverage_report.yml @@ -18,6 +18,9 @@ on: artifact-name: description: 'Name of the coverage report artifact' value: ${{ jobs.coverage_report.outputs.artifact-name }} + duration-seconds: + description: Runtime of the coverage check in seconds. + value: ${{ jobs.coverage_report.outputs.duration-seconds }} permissions: contents: read @@ -33,8 +36,13 @@ jobs: runs-on: ubuntu-24.04 outputs: artifact-name: ${{ steps.set-artifact-name.outputs.artifact-name }} + duration-seconds: ${{ steps.timer.outputs.duration-seconds }} steps: + - name: Start timer + id: start_time + run: echo "start=$(date +%s)" >> "$GITHUB_OUTPUT" + - name: Checkout Repository uses: actions/checkout@v6.0.2 @@ -93,4 +101,14 @@ jobs: name: ${{ steps.set-artifact-name.outputs.artifact-name }} path: ${{ github.event.repository.name }}_coverage_report_${{ github.sha }}.zip + - name: End timer + if: ${{ always() }} + id: timer + run: | + end=$(date +%s) + start=${{ steps.start_time.outputs.start }} + duration=$((end - start)) + echo "duration-seconds=$duration" >> "$GITHUB_OUTPUT" + echo "Coverage duration: ${duration}s" >> "$GITHUB_STEP_SUMMARY" + diff --git a/.github/workflows/hybrid_quality_demo.yml b/.github/workflows/hybrid_quality_demo.yml index b20599690..f4cd17210 100644 --- a/.github/workflows/hybrid_quality_demo.yml +++ b/.github/workflows/hybrid_quality_demo.yml @@ -44,7 +44,7 @@ jobs: coverage: name: Coverage report - if: ${{ github.event_name == 'schedule' || (github.event_name == 'workflow_dispatch' && github.event.inputs.run_nightly_checks == 'true') }} + if: ${{ github.event_name == 'pull_request' || github.event_name == 'schedule' || (github.event_name == 'workflow_dispatch' && github.event.inputs.run_nightly_checks == 'true') }} needs: pr_checks uses: ./.github/workflows/coverage_report.yml @@ -72,12 +72,6 @@ jobs: needs: pr_checks uses: ./.github/workflows/clang_tidy_analysis.yml - coverity: - name: Coverity analysis - if: ${{ github.event_name == 'pull_request' || github.event_name == 'schedule' || (github.event_name == 'workflow_dispatch' && github.event.inputs.run_nightly_checks == 'true') }} - needs: pr_checks - uses: ./.github/workflows/coverity_analysis.yml - dashboard: name: Generate quality dashboard with timing if: ${{ always() }} @@ -88,7 +82,6 @@ jobs: - address_sanitizer - codeql - clang_tidy - - coverity runs-on: ubuntu-24.04 permissions: contents: read @@ -104,10 +97,9 @@ jobs: ADDRESS_SANITIZER_RESULT: ${{ needs.address_sanitizer.result || 'skipped' }} CODEQL_RESULT: ${{ needs.codeql.result || 'skipped' }} CLANG_TIDY_RESULT: ${{ needs.clang_tidy.result || 'skipped' }} - COVERITY_RESULT: ${{ needs.coverity.result || 'skipped' }} CODEQL_DURATION_SECONDS: ${{ needs.codeql.outputs.duration-seconds || '' }} CLANG_TIDY_DURATION_SECONDS: ${{ needs.clang_tidy.outputs.duration-seconds || '' }} - COVERITY_DURATION_SECONDS: ${{ needs.coverity.outputs.duration-seconds || '' }} + COVERAGE_DURATION_SECONDS: ${{ needs.coverage.outputs.duration-seconds || '' }} COVERAGE_ARTIFACT_NAME: ${{ needs.coverage.outputs.artifact-name }} REPOSITORY_NAME: ${{ github.repository }} RUN_ID: ${{ github.run_id }} diff --git a/tools/ci/generate_hybrid_quality_dashboard.py b/tools/ci/generate_hybrid_quality_dashboard.py index e5c9c1e27..15a1cd178 100644 --- a/tools/ci/generate_hybrid_quality_dashboard.py +++ b/tools/ci/generate_hybrid_quality_dashboard.py @@ -87,17 +87,11 @@ def main() -> int: format_duration(os.environ.get("CLANG_TIDY_DURATION_SECONDS", "")), "Clang static code analysis and linting.", ), - ( - "Coverity analysis", - normalize_status(os.environ.get("COVERITY_RESULT", "skipped")), - format_duration(os.environ.get("COVERITY_DURATION_SECONDS", "")), - "Coverity static code analysis.", - ), ( "Coverage report", normalize_status(os.environ.get("COVERAGE_RESULT", "skipped")), - "-", - "Nightly-style coverage generation." + format_duration(os.environ.get("COVERAGE_DURATION_SECONDS", "")), + "Code coverage analysis and reporting." + (f" Artifact: {coverage_artifact_name}." if coverage_artifact_name else ""), ), ( @@ -131,7 +125,7 @@ def main() -> int: markdown_lines.extend( [ "", - "This demo shows the hybrid model: fast PR checks run first, and heavier quality checks (CodeQL, clang-tidy, Coverity, coverage, and sanitizers) run separately for nightly-style visibility.", + "This demo shows the hybrid model: fast PR checks run first, and heavier quality checks (CodeQL, clang-tidy, and coverage) run on demand or nightly for visibility.", ] ) (output_dir / "summary.md").write_text("\n".join(markdown_lines) + "\n", encoding="utf-8") From b9b648656fd6d12e4e834ea267a061f87bc47e65 Mon Sep 17 00:00:00 2001 From: komalmahale Date: Mon, 18 May 2026 08:40:02 +0200 Subject: [PATCH 6/8] ci: add hybrid quality workflows and main-push cache seeding --- .github/workflows/codeql_analysis.yml | 116 ------------------ .github/workflows/coverity_analysis.yml | 78 ------------ .github/workflows/hybrid_quality_demo.yml | 10 -- tools/ci/generate_hybrid_quality_dashboard.py | 8 +- 4 files changed, 1 insertion(+), 211 deletions(-) delete mode 100644 .github/workflows/codeql_analysis.yml delete mode 100644 .github/workflows/coverity_analysis.yml diff --git a/.github/workflows/codeql_analysis.yml b/.github/workflows/codeql_analysis.yml deleted file mode 100644 index ae2d9371c..000000000 --- a/.github/workflows/codeql_analysis.yml +++ /dev/null @@ -1,116 +0,0 @@ -# ******************************************************************************* -# Copyright (c) 2026 Contributors to the Eclipse Foundation -# -# See the NOTICE file(s) distributed with this work for additional -# information regarding copyright ownership. -# -# This program and the accompanying materials are made available under the -# terms of the Apache License Version 2.0 which is available at -# https://www.apache.org/licenses/LICENSE-2.0 -# -# SPDX-License-Identifier: Apache-2.0 -# ******************************************************************************* - -name: CodeQL Analysis - -on: - workflow_call: - outputs: - duration-seconds: - description: Runtime of the CodeQL check in seconds. - value: ${{ jobs.codeql.outputs.duration-seconds }} - workflow_dispatch: - -permissions: - actions: read - contents: read - security-events: write - -concurrency: - group: codeql_analysis-${{ github.event.pull_request.number || github.run_id }} - cancel-in-progress: ${{ github.event_name == 'pull_request' }} - -env: - ANDROID_HOME: "" - ANDROID_SDK_ROOT: "" - -jobs: - codeql: - runs-on: ubuntu-24.04 - outputs: - duration-seconds: ${{ steps.timer.outputs.duration-seconds }} - steps: - - name: Start timer - id: start_time - run: echo "start=$(date +%s)" >> "$GITHUB_OUTPUT" - - - name: Checkout repository - uses: actions/checkout@v6.0.2 - - - name: Free Disk Space (Ubuntu) - uses: eclipse-score/more-disk-space@v1 - with: - level: 4 - - - name: Setup Bazel with shared caching - uses: bazel-contrib/setup-bazel@0.18.0 - with: - bazelisk-cache: true - disk-cache: "codeql_analysis" - repository-cache: true - # --config=codeql sets --disk_cache= (disabled), so saving the disk cache - # would always write an empty entry. Bazelisk cache and repo cache are still useful. - cache-save: false - - - name: Allow linux-sandbox - uses: ./actions/unblock_user_namespace_for_linux_sandbox - - - name: Run CodeQL via Bazel - run: | - bazel run //quality/static_analysis:codeql_lint -- --target=//... - - - name: Locate SARIF output - if: always() - id: sarif_path - run: | - OUTPUT_PATH="$(bazel info output_path)" - echo "sarif=${OUTPUT_PATH}/codeql.sarif" >> "$GITHUB_OUTPUT" - echo "csv=${OUTPUT_PATH}/codeql.csv" >> "$GITHUB_OUTPUT" - - - name: Check SARIF file exists - if: always() - id: sarif_check - run: | - if [ -f "${{ steps.sarif_path.outputs.sarif }}" ]; then - echo "exists=true" >> "$GITHUB_OUTPUT" - else - echo "exists=false" >> "$GITHUB_OUTPUT" - echo "No SARIF file found — skipping upload." - fi - - - name: Upload SARIF to GitHub Security tab - if: always() && steps.sarif_check.outputs.exists == 'true' - uses: github/codeql-action/upload-sarif@v3 - with: - sarif_file: ${{ steps.sarif_path.outputs.sarif }} - category: codeql-misra-cpp - - - name: Upload CodeQL artifacts - if: always() && steps.sarif_check.outputs.exists == 'true' - uses: actions/upload-artifact@v4 - with: - name: codeql-results-${{ github.sha }} - path: | - ${{ steps.sarif_path.outputs.sarif }} - ${{ steps.sarif_path.outputs.csv }} - if-no-files-found: ignore - - - name: End timer - if: ${{ always() }} - id: timer - run: | - end=$(date +%s) - start=${{ steps.start_time.outputs.start }} - duration=$((end - start)) - echo "duration-seconds=$duration" >> "$GITHUB_OUTPUT" - echo "CodeQL duration: ${duration}s" >> "$GITHUB_STEP_SUMMARY" diff --git a/.github/workflows/coverity_analysis.yml b/.github/workflows/coverity_analysis.yml deleted file mode 100644 index 9675470ad..000000000 --- a/.github/workflows/coverity_analysis.yml +++ /dev/null @@ -1,78 +0,0 @@ -# ******************************************************************************* -# Copyright (c) 2026 Contributors to the Eclipse Foundation -# -# See the NOTICE file(s) distributed with this work for additional -# information regarding copyright ownership. -# -# This program and the accompanying materials are made available under the -# terms of the Apache License Version 2.0 which is available at -# https://www.apache.org/licenses/LICENSE-2.0 -# -# SPDX-License-Identifier: Apache-2.0 -# ******************************************************************************* - -name: Coverity Analysis - -on: - workflow_call: - outputs: - duration-seconds: - description: Runtime of the Coverity check in seconds. - value: ${{ jobs.coverity.outputs.duration-seconds }} - workflow_dispatch: - -permissions: - contents: read - -concurrency: - group: coverity_analysis-${{ github.event.pull_request.number || github.run_id }} - cancel-in-progress: ${{ github.event_name == 'pull_request' }} - -env: - ANDROID_HOME: "" - ANDROID_SDK_ROOT: "" - -jobs: - coverity: - runs-on: ubuntu-24.04 - outputs: - duration-seconds: ${{ steps.timer.outputs.duration-seconds }} - steps: - - name: Start timer - id: start_time - run: echo "start=$(date +%s)" >> "$GITHUB_OUTPUT" - - - name: Checkout repository - uses: actions/checkout@v6.0.2 - - - name: Free Disk Space (Ubuntu) - uses: eclipse-score/more-disk-space@v1 - with: - level: 4 - - - uses: castler/setup-bazel@8818d35864b4088fb3a12e7a3191777dc418fd69 - with: - bazelisk-cache: true - disk-cache: "coverity_analysis" - disk-cache-key: "main" - repository-cache: true - cache-save: ${{ github.ref == 'refs/heads/main' }} - - - name: Allow linux-sandbox - uses: ./actions/unblock_user_namespace_for_linux_sandbox - - - name: Run Coverity scan - run: | - echo "Coverity integration placeholder" - echo "To enable: configure Coverity account and API token in secrets" - bazel build //... - - - name: End timer - if: ${{ always() }} - id: timer - run: | - end=$(date +%s) - start=${{ steps.start_time.outputs.start }} - duration=$((end - start)) - echo "duration-seconds=$duration" >> "$GITHUB_OUTPUT" - echo "Coverity duration: ${duration}s" >> "$GITHUB_STEP_SUMMARY" diff --git a/.github/workflows/hybrid_quality_demo.yml b/.github/workflows/hybrid_quality_demo.yml index f4cd17210..9cbf42c2c 100644 --- a/.github/workflows/hybrid_quality_demo.yml +++ b/.github/workflows/hybrid_quality_demo.yml @@ -29,7 +29,6 @@ on: permissions: actions: write contents: read - security-events: write concurrency: group: hybrid_quality_demo-${{ github.ref }}-${{ github.event_name }} @@ -60,12 +59,6 @@ jobs: needs: pr_checks uses: ./.github/workflows/address_undefined_behavior_leak_sanitizer.yml - codeql: - name: CodeQL analysis - if: ${{ github.event_name == 'pull_request' || github.event_name == 'schedule' || (github.event_name == 'workflow_dispatch' && github.event.inputs.run_nightly_checks == 'true') }} - needs: pr_checks - uses: ./.github/workflows/codeql_analysis.yml - clang_tidy: name: Clang-Tidy analysis if: ${{ github.event_name == 'pull_request' || github.event_name == 'schedule' || (github.event_name == 'workflow_dispatch' && github.event.inputs.run_nightly_checks == 'true') }} @@ -80,7 +73,6 @@ jobs: - coverage - thread_sanitizer - address_sanitizer - - codeql - clang_tidy runs-on: ubuntu-24.04 permissions: @@ -95,9 +87,7 @@ jobs: COVERAGE_RESULT: ${{ needs.coverage.result || 'skipped' }} THREAD_SANITIZER_RESULT: ${{ needs.thread_sanitizer.result || 'skipped' }} ADDRESS_SANITIZER_RESULT: ${{ needs.address_sanitizer.result || 'skipped' }} - CODEQL_RESULT: ${{ needs.codeql.result || 'skipped' }} CLANG_TIDY_RESULT: ${{ needs.clang_tidy.result || 'skipped' }} - CODEQL_DURATION_SECONDS: ${{ needs.codeql.outputs.duration-seconds || '' }} CLANG_TIDY_DURATION_SECONDS: ${{ needs.clang_tidy.outputs.duration-seconds || '' }} COVERAGE_DURATION_SECONDS: ${{ needs.coverage.outputs.duration-seconds || '' }} COVERAGE_ARTIFACT_NAME: ${{ needs.coverage.outputs.artifact-name }} diff --git a/tools/ci/generate_hybrid_quality_dashboard.py b/tools/ci/generate_hybrid_quality_dashboard.py index 15a1cd178..729bbf34f 100644 --- a/tools/ci/generate_hybrid_quality_dashboard.py +++ b/tools/ci/generate_hybrid_quality_dashboard.py @@ -75,12 +75,6 @@ def main() -> int: "-", "Build and unit tests on the default host configuration.", ), - ( - "CodeQL analysis", - normalize_status(os.environ.get("CODEQL_RESULT", "skipped")), - format_duration(os.environ.get("CODEQL_DURATION_SECONDS", "")), - "Nightly static security analysis.", - ), ( "Clang-Tidy analysis", normalize_status(os.environ.get("CLANG_TIDY_RESULT", "skipped")), @@ -125,7 +119,7 @@ def main() -> int: markdown_lines.extend( [ "", - "This demo shows the hybrid model: fast PR checks run first, and heavier quality checks (CodeQL, clang-tidy, and coverage) run on demand or nightly for visibility.", + "This demo shows the hybrid model: fast PR checks run first, and heavier quality checks (clang-tidy and coverage) run on demand or nightly for visibility.", ] ) (output_dir / "summary.md").write_text("\n".join(markdown_lines) + "\n", encoding="utf-8") From ae5600688518ab0ed2c2e1cb71cac11921c45748 Mon Sep 17 00:00:00 2001 From: komalmahale Date: Mon, 18 May 2026 11:06:25 +0200 Subject: [PATCH 7/8] ci: seed coverage and clang caches from main pushes --- .github/workflows/coverage_report.yml | 3 ++- .github/workflows/hybrid_quality_demo.yml | 6 ++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/.github/workflows/coverage_report.yml b/.github/workflows/coverage_report.yml index fbd3f7282..7939133e5 100644 --- a/.github/workflows/coverage_report.yml +++ b/.github/workflows/coverage_report.yml @@ -61,8 +61,9 @@ jobs: with: bazelisk-cache: true disk-cache: "coverage_report" + disk-cache-key: "main" repository-cache: true - cache-save: ${{ github.event_name == 'merge_group' }} + cache-save: ${{ github.ref == 'refs/heads/main' }} - name: Allow linux-sandbox uses: ./actions/unblock_user_namespace_for_linux_sandbox diff --git a/.github/workflows/hybrid_quality_demo.yml b/.github/workflows/hybrid_quality_demo.yml index 9cbf42c2c..e55927dc7 100644 --- a/.github/workflows/hybrid_quality_demo.yml +++ b/.github/workflows/hybrid_quality_demo.yml @@ -14,6 +14,8 @@ name: Hybrid Quality Demo on: + push: + branches: [main] pull_request: types: [opened, reopened, synchronize] workflow_dispatch: @@ -43,7 +45,7 @@ jobs: coverage: name: Coverage report - if: ${{ github.event_name == 'pull_request' || github.event_name == 'schedule' || (github.event_name == 'workflow_dispatch' && github.event.inputs.run_nightly_checks == 'true') }} + if: ${{ github.event_name == 'pull_request' || github.event_name == 'push' || github.event_name == 'schedule' || (github.event_name == 'workflow_dispatch' && github.event.inputs.run_nightly_checks == 'true') }} needs: pr_checks uses: ./.github/workflows/coverage_report.yml @@ -61,7 +63,7 @@ jobs: clang_tidy: name: Clang-Tidy analysis - if: ${{ github.event_name == 'pull_request' || github.event_name == 'schedule' || (github.event_name == 'workflow_dispatch' && github.event.inputs.run_nightly_checks == 'true') }} + if: ${{ github.event_name == 'pull_request' || github.event_name == 'push' || github.event_name == 'schedule' || (github.event_name == 'workflow_dispatch' && github.event.inputs.run_nightly_checks == 'true') }} needs: pr_checks uses: ./.github/workflows/clang_tidy_analysis.yml From fe28f1a29800ad2f84c965cf5a35f675c2de4e82 Mon Sep 17 00:00:00 2001 From: komalmahale Date: Mon, 18 May 2026 11:24:29 +0200 Subject: [PATCH 8/8] ci: add no-cache job variants to measure cache effectiveness --- .github/workflows/clang_tidy_analysis.yml | 16 +++++++- .github/workflows/coverage_report.yml | 10 ++++- .github/workflows/hybrid_quality_demo.yml | 30 +++++++++++++- tools/ci/generate_hybrid_quality_dashboard.py | 39 +++++++++++-------- 4 files changed, 73 insertions(+), 22 deletions(-) diff --git a/.github/workflows/clang_tidy_analysis.yml b/.github/workflows/clang_tidy_analysis.yml index 2b9274005..96dac341a 100644 --- a/.github/workflows/clang_tidy_analysis.yml +++ b/.github/workflows/clang_tidy_analysis.yml @@ -15,11 +15,23 @@ name: Clang-Tidy Analysis on: workflow_call: + inputs: + use_cache: + description: Whether to use Bazel disk cache. + required: false + type: boolean + default: true outputs: duration-seconds: description: Runtime of the clang-tidy check in seconds. value: ${{ jobs.clang_tidy.outputs.duration-seconds }} workflow_dispatch: + inputs: + use_cache: + description: Whether to use Bazel disk cache. + required: false + type: boolean + default: true permissions: contents: read @@ -56,14 +68,14 @@ jobs: disk-cache: "clang_tidy_analysis" disk-cache-key: "main" repository-cache: true - cache-save: ${{ github.ref == 'refs/heads/main' }} + cache-save: ${{ github.ref == 'refs/heads/main' && (inputs.use_cache == true || inputs.use_cache == '') }} - name: Allow linux-sandbox uses: ./actions/unblock_user_namespace_for_linux_sandbox - name: Run clang-tidy analysis run: | - bazel build //... --aspects=//:tools/lint/linters.bzl%clang_tidy_aspect + bazel build //... --aspects=//:tools/lint/linters.bzl%clang_tidy_aspect ${{ (inputs.use_cache == false) && '--disk_cache=' || '' }} - name: End timer if: ${{ always() }} diff --git a/.github/workflows/coverage_report.yml b/.github/workflows/coverage_report.yml index 7939133e5..76a6e92da 100644 --- a/.github/workflows/coverage_report.yml +++ b/.github/workflows/coverage_report.yml @@ -14,6 +14,12 @@ name: Coverage Report on: workflow_call: + inputs: + use_cache: + description: Whether to use Bazel disk cache. + required: false + type: boolean + default: true outputs: artifact-name: description: 'Name of the coverage report artifact' @@ -63,14 +69,14 @@ jobs: disk-cache: "coverage_report" disk-cache-key: "main" repository-cache: true - cache-save: ${{ github.ref == 'refs/heads/main' }} + cache-save: ${{ github.ref == 'refs/heads/main' && (inputs.use_cache == true || inputs.use_cache == '') }} - name: Allow linux-sandbox uses: ./actions/unblock_user_namespace_for_linux_sandbox - name: Run Unit Test with Coverage for C++ run: | - bazel coverage //... --build_tests_only + bazel coverage //... --build_tests_only ${{ (inputs.use_cache == false) && '--disk_cache=' || '' }} - name: Generate HTML Coverage Report # FIXME: "--ignore-errors category,inconsistent" is a workaround to cope with gcov messing up hit counts because of internal data races diff --git a/.github/workflows/hybrid_quality_demo.yml b/.github/workflows/hybrid_quality_demo.yml index e55927dc7..5893c811f 100644 --- a/.github/workflows/hybrid_quality_demo.yml +++ b/.github/workflows/hybrid_quality_demo.yml @@ -44,10 +44,20 @@ jobs: run_all_configurations: false coverage: - name: Coverage report + name: Coverage report (with cache) if: ${{ github.event_name == 'pull_request' || github.event_name == 'push' || github.event_name == 'schedule' || (github.event_name == 'workflow_dispatch' && github.event.inputs.run_nightly_checks == 'true') }} needs: pr_checks uses: ./.github/workflows/coverage_report.yml + with: + use_cache: true + + coverage_no_cache: + name: Coverage report (no cache) + if: ${{ github.event_name == 'pull_request' || github.event_name == 'schedule' || (github.event_name == 'workflow_dispatch' && github.event.inputs.run_nightly_checks == 'true') }} + needs: pr_checks + uses: ./.github/workflows/coverage_report.yml + with: + use_cache: false thread_sanitizer: name: Thread sanitizer @@ -62,10 +72,20 @@ jobs: uses: ./.github/workflows/address_undefined_behavior_leak_sanitizer.yml clang_tidy: - name: Clang-Tidy analysis + name: Clang-Tidy analysis (with cache) if: ${{ github.event_name == 'pull_request' || github.event_name == 'push' || github.event_name == 'schedule' || (github.event_name == 'workflow_dispatch' && github.event.inputs.run_nightly_checks == 'true') }} needs: pr_checks uses: ./.github/workflows/clang_tidy_analysis.yml + with: + use_cache: true + + clang_tidy_no_cache: + name: Clang-Tidy analysis (no cache) + if: ${{ github.event_name == 'pull_request' || github.event_name == 'schedule' || (github.event_name == 'workflow_dispatch' && github.event.inputs.run_nightly_checks == 'true') }} + needs: pr_checks + uses: ./.github/workflows/clang_tidy_analysis.yml + with: + use_cache: false dashboard: name: Generate quality dashboard with timing @@ -73,9 +93,11 @@ jobs: needs: - pr_checks - coverage + - coverage_no_cache - thread_sanitizer - address_sanitizer - clang_tidy + - clang_tidy_no_cache runs-on: ubuntu-24.04 permissions: contents: read @@ -87,11 +109,15 @@ jobs: env: PR_CHECKS_RESULT: ${{ needs.pr_checks.result }} COVERAGE_RESULT: ${{ needs.coverage.result || 'skipped' }} + COVERAGE_NO_CACHE_RESULT: ${{ needs.coverage_no_cache.result || 'skipped' }} THREAD_SANITIZER_RESULT: ${{ needs.thread_sanitizer.result || 'skipped' }} ADDRESS_SANITIZER_RESULT: ${{ needs.address_sanitizer.result || 'skipped' }} CLANG_TIDY_RESULT: ${{ needs.clang_tidy.result || 'skipped' }} + CLANG_TIDY_NO_CACHE_RESULT: ${{ needs.clang_tidy_no_cache.result || 'skipped' }} CLANG_TIDY_DURATION_SECONDS: ${{ needs.clang_tidy.outputs.duration-seconds || '' }} + CLANG_TIDY_NO_CACHE_DURATION_SECONDS: ${{ needs.clang_tidy_no_cache.outputs.duration-seconds || '' }} COVERAGE_DURATION_SECONDS: ${{ needs.coverage.outputs.duration-seconds || '' }} + COVERAGE_NO_CACHE_DURATION_SECONDS: ${{ needs.coverage_no_cache.outputs.duration-seconds || '' }} COVERAGE_ARTIFACT_NAME: ${{ needs.coverage.outputs.artifact-name }} REPOSITORY_NAME: ${{ github.repository }} RUN_ID: ${{ github.run_id }} diff --git a/tools/ci/generate_hybrid_quality_dashboard.py b/tools/ci/generate_hybrid_quality_dashboard.py index 729bbf34f..b4dfb9c5c 100644 --- a/tools/ci/generate_hybrid_quality_dashboard.py +++ b/tools/ci/generate_hybrid_quality_dashboard.py @@ -38,14 +38,15 @@ def format_duration(duration_value: str) -> str: return f"{minutes}m {remaining_seconds}s ({seconds}s)" -def render_markdown_row(name: str, status: str, duration: str, notes: str) -> str: - return f"| {name} | {STATUS_LABELS[status]} | {duration} | {notes} |" +def render_markdown_row(name: str, status: str, duration: str, duration_no_cache: str, notes: str) -> str: + return f"| {name} | {STATUS_LABELS[status]} | {duration} | {duration_no_cache} | {notes} |" -def render_html_row(name: str, status: str, duration: str, notes: str) -> str: +def render_html_row(name: str, status: str, duration: str, duration_no_cache: str, notes: str) -> str: safe_name = html.escape(name) safe_notes = html.escape(notes) safe_duration = html.escape(duration) + safe_duration_no_cache = html.escape(duration_no_cache) label = html.escape(STATUS_LABELS[status]) color = STATUS_COLORS[status] return ( @@ -53,6 +54,7 @@ def render_html_row(name: str, status: str, duration: str, notes: str) -> str: f"{safe_name}" f"{label}" f"{safe_duration}" + f"{safe_duration_no_cache}" f"{safe_notes}" "" ) @@ -73,31 +75,36 @@ def main() -> int: "Fast PR checks", normalize_status(os.environ.get("PR_CHECKS_RESULT", "skipped")), "-", + "-", "Build and unit tests on the default host configuration.", ), ( - "Clang-Tidy analysis", + "Clang-Tidy (with cache)", normalize_status(os.environ.get("CLANG_TIDY_RESULT", "skipped")), format_duration(os.environ.get("CLANG_TIDY_DURATION_SECONDS", "")), - "Clang static code analysis and linting.", + format_duration(os.environ.get("CLANG_TIDY_NO_CACHE_DURATION_SECONDS", "")), + "Clang static code analysis and linting — using Bazel disk cache.", ), ( - "Coverage report", + "Coverage (with cache)", normalize_status(os.environ.get("COVERAGE_RESULT", "skipped")), format_duration(os.environ.get("COVERAGE_DURATION_SECONDS", "")), - "Code coverage analysis and reporting." + format_duration(os.environ.get("COVERAGE_NO_CACHE_DURATION_SECONDS", "")), + "Code coverage analysis and reporting — using Bazel disk cache." + (f" Artifact: {coverage_artifact_name}." if coverage_artifact_name else ""), ), ( "Thread sanitizer", normalize_status(os.environ.get("THREAD_SANITIZER_RESULT", "skipped")), "-", + "-", "Nightly thread sanitizer run.", ), ( "Address/UB/leak sanitizer", normalize_status(os.environ.get("ADDRESS_SANITIZER_RESULT", "skipped")), "-", + "-", "Nightly address, undefined behavior, and leak sanitizer run.", ), ] @@ -110,11 +117,11 @@ def main() -> int: f"- Event: `{event_name}`", f"- Run: `{run_id}`", "", - "| Check | Status | Runtime | Notes |", - "| --- | --- | --- | --- |", + "| Check | Status | Runtime (cached) | Runtime (no cache) | Notes |", + "| --- | --- | --- | --- | --- |", ] markdown_lines.extend( - render_markdown_row(name, status, duration, notes) for name, status, duration, notes in checks + render_markdown_row(name, status, duration, duration_no_cache, notes) for name, status, duration, duration_no_cache, notes in checks ) markdown_lines.extend( [ @@ -129,15 +136,15 @@ def main() -> int: "", "## Measured Runtime", "", - "| Check | Status | Runtime |", - "| --- | --- | --- |", + "| Check | Status | Runtime (cached) | Runtime (no cache) |", + "| --- | --- | --- | --- |", ] - for name, status, duration, _ in checks: - timing_lines.append(f"| {name} | {STATUS_LABELS[status]} | {duration} |") + for name, status, duration, duration_no_cache, _ in checks: + timing_lines.append(f"| {name} | {STATUS_LABELS[status]} | {duration} | {duration_no_cache} |") (output_dir / "timing.txt").write_text("\n".join(timing_lines) + "\n", encoding="utf-8") html_rows = "\n".join( - render_html_row(name, status, duration, notes) for name, status, duration, notes in checks + render_html_row(name, status, duration, duration_no_cache, notes) for name, status, duration, duration_no_cache, notes in checks ) html_document = f""" @@ -163,7 +170,7 @@ def main() -> int:

This dashboard shows measured runtime for quality checks.

- + {html_rows}
CheckStatusRuntimeNotes
CheckStatusRuntime (cached)Runtime (no cache)Notes