diff --git a/.clang-format b/.clang-format deleted file mode 100644 index 7707fc51..00000000 --- a/.clang-format +++ /dev/null @@ -1,10 +0,0 @@ ---- -BasedOnStyle: Google -Language: Cpp -Standard: c++17 - -# LiveKit modifications to Google style below - -ColumnLimit: 120 # more width on modern screens -SpacesBeforeTrailingComments: 1 # one space for trailing namespace comments, e.g. `} // namespace foo` (Google uses 2) -AccessModifierOffset: -2 # left-align public/protected/private with the class keyword (Google indents by 1) diff --git a/.clang-format b/.clang-format new file mode 120000 index 00000000..ce9ee47c --- /dev/null +++ b/.clang-format @@ -0,0 +1 @@ +cpp-tools/configs/.clang-format \ No newline at end of file diff --git a/.clang-tidy b/.clang-tidy deleted file mode 100644 index 2b4a891d..00000000 --- a/.clang-tidy +++ /dev/null @@ -1,45 +0,0 @@ -Checks: > - -*, - bugprone-*, - misc-const-correctness, - performance-*, - modernize-*, - readability-misleading-indentation, - readability-redundant-smartptr-get, - readability-identifier-naming, - -bugprone-easily-swappable-parameters, - -modernize-use-trailing-return-type, - -modernize-avoid-c-arrays, - -modernize-type-traits, - -modernize-use-auto, - -modernize-use-nodiscard, - -modernize-return-braced-init-list, - -performance-enum-size, - -readability-braces-around-statements, - -# These warnings have determined to be critical and are as such treated as errors -WarningsAsErrors: > - clang-analyzer-*, - bugprone-use-after-move, - bugprone-dangling-handle, - bugprone-infinite-loop, - bugprone-narrowing-conversions, - bugprone-undefined-memory-manipulation, - bugprone-move-forwarding-reference, - bugprone-incorrect-roundings, - bugprone-sizeof-expression, - bugprone-string-literal-with-embedded-nul, - bugprone-suspicious-memset-usage, - -HeaderFilterRegex: '.*/(include/livekit|src)/.*\.(h|hpp)$' -ExcludeHeaderFilterRegex: '(.*/src/tests/.*)|(.*/_deps/.*)|(.*/build-[^/]*/.*)' - -FormatStyle: file - -CheckOptions: - - key: modernize-use-nullptr.NullMacros - value: 'NULL' - - key: readability-identifier-naming.ClassCase - value: CamelCase - - key: readability-identifier-naming.MethodCase - value: camelBack diff --git a/.clang-tidy b/.clang-tidy new file mode 120000 index 00000000..9ca677b8 --- /dev/null +++ b/.clang-tidy @@ -0,0 +1 @@ +cpp-tools/configs/.clang-tidy \ No newline at end of file diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6831a9f5..eeb7adee 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -25,7 +25,7 @@ jobs: builds: ${{ steps.filter.outputs.builds }} tests: ${{ steps.filter.outputs.tests }} docs: ${{ steps.filter.outputs.docs }} - cpp_checks: ${{ steps.filter.outputs.cpp_checks }} + cpp_tools: ${{ steps.filter.outputs.cpp_tools }} rust_release_check: ${{ steps.filter.outputs.rust_release_check }} steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 @@ -70,7 +70,7 @@ jobs: - .github/workflows/ci.yml - .github/workflows/generate-docs.yml - .github/workflows/publish-docs.yml - cpp_checks: + cpp_tools: - src/** - include/** - benchmarks/** @@ -78,12 +78,15 @@ jobs: - cmake/** - CMakeLists.txt - CMakePresets.json + - cpp-tools + - cpp-tools/** - scripts/clang-format.sh - scripts/clang-tidy.sh + - scripts/install-pre-commit.sh - .clang-format - .clang-tidy + - .gitmodules - .github/workflows/ci.yml - - .github/workflows/cpp-checks.yml rust_release_check: - client-sdk-rust @@ -107,11 +110,30 @@ jobs: name: License Check uses: ./.github/workflows/license_check.yml - cpp-checks: - name: C++ Checks + cpp-tools: + name: C++ Tools needs: changes - if: ${{ needs.changes.outputs.cpp_checks == 'true' || github.event_name == 'workflow_dispatch' }} - uses: ./.github/workflows/cpp-checks.yml + if: ${{ needs.changes.outputs.cpp_tools == 'true' || github.event_name == 'workflow_dispatch' }} + uses: livekit/cpp-tools/.github/workflows/cpp-tools.yml@alan/bootstrap-cpp-tools + with: + clang_format: true + clang_tidy: true + doxygen: true + checkout_submodules: recursive + clang_format_paths: src include benchmarks + clang_tidy_install_rust: true + clang_tidy_setup_command: | + echo "CXXFLAGS=-Wno-deprecated-declarations" >> "$GITHUB_ENV" + echo "CFLAGS=-Wno-deprecated-declarations" >> "$GITHUB_ENV" + LLVM_VERSION=$(llvm-config --version | cut -d. -f1) + echo "LIBCLANG_PATH=/usr/lib/llvm-${LLVM_VERSION}/lib" >> "$GITHUB_ENV" + clang_tidy_configure_command: cmake --preset linux-release + clang_tidy_generate_command: cmake --build build-release --target livekit_proto + clang_tidy_build_dir: build-release + clang_tidy_file_regex: '^(?!.*/(_deps|build-[^/]*|client-sdk-rust|cpp-example-collection|vcpkg_installed|docker|docs|data)/).*/src/(?!tests/).*\.(c|cpp|cc|cxx)$' + clang_tidy_header_filter: '.*/(include/livekit|src)/.*\.(h|hpp)$' + clang_tidy_exclude_header_filter: '(.*/src/tests/.*)|(.*/_deps/.*)|(.*/build-[^/]*/.*)' + clang_tidy_require_generated_protobuf: build-release/generated generate-docs: name: Generate Docs @@ -139,7 +161,7 @@ jobs: - builds - tests - license-check - - cpp-checks + - cpp-tools - generate-docs - rust-release-check if: always() diff --git a/.github/workflows/cpp-checks.yml b/.github/workflows/cpp-checks.yml deleted file mode 100644 index e2f740c3..00000000 --- a/.github/workflows/cpp-checks.yml +++ /dev/null @@ -1,111 +0,0 @@ -name: C++ Checks - -# Called by top-level ci.yml -on: - workflow_call: {} - workflow_dispatch: {} - -permissions: - contents: read - -jobs: - clang-format: - name: clang-format - runs-on: ubuntu-latest - continue-on-error: false - - steps: - - name: Checkout - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - with: - # No submodules: scripts/clang-format.sh only walks our own src/ tree - fetch-depth: 1 - - - name: Install clang-format 22 - run: | - set -eux - # Pin clang-format 22 to match the current macOS Homebrew LLVM - # Ubuntu 24.04's default clang-format ships with LLVM 18 - sudo install -m 0755 -d /etc/apt/keyrings - wget -qO- https://apt.llvm.org/llvm-snapshot.gpg.key \ - | sudo tee /etc/apt/keyrings/llvm.asc >/dev/null - sudo chmod a+r /etc/apt/keyrings/llvm.asc - codename=$(lsb_release -cs) - echo "deb [signed-by=/etc/apt/keyrings/llvm.asc] http://apt.llvm.org/${codename}/ llvm-toolchain-${codename}-22 main" \ - | sudo tee /etc/apt/sources.list.d/llvm-22.list >/dev/null - sudo apt-get update - sudo apt-get install -y clang-format-22 - sudo ln -sf /usr/bin/clang-format-22 /usr/local/bin/clang-format - clang-format --version - - - name: Run clang-format - env: - FORMAT_BLOB_SHA: ${{ github.event.pull_request.head.sha || github.sha }} - run: ./scripts/clang-format.sh - - clang-tidy: - name: clang-tidy - runs-on: ubuntu-latest - continue-on-error: false - - steps: - - name: Checkout (with submodules) - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - with: - submodules: recursive - fetch-depth: 1 - - - name: Install dependencies - run: | - set -eux - sudo apt-get update - sudo apt-get install -y \ - build-essential cmake ninja-build pkg-config \ - llvm-dev libclang-dev clang \ - libssl-dev wget ca-certificates gnupg - - - name: Install clang-tidy 19 (for ExcludeHeaderFilterRegex support) - run: | - set -eux - # Ubuntu 24.04 apt ships clang-tidy 18, which doesn't understand - # ExcludeHeaderFilterRegex (added in 19). Pull clang-tidy 19 from - # the upstream LLVM apt repository and pin the unversioned names. - sudo install -m 0755 -d /etc/apt/keyrings - wget -qO- https://apt.llvm.org/llvm-snapshot.gpg.key \ - | sudo tee /etc/apt/keyrings/llvm.asc >/dev/null - sudo chmod a+r /etc/apt/keyrings/llvm.asc - codename=$(lsb_release -cs) - echo "deb [signed-by=/etc/apt/keyrings/llvm.asc] http://apt.llvm.org/${codename}/ llvm-toolchain-${codename}-19 main" \ - | sudo tee /etc/apt/sources.list.d/llvm-19.list >/dev/null - sudo apt-get update - sudo apt-get install -y clang-tidy-19 clang-tools-19 - sudo ln -sf /usr/bin/clang-tidy-19 /usr/local/bin/clang-tidy - sudo ln -sf /usr/bin/run-clang-tidy-19 /usr/local/bin/run-clang-tidy - clang-tidy --version - run-clang-tidy --help | head -1 || true - - - name: Install Rust (stable) - uses: dtolnay/rust-toolchain@3c5f7ea28cd621ae0bf5283f0e981fb97b8a7af9 - with: - toolchain: stable - - - name: Set Linux build environment - run: | - echo "CXXFLAGS=-Wno-deprecated-declarations" >> "$GITHUB_ENV" - echo "CFLAGS=-Wno-deprecated-declarations" >> "$GITHUB_ENV" - LLVM_VERSION=$(llvm-config --version | cut -d. -f1) - echo "LIBCLANG_PATH=/usr/lib/llvm-${LLVM_VERSION}/lib" >> "$GITHUB_ENV" - - - name: CMake configure - run: cmake --preset linux-release - - - name: Generate protobuf headers - run: cmake --build build-release --target livekit_proto - - - name: Run clang-tidy - env: - TIDY_BLOB_SHA: ${{ github.event.pull_request.head.sha || github.sha }} - # This script is intended to be run locally and in CI. It will auto-detect - # the GHA environment and add PR annotations and a run summary. - # As of writing all warnings are treateded as errors to avoid tech debt build up - run: ./scripts/clang-tidy.sh --fail-on-warning diff --git a/.gitmodules b/.gitmodules index 0a43c64f..919b9dd2 100644 --- a/.gitmodules +++ b/.gitmodules @@ -4,3 +4,6 @@ [submodule "cpp-example-collection"] path = cpp-example-collection url = https://github.com/livekit-examples/cpp-example-collection.git +[submodule "cpp-tools"] + path = cpp-tools + url = https://github.com/livekit/cpp-tools.git diff --git a/AGENTS.md b/AGENTS.md index 64107fd0..4c314d4e 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -76,10 +76,11 @@ Be sure to update the directory layout in this file if the directory layout chan | `src/tests/` | Google Test integration and stress tests | | `examples/` | In-tree example applications | | `client-sdk-rust/` | Git submodule holding the Rust core of the SDK| +| `cpp-tools/` | Git submodule holding shared LiveKit C++ clang-format / clang-tidy configs, scripts, and docs | | `client-sdk-rust/livekit-ffi/protocol/*.proto` | FFI contract (protobuf definitions, read-only reference) | | `cmake/` | Build helpers (`protobuf.cmake`, `spdlog.cmake`, `LiveKitConfig.cmake.in`) | | `docker/` | Dockerfile for CI and SDK distribution images | -| `scripts/` | Developer / CI helper scripts (e.g. `clang-tidy.sh`) | +| `scripts/` | Local helper scripts and transition wrappers that delegate shared clang tooling to `cpp-tools/` | | `docs/` | Documentation root. `docs/` holds hand-written long-form Markdown intended to also read well on GitHub. | | `docs/doxygen/` | Doxygen tool config, theme assets, and Doxygen-only content (`Doxyfile`, `index.md` mainpage, `customization/*.css`, `customization/header.html`, `customization/favicon.ico`). Files here use Doxygen-only syntax (`@ref`, `@brief`, …) and are not intended for human reading on their own. | | `.github/workflows/` | GitHub Actions CI workflows | @@ -326,11 +327,11 @@ malformed table, missing `@param` on a documented function, …) fails the build Code should be easy to read and understand. If a sacrifice is made for performance or readability, it should be documented. -Adhere to clang-format checks configured in `.clang-format`. Run `./scripts/clang-format.sh` if needed to confirm code styling, and `./scripts/clang-format.sh --fix` to auto-format generated code created. +Adhere to clang-format checks configured in `.clang-format`, which is a symlink to `cpp-tools/configs/.clang-format`. Run `./cpp-tools/scripts/clang-format.sh --path src --path include --path benchmarks` if needed to confirm code styling. During the transition, `./scripts/clang-format.sh` and `./scripts/clang-format.sh --fix` remain compatibility wrappers. ### Static Analysis -Adhere to clang-tidy checks configured in `.clang-tidy`. Run `./scripts/clang-tidy.sh` if needed to confirm code quality. +Adhere to clang-tidy checks configured in `.clang-tidy`, which is a symlink to `cpp-tools/configs/.clang-tidy`. Run `./scripts/clang-tidy.sh` if needed to confirm code quality; the wrapper supplies this repo's build directory, file regex, and header filters to the shared `cpp-tools` script. ## Dependencies @@ -339,6 +340,7 @@ Adhere to clang-tidy checks configured in `.clang-tidy`. Run `./scripts/clang-ti | protobuf | Private (built-in) | Vendored via FetchContent (Unix) or vcpkg (Windows) | | spdlog | **Private** | FetchContent or system package; must NOT leak into public API | | client-sdk-rust | Build-time | Git submodule, built via cargo during CMake build | +| cpp-tools | Developer / CI | Git submodule containing shared LiveKit C++ formatting and static-analysis tooling | | Google Test | Test only | FetchContent in `src/tests/CMakeLists.txt` | When adding a new private/vendored dependency to this table, also add a @@ -387,8 +389,8 @@ all filtered stages; normal pull requests and pushes use the path filters. - `.github/workflows/builds.yml` — Reusable SDK and example-collection build matrix. - `.github/workflows/tests.yml` — Reusable unit/integration test matrix. -- `.github/workflows/cpp-checks.yml` — Reusable `clang-format` and - `clang-tidy` checks. +- `livekit/cpp-tools/.github/workflows/cpp-tools.yml` — Shared reusable + workflow called directly by `ci.yml` for `clang-format` and `clang-tidy`. - `.github/workflows/generate-docs.yml` — Reusable Doxygen docs validation. - `.github/workflows/rust-release-check.yml` — Reusable check that the pinned `client-sdk-rust` submodule commit maps to a published release. Gated by the @@ -407,7 +409,8 @@ When adding or renaming files that affect a CI stage, update the matching `ci.yml` `changes` filter in the same PR. For example, new build scripts, CMake files, package manifests, or reusable build workflows should be added to the `builds` filter; test-only helpers to `tests`; formatting/static-analysis -configuration to `cpp_checks`; and docs generation inputs to `docs`. +configuration (including `cpp-tools` submodule bumps) to `cpp_checks`; and docs +generation inputs to `docs`. Keep broad agent guidance files such as `AGENTS.md` out of the expensive `builds`, `tests`, `cpp_checks`, and `docs` filters unless they start affecting diff --git a/README.md b/README.md index d9fd9d71..b3a6392e 100644 --- a/README.md +++ b/README.md @@ -221,12 +221,13 @@ local `livekit-server --dev`, and run via `ctest` or directly. See ## Developer tools -`clang-tidy`, `clang-format`, `valgrind`, and Doxygen are all wired up via -scripts under `scripts/`. Set up the pre-commit auto-formatter -with: +`clang-tidy` and `clang-format` are provided by the `cpp-tools` submodule, with +transition wrappers kept under `scripts/`. `valgrind` and Doxygen remain +documented with the rest of the local developer tools. Set up the pre-commit +auto-formatter with: ```bash -./scripts/install-pre-commit.sh +./cpp-tools/scripts/install-pre-commit.sh ``` See [docs/tools.md](docs/tools.md). diff --git a/cpp-tools b/cpp-tools new file mode 160000 index 00000000..9f3067c6 --- /dev/null +++ b/cpp-tools @@ -0,0 +1 @@ +Subproject commit 9f3067c6e19fdbe528baf6abb7f1c2dbbb703984 diff --git a/docs/tools.md b/docs/tools.md index b67826f5..606601ab 100644 --- a/docs/tools.md +++ b/docs/tools.md @@ -5,10 +5,21 @@ are also enforced in CI on PRs. ## Clang tools -- **`clang-tidy`** — static analysis. See [.clang-tidy](https://github.com/livekit/client-sdk-cpp/blob/main/.clang-tidy) for the - enabled checks. Enforced in CI on PR. +`clang-tidy` and `clang-format` are owned by the +[`livekit/cpp-tools`](https://github.com/livekit/cpp-tools) submodule. The +root `.clang-tidy` and `.clang-format` files are symlinks into that submodule +so editor integrations can still discover them automatically. +The shared CI workflow source of truth is +`cpp-tools/.github/workflows/cpp-tools.yml`; this repo's `ci.yml` calls it +directly with repo-specific inputs such as `clang_tidy: true` and +`doxygen: false`. + +- **`clang-tidy`** — static analysis. See + [`.clang-tidy`](https://github.com/livekit/client-sdk-cpp/blob/main/.clang-tidy) + for the enabled checks. Enforced in CI on PR. - **`clang-format`** — code formatting and style consistency. See - [.clang-format](https://github.com/livekit/client-sdk-cpp/blob/main/.clang-format) for the rules. Enforced in CI on PR. + [`.clang-format`](https://github.com/livekit/client-sdk-cpp/blob/main/.clang-format) + for the rules. Enforced in CI on PR. > **Note (Windows):** `clang-tidy` is not currently driven by our scripts on > Windows. The MSBuild CMake generator doesn't emit @@ -42,9 +53,20 @@ sudo apt-get install clang-format clang-tidy clang-tools ./build.sh release ``` -2. Run the wrapper, which uses the same file set, regex filters, and +2. Run the shared wrapper, which uses the same file set, regex filters, and `.clang-tidy` config as CI: + ```bash + ./cpp-tools/scripts/clang-tidy.sh \ + --build-dir build-release \ + --file-regex '^(?!.*/(_deps|build-[^/]*|client-sdk-rust|cpp-example-collection|vcpkg_installed|docker|docs|data)/).*/src/(?!tests/).*\.(c|cpp|cc|cxx)$' \ + --header-filter '.*/(include/livekit|src)/.*\.(h|hpp)$' \ + --exclude-header-filter '(.*/src/tests/.*)|(.*/_deps/.*)|(.*/build-[^/]*/.*)' \ + --require-generated-protobuf build-release/generated + ``` + + During the transition, the compatibility wrapper is equivalent: + ```bash ./scripts/clang-tidy.sh ``` @@ -54,7 +76,7 @@ The wrapper forwards extra arguments to `run-clang-tidy`: ```bash ./scripts/clang-tidy.sh -j 4 # Number of cores ./scripts/clang-tidy.sh -checks='-*,misc-const-correctness' # Only specific checks -./scripts/clang-tidy.sh -fix # Apply fixes +./scripts/clang-tidy.sh --fix # Apply fixes ``` Output is captured to `clang-tidy.log` at the repo root, since the terminal @@ -63,13 +85,18 @@ buffer often can't hold all of it. ### Run `clang-format` ```bash -./scripts/clang-format.sh +./cpp-tools/scripts/clang-format.sh --path src --path include --path benchmarks ``` -With no arguments, runs against every relevant file in the repository against -the rules in `.clang-format`. +The compatibility wrapper supplies this repo's default paths, so the shorter +transition command is: + +```bash +./scripts/clang-format.sh +``` ```bash +./cpp-tools/scripts/clang-format.sh --path src --path include --path benchmarks --fix ./scripts/clang-format.sh --fix # Rewrite files in place ./scripts/clang-format.sh src/room.cpp include/livekit/room.h # Check just these files ./scripts/clang-format.sh --fix src/room.cpp # Fix just this file @@ -85,11 +112,12 @@ A simple pre-commit hook that auto-formats staged C/C++ files using the project's `.clang-format` rules: ```bash -./scripts/install-pre-commit.sh +./cpp-tools/scripts/install-pre-commit.sh ``` -This installs `.git/hooks/pre-commit`. Re-run after `git clone` on a fresh -checkout. +During the transition, `./scripts/install-pre-commit.sh` delegates to the +shared installer. This installs `.git/hooks/pre-commit`. Re-run after +`git clone` on a fresh checkout. --- diff --git a/scripts/clang-format.sh b/scripts/clang-format.sh index f2b58dae..2d66ca73 100755 --- a/scripts/clang-format.sh +++ b/scripts/clang-format.sh @@ -14,477 +14,12 @@ # See the License for the specific language governing permissions and # limitations under the License. -# -# clang-format.sh -- Run clang-format locally and in CI using the same file -# set and config. Picks up style from the repo-root .clang-format -# automatically. -# -# Usage (from anywhere; the script self-anchors to the repo root): -# ./scripts/clang-format.sh # check the full src/ tree -# ./scripts/clang-format.sh --fix # apply formatting in place -# ./scripts/clang-format.sh src/room.cpp src/foo.h # check just these files -# ./scripts/clang-format.sh --fix src/room.cpp # fix just this file -# ./scripts/clang-format.sh --github-actions # force CI annotation mode -# -# Default mode is "check" (clang-format --dry-run --Werror): no files are -# modified and the script exits non-zero if any file diverges from -# .clang-format. Pass --fix / -i to apply formatting in place instead. -# -# Every run prints a concise stdout summary at the end with the number of -# files checked / fixed (and the list of offending paths when checking). -# -# In GitHub Actions (auto-detected via $GITHUB_ACTIONS=true, or forced with -# --github-actions), this script additionally: -# - Emits ::error workflow commands so violations appear as PR file -# annotations (red). -# - Writes a markdown summary to $GITHUB_STEP_SUMMARY listing every file -# that needs formatting, linkified to github.com when possible. -# -# Exit code: -# - 0 when every file is already formatted (check mode) or when --fix -# completed successfully. -# - 1 when at least one file needs formatting (check mode only). -# - The clang-format / xargs exit code on any other failure (missing tool, -# unreadable file, etc.). - set -euo pipefail -# Anchor every relative path (clang-format.log, git ls-files, etc.) to the -# repo root regardless of how/where this script is invoked. Without this, -# calling the script from a subdirectory or via an absolute path would break -# git ls-files scoping and the log path below. -script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd -P)" -cd "${script_dir}/.." - -# Usage banner. Printed by --help and referenced by the EXIT trap below so -# users who hit a pre-flight error or a bad argument get pointed at this. -usage() { - cat <<'EOF' -Usage: ./scripts/clang-format.sh [OPTIONS] [FILE...] - -Run clang-format locally or in CI using the repo-root .clang-format. Defaults -to check-only (dry-run); pass --fix to rewrite files in place. - -Options: - -h, --help, -? - Show this help and exit. - --fix, -i - Apply formatting in place. Without this, the script runs in check - mode (--dry-run --Werror) and exits non-zero on any divergence. - --github-actions, --gh - Force GitHub Actions annotation + step-summary mode. - Auto-detected when GITHUB_ACTIONS=true. - -Positional arguments: - FILE... - Explicit list of files to format / check. When omitted, the script - walks the tracked first-party C/C++ trees (src/, include/, and - benchmarks/) and operates on every source / header it finds. Pass - paths to make the script work as a precommit hook on a curated set - of files, e.g.: - git diff --cached --name-only --diff-filter=ACMR \ - | grep -E '\.(c|cc|cpp|cxx|h|hpp|hxx)$' \ - | xargs ./scripts/clang-format.sh --fix - -Examples: - ./scripts/clang-format.sh # check src/, include/, benchmarks/ - ./scripts/clang-format.sh --fix # fix the same trees - ./scripts/clang-format.sh src/room.cpp include/livekit/room.h - # check just these files - ./scripts/clang-format.sh --fix src/room.cpp # fix just this file - ./scripts/clang-format.sh --github-actions # force CI annotations - -Pre-requisites: - clang-format must be installed and on PATH: - brew install clang-format # macOS - apt install clang-format-19 # Linux (or any version >= 11) -EOF -} - -# Print a one-line "see --help" hint on any non-zero exit during pre-flight -# (argument parsing, missing tool, etc.). Suppressed once we hand off to -# clang-format itself, so legitimate formatting violations don't trigger -# the hint. -__fmt_hint_active=1 -__fmt_print_hint() { - local rc=$? - if (( rc != 0 )) && (( __fmt_hint_active )); then - echo >&2 - echo "Run './scripts/clang-format.sh --help' for usage." >&2 - fi -} -trap __fmt_print_hint EXIT - -CI_MODE=0 -# Automatically detect CI mode if in GitHub Actions environment. -if [[ "${GITHUB_ACTIONS:-}" == "true" ]]; then - CI_MODE=1 -fi - -# Default to check (dry-run) mode; --fix flips to in-place rewriting. -FIX_MODE=0 - -explicit_files=() -while (($#)); do - case "$1" in - -h|--help|-\?) - usage - __fmt_hint_active=0 - exit 0 - ;; - --fix|-i) - FIX_MODE=1 - shift - ;; - --github-actions|--gh) - CI_MODE=1 - shift - ;; - --) - # Explicit positional separator: everything after `--` is a file path. - shift - explicit_files+=("$@") - break - ;; - --*|-*) - # Long / short options we don't recognize are user typos far more often - # than legitimate clang-format flags. Reject and surface usage. - echo "ERROR: unknown option: $1" >&2 - usage >&2 - exit 2 - ;; - *) - explicit_files+=("$1") - shift - ;; - esac -done - -if ! command -v clang-format >/dev/null 2>&1; then - echo "ERROR: clang-format not found in PATH." >&2 - echo "Install: brew install clang-format (macOS)" >&2 - echo " apt install clang-format-19 (Linux)" >&2 - exit 1 -fi - -# Surface the version in CI logs so future regressions tied to a specific -# clang-format release are easy to bisect. -clang-format --version - -# Build the list of files to operate on. When the user passes explicit paths -# we honor them verbatim so a precommit hook can supply its own staged-file -# list. Otherwise we walk the tracked first-party C/C++ trees: -# - src/ (implementation + internal headers, including src/tests/) -# - include/ (public API headers shipped in the installed SDK) -# - benchmarks/ (in-tree micro-benchmarks) -# Using `git ls-files` automatically skips the client-sdk-rust/ submodule, -# build-*/, _deps/, local-install/, vcpkg_installed/, etc., without having -# to maintain a lookahead exclusion regex. -files=() -if (( ${#explicit_files[@]} > 0 )); then - files=("${explicit_files[@]}") -else - while IFS= read -r -d '' path; do - files+=("${path}") - done < <(git ls-files -z \ - 'src/*.'{c,cc,cpp,cxx,h,hpp,hxx} \ - 'include/*.'{h,hpp,hxx} \ - 'benchmarks/*.'{c,cc,cpp,cxx,h,hpp,hxx}) -fi - -file_count=${#files[@]} -if (( file_count == 0 )); then - echo "clang-format: no files to process." - __fmt_hint_active=0 - exit 0 -fi - -# Capture clang-format's combined stdout+stderr to a stable, repo-local path -# so it can be re-parsed after the run (for annotations and the step summary) -# and re-read by the user afterwards. `*.log` is gitignored so this file -# never gets committed. -log="clang-format.log" -: > "${log}" - -# Past pre-flight: any non-zero exit from here on is a clang-format result -# (violations, internal error, etc.), not a user-facing argument/usage error, -# so suppress the "see --help" hint installed via the EXIT trap above. -__fmt_hint_active=0 - -# -------- Begin GitHub Actions annotations -------- - -# Emit GitHub Actions workflow commands for each clang-format diagnostic line -# in the given log. Source / caret lines are skipped; only the -# `path:line:col: error: ... [-Wclang-format-violations]` lines become -# annotations. Severity is always ::error because --Werror promotes every -# violation to an error and we treat any divergence as a hard CI failure. -emit_annotations() { - local log_file="$1" - local workspace="${GITHUB_WORKSPACE:-${PWD}}" - local line path lineno col message rel_path - - while IFS= read -r line; do - [[ "${line}" =~ ^(.+):([0-9]+):([0-9]+):[[:space:]]+(error|warning):[[:space:]]+(.+)[[:space:]]\[-Wclang-format-violations\][[:space:]]*$ ]] || continue - path="${BASH_REMATCH[1]}" - lineno="${BASH_REMATCH[2]}" - col="${BASH_REMATCH[3]}" - message="${BASH_REMATCH[5]}" - - rel_path="${path#${workspace}/}" - - message="${message//$'%'/%25}" - message="${message//$'\r'/%0D}" - message="${message//$'\n'/%0A}" - - printf '::error file=%s,line=%s,col=%s,title=clang-format::%s\n' \ - "${rel_path}" "${lineno}" "${col}" "${message}" - done < "${log_file}" -} - -# Append a markdown summary (count + per-file list) to $GITHUB_STEP_SUMMARY so -# the GitHub job page surfaces every offending file without scanning the raw -# log. Each file is linkified to github.com when we can resolve a blob URL. -write_step_summary() { - local log_file="$1" - local summary_file="${GITHUB_STEP_SUMMARY:-}" - [[ -n "${summary_file}" ]] || return 0 - - local workspace="${GITHUB_WORKSPACE:-${PWD}}" - local files_tsv - files_tsv="$(mktemp -t fmt-files.XXXXXX)" - - # Extract first violation line per file as path\tline. Multiple violations - # in one file collapse to a single row -- clicking through to the file is - # enough; the developer fixes them with `clang-format -i` regardless. - local sline spath slineno - declare -A seen=() - while IFS= read -r sline; do - [[ "${sline}" =~ ^(.+):([0-9]+):([0-9]+):[[:space:]]+(error|warning):[[:space:]]+(.+)[[:space:]]\[-Wclang-format-violations\][[:space:]]*$ ]] || continue - spath="${BASH_REMATCH[1]#${workspace}/}" - slineno="${BASH_REMATCH[2]}" - if [[ -z "${seen[${spath}]:-}" ]]; then - seen[${spath}]=1 - printf '%s\t%s\n' "${spath}" "${slineno}" >> "${files_tsv}" - fi - done < "${log_file}" - - local violation_files - violation_files=$(wc -l < "${files_tsv}" | tr -d ' ') - - # Resolve a blob URL prefix so files in the table become clickable links. - # Prefer FORMAT_BLOB_SHA (set by the workflow to the PR head SHA) over - # GITHUB_SHA -- on pull_request events GITHUB_SHA points at the ephemeral - # refs/pull/N/merge commit, whose blob URLs stop resolving once the PR - # closes. On push / workflow_dispatch / schedule runs FORMAT_BLOB_SHA is - # unset and we fall through to GITHUB_SHA, which is the pushed / selected - # commit respectively. - local repo_url="" - if [[ -n "${GITHUB_SERVER_URL:-}" && -n "${GITHUB_REPOSITORY:-}" ]]; then - local blob_sha="${FORMAT_BLOB_SHA:-${GITHUB_SHA:-}}" - if [[ -n "${blob_sha}" ]]; then - repo_url="${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/blob/${blob_sha}" - fi - fi - - { - echo "## clang-format results" - echo - if (( violation_files == 0 )); then - echo ":white_check_mark: All files are properly formatted." - else - echo ":x: ${violation_files} file(s) need formatting." - echo - echo "
Files needing formatting" - echo - echo '| File |' - echo '|------|' - while IFS=$'\t' read -r path lineno; do - local file_cell - if [[ -n "${repo_url}" && "${path}" != /* ]]; then - file_cell="[\`${path}\`](${repo_url}/${path}#L${lineno})" - else - file_cell="\`${path}\`" - fi - printf '| %s |\n' "${file_cell}" - done < "${files_tsv}" - echo - echo "
" - echo - echo "Run \`./scripts/clang-format.sh --fix\` locally to apply formatting." - fi - } >> "${summary_file}" - - rm -f "${files_tsv}" -} - -# Print a one-line summary plus the offending file list to stdout. Always -# runs (regardless of CI_MODE) so local invocations get the same headline -# view the GitHub step summary provides. Sets a global consumed by the -# exit-code logic below: __FMT_VIOLATION_FILES. -print_stdout_summary() { - local log_file="$1" - local total="$2" - local files_tsv - files_tsv="$(mktemp -t fmt-stdout.XXXXXX)" - - local line spath - declare -A seen=() - while IFS= read -r line; do - [[ "${line}" =~ ^(.+):([0-9]+):([0-9]+):[[:space:]]+(error|warning):[[:space:]]+(.+)[[:space:]]\[-Wclang-format-violations\][[:space:]]*$ ]] || continue - spath="${BASH_REMATCH[1]}" - if [[ -z "${seen[${spath}]:-}" ]]; then - seen[${spath}]=1 - echo "${spath}" >> "${files_tsv}" - fi - done < "${log_file}" - - local violation_files - violation_files=$(wc -l < "${files_tsv}" | tr -d ' ') - - echo "------------------------------------------------------------" - if (( violation_files == 0 )); then - printf 'clang-format summary: clean (%d file(s) checked)\n' "${total}" - else - printf 'clang-format summary: %d of %d file(s) need formatting\n' \ - "${violation_files}" "${total}" - echo " files:" - while IFS= read -r f; do - printf ' %s\n' "${f}" - done < "${files_tsv}" - echo - echo " Run './scripts/clang-format.sh --fix' to apply formatting." - fi - echo "------------------------------------------------------------" - - rm -f "${files_tsv}" - __FMT_VIOLATION_FILES="${violation_files}" -} - -# --------- End GitHub Actions annotations --------- - -# Run clang-format. We run serially (no -P parallelism) so per-file -# diagnostics never interleave -- clang-format itself is fast enough -# (~1-2 ms per file) that a few hundred files still complete in well -# under a second, and CI workflow startup dwarfs the runtime regardless. -# `xargs` (without -P) still chunks the file list so we don't blow past -# ARG_MAX when the tree grows: each invocation gets as many files as fit -# in one command line, and successive invocations append to the log in -# discovery order. -# -# In fix mode we snapshot file content hashes before and after invoking -# clang-format so the summary can report only the files that *actually -# changed* (vs. the entire scanned set). `git hash-object --stdin-paths` -# hashes every input path in a single git invocation, sidestepping the -# portability headache of sha1sum (Linux) vs shasum (macOS) vs cksum -# (POSIX, but only 32-bit) and getting us per-file SHA-1s in one shot. -__hash_files() { - if (( ${#files[@]} == 0 )); then - return - fi - printf '%s\n' "${files[@]}" | git hash-object --stdin-paths -} - -pre_hashes=() -if (( FIX_MODE == 1 )); then - while IFS= read -r __h; do - pre_hashes+=("${__h}") - done < <(__hash_files) -fi - -set +e -if (( FIX_MODE == 1 )); then - printf '%s\0' "${files[@]}" \ - | xargs -0 clang-format -i \ - >"${log}" 2>&1 - rc=$? -else - printf '%s\0' "${files[@]}" \ - | xargs -0 clang-format --dry-run --Werror \ - >"${log}" 2>&1 - rc=$? -fi -set -e - -# Identify which files actually changed on disk during the fix pass by -# rehashing and comparing against the pre-run snapshot. Files where the -# hash matches were no-ops (already conformant) and are excluded from -# the summary count. Order is preserved (git hash-object emits hashes in -# input order) so the i-th post-hash corresponds to the i-th file. -changed_files=() -if (( FIX_MODE == 1 )); then - post_hashes=() - while IFS= read -r __h; do - post_hashes+=("${__h}") - done < <(__hash_files) - for i in "${!files[@]}"; do - if [[ "${pre_hashes[$i]:-}" != "${post_hashes[$i]:-}" ]]; then - changed_files+=("${files[$i]}") - fi - done -fi - -# Mirror the captured log to stdout so users see violations inline. In fix -# mode there's nothing useful in the log (clang-format -i writes silently), -# so skip the cat to keep the output focused on the summary below. -if (( FIX_MODE == 0 )); then - cat "${log}" -fi - -# CI annotations / step summary only make sense for the check-mode log, -# which contains the violation diagnostics. Fix mode has already resolved -# them, so emitting annotations would be misleading. -if [[ "${CI_MODE}" == "1" ]] && (( FIX_MODE == 0 )); then - emit_annotations "${log}" - write_step_summary "${log}" -fi - -# Always emit the concise headline summary to stdout. Sets -# __FMT_VIOLATION_FILES which the exit-code logic below consumes. -# print_stdout_summary reads the log, so this must happen before the -# conditional log cleanup below. -__FMT_VIOLATION_FILES=0 -if (( FIX_MODE == 1 )); then - echo "------------------------------------------------------------" - if (( ${#changed_files[@]} == 0 )); then - printf 'clang-format summary: clean (0 of %d file(s) needed formatting)\n' \ - "${file_count}" - else - printf 'clang-format summary: formatted %d of %d file(s)\n' \ - "${#changed_files[@]}" "${file_count}" - echo " files:" - for __cf in "${changed_files[@]}"; do - printf ' %s\n' "${__cf}" - done - fi - echo "------------------------------------------------------------" -else - print_stdout_summary "${log}" "${file_count}" -fi - -# Only advertise the log when it actually has content -- a clean check run -# and a no-op fix run both leave it empty, and the summary banner above -# already conveys "nothing to see". Drop the empty file so a stale log -# from a previous dirty run doesn't linger and confuse the next reader. -if [[ -s "${log}" ]]; then - echo "Results written to: $(pwd)/${log}" -else - rm -f "${log}" -fi - -# Exit-code policy: -# - In --fix mode, propagate clang-format's own exit code (non-zero means -# a real failure such as a missing file, not "needed reformatting"). -# - In check mode, treat xargs's "child exited 1-125" status (123) as a -# formatting violation -> exit 1. Any other non-zero status is a real -# failure (missing file, IO error, etc.) and we propagate it as-is. -if (( FIX_MODE == 1 )); then - exit "${rc}" -fi - -if (( rc == 0 )); then - exit 0 -elif (( rc == 123 )) || (( __FMT_VIOLATION_FILES > 0 )); then - exit 1 -else - exit "${rc}" -fi +repo_root="$(git rev-parse --show-toplevel)" +exec "${repo_root}/cpp-tools/scripts/clang-format.sh" \ + --repo-root "${repo_root}" \ + --path src \ + --path include \ + --path benchmarks \ + "$@" diff --git a/scripts/clang-tidy.sh b/scripts/clang-tidy.sh index 9ff1d67e..9faad693 100755 --- a/scripts/clang-tidy.sh +++ b/scripts/clang-tidy.sh @@ -14,564 +14,14 @@ # See the License for the specific language governing permissions and # limitations under the License. -# -# clang-tidy.sh -- Run clang-tidy locally and in CI using the same file set -# and config. Picks up checks from the repo-root .clang-tidy automatically. -# -# Usage (from anywhere; the script self-anchors to the repo root): -# ./scripts/clang-tidy.sh # run on full src/ tree -# ./scripts/clang-tidy.sh src/room.cpp # run on a single file -# ./scripts/clang-tidy.sh -j 4 # override parallelism -# ./scripts/clang-tidy.sh --github-actions # force GitHub Actions annotation mode -# ./scripts/clang-tidy.sh --fail-on-warning # exit non-zero on any finding (warning OR error) -# ./scripts/clang-tidy.sh --fix # apply fixes in place -# -# Every run prints a concise stdout summary at the end with the warning and -# error counts (and a per-check breakdown when there are findings). -# -# In GitHub Actions (auto-detected via $GITHUB_ACTIONS=true, or forced with -# --github-actions), this script additionally: -# - Emits ::warning/::error workflow commands so findings appear as PR file -# annotations (yellow for warnings, red for errors). Severity comes from -# clang-tidy itself -- errors are findings promoted by WarningsAsErrors -# in .clang-tidy. -# - Writes a markdown summary to $GITHUB_STEP_SUMMARY. -# -# Exit code: -# - Non-zero when run-clang-tidy itself reports errors (findings promoted -# to errors via WarningsAsErrors in .clang-tidy). -# - Also non-zero when --fail-on-warning is set and at least one warning -# was emitted. This is what CI uses to gate merges so warning-level -# tech debt cannot accumulate silently. -# - Zero otherwise. -# -# Requires CMake to have generated build-release/compile_commands.json. -# Run once: ./build.sh release (configures, builds, generates protobuf headers) - set -euo pipefail -# Anchor every relative path (BUILD_DIR, clang-tidy.log, etc.) to the repo root -# regardless of how/where this script is invoked. Without this, calling the -# script from a subdirectory or via an absolute path would break the -# compile_commands.json / generated/ checks below. -script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd -P)" -cd "${script_dir}/.." - -# Usage banner. Printed by --help and referenced by the EXIT trap below so -# users who hit a pre-flight error or a bad argument get pointed at this. -usage() { - cat <<'EOF' -Usage: ./scripts/clang-tidy.sh [OPTIONS] [FILE...] [run-clang-tidy args...] - -Run clang-tidy locally or in CI using the same file set and config that the -repo's CI workflow uses. Picks up checks from the repo-root .clang-tidy. - -Options: - -h, --help, -? - Show this help and exit. - --fix - Apply fixes in place (forwarded to run-clang-tidy as -fix). - --github-actions, --gh - Force GitHub Actions annotation + step-summary mode. - Auto-detected when GITHUB_ACTIONS=true. - --fail-on-warning, --strict - Exit non-zero when any warning is emitted (errors already exit - non-zero on their own). Off by default for local edit/run cycles; - CI opts in via the workflow file. - -Positional arguments: - FILE... - Explicit list of files to analyze. When omitted, the script walks - the tracked first-party src/ tree (excluding src/tests/). Pass paths - to scope the run to a single file or a curated set, e.g.: - ./scripts/clang-tidy.sh - -Any other arguments are forwarded verbatim to run-clang-tidy. Common ones: - -j N worker count (defaults to nproc / sysctl hw.ncpu) - -checks=... override the .clang-tidy check list for this run - -Examples: - ./scripts/clang-tidy.sh # full src/ tree - ./scripts/clang-tidy.sh # single file - ./scripts/clang-tidy.sh -j 4 # override parallelism - ./scripts/clang-tidy.sh --github-actions # force CI annotation mode - ./scripts/clang-tidy.sh --fail-on-warning # exit non-zero on any finding - ./scripts/clang-tidy.sh --fix # apply fixes in place - -Pre-requisites: - CMake must have generated build-release/compile_commands.json AND the - protobuf headers under build-release/generated/. Run once: - ./build.sh release -EOF -} - -# Print a one-line "see --help" hint on any non-zero exit during pre-flight -# (argument parsing, missing build artifacts, missing run-clang-tidy, etc.). -# It is suppressed once we hand off to run-clang-tidy itself, so legitimate -# clang-tidy findings don't trigger the hint. -__tidy_hint_active=1 -__tidy_print_hint() { - local rc=$? - if (( rc != 0 )) && (( __tidy_hint_active )); then - echo >&2 - echo "Run './scripts/clang-tidy.sh --help' for usage." >&2 - fi -} -trap __tidy_print_hint EXIT - -BUILD_DIR="build-release" -# Positive match for top-level src/*.{c,cpp,cc,cxx}; negative lookahead excludes -# dep paths (_deps/, build-*/, -src/src/) and every other top-level dir. Python -# regex (PCRE-ish) supports lookahead; this regex is evaluated by run-clang-tidy. -FILE_REGEX='^(?!.*/(_deps|build-[^/]*|client-sdk-rust|cpp-example-collection|vcpkg_installed|docker|docs|data)/).*/src/(?!tests/).*\.(c|cpp|cc|cxx)$' - -CI_MODE=0 -# Automatically detect CI mode if in GitHub actions environment -if [[ "${GITHUB_ACTIONS:-}" == "true" ]]; then - CI_MODE=1 -fi - -# When set, the script exits non-zero on any finding (warning or error), -# not just on errors. Off by default so local edit/run cycles aren't blocked -# by in-progress code; CI opts in via the workflow file. -FAIL_ON_WARNING=0 - -forward_args=() -explicit_files=() -while (($#)); do - case "$1" in - -h|--help|-\?) - usage - __tidy_hint_active=0 - exit 0 - ;; - --fix) - # Unify with clang-format.sh's --fix spelling. run-clang-tidy only - # understands the single-dash -fix, so normalize every accepted form - # to that when forwarding. - forward_args+=("-fix") - shift - ;; - --github-actions|--gh) - CI_MODE=1 - shift - ;; - --fail-on-warning|--strict) - FAIL_ON_WARNING=1 - shift - ;; - --) - # Explicit forwarding separator: everything after `--` goes straight - # to run-clang-tidy without further interpretation. - shift - forward_args+=("$@") - break - ;; - --*) - # Long options we don't recognize are user typos far more often than - # genuine run-clang-tidy long options (run-clang-tidy uses single-dash - # long options like -fix, -j, -checks=...). Reject and surface usage. - echo "ERROR: unknown option: $1" >&2 - usage >&2 - exit 2 - ;; - *) - # A positional arg naming an existing file is an explicit target to - # analyze; anything else (e.g. the value after -j, a -checks=... flag) - # is forwarded verbatim to run-clang-tidy. When explicit files are - # given they replace the default src/ FILE_REGEX below so the run is - # scoped to exactly those files. - if [[ -f "$1" ]]; then - explicit_files+=("$1") - else - forward_args+=("$1") - fi - shift - ;; - esac -done - -if [[ ! -f "${BUILD_DIR}/compile_commands.json" ]]; then - echo "ERROR: ${BUILD_DIR}/compile_commands.json not found." >&2 - echo "Run: ./build.sh release (configures + builds, generates protobuf headers)" >&2 - exit 1 -fi - -# Protobuf sanity check. `cmake --preset` only configures -- it doesn't invoke -# the Rust/protoc chain that writes livekit's generated headers into -# build-release/generated/. If those headers are missing or stale, every TU -# that #includes "room.pb.h" / "ffi.pb.h" / etc. fails with dozens of -# clang-diagnostic-error diagnostics (e.g. "no member named 'FrameMetadata' in -# namespace 'livekit::proto'") that drown out the real findings. Detect the -# "configured but never built" state early and point the user at ./build.sh. -proto_dir="${BUILD_DIR}/generated" -if [[ ! -d "${proto_dir}" ]] || ! compgen -G "${proto_dir}/*.pb.h" >/dev/null; then - echo "ERROR: no generated protobuf headers found in ${proto_dir}/." >&2 - echo "clang-tidy needs .pb.h files that are produced during the build step," >&2 - echo "not by 'cmake --preset' alone. To generate them, run:" >&2 - echo "" >&2 - echo " ./build.sh release # or release-tests / release-all, as needed" >&2 - echo "" >&2 - echo "If you previously bumped client-sdk-rust, also run 'clean-all' first:" >&2 - echo "" >&2 - echo " ./build.sh clean-all && ./build.sh release" >&2 - exit 1 -fi - -if ! command -v run-clang-tidy >/dev/null 2>&1; then - echo "ERROR: run-clang-tidy not found in PATH." >&2 - echo "Install LLVM: brew install llvm (macOS)" >&2 - echo " apt install clang-tools-NN (Linux)" >&2 - exit 1 -fi - -# On macOS, the C++ standard library headers (, , ...) live -# inside the active Xcode / Command Line Tools SDK rather than on the default -# include path. Homebrew's clang-tidy doesn't know where that SDK is, so -# without -isysroot it fails every TU with "'cstdint' file not found" before -# any real check runs. `xcrun --show-sdk-path` resolves the currently selected -# SDK and we forward it to every clang-tidy invocation via --extra-arg. Linux -# CI doesn't need this -- the system clang-tidy already finds libstdc++/libc++ -# through its built-in resource dir. -# Match the Clang build's variadic macro diagnostic suppression when clang-tidy -# is driven from GCC compile commands in Linux CI. -extra_args=(-extra-arg=-Wno-gnu-zero-variadic-macro-arguments) -if [[ "$(uname)" == "Darwin" ]]; then - sdk_path="$(xcrun --show-sdk-path 2>/dev/null || true)" - if [[ -n "${sdk_path}" ]]; then - extra_args+=(-extra-arg="-isysroot${sdk_path}") - fi -fi - -# run-clang-tidy parallelizes across TUs via -j. Default to one worker per -# logical CPU so local runs aren't artificially slow. `nproc` is the Linux -# coreutils tool; macOS doesn't ship it, so fall back to `sysctl hw.ncpu`, -# and finally to a conservative 4 if neither is available (e.g. a minimal -# container). -if command -v nproc >/dev/null 2>&1; then - jobs=$(nproc) -else - jobs=$(sysctl -n hw.ncpu 2>/dev/null || echo 4) -fi - -# -------- Begin GitHub Actions annotations -------- - -# Emit GitHub Actions workflow commands for each clang-tidy diagnostic line -# in the given log. Notes (`path:L:C: note: ...`) are deliberately skipped -- -# they belong to the preceding warning/error and would produce noisy extra -# annotations. Severity (::warning vs ::error) mirrors clang-tidy's prefix. -emit_annotations() { - local log="$1" - local workspace="${GITHUB_WORKSPACE:-${PWD}}" - local line path lineno col severity message check rel_path - - while IFS= read -r line; do - [[ "${line}" =~ ^(.+):([0-9]+):([0-9]+):[[:space:]]+(warning|error):[[:space:]]+(.+)[[:space:]]\[([^]]+)\][[:space:]]*$ ]] || continue - path="${BASH_REMATCH[1]}" - lineno="${BASH_REMATCH[2]}" - col="${BASH_REMATCH[3]}" - severity="${BASH_REMATCH[4]}" - message="${BASH_REMATCH[5]}" - check="${BASH_REMATCH[6]}" - # clang-tidy appends ",-warnings-as-errors" to the bracket suffix when a - # diagnostic is promoted from warning to error via WarningsAsErrors. Strip - # it so the title, docs link, and Top-checks bucket are identical for the - # warning and error code paths -- severity is already conveyed by the - # ::warning / ::error workflow command prefix below. - check="${check%,-warnings-as-errors}" - - rel_path="${path#${workspace}/}" - - message="${message//$'%'/%25}" - message="${message//$'\r'/%0D}" - message="${message//$'\n'/%0A}" - - printf '::%s file=%s,line=%s,col=%s,title=clang-tidy (%s)::%s\n' \ - "${severity}" "${rel_path}" "${lineno}" "${col}" "${check}" "${message}" - done < "${log}" -} - -# Append a markdown summary (counts + top checks + full finding list) to -# $GITHUB_STEP_SUMMARY so the GitHub job page surfaces every finding without -# needing to scan the raw log. Counts require a [check-name] suffix so -# clang-tidy's own config-parse errors can't inflate the totals. -write_step_summary() { - local log="$1" - local summary_file="${GITHUB_STEP_SUMMARY:-}" - [[ -n "${summary_file}" ]] || return 0 - - local workspace="${GITHUB_WORKSPACE:-${PWD}}" - local findings_tsv - findings_tsv="$(mktemp -t tidy-findings.XXXXXX)" - - # Extract every real finding (severity must be followed by [check-name]) - # as tab-separated severity\tpath\tline\tcol\tcheck\tmessage. Use bash's - # regex engine (same as emit_annotations) for portability -- BSD awk and - # mawk don't support gawk's 3-argument match(). - local sline spath slineno scol sseverity smessage scheck - while IFS= read -r sline; do - [[ "${sline}" =~ ^(.+):([0-9]+):([0-9]+):[[:space:]]+(warning|error):[[:space:]]+(.+)[[:space:]]\[([^]]+)\][[:space:]]*$ ]] || continue - spath="${BASH_REMATCH[1]#${workspace}/}" - slineno="${BASH_REMATCH[2]}" - scol="${BASH_REMATCH[3]}" - sseverity="${BASH_REMATCH[4]}" - smessage="${BASH_REMATCH[5]}" - scheck="${BASH_REMATCH[6]}" - # Strip the ",-warnings-as-errors" promotion marker so error rows bucket - # under the same check name as their warning counterparts (see - # emit_annotations for the rationale). - scheck="${scheck%,-warnings-as-errors}" - printf '%s\t%s\t%s\t%s\t%s\t%s\n' \ - "${sseverity}" "${spath}" "${slineno}" "${scol}" "${scheck}" "${smessage}" \ - >> "${findings_tsv}" - done < "${log}" - - local warnings errors total - warnings=$(awk -F'\t' '$1=="warning"{c++} END{print c+0}' "${findings_tsv}") - errors=$(awk -F'\t' '$1=="error"{c++} END{print c+0}' "${findings_tsv}") - total=$((warnings + errors)) - - # Resolve a blob URL prefix so findings in the table below become clickable - # links to github.com. Prefer TIDY_BLOB_SHA (set by the workflow to the PR - # head SHA) over GITHUB_SHA -- on pull_request events GITHUB_SHA points at - # the ephemeral refs/pull/N/merge commit, whose blob URLs stop resolving - # once the PR closes. On push / workflow_dispatch / schedule runs - # TIDY_BLOB_SHA is unset and we fall through to GITHUB_SHA, which is the - # pushed / selected commit respectively. When neither is set (local runs), - # repo_url stays empty and the file column renders as plain code. - local repo_url="" - if [[ -n "${GITHUB_SERVER_URL:-}" && -n "${GITHUB_REPOSITORY:-}" ]]; then - local blob_sha="${TIDY_BLOB_SHA:-${GITHUB_SHA:-}}" - if [[ -n "${blob_sha}" ]]; then - repo_url="${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/blob/${blob_sha}" - fi - fi - - # Render a check name as a markdown link to its official clang-tidy docs page - # (mirrors what cpp-linter-action used to do). The canonical URL layout is - # https://clang.llvm.org/extra/clang-tidy/checks//.html - # where is everything up to the first '-'. Two categories don't - # follow that layout: - # * clang-diagnostic-* -- compiler diagnostics, no per-check doc page - # * clang-analyzer-* -- static analyzer, documented on a single page - # Those fall back to plain code formatting / the analyzer index page. - check_link() { - local name="$1" - local module="${name%%-*}" - local rest="${name#*-}" - case "${name}" in - clang-diagnostic-*) - printf '`%s`' "${name}" - ;; - clang-analyzer-*) - printf '[`%s`](https://clang.llvm.org/docs/analyzer/checkers.html)' "${name}" - ;; - *) - printf '[`%s`](https://clang.llvm.org/extra/clang-tidy/checks/%s/%s.html)' \ - "${name}" "${module}" "${rest}" - ;; - esac - } - - { - echo "## clang-tidy results" - echo - if (( total == 0 )); then - # Mirror clang-format.sh's clean-run summary exactly so both CI jobs - # surface the same green check line. - echo ":white_check_mark: All files passed the configured checks." - else - echo "| Severity | Count |" - echo "|----------|-------|" - # Same GFM shortcodes used in the detailed findings table below, so the - # count summary and per-finding severity column read consistently. - echo "| :x: Error | ${errors} |" - echo "| :warning: Warning | ${warnings} |" - echo - - echo "### Top checks" - echo - echo '| Check | Count |' - echo '|-------|-------|' - awk -F'\t' '{print $5}' "${findings_tsv}" \ - | sort | uniq -c | sort -rn | head -5 \ - | while read -r count name; do - printf '| %s | %d |\n' "$(check_link "${name}")" "${count}" - done - echo - - echo "
All ${total} findings" - echo - echo '| Severity | File | Check | Message |' - echo '|----------|------|-------|---------|' - # Stream errors first, then warnings, by running two awk passes over the - # TSV. This preserves log-discovery order within each severity bucket - # without relying on `sort -s` (whose stable-sort semantics differ - # subtly between GNU and BSD coreutils). - { - awk -F'\t' '$1=="error"' "${findings_tsv}" - awk -F'\t' '$1=="warning"' "${findings_tsv}" - } | while IFS=$'\t' read -r sev path lineno col check msg; do - msg="${msg//|/\\|}" - local icon label file_cell - # GFM emoji shortcodes render in step summaries; :x: (red X) and - # :warning: (yellow triangle) are the closest visual analogs to - # GitHub's native annotation pills shown in the PR review UI. The - # title-cased label matches the count table at the top of the summary. - if [[ "${sev}" == "error" ]]; then - icon=':x:' - label='Error' - else - icon=':warning:' - label='Warning' - fi - # Link to github.com when we have a blob URL and a repo-relative path. - # Absolute paths (leading '/') are system headers that leaked past the - # note filter -- rendering them as a github.com link would 404, so - # fall back to plain code formatting. GitHub blob anchors support - # #L but not columns, so the column is kept in the label only. - if [[ -n "${repo_url}" && "${path}" != /* ]]; then - file_cell="[\`${path}:${lineno}\`](${repo_url}/${path}#L${lineno})" - else - file_cell="\`${path}:${lineno}\`" - fi - printf '| %s %s | %s | %s | %s |\n' \ - "${icon}" "${label}" "${file_cell}" "$(check_link "${check}")" "${msg}" - done - echo - echo "
" - echo - fi - } >> "${summary_file}" - - rm -f "${findings_tsv}" -} - -# Print a one-line summary plus a small per-check breakdown to stdout. Always -# runs (regardless of CI_MODE) so local invocations get the same headline view -# the GitHub step summary provides. Sets two globals consumed by the exit-code -# logic below: __TIDY_WARNINGS and __TIDY_ERRORS. The parsing regex matches -# the one used by emit_annotations / write_step_summary so the three views -# can't drift apart. -print_stdout_summary() { - local log="$1" - local workspace="${GITHUB_WORKSPACE:-${PWD}}" - local checks_tsv - checks_tsv="$(mktemp -t tidy-stdout.XXXXXX)" - - local line severity check - while IFS= read -r line; do - [[ "${line}" =~ ^(.+):([0-9]+):([0-9]+):[[:space:]]+(warning|error):[[:space:]]+(.+)[[:space:]]\[([^]]+)\][[:space:]]*$ ]] || continue - severity="${BASH_REMATCH[4]}" - check="${BASH_REMATCH[6]}" - # Match the annotation/summary normalization: strip the - # ",-warnings-as-errors" promotion suffix so a check buckets under one - # name regardless of severity. - check="${check%,-warnings-as-errors}" - printf '%s\t%s\n' "${severity}" "${check}" >> "${checks_tsv}" - done < "${log}" - - local warnings errors - warnings=$(awk -F'\t' '$1=="warning"{c++} END{print c+0}' "${checks_tsv}") - errors=$(awk -F'\t' '$1=="error"{c++} END{print c+0}' "${checks_tsv}") - - echo "------------------------------------------------------------" - if (( warnings == 0 && errors == 0 )); then - echo "clang-tidy summary: clean (0 warnings, 0 errors)" - else - printf 'clang-tidy summary: %d warning(s), %d error(s)\n' \ - "${warnings}" "${errors}" - echo " by check:" - awk -F'\t' '{print $2}' "${checks_tsv}" \ - | sort | uniq -c | sort -rn \ - | while read -r count name; do - printf ' %-50s %d\n' "${name}" "${count}" - done - fi - echo "------------------------------------------------------------" - - rm -f "${checks_tsv}" - __TIDY_WARNINGS="${warnings}" - __TIDY_ERRORS="${errors}" -} - -# --------- End GitHub Actions annotations --------- - -# Capture clang-tidy's combined stdout+stderr to a stable, repo-local path so -# it can be re-parsed after the run (for annotations and the step summary) and -# re-read by the user afterwards (e.g. `grep misc-const clang-tidy.log`). -# `*.log` is gitignored so this file never gets committed. Each run overwrites -# the previous log via `tee` (no -a), keeping the path predictable. -log="clang-tidy.log" - -# Past pre-flight: any non-zero exit from here on is a clang-tidy result -# (findings, internal error, etc.), not a user-facing argument/usage error, -# so suppress the "see --help" hint installed via the EXIT trap above. -__tidy_hint_active=0 - -# Scope the run to the user's explicit files when any were passed; otherwise -# fall back to the default src/ tree regex. run-clang-tidy treats each -# positional argument as a regex matched against compile_commands.json paths, -# so a plain path like src/room.cpp narrows the run to that translation unit. -if (( ${#explicit_files[@]} > 0 )); then - tidy_targets=("${explicit_files[@]}") -else - tidy_targets=("${FILE_REGEX}") -fi - -set +e -# run-clang-tidy is a Python script that doesn't flush stdout in its per-file -# loop; it only flushes once at exit. When its stdout is a pipe (the `| tee` -# below), Python's stdio defaults to block-buffered mode, so diagnostics -# accumulate in an ~8 KB buffer and the user sees nothing until the run is -# essentially over. PYTHONUNBUFFERED=1 forces line/unbuffered writes so each -# file's findings appear as soon as that file's clang-tidy finishes. -# Empty-array expansion under `set -u` is treated as "unbound" on bash <4.4 -# (notably macOS's system /bin/bash 3.2 and minimal Linux containers). The -# `${arr[@]+"${arr[@]}"}` idiom expands to nothing when the array is empty -# and to the quoted elements when non-empty, sidestepping the issue -# portably back to bash 3.2. -PYTHONUNBUFFERED=1 run-clang-tidy \ - -p "${BUILD_DIR}" \ - -quiet \ - -j "${jobs}" \ - ${extra_args[@]+"${extra_args[@]}"} \ - ${forward_args[@]+"${forward_args[@]}"} \ - "${tidy_targets[@]}" \ - 2>&1 | tee "${log}" -rc="${PIPESTATUS[0]}" -set -e - -if [[ "${CI_MODE}" == "1" ]]; then - emit_annotations "${log}" - write_step_summary "${log}" -fi - -# Always emit the concise headline summary to stdout. Sets __TIDY_WARNINGS / -# __TIDY_ERRORS globals which the strict-mode escalation and the log -# advertisement below consume. -__TIDY_WARNINGS=0 -__TIDY_ERRORS=0 -print_stdout_summary "${log}" - -# Only advertise the log when the run actually produced findings. A clean -# run still leaves per-file progress lines (`[N/M][T.Ts] /path/...`) in the -# file, but that's transient noise -- delete it so a stale log from a -# previous dirty run doesn't linger and confuse the next reader. CI -# annotations and the step summary above have already been emitted from -# the in-flight log, so cleanup here is purely about post-run state. -if (( __TIDY_WARNINGS > 0 || __TIDY_ERRORS > 0 )); then - echo "Results written to: $(cd "$(dirname "${log}")" && pwd)/$(basename "${log}")" -else - rm -f "${log}" -fi - -# Strict mode: any warning escalates to a non-zero exit. Errors already make -# run-clang-tidy itself exit non-zero (rc != 0), so this only changes the -# warnings-only case. Used by CI to gate merges on a clean clang-tidy result. -if [[ "${FAIL_ON_WARNING}" == "1" && "${rc}" == "0" && "${__TIDY_WARNINGS}" -gt 0 ]]; then - echo "clang-tidy: --fail-on-warning is set and ${__TIDY_WARNINGS} warning(s) were emitted; exiting non-zero." >&2 - rc=1 -fi - -exit "${rc}" +repo_root="$(git rev-parse --show-toplevel)" +exec "${repo_root}/cpp-tools/scripts/clang-tidy.sh" \ + --repo-root "${repo_root}" \ + --build-dir build-release \ + --file-regex '^(?!.*/(_deps|build-[^/]*|client-sdk-rust|cpp-example-collection|vcpkg_installed|docker|docs|data)/).*/src/(?!tests/).*\.(c|cpp|cc|cxx)$' \ + --header-filter '.*/(include/livekit|src)/.*\.(h|hpp)$' \ + --exclude-header-filter '(.*/src/tests/.*)|(.*/_deps/.*)|(.*/build-[^/]*/.*)' \ + --require-generated-protobuf build-release/generated \ + "$@" diff --git a/scripts/install-pre-commit.sh b/scripts/install-pre-commit.sh index fa37da84..c146fcef 100755 --- a/scripts/install-pre-commit.sh +++ b/scripts/install-pre-commit.sh @@ -13,25 +13,8 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. -# -# install-pre-commit.sh -- Install a git pre-commit hook that runs -# `scripts/clang-format.sh --fix` on staged C/C++ files. Re-stages any -# files the formatter rewrote so the commit includes the fixes. set -euo pipefail repo_root="$(git rev-parse --show-toplevel)" -hook_path="${repo_root}/.git/hooks/pre-commit" - -cat >"${hook_path}" <<'HOOK' -#!/bin/sh -# Auto-format staged C/C++ files using ./scripts/clang-format.sh --fix. -files=$(git diff --cached --name-only --diff-filter=ACMR \ - -- "*.c" "*.cc" "*.cpp" "*.cxx" "*.h" "*.hpp" "*.hxx") -[ -z "${files}" ] && exit 0 -echo "${files}" | xargs ./scripts/clang-format.sh --fix -echo "${files}" | xargs git add -HOOK - -chmod +x "${hook_path}" -echo "Installed pre-commit hook at ${hook_path}" +exec "${repo_root}/cpp-tools/scripts/install-pre-commit.sh" --repo-root "${repo_root}" "$@"