From 0fc8b0a5466f0d3f8b907dcb3af5e5aa9c74784c Mon Sep 17 00:00:00 2001 From: komal mahale Date: Thu, 16 Jul 2026 23:02:08 +0530 Subject: [PATCH 1/2] feat: display CodeQL MISRA findings on nightly quality dashboard Enable the nightly quality dashboard to surface CodeQL MISRA compliance reports generated in CI, and fix the CodeQL CI job so the reports are produced and uploaded reliably. CI fixes (.github/workflows/_codeql.yml, quality/static_analysis/codeql_lint.py): - Resolve the CodeQL analysis_report binary path correctly. - Trigger a bazel cquery fetch of @codeql_coding_standards before searching for the pack root, so the pack root discovery reliably finds cpp/common/src in the bazel external cache. - Add the CodeQL bin directory to PATH so analysis_report can find its dependencies. - Make MISRA report generation/upload non-fatal, so a single report failure doesn't fail the whole workflow. Dashboard wiring (.github/workflows/nightly_quality.yml, bazel/rules/generate_quality_links.bzl, quality/dashboard/*, quality/scripts/bundle_quality_reports.sh, quality/quality.md, docs/sphinx/quality_reports.rst): - Copy CodeQL MISRA reports into the quality output directory. - Wire generated report links into the dashboard and docs. --- .github/workflows/_codeql.yml | 66 ++++++++++++++++++++++ .github/workflows/nightly_quality.yml | 24 ++++++-- bazel/rules/generate_quality_links.bzl | 56 +++++++++---------- docs/sphinx/quality_reports.rst | 14 +++-- quality/dashboard/dashboard.html.j2 | 2 - quality/dashboard/generate_dashboard.py | 1 + quality/quality.md | 5 ++ quality/scripts/bundle_quality_reports.sh | 2 + quality/static_analysis/codeql_lint.py | 68 +++++++++-------------- 9 files changed, 155 insertions(+), 83 deletions(-) diff --git a/.github/workflows/_codeql.yml b/.github/workflows/_codeql.yml index 527365f9e..8b7fd7cea 100644 --- a/.github/workflows/_codeql.yml +++ b/.github/workflows/_codeql.yml @@ -49,10 +49,66 @@ jobs: if: inputs.fetch-only == 'true' run: bazel build --nobuild //... + - name: Install CodeQL query pack dependencies + if: inputs.fetch-only != 'true' + run: | + set -euo pipefail + + CODEQL_BIN_REL=$(bazel cquery @codeql_bundle//:codeql_cli --output=files | head -n1) + # This cquery call also forces Bazel to fetch/materialize the + # codeql_coding_standards external repo so it exists on disk below. + bazel cquery @codeql_coding_standards//:analysis_report --output=files >/dev/null + BAZEL_OUTPUT_BASE=$(bazel info output_base) + + if [[ "$CODEQL_BIN_REL" = /* ]]; then + CODEQL_BIN="$CODEQL_BIN_REL" + else + CODEQL_BIN="${GITHUB_WORKSPACE}/${CODEQL_BIN_REL}" + fi + + # Try to locate the actual CodeQL executable via Bazel external repo cache + CODEQL_REPO=$(echo "$CODEQL_BIN_REL" | sed -n 's|.*external/\([^/]*\)/.*|\1|p') + if [[ -n "$CODEQL_REPO" ]]; then + CANDIDATE_CODEQL="${BAZEL_OUTPUT_BASE}/external/${CODEQL_REPO}/codeql/codeql" + if [[ -x "$CANDIDATE_CODEQL" ]]; then + CODEQL_BIN="$CANDIDATE_CODEQL" + fi + fi + + find_pack_root() { + local bazel_output_base="$1" + + # Direct search for codeql_coding_standards cpp/common/src in the bazel external cache + # This is more reliable than trying to navigate from the report_bin path + local pack_root + pack_root=$(find "${bazel_output_base}/external" -type d -path "*codeql_coding_standards/cpp/common/src" -print -quit 2>/dev/null) + + if [[ -n "$pack_root" && -d "$pack_root" ]]; then + echo "$pack_root" + return 0 + fi + + return 1 + } + + if ! PACK_ROOT=$(find_pack_root "$BAZEL_OUTPUT_BASE"); then + echo "::error::Unable to locate CodeQL pack root (cpp/common/src)" + exit 1 + fi + + if [[ ! -x "${CODEQL_BIN}" ]]; then + echo "::error::CodeQL binary not found or not executable" + exit 1 + fi + + "${CODEQL_BIN}" pack install "${PACK_ROOT}" + - name: Run CodeQL analysis if: inputs.fetch-only != 'true' id: run-codeql run: | + set -euo pipefail + bazel run //quality/static_analysis:codeql_lint -- \ --output-dir /tmp/codeql-results \ --output-prefix codeql-nightly \ @@ -60,6 +116,7 @@ jobs: - name: Upload SARIF to GitHub Code Scanning if: inputs.fetch-only != 'true' && always() && steps.run-codeql.outcome == 'success' + continue-on-error: true uses: github/codeql-action/upload-sarif@v4 with: sarif_file: /tmp/codeql-results/codeql-nightly.sarif @@ -72,3 +129,12 @@ jobs: name: codeql-csv-results path: /tmp/codeql-results/codeql-nightly.csv retention-days: 30 + + - name: Upload MISRA compliance reports + if: inputs.fetch-only != 'true' && always() + uses: actions/upload-artifact@v4 + with: + name: codeql-misra-reports + path: /tmp/codeql-results/analysis_reports/ + retention-days: 30 + if-no-files-found: warn diff --git a/.github/workflows/nightly_quality.yml b/.github/workflows/nightly_quality.yml index 0d399ea6e..3b786f3ff 100644 --- a/.github/workflows/nightly_quality.yml +++ b/.github/workflows/nightly_quality.yml @@ -27,7 +27,7 @@ # https://eclipse-score.github.io/communication/latest/quality/index.html ← dashboard # https://eclipse-score.github.io/communication/latest/quality/coverage/index.html ← lcov HTML # https://eclipse-score.github.io/communication/latest/quality/clang_tidy_findings.txt ← raw findings -# https://eclipse-score.github.io/communication/latest/quality/codeql/index.html ← CodeQL report +# https://eclipse-score.github.io/communication/latest/quality/codeql/index.html ← CodeQL reports name: Nightly Quality Jobs @@ -164,10 +164,25 @@ jobs: if [[ -f /tmp/codeql/codeql-nightly.csv ]]; then cp /tmp/codeql/codeql-nightly.csv \ "${GITHUB_WORKSPACE}/_quality/codeql_findings.txt" - else - echo "::warning::codeql-nightly.csv not found; skipping copy." fi + # ------------------------------------------------------------------ + # Download CodeQL MISRA compliance reports artifact + # ------------------------------------------------------------------ + - name: Download CodeQL MISRA reports artifact + if: always() + continue-on-error: true + uses: actions/download-artifact@v4 + with: + name: codeql-misra-reports + path: /tmp/codeql_reports + + - name: Copy CodeQL MISRA reports into quality output + if: always() + run: | + mkdir -p "${GITHUB_WORKSPACE}/_quality/codeql" + find /tmp/codeql_reports -name '*.md' -type f -exec cp {} "${GITHUB_WORKSPACE}/_quality/codeql/" \; + # ------------------------------------------------------------------ # Generate coverage KPI dashboard via generate_dashboard py_binary # ------------------------------------------------------------------ @@ -183,9 +198,6 @@ jobs: --html "${GITHUB_WORKSPACE}/_quality/index.html" \ --github-summary - echo "Dashboard generated. Contents of _quality/:" - find "${GITHUB_WORKSPACE}/_quality" -type f | sort - # ------------------------------------------------------------------ # Upload all quality reports as a single artifact for docs.yml to # pick up and deploy as part of the unified Sphinx site. diff --git a/bazel/rules/generate_quality_links.bzl b/bazel/rules/generate_quality_links.bzl index 0213cc44e..3f690f331 100644 --- a/bazel/rules/generate_quality_links.bzl +++ b/bazel/rules/generate_quality_links.bzl @@ -36,47 +36,35 @@ def _generate_quality_links_impl(ctx): docs_version = ctx.var.get("DOCS_VERSION", "") docs_base_url = ctx.var.get("DOCS_BASE_URL", "").rstrip("/") - release_coverage_asset_url = "" - if docs_base_url.startswith("https://"): - url_without_scheme = docs_base_url[8:] - url_parts = url_without_scheme.split("/") - if len(url_parts) >= 2: - host = url_parts[0] - repo = url_parts[1] - if host.endswith(".github.io") and repo: - owner = host[:-len(".github.io")] - if owner.endswith("."): - owner = owner[:-1] - if owner: - release_coverage_asset_url = ( - "https://github.com/" + owner + "/" + repo + - "/releases/download/" + docs_version + "/" + - repo + "_coverage_report_" + docs_version + ".zip" - ) - if docs_version == "latest": # quality reports are published alongside the latest/ docs coverage_ref = "`Coverage report `__" dashboard_ref = "`Quality Dashboard `__" clang_tidy_ref = "`Clang-Tidy report `__" codeql_ref = "`CodeQL findings `__" + codeql_integrity_ref = "`Database integrity `__" + codeql_deviations_ref = "`Deviations `__" + codeql_compliance_ref = "`Compliance summary `__" + codeql_recat_ref = "`Recategorizations `__" elif docs_version and docs_base_url: - # versioned release — quality reports generally live at latest/ + # versioned release — quality reports only live at latest/ latest = docs_base_url + "/latest" - if release_coverage_asset_url: - coverage_ref = ( - "`Coverage report (release artifact) <" + - release_coverage_asset_url + ">`__" - ) - else: - coverage_ref = ("`Coverage report (latest) <" + latest + - "/quality/coverage/index.html>`__") + 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 findings (latest) <" + latest + "/quality/codeql_findings.txt>`__") + codeql_integrity_ref = ("`Database integrity (latest) <" + latest + + "/quality/codeql/database_integrity_report.md>`__") + codeql_deviations_ref = ("`Deviations (latest) <" + latest + + "/quality/codeql/deviations_report.md>`__") + codeql_compliance_ref = ("`Compliance summary (latest) <" + latest + + "/quality/codeql/guideline_compliance_summary.md>`__") + codeql_recat_ref = ("`Recategorizations (latest) <" + latest + + "/quality/codeql/guideline_recategorizations_report.md>`__") else: # local build — no published reports; show the equivalent bazel command coverage_ref = ( @@ -94,12 +82,24 @@ def _generate_quality_links_impl(ctx): "*local build* — run " + "``bazel run //quality/static_analysis:codeql_lint``" ) + codeql_report_hint = ( + "*local build* — run " + + "``bazel run //quality/static_analysis:codeql_lint``" + ) + codeql_integrity_ref = codeql_report_hint + codeql_deviations_ref = codeql_report_hint + codeql_compliance_ref = codeql_report_hint + codeql_recat_ref = codeql_report_hint content = ( ":orphan:\n\n" + ".. |coverage_report_link| replace:: " + coverage_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" + ".. |codeql_report_link| replace:: " + codeql_ref + "\n" + + ".. |codeql_integrity_report_link| replace:: " + codeql_integrity_ref + "\n" + + ".. |codeql_deviations_report_link| replace:: " + codeql_deviations_ref + "\n" + + ".. |codeql_compliance_report_link| replace:: " + codeql_compliance_ref + "\n" + + ".. |codeql_recat_report_link| replace:: " + codeql_recat_ref + "\n" ) output = ctx.actions.declare_file(ctx.label.name + ".rst") diff --git a/docs/sphinx/quality_reports.rst b/docs/sphinx/quality_reports.rst index e0152783c..0e17797c8 100644 --- a/docs/sphinx/quality_reports.rst +++ b/docs/sphinx/quality_reports.rst @@ -35,7 +35,11 @@ nightly run of the `Nightly Quality Jobs`_ workflow. - |clang_tidy_report_link| * - CodeQL - MISRA C++ compliance findings via CodeQL (codeql/misra-cpp-coding-standards) - - |codeql_report_link| + - | |codeql_report_link| + | |codeql_integrity_report_link| + | |codeql_deviations_report_link| + | |codeql_compliance_report_link| + | |codeql_recat_report_link| |quality_dashboard_link| @@ -43,9 +47,9 @@ nightly run of the `Nightly Quality Jobs`_ workflow. Quality reports are generated by the nightly CI job and published exclusively alongside the ``latest`` documentation on GitHub Pages. - In local Sphinx builds the link cells above show equivalent ``bazel run`` - commands. In versioned release docs, coverage links point to the uploaded - release artifact while the remaining quality links point to the ``latest`` - reports. + In local Sphinx builds and in versioned release archives the link cells + above show the equivalent ``bazel run`` command or a link to the + ``latest`` reports respectively. The deploy workflow controls this + automatically via the ``DOCS_VERSION`` build variable. .. _Nightly Quality Jobs: https://github.com/eclipse-score/communication/actions/workflows/nightly_quality.yml diff --git a/quality/dashboard/dashboard.html.j2 b/quality/dashboard/dashboard.html.j2 index 21324acdd..b533babea 100644 --- a/quality/dashboard/dashboard.html.j2 +++ b/quality/dashboard/dashboard.html.j2 @@ -101,8 +101,6 @@

No CodeQL data available.

{% endif %} - - {# ── KPI Trends ── #} {% if history|length >= 2 %}

Coverage Trend

diff --git a/quality/dashboard/generate_dashboard.py b/quality/dashboard/generate_dashboard.py index de44d7274..9675d3d8f 100644 --- a/quality/dashboard/generate_dashboard.py +++ b/quality/dashboard/generate_dashboard.py @@ -20,6 +20,7 @@ bazel run //quality/dashboard:generate_dashboard -- \\ --lcov /tmp/coverage_zip/extracted/artifacts/coverage_report.dat \\ --clang-tidy /tmp/clang_tidy/clang_tidy_findings.txt \\ + --codeql-csv /tmp/codeql-results/codeql-nightly.csv \\ --html _quality/index.html \\ --github-summary """ diff --git a/quality/quality.md b/quality/quality.md index 57e7edba6..2d820abd7 100644 --- a/quality/quality.md +++ b/quality/quality.md @@ -104,6 +104,11 @@ This single command automatically: 2. Generates SARIF file (JSON with 274+ findings) 3. Generates 4 Markdown reports from SARIF + database +The markdown reports are written to `bazel-out/analysis_reports/` for local runs. +When `--output-dir` is used, they are written to `/analysis_reports/` instead. +In CI, the nightly workflow copies that directory into `/tmp/codeql-results/analysis_reports/` +before publishing the artifact. + #### Generated Reports The following markdown files are automatically created in `bazel-out/analysis_reports/`: diff --git a/quality/scripts/bundle_quality_reports.sh b/quality/scripts/bundle_quality_reports.sh index c6e83f301..5e9bb308a 100644 --- a/quality/scripts/bundle_quality_reports.sh +++ b/quality/scripts/bundle_quality_reports.sh @@ -29,8 +29,10 @@ EVENT_NAME="${1:?event_name required}" WORKFLOW_RUN_ID="${2:-}" REPOSITORY="${3:?repository required}" PUBLISH_DIR="${4:-publish/latest/quality}" +CODEQL_REPORTS_DIR="${PUBLISH_DIR}/codeql" mkdir -p "${PUBLISH_DIR}" +mkdir -p "${CODEQL_REPORTS_DIR}" if [[ "${EVENT_NAME}" == "workflow_run" ]]; then # Triggered by nightly — download the fresh artifact directly. diff --git a/quality/static_analysis/codeql_lint.py b/quality/static_analysis/codeql_lint.py index b27e86636..253a478cd 100644 --- a/quality/static_analysis/codeql_lint.py +++ b/quality/static_analysis/codeql_lint.py @@ -17,8 +17,6 @@ import subprocess import datetime import shutil -import time -import glob TMP_PATH_FOR_DATABASES = "/var/tmp/codeql_databases" @@ -50,7 +48,15 @@ def create_database(code_ql_path, config_path, target, source_root, database_pat subprocess.run(f"{code_ql_path} database finalize -j=0 -- {database_path}", shell=True, check=True) -def analyze_database(code_ql_path, database_path, source_root, analysis_report_path=None, query_spec=None, output_prefix="codeql", output_dir=None): +def analyze_database( + code_ql_path, + database_path, + source_root, + analysis_report_path=None, + query_spec=None, + output_prefix="codeql", + output_dir=None, +): """Run CodeQL analysis and generate MISRA C++ compliance reports.""" output_base = output_dir or _get_bazel_info(source_root).get('output_path') os.makedirs(output_base, exist_ok=True) @@ -60,7 +66,6 @@ def analyze_database(code_ql_path, database_path, source_root, analysis_report_p csv_path = f"{output_base}/{output_prefix}.csv" # Run CodeQL analysis (generates SARIF) - print("\n Running CodeQL analysis...") subprocess.run( f"{code_ql_path} database analyze -j=0 {database_path}{query_arg} " f"--format=sarifv2.1.0 --output={sarif_path}", @@ -74,34 +79,21 @@ def analyze_database(code_ql_path, database_path, source_root, analysis_report_p # Generate reports using CodeQL analysis_report tool if analysis_report_path and os.path.exists(analysis_report_path): - print(" Generating MISRA C++ compliance reports...") try: # Make analysis_report executable and run it os.chmod(analysis_report_path, 0o755) - print(f" Using database: {database_path}") - print(f" Using SARIF: {sarif_path}") - print(f" Output directory: {output_base}") - # Prepare environment with CodeQL binary path + # Prepare environment with CodeQL binary path so analysis_report can find 'codeql' command env = os.environ.copy() - # codeql_cli is a symlink to the actual codeql binary - # Resolve the symlink to get the real directory - try: - real_codeql_path = os.path.realpath(code_ql_path) - codeql_bin_dir = os.path.dirname(real_codeql_path) - except: - codeql_bin_dir = os.path.dirname(os.path.abspath(code_ql_path)) - - # Prepend codeql directory to PATH + codeql_bin_dir = os.path.dirname(os.path.realpath(code_ql_path)) + print(f" Resolved CodeQL bin dir: {codeql_bin_dir}") + print(f" CodeQL bin dir exists: {os.path.isdir(codeql_bin_dir)}") env["PATH"] = f"{codeql_bin_dir}:{env.get('PATH', '')}" + print(f" PATH for analysis_report: {env['PATH']}") # analysis_report expects positional args: database-dir sarif-file output-dir reports_output_dir = os.path.join(output_base, "analysis_reports") - # Remove existing reports directory if it exists - if os.path.exists(reports_output_dir): - shutil.rmtree(reports_output_dir) - result = subprocess.run( [analysis_report_path, database_path, @@ -109,25 +101,18 @@ def analyze_database(code_ql_path, database_path, source_root, analysis_report_p reports_output_dir], capture_output=True, text=True, env=env) - # Check if reports were generated even if there was an error - # (analysis_report writes files before failing on some post-processing) - if os.path.exists(reports_output_dir): - report_files = glob.glob(os.path.join(reports_output_dir, "*")) - if report_files: - print(f"✓ Reports generated successfully") - print(f"\n Generated Report Files:") - for f in sorted(report_files): - file_size = os.path.getsize(f) / 1024 # KB - print(f" ✓ {os.path.basename(f)} ({file_size:.1f} KB)") - else: - # If no files, raise the error - result.check_returncode() - else: - result.check_returncode() - except subprocess.CalledProcessError as e: - print(f"⚠️ Report generation warning: {e.stderr if e.stderr else e}") - else: - print(" Report generation skipped (analysis_report tool not available)") + # Always show subprocess output for diagnostics + if result.stdout: + print(f" [analysis_report stdout]: {result.stdout.strip()}") + + if result.stderr: + print(f" [analysis_report stderr]: {result.stderr.strip()}") + if result.returncode != 0: + print(f" ⚠️ analysis_report exited with code {result.returncode}") + # Don't raise exception - allow workflow to continue + + except Exception as e: + print(f"Report generation exception: {e}") def main(): @@ -175,7 +160,6 @@ def main(): analysis_report_path=args.analysis_report_path, query_spec=args.query_spec, output_prefix=args.output_prefix, output_dir=args.output_dir) - print(f" Use this database for future report generation") def _get_action_env_extension(codeql_env): From a6e4d4117f374ee1db1302b2da14d320301b00db Mon Sep 17 00:00:00 2001 From: komal mahale Date: Fri, 17 Jul 2026 16:30:52 +0530 Subject: [PATCH 2/2] Restore visibility logging and report cleanup in CodeQL analysis - Add back print statements for analysis and report generation steps - Restore directory cleanup to prevent stale MISRA reports from persisting - Addresses reviewer feedback (@castler comments on PR #720) --- .github/workflows/_codeql.yml | 54 ------------------------- bazel/rules/generate_quality_links.bzl | 28 ++++++++++++- docs/sphinx/quality_reports.rst | 8 ++-- quality/dashboard/dashboard.html.j2 | 2 + quality/dashboard/generate_dashboard.py | 1 - quality/static_analysis/BUILD | 1 + quality/static_analysis/codeql_lint.py | 38 ++++++++++++++++- 7 files changed, 69 insertions(+), 63 deletions(-) diff --git a/.github/workflows/_codeql.yml b/.github/workflows/_codeql.yml index 8b7fd7cea..e5937b54f 100644 --- a/.github/workflows/_codeql.yml +++ b/.github/workflows/_codeql.yml @@ -49,60 +49,6 @@ jobs: if: inputs.fetch-only == 'true' run: bazel build --nobuild //... - - name: Install CodeQL query pack dependencies - if: inputs.fetch-only != 'true' - run: | - set -euo pipefail - - CODEQL_BIN_REL=$(bazel cquery @codeql_bundle//:codeql_cli --output=files | head -n1) - # This cquery call also forces Bazel to fetch/materialize the - # codeql_coding_standards external repo so it exists on disk below. - bazel cquery @codeql_coding_standards//:analysis_report --output=files >/dev/null - BAZEL_OUTPUT_BASE=$(bazel info output_base) - - if [[ "$CODEQL_BIN_REL" = /* ]]; then - CODEQL_BIN="$CODEQL_BIN_REL" - else - CODEQL_BIN="${GITHUB_WORKSPACE}/${CODEQL_BIN_REL}" - fi - - # Try to locate the actual CodeQL executable via Bazel external repo cache - CODEQL_REPO=$(echo "$CODEQL_BIN_REL" | sed -n 's|.*external/\([^/]*\)/.*|\1|p') - if [[ -n "$CODEQL_REPO" ]]; then - CANDIDATE_CODEQL="${BAZEL_OUTPUT_BASE}/external/${CODEQL_REPO}/codeql/codeql" - if [[ -x "$CANDIDATE_CODEQL" ]]; then - CODEQL_BIN="$CANDIDATE_CODEQL" - fi - fi - - find_pack_root() { - local bazel_output_base="$1" - - # Direct search for codeql_coding_standards cpp/common/src in the bazel external cache - # This is more reliable than trying to navigate from the report_bin path - local pack_root - pack_root=$(find "${bazel_output_base}/external" -type d -path "*codeql_coding_standards/cpp/common/src" -print -quit 2>/dev/null) - - if [[ -n "$pack_root" && -d "$pack_root" ]]; then - echo "$pack_root" - return 0 - fi - - return 1 - } - - if ! PACK_ROOT=$(find_pack_root "$BAZEL_OUTPUT_BASE"); then - echo "::error::Unable to locate CodeQL pack root (cpp/common/src)" - exit 1 - fi - - if [[ ! -x "${CODEQL_BIN}" ]]; then - echo "::error::CodeQL binary not found or not executable" - exit 1 - fi - - "${CODEQL_BIN}" pack install "${PACK_ROOT}" - - name: Run CodeQL analysis if: inputs.fetch-only != 'true' id: run-codeql diff --git a/bazel/rules/generate_quality_links.bzl b/bazel/rules/generate_quality_links.bzl index 3f690f331..46c7029be 100644 --- a/bazel/rules/generate_quality_links.bzl +++ b/bazel/rules/generate_quality_links.bzl @@ -36,6 +36,24 @@ def _generate_quality_links_impl(ctx): docs_version = ctx.var.get("DOCS_VERSION", "") docs_base_url = ctx.var.get("DOCS_BASE_URL", "").rstrip("/") + release_coverage_asset_url = "" + if docs_base_url.startswith("https://"): + url_without_scheme = docs_base_url[8:] + url_parts = url_without_scheme.split("/") + if len(url_parts) >= 2: + host = url_parts[0] + repo = url_parts[1] + if host.endswith(".github.io") and repo: + owner = host[:-len(".github.io")] + if owner.endswith("."): + owner = owner[:-1] + if owner: + release_coverage_asset_url = ( + "https://github.com/" + owner + "/" + repo + + "/releases/download/" + docs_version + "/" + + repo + "_coverage_report_" + docs_version + ".zip" + ) + if docs_version == "latest": # quality reports are published alongside the latest/ docs coverage_ref = "`Coverage report `__" @@ -49,8 +67,14 @@ def _generate_quality_links_impl(ctx): 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>`__") + if release_coverage_asset_url: + coverage_ref = ( + "`Coverage report (release artifact) <" + + release_coverage_asset_url + ">`__" + ) + else: + 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 + diff --git a/docs/sphinx/quality_reports.rst b/docs/sphinx/quality_reports.rst index 0e17797c8..0922561af 100644 --- a/docs/sphinx/quality_reports.rst +++ b/docs/sphinx/quality_reports.rst @@ -47,9 +47,9 @@ nightly run of the `Nightly Quality Jobs`_ workflow. Quality reports are generated by the nightly CI job and published exclusively alongside the ``latest`` documentation on GitHub Pages. - In local Sphinx builds and in versioned release archives the link cells - above show the equivalent ``bazel run`` command or a link to the - ``latest`` reports respectively. The deploy workflow controls this - automatically via the ``DOCS_VERSION`` build variable. + In local Sphinx builds the link cells above show equivalent ``bazel run`` + commands. In versioned release docs, coverage links point to the uploaded + release artifact while the remaining quality links point to the ``latest`` + reports. .. _Nightly Quality Jobs: https://github.com/eclipse-score/communication/actions/workflows/nightly_quality.yml diff --git a/quality/dashboard/dashboard.html.j2 b/quality/dashboard/dashboard.html.j2 index b533babea..21324acdd 100644 --- a/quality/dashboard/dashboard.html.j2 +++ b/quality/dashboard/dashboard.html.j2 @@ -101,6 +101,8 @@

No CodeQL data available.

{% endif %} + + {# ── KPI Trends ── #} {% if history|length >= 2 %}

Coverage Trend

diff --git a/quality/dashboard/generate_dashboard.py b/quality/dashboard/generate_dashboard.py index 9675d3d8f..de44d7274 100644 --- a/quality/dashboard/generate_dashboard.py +++ b/quality/dashboard/generate_dashboard.py @@ -20,7 +20,6 @@ bazel run //quality/dashboard:generate_dashboard -- \\ --lcov /tmp/coverage_zip/extracted/artifacts/coverage_report.dat \\ --clang-tidy /tmp/clang_tidy/clang_tidy_findings.txt \\ - --codeql-csv /tmp/codeql-results/codeql-nightly.csv \\ --html _quality/index.html \\ --github-summary """ diff --git a/quality/static_analysis/BUILD b/quality/static_analysis/BUILD index 229c73acc..cf40ab76a 100644 --- a/quality/static_analysis/BUILD +++ b/quality/static_analysis/BUILD @@ -27,6 +27,7 @@ py_binary( main = "codeql_lint.py", tags = ["local"], target_compatible_with = ["@platforms//os:linux"], + deps = ["@rules_python//python/runfiles"], ) filegroup( diff --git a/quality/static_analysis/codeql_lint.py b/quality/static_analysis/codeql_lint.py index 253a478cd..f46b70d34 100644 --- a/quality/static_analysis/codeql_lint.py +++ b/quality/static_analysis/codeql_lint.py @@ -21,6 +21,31 @@ TMP_PATH_FOR_DATABASES = "/var/tmp/codeql_databases" + +def _find_pack_root(): + """Locate the MISRA C++ query pack root (cpp/common/src) via Bazel runfiles. + + The codeql_coding_standards repo's cpp/** sources are already declared as a + `data` dependency of @codeql_coding_standards//:analysis_report, which is in + turn a `data` dependency of this py_binary. That means Bazel places them in + our own runfiles tree, so we can resolve the pack root the same way + everywhere (locally and in CI) without any manual filesystem searching. + """ + from python.runfiles import Runfiles + + runfiles = Runfiles.Create() + anchor = runfiles.Rlocation("codeql_coding_standards/cpp/common/src/qlpack.yml") + if not anchor or not os.path.exists(anchor): + raise RuntimeError("Unable to locate CodeQL pack root (cpp/common/src)") + return os.path.dirname(anchor) + + +def _install_query_pack(code_ql_path): + """Resolve the MISRA C++ query pack's own dependencies (like `npm install`).""" + pack_root = _find_pack_root() + subprocess.run(f"{code_ql_path} pack install {pack_root}", shell=True, check=True) + + def create_database(code_ql_path, config_path, target, source_root, database_path): """Create the CodeQL database: init, build with tracing, finalize.""" subprocess.run( @@ -66,6 +91,7 @@ def analyze_database( csv_path = f"{output_base}/{output_prefix}.csv" # Run CodeQL analysis (generates SARIF) + print("\n Running CodeQL analysis...") subprocess.run( f"{code_ql_path} database analyze -j=0 {database_path}{query_arg} " f"--format=sarifv2.1.0 --output={sarif_path}", @@ -79,10 +105,16 @@ def analyze_database( # Generate reports using CodeQL analysis_report tool if analysis_report_path and os.path.exists(analysis_report_path): + print(" Generating MISRA C++ compliance reports...") try: # Make analysis_report executable and run it os.chmod(analysis_report_path, 0o755) + # Remove existing reports directory if it exists + reports_output_dir = os.path.join(output_base, "analysis_reports") + if os.path.exists(reports_output_dir): + shutil.rmtree(reports_output_dir) + # Prepare environment with CodeQL binary path so analysis_report can find 'codeql' command env = os.environ.copy() codeql_bin_dir = os.path.dirname(os.path.realpath(code_ql_path)) @@ -92,7 +124,6 @@ def analyze_database( print(f" PATH for analysis_report: {env['PATH']}") # analysis_report expects positional args: database-dir sarif-file output-dir - reports_output_dir = os.path.join(output_base, "analysis_reports") result = subprocess.run( [analysis_report_path, @@ -108,7 +139,7 @@ def analyze_database( if result.stderr: print(f" [analysis_report stderr]: {result.stderr.strip()}") if result.returncode != 0: - print(f" ⚠️ analysis_report exited with code {result.returncode}") + print(f" analysis_report exited with code {result.returncode}") # Don't raise exception - allow workflow to continue except Exception as e: @@ -135,6 +166,9 @@ def main(): # Make codeql_path absolute codeql_path = os.path.abspath(args.codeql_path) if args.codeql_path else None + if codeql_path: + _install_query_pack(codeql_path) + if args.phase == "create-database": os.makedirs(os.path.dirname(args.database_path), exist_ok=True) create_database(codeql_path, args.config_path, target, source_root, args.database_path)