Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
fcb0fde
workflow/: enable workflow_call for clag-tidy to be called from night…
ahmed0mousa Jun 2, 2026
ae8e146
workflow: skip Fail check on nightly job
ahmed0mousa Jun 2, 2026
23220f2
workflow: invoke clag-tidy from nightly quality
ahmed0mousa Jun 2, 2026
b5b4680
docs: add clag-tidy to quality reports
ahmed0mousa Jun 2, 2026
fd93974
quality: add clag-tidy to dashborad Jinja2 HTML template
ahmed0mousa Jun 2, 2026
2dbfa50
quality: add clag-tidy to generate dashboard script
ahmed0mousa Jun 2, 2026
7d60bad
workflow: introduce codeql workflow
ahmed0mousa Jun 2, 2026
500ea75
workflow: add codeql run to nightly quality
ahmed0mousa Jun 2, 2026
3cadb78
bazel: add clang-tidy to the generated quality links
ahmed0mousa Jun 2, 2026
afa4ed3
bazel: add codeql result to the generated quality links
ahmed0mousa Jun 2, 2026
c34bbad
docs: add codeQL to findings to quality report
ahmed0mousa Jun 2, 2026
b43559f
quality: add codeQL to dashboard Jinja2 HTML template
ahmed0mousa Jun 2, 2026
2ad15b7
quality: add codeql to the generated dashboard
ahmed0mousa Jun 2, 2026
1d0dbd4
quality: github run download, places the artifact contents into <dir>…
ahmed0mousa Jun 2, 2026
7ccce7a
workflow: using if: always() to always writes the output regardless o…
ahmed0mousa Jun 2, 2026
35e4bb0
workflow: prevents set -e from failing the step
ahmed0mousa Jun 2, 2026
3bc10ed
workflow: rationalize quality pipeline consistency and correctness
ahmed0mousa Jun 2, 2026
466c6dd
quality: add collect_codeql_findings.sh script
ahmed0mousa Jun 2, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 33 additions & 1 deletion .github/workflows/clang_tidy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,20 @@ on:
branches: [main]
merge_group:
types: [checks_requested]
workflow_call:
outputs:
artifact-name:
description: "Name of the clang-tidy findings artifact"
value: ${{ jobs.clang-tidy.outputs.artifact-name }}
errors:
description: "Total clang-tidy error count"
value: ${{ jobs.clang-tidy.outputs.errors }}
warnings:
description: "Total clang-tidy warning count"
value: ${{ jobs.clang-tidy.outputs.warnings }}
conclusion:
description: "Job conclusion: success or failure"
value: ${{ jobs.clang-tidy.outputs.conclusion }}

permissions:
contents: read
Expand All @@ -48,6 +62,11 @@ env:
jobs:
clang-tidy:
runs-on: ubuntu-24.04
outputs:
artifact-name: clang-tidy-findings
errors: ${{ steps.findings.outputs.errors }}
warnings: ${{ steps.findings.outputs.warnings }}
conclusion: ${{ steps.set-conclusion.outputs.conclusion }}

steps:
- name: Checkout repository
Expand Down Expand Up @@ -157,11 +176,24 @@ jobs:
if-no-files-found: ignore
retention-days: 7

# Clang-tidy findings are always collected and uploaded as an artifact,
# but they should only block merging when a developer is actively
# submitting code — not during nightly reporting runs.
- name: Fail check if clang-tidy errors found in changed files
if: steps.findings.outputs.errors_blocking != '0'
if: (github.event_name == 'pull_request' || github.event_name == 'push' || github.event_name == 'merge_group') && steps.findings.outputs.errors_blocking != '0'
run: |
echo "::error::Clang-tidy found ${{ steps.findings.outputs.errors_blocking }} error(s) in changed files. See the 'clang-tidy-findings' artifact for details."
echo ""
echo "=== Clang-tidy errors ==="
cat clang_tidy_errors.txt || true
exit 1

- name: Set conclusion
id: set-conclusion
if: always()
run: |
if [[ "${{ steps.run-clang-tidy.outcome }}" == "success" ]]; then
echo "conclusion=success" >> $GITHUB_OUTPUT
else
echo "conclusion=failure" >> $GITHUB_OUTPUT
fi
114 changes: 114 additions & 0 deletions .github/workflows/codeql.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
# *******************************************************************************
# 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
# *******************************************************************************

# Workflow: CodeQL / MISRA C++ static analysis (nightly reporting).
#
# Runs the Bazel-managed CodeQL analysis against all C++ targets:
# bazel run //quality/static_analysis:codeql_lint -- --target //...
#
# Produces a SARIF report and a CSV summary. Both are uploaded as an artifact
# and the CSV is parsed to expose error/warning counts to the caller.
#
# This workflow is designed to be called from nightly_quality.yml via
# workflow_call. It does NOT gate pull requests (CodeQL analysis is too slow
# and resource-intensive to run on every PR).

name: CodeQL Analysis

on:
workflow_call:
outputs:
artifact-name:
description: 'Name of the CodeQL findings artifact'
value: ${{ jobs.codeql-analysis.outputs.artifact-name }}
errors:
description: 'Total CodeQL error count'
value: ${{ jobs.codeql-analysis.outputs.errors }}
warnings:
description: 'Total CodeQL warning count'
value: ${{ jobs.codeql-analysis.outputs.warnings }}
conclusion:
description: 'Job conclusion: success or failure'
value: ${{ jobs.codeql-analysis.outputs.conclusion }}

permissions:
contents: read

env:
ANDROID_HOME: ""
ANDROID_SDK_ROOT: ""
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true

jobs:
codeql-analysis:
runs-on: ubuntu-24.04
outputs:
artifact-name: codeql-findings
errors: ${{ steps.findings.outputs.errors }}
warnings: ${{ steps.findings.outputs.warnings }}
conclusion: ${{ steps.set-conclusion.outputs.conclusion }}

steps:
- 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: castler/setup-bazel@cache-optimized
with:
bazelisk-cache: true
disk-cache: "codeql"
repository-cache: true
cache-optimized: true
cache-save: ${{ github.ref == 'refs/heads/main' }}

- name: Allow linux-sandbox
uses: ./actions/unblock_user_namespace_for_linux_sandbox

- name: Run CodeQL analysis via Bazel
id: run-codeql
# continue-on-error so we can collect findings and upload the artifact
# even when the Bazel invocation exits non-zero.
continue-on-error: true
run: |
bazel run //quality/static_analysis:codeql_lint -- --target //...

- name: Collect findings
id: findings
run: |
bash quality/scripts/collect_codeql_findings.sh

- name: Upload CodeQL findings as artifact
if: always()
uses: actions/upload-artifact@v4
with:
name: codeql-findings
path: |
codeql_findings.sarif
codeql_findings.csv
if-no-files-found: ignore
retention-days: 7

- name: Set conclusion
id: set-conclusion
if: always()
run: |
if [[ "${{ steps.run-codeql.outcome }}" == "success" ]]; then
echo "conclusion=success" >> $GITHUB_OUTPUT
else
echo "conclusion=failure" >> $GITHUB_OUTPUT
fi
2 changes: 1 addition & 1 deletion .github/workflows/coverage_report.yml
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,6 @@ jobs:

- name: Run Unit Test with Coverage for C++
id: run-coverage
continue-on-error: true
run: |
bazel coverage //... --build_tests_only

Expand All @@ -78,6 +77,7 @@ jobs:

- name: Set conclusion
id: set-conclusion
if: always()
run: |
if [[ "${{ steps.run-coverage.outcome }}" == "success" ]]; then
echo "conclusion=success" >> $GITHUB_OUTPUT
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/deploy_docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ jobs:
fi
echo "Documentation built successfully"
echo "Files generated:"
find docs_output -type f | head -20
find docs_output -type f | head -20 || true # head closes the pipe early, causing find to get SIGPIPE. prevents set -e from failing the step

- name: Upload docs to release
if: startsWith(github.ref, 'refs/tags/v')
Expand Down
80 changes: 53 additions & 27 deletions .github/workflows/nightly_quality.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ name: Nightly Quality Jobs

on:
schedule:
- cron: '0 0 * * *' # every day at midnight UTC
- cron: "0 0 * * *" # every day at midnight UTC
workflow_dispatch:

permissions:
Expand All @@ -49,11 +49,27 @@ jobs:
permissions:
contents: read

# --------------------------------------------------------------------
# Quality job 2: Clang-Tidy
# --------------------------------------------------------------------
run-clang-tidy:
uses: ./.github/workflows/clang_tidy.yml
permissions:
contents: read

# --------------------------------------------------------------------
# Quality job 3: CodeQL / MISRA C++ static analysis
# --------------------------------------------------------------------
run-codeql:
uses: ./.github/workflows/codeql.yml
permissions:
contents: read

# --------------------------------------------------------------------
# Collect results, build the dashboard, upload artifact for docs.yml
# --------------------------------------------------------------------
deploy-quality-reports:
needs: [run-coverage]
needs: [run-coverage, run-clang-tidy, run-codeql]
# Always run even if individual quality jobs fail, so the dashboard
# still reflects which jobs passed and which failed.
if: always()
Expand All @@ -75,53 +91,63 @@ jobs:
cache-save: ${{ github.ref == 'refs/heads/main' }}

- name: Allow linux-sandbox
id: setup
uses: ./actions/unblock_user_namespace_for_linux_sandbox

# ------------------------------------------------------------------
# Determine job conclusions (needs.*.result = success|failure|skipped|cancelled)
# ------------------------------------------------------------------
- name: Resolve job conclusions
id: conclusions
run: |
map_conclusion() {
case "$1" in
success) echo "success" ;;
failure) echo "failure" ;;
*) echo "skipped" ;;
esac
}
echo "coverage=$(map_conclusion '${{ needs.run-coverage.result }}')" >> $GITHUB_OUTPUT

# ------------------------------------------------------------------
# Download artifacts (only if the upstream job succeeded)
# Download Coverage artifacts (only if the upstream job succeeded)
# ------------------------------------------------------------------
- name: Download coverage artifact
if: needs.run-coverage.result == 'success'
id: download-coverage
if: needs.run-coverage.outputs.conclusion == 'success'
uses: actions/download-artifact@v4
with:
name: ${{ needs.run-coverage.outputs.artifact-name }}
path: /tmp/coverage_zip

# ------------------------------------------------------------------
# Extract coverage artifact to a known location for the dashboard generator
# ------------------------------------------------------------------
- name: Extract coverage HTML report
if: needs.run-coverage.result == 'success'
if: needs.run-coverage.outputs.conclusion == 'success' && steps.download-coverage.outcome == 'success'
run: |
bash quality/scripts/extract_coverage_artifact.sh \
/tmp/coverage_zip \
"${GITHUB_WORKSPACE}/_quality/coverage"

# ------------------------------------------------------------------
# Generate coverage KPI dashboard via generate_dashboard py_binary
# Download clang-tidy findings artifact
# ------------------------------------------------------------------
- name: Download clang-tidy findings
if: needs.run-clang-tidy.outputs.conclusion == 'success'
uses: actions/download-artifact@v4
with:
name: ${{ needs.run-clang-tidy.outputs.artifact-name }}
path: /tmp/clang_tidy

# ------------------------------------------------------------------
# Download CodeQL findings artifact
# ------------------------------------------------------------------
- name: Download CodeQL findings
if: needs.run-codeql.outputs.conclusion == 'success'
uses: actions/download-artifact@v4
with:
name: ${{ needs.run-codeql.outputs.artifact-name }}
path: /tmp/codeql

# ------------------------------------------------------------------
# Generate coverage KPI dashboard via generate_dashboard py_binary
# ------------------------------------------------------------------
- name: Generate quality dashboard
if: always() && steps.conclusions.outcome == 'success'
if: always() && steps.setup.outcome == 'success'
run: |
# The LCOV .dat file is inside the extracted coverage zip at a known
# path; pass it unconditionally — generate_dashboard handles the
# case where the file is absent (returns empty coverage data).
# Pass LCOV data, clang-tidy findings, and CodeQL findings, generate_dashboard handles
# absent files gracefully (shows N/A for that metric).
bazel run //quality/dashboard:generate_dashboard -- \
--lcov /tmp/coverage_zip/extracted/artifacts/coverage_report.dat \
--html "${GITHUB_WORKSPACE}/_quality/index.html" \
--clang-tidy /tmp/clang_tidy/clang_tidy_findings.txt \
--codeql /tmp/codeql/codeql_findings.csv \
--html _quality/index.html \
--github-summary

echo "Dashboard generated. Contents of _quality/:"
Expand All @@ -132,7 +158,7 @@ jobs:
# pick up and deploy as part of the unified Sphinx site.
# ------------------------------------------------------------------
- name: Upload quality reports artifact
if: always() && steps.conclusions.outcome == 'success'
if: always() && steps.setup.outcome == 'success'
uses: actions/upload-artifact@v4
with:
name: nightly-quality-reports
Expand Down
18 changes: 17 additions & 1 deletion bazel/rules/generate_quality_links.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -40,13 +40,19 @@ def _generate_quality_links_impl(ctx):
# quality reports are published alongside the latest/ docs
coverage_ref = "`Coverage report <quality/coverage/index.html>`__"
dashboard_ref = "`Quality Dashboard <quality/index.html>`__"
clang_tidy_ref = "`Clang-Tidy report <quality/clang_tidy_findings.txt>`__"
codeql_ref = "`CodeQL report <quality/codeql_findings.sarif>`__"
elif docs_version and docs_base_url:
# versioned release — quality reports only live at latest/
latest = docs_base_url + "/latest"
coverage_ref = ("`Coverage report (latest) <" + latest +
"/quality/coverage/index.html>`__")
dashboard_ref = ("`Quality Dashboard (latest) <" + latest +
"/quality/index.html>`__")
clang_tidy_ref = ("`Clang-Tidy report (latest) <" + latest +
"/quality/clang_tidy_findings.txt>`__")
codeql_ref = ("`CodeQL report (latest) <" + latest +
"/quality/codeql_findings.sarif>`__")
else:
# local build — no published reports; show the equivalent bazel command
coverage_ref = (
Expand All @@ -56,10 +62,20 @@ def _generate_quality_links_impl(ctx):
dashboard_ref = (
"*local build* — dashboard only available on GitHub Pages"
)
clang_tidy_ref = (
"*local build* — run " +
"``bazel test --config=clang-tidy //...``"
)
codeql_ref = (
"*local build* — run " +
"``bazel run //quality/static_analysis:codeql_lint -- --target //...``"
)

content = (
".. |coverage_report_link| replace:: " + coverage_ref + "\n" +
".. |quality_dashboard_link| replace:: " + dashboard_ref + "\n"
".. |quality_dashboard_link| replace:: " + dashboard_ref + "\n" +
".. |clang_tidy_report_link| replace:: " + clang_tidy_ref + "\n" +
".. |codeql_report_link| replace:: " + codeql_ref + "\n"
)

output = ctx.actions.declare_file(ctx.label.name + ".rst")
Expand Down
6 changes: 6 additions & 0 deletions docs/sphinx/quality_reports.rst
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,12 @@ nightly run of the `Nightly Quality Jobs`_ workflow.
* - Coverage
- Line, function, and branch coverage from C++ unit tests (gcov/lcov)
- |coverage_report_link|
* - Clang-Tidy
- Static analysis findings (errors and warnings) across all C++ targets
- |clang_tidy_report_link|
* - CodeQL
- MISRA C++ static analysis findings (SARIF format)
- |codeql_report_link|

|quality_dashboard_link|

Expand Down
Loading
Loading