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}" "$@"