diff --git a/.github/workflows/deploy_docs.yml b/.github/workflows/deploy_docs.yml index 6dd11c614..cadad5775 100644 --- a/.github/workflows/deploy_docs.yml +++ b/.github/workflows/deploy_docs.yml @@ -22,6 +22,10 @@ on: branches: [main] paths: - 'docs/**' + - 'tools/generate_code_analysis.py' + - 'tools/BUILD' + - 'quality/coverage.bazelrc' + - 'quality/static_analysis/**' - '.github/workflows/deploy_docs.yml' workflow_dispatch: @@ -73,6 +77,23 @@ jobs: echo "should_deploy=true" >> "$GITHUB_OUTPUT" fi + - name: Install lcov + run: | + sudo apt-get update + sudo apt-get install -y lcov + + - name: Generate Coverage Report + run: | + bazel run //tools:generate_code_analysis -- --coverage + + - name: Generate CodeQL Report + run: | + bazel run //tools:generate_code_analysis -- --codeql + + - name: Generate Clang-Tidy Report + run: | + bazel run //tools:generate_code_analysis -- --clang-tidy + - name: Build Sphinx documentation env: DOCS_BASE_URL: "https://${{ github.repository_owner }}.github.io/${{ github.event.repository.name }}" @@ -149,6 +170,24 @@ jobs: fi done + # Inject shared CSS/JS into old versions that lack them + REPO_NAME="${{ github.event.repository.name }}" + CUSTOM_CSS="" + FLYOUT_CSS="" + FLYOUT_JS="" + for dir in publish/v*/; do + [ -d "$dir" ] || continue + while IFS= read -r -d '' f; do + if ! grep -q 'default_custom' "$f"; then + sed -i "s||${CUSTOM_CSS}\n|" "$f" + fi + if ! grep -q 'version_flyout' "$f"; then + sed -i "s||${FLYOUT_CSS}\n|" "$f" + sed -i "s|
+{message}
+Run tools/generate_code_analysis.sh to generate this report.
|${FLYOUT_JS}\n|" "$f" + fi + done < <(find "$dir" -name '*.html' -print0) + done + # stable = newest tagged version that has docs if [[ "${IS_TAG}" == "true" ]]; then rm -rf publish/stable @@ -163,6 +202,7 @@ jobs: # Shared assets mkdir -p publish/_shared/css publish/_shared/js + cp docs/sphinx/_static/css/default_custom.css publish/_shared/css/ cp docs/sphinx/_static/css/version_flyout.css publish/_shared/css/ cp docs/sphinx/_static/js/version_flyout.js publish/_shared/js/ diff --git a/bazel/toolchains/template/conf.template.py b/bazel/toolchains/template/conf.template.py index ae8c80deb..fdfaf91ab 100644 --- a/bazel/toolchains/template/conf.template.py +++ b/bazel/toolchains/template/conf.template.py @@ -73,6 +73,11 @@ # -- Options for HTML output -- html_theme = 'pydata_sphinx_theme' +html_static_path = ["_static"] +html_css_files = [ + "css/default_custom.css", +] +html_js_files = [] # Professional theme configuration inspired by modern open-source projects html_theme_options = { diff --git a/docs/sphinx/BUILD b/docs/sphinx/BUILD index b0267a098..314a76d77 100644 --- a/docs/sphinx/BUILD +++ b/docs/sphinx/BUILD @@ -26,7 +26,7 @@ sphinx_docs_library( ) # Static assets for Sphinx documentation -filegroup( +sphinx_docs_library( name = "static_assets", srcs = glob(["_static/**/*"]), ) @@ -52,6 +52,10 @@ sphinx_module( testonly = True, srcs = [ + "code_analysis.rst", + "code_analysis/clang_tidy.rst", + "code_analysis/codeql.rst", + "code_analysis/coverage.rst", "how_to_document.rst", "index.rst", "introduction.rst", @@ -59,9 +63,9 @@ sphinx_module( "safety_reports.rst", ":doxygen_xml", ":generate_api_rst", - ":static_assets", ], docs_library_deps = [ + ":static_assets", "//score/mw/com:readme_md", ], exec_compatible_with = ["@platforms//os:linux"], diff --git a/docs/sphinx/_static/codeanalysis/.gitignore b/docs/sphinx/_static/codeanalysis/.gitignore new file mode 100644 index 000000000..d6b7ef32c --- /dev/null +++ b/docs/sphinx/_static/codeanalysis/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore diff --git a/docs/sphinx/code_analysis.rst b/docs/sphinx/code_analysis.rst new file mode 100644 index 000000000..08b96b0f6 --- /dev/null +++ b/docs/sphinx/code_analysis.rst @@ -0,0 +1,15 @@ +Code Analysis Reports +===================== + +This section links to locally generated code-quality reports for coverage, +CodeQL, and clang-tidy. + +.. note:: + Run ``tools/generate_code_analysis.sh`` to generate reports locally. + +.. toctree:: + :maxdepth: 1 + + code_analysis/coverage + code_analysis/codeql + code_analysis/clang_tidy diff --git a/docs/sphinx/code_analysis/clang_tidy.rst b/docs/sphinx/code_analysis/clang_tidy.rst new file mode 100644 index 000000000..7a4f13c2c --- /dev/null +++ b/docs/sphinx/code_analysis/clang_tidy.rst @@ -0,0 +1,14 @@ +Clang-Tidy Report +================= + +clang-tidy analysis runs via ``bazel test --config=clang-tidy`` and its lint +outputs are collected into an HTML summary. + +.. note:: + Run ``tools/generate_code_analysis.sh --clang-tidy`` to generate reports locally. + +Generated HTML report: + +`Open clang-tidy report <../_static/codeanalysis/clang_tidy/index.html>`_ + +If the report has not been generated yet, the link points to a placeholder page. diff --git a/docs/sphinx/code_analysis/codeql.rst b/docs/sphinx/code_analysis/codeql.rst new file mode 100644 index 000000000..8e26afdb5 --- /dev/null +++ b/docs/sphinx/code_analysis/codeql.rst @@ -0,0 +1,14 @@ +CodeQL Report +============= + +CodeQL analysis runs via ``//quality/static_analysis:codeql_lint`` and produces +SARIF output that is converted to an HTML summary. + +.. note:: + Run ``tools/generate_code_analysis.sh --codeql`` to generate reports locally. + +Generated HTML report: + +`Open CodeQL report <../_static/codeanalysis/codeql/index.html>`_ + +If the report has not been generated yet, the link points to a placeholder page. diff --git a/docs/sphinx/code_analysis/coverage.rst b/docs/sphinx/code_analysis/coverage.rst new file mode 100644 index 000000000..a578e85ac --- /dev/null +++ b/docs/sphinx/code_analysis/coverage.rst @@ -0,0 +1,14 @@ +Coverage Report +=============== + +Coverage is generated from ``bazel coverage //...`` and rendered with +``genhtml``. + +.. note:: + Run ``tools/generate_code_analysis.sh --coverage`` to generate reports locally. + +Generated HTML report: + +`Open coverage report <../_static/codeanalysis/coverage/index.html>`_ + +If the report has not been generated yet, the link points to a placeholder page. diff --git a/docs/sphinx/index.rst b/docs/sphinx/index.rst index 1d6e7ba66..5dd8ef7ee 100644 --- a/docs/sphinx/index.rst +++ b/docs/sphinx/index.rst @@ -11,6 +11,7 @@ including the LoLa (Low Latency) implementation and Message Passing library. introduction README message_passing + code_analysis how_to_document .. toctree:: diff --git a/quality/static_analysis/static_analysis.bazelrc b/quality/static_analysis/static_analysis.bazelrc index 6257dab35..03fa9bf72 100644 --- a/quality/static_analysis/static_analysis.bazelrc +++ b/quality/static_analysis/static_analysis.bazelrc @@ -13,7 +13,7 @@ # Clang-tidy configuration # Run clang-tidy on all C++ targets with: bazel test --config=clang-tidy //... -test:clang-tidy --aspects=//:tools/lint/linters.bzl%clang_tidy_aspect +test:clang-tidy --aspects=//tools:lint/linters.bzl%clang_tidy_aspect test:clang-tidy --output_groups=+rules_lint_report # Use LLVM toolchain for clang-tidy so it can find system headers test:clang-tidy --extra_toolchains=@llvm_toolchain//:cc-toolchain-x86_64-linux diff --git a/tools/BUILD b/tools/BUILD new file mode 100644 index 000000000..2d4283f3d --- /dev/null +++ b/tools/BUILD @@ -0,0 +1,19 @@ +# ******************************************************************************* +# 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 +# ******************************************************************************* + +py_binary( + name = "generate_code_analysis", + srcs = ["generate_code_analysis.py"], + main = "generate_code_analysis.py", + visibility = ["//visibility:public"], +) diff --git a/tools/__pycache__/generate_code_analysis.cpython-312.pyc b/tools/__pycache__/generate_code_analysis.cpython-312.pyc new file mode 100644 index 000000000..a7dddb581 Binary files /dev/null and b/tools/__pycache__/generate_code_analysis.cpython-312.pyc differ diff --git a/tools/generate_code_analysis.py b/tools/generate_code_analysis.py new file mode 100644 index 000000000..f7f5e8e36 --- /dev/null +++ b/tools/generate_code_analysis.py @@ -0,0 +1,493 @@ +#!/usr/bin/env python3 +# ******************************************************************************* +# 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 +# ******************************************************************************* +"""Generate code analysis reports (coverage, CodeQL, clang-tidy) under docs/sphinx/_static/codeanalysis.""" + +import argparse +import collections +import glob +import hashlib +import html +import json +import os +import shutil +import subprocess +import sys +from pathlib import Path + +ROOT_DIR = Path( + os.environ.get("BUILD_WORKSPACE_DIRECTORY") + or Path(__file__).resolve().parent.parent +) +REPORT_ROOT = ROOT_DIR / "docs" / "sphinx" / "_static" / "codeanalysis" + +DEFAULT_TARGETS = ["//score/message_passing:client_connection_test"] + +# Maximum filename length for most filesystems +MAX_FILENAME_LEN = 200 + + +def run_bazel(*args): + """Run a bazel command from the repository root.""" + return subprocess.run( + ["bazel", *args], cwd=ROOT_DIR, capture_output=True, text=True + ) + + +def run_bazel_checked(*args): + """Run a bazel command, returning True on success.""" + result = subprocess.run(["bazel", *args], cwd=ROOT_DIR) + return result.returncode == 0 + + +def get_output_path(): + """Get the Bazel output path.""" + result = run_bazel("info", "output_path") + return result.stdout.strip() + + +def write_placeholder(dest_dir, title, message): + """Write a placeholder HTML page when a report cannot be generated.""" + os.makedirs(dest_dir, exist_ok=True) + (Path(dest_dir) / "index.html").write_text( + f""" + +
+ + +
+ + +
+