Skip to content

Commit f9e04d1

Browse files
committed
Add some pre-commit hooks
1 parent 5b26435 commit f9e04d1

11 files changed

Lines changed: 295 additions & 66 deletions

.github/workflows/bvt-clang.yml

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ jobs:
1717
- name: Install clang
1818
run: |
1919
curl https://apt.llvm.org/llvm.sh | sudo bash -s -- "$CLANG_VERSION"
20-
sudo apt install -y "clang-$CLANG_VERSION" "libc++-$CLANG_VERSION-dev" "clang-format-$CLANG_VERSION"
20+
sudo apt install -y "clang-$CLANG_VERSION" "libc++-$CLANG_VERSION-dev"
2121
{
2222
echo "CC=clang-$CLANG_VERSION"
2323
echo "CXX=clang++-$CLANG_VERSION"
@@ -27,7 +27,6 @@ jobs:
2727
- name: Check toolchain versions
2828
run: |
2929
"$CXX" --version
30-
"clang-format-$CLANG_VERSION" --version
3130
cmake --version
3231
ninja --version
3332
meson --version
@@ -36,9 +35,6 @@ jobs:
3635
- name: Build and run test with clang ${{ env.CLANG_VERSION }} on cmake
3736
run: |
3837
cmake -B build-cmake -GNinja -DCMAKE_CXX_STANDARD=23 -DCMAKE_BUILD_TYPE=Release -DPROXY_BUILD_MODULES=TRUE
39-
mapfile -t FILES < <(find include tests benchmarks build-cmake/examples_from_docs tools -type f \( -name '*.h' -o -name '*.ixx' -o -name '*.cpp' \))
40-
echo "Running clang-format on ${#FILES[@]} files: ${FILES[*]}"
41-
"clang-format-$CLANG_VERSION" --dry-run --Werror "${FILES[@]}"
4238
cmake --build build-cmake -j
4339
ctest --test-dir build-cmake -j
4440
mkdir build-cmake/drop

.github/workflows/bvt-compatibility.yml

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,10 +34,11 @@ jobs:
3434
- name: Install clang
3535
if: ${{ matrix.compiler.family == 'clang' }}
3636
run: |
37-
if [ '${{ matrix.compiler.version }}' -ge 21 ]; then
38-
curl https://apt.llvm.org/llvm.sh | sudo bash -s -- '${{ matrix.compiler.version }}'
37+
version='${{ matrix.compiler.version }}'
38+
if [ "$version" -ge 21 ]; then
39+
curl https://apt.llvm.org/llvm.sh | sudo bash -s -- "$version"
3940
fi
40-
sudo apt install -y 'clang-${{ matrix.compiler.version }}' 'clang-tools-${{ matrix.compiler.version }}' 'libc++-${{ matrix.compiler.version }}-dev' 'libc++abi-${{ matrix.compiler.version }}-dev'
41+
sudo apt install -y "clang-$version" "clang-tools-$version" "libc++-$version-dev" "libc++abi-$version-dev"
4142
cat <<'EOF' >> "$GITHUB_ENV"
4243
CC=clang-${{ matrix.compiler.version }}
4344
CXX=clang++-${{ matrix.compiler.version }}

.github/workflows/bvt-lint.yml

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
on:
2+
workflow_call:
3+
workflow_dispatch:
4+
5+
jobs:
6+
bvt-lint:
7+
runs-on: ubuntu-24.04
8+
steps:
9+
- uses: actions/checkout@v6
10+
11+
- name: Setup Python
12+
uses: actions/setup-python@v6
13+
with:
14+
python-version: "3.13"
15+
16+
- name: Cache pre-commit envs
17+
uses: actions/cache@v4
18+
with:
19+
path: ~/.cache/pre-commit
20+
key: pre-commit-${{ runner.os }}-${{ hashFiles('.pre-commit-config.yaml') }}
21+
22+
- name: Install pre-commit
23+
run: python3 -m pip install --upgrade pip pre-commit
24+
25+
- name: Run pre-commit hooks
26+
run: pre-commit run --all-files --show-diff-on-failure --color always
27+
28+
- name: Install mkdocs
29+
run: python3 -m pip install -r mkdocs/requirements.txt
30+
31+
- name: Build docs (mkdocs --strict)
32+
run: mkdocs build --strict

.github/workflows/mkdocs.yml

Lines changed: 0 additions & 34 deletions
This file was deleted.

.github/workflows/pipeline-ci.yml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,10 @@ on:
88
workflow_dispatch:
99

1010
jobs:
11+
run-bvt-lint:
12+
uses: ./.github/workflows/bvt-lint.yml
13+
name: Run BVT for lint
14+
1115
run-bvt-gcc:
1216
uses: ./.github/workflows/bvt-gcc.yml
1317
name: Run BVT with GCC
@@ -40,7 +44,3 @@ jobs:
4044
uses: ./.github/workflows/bvt-report.yml
4145
name: Generate report
4246
needs: [run-bvt-gcc, run-bvt-clang, run-bvt-msvc, run-bvt-appleclang, run-bvt-nvhpc, run-bvt-oneapi]
43-
44-
mkdocs:
45-
uses: ./.github/workflows/mkdocs.yml
46-
name: Build mkdocs

.github/workflows/pipeline-pages.yml

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,29 @@ on:
77

88
jobs:
99
build:
10-
uses: ./.github/workflows/mkdocs.yml
11-
with:
12-
upload-artifacts: true
10+
runs-on: ubuntu-24.04
11+
steps:
12+
- uses: actions/checkout@v6
13+
with:
14+
fetch-depth: 0 # Fetch all history for .GitInfo and .Lastmod
15+
16+
- name: Setup Python
17+
uses: actions/setup-python@v6
18+
with:
19+
python-version: "3.13"
20+
21+
- name: Install dependencies
22+
run: |
23+
python3 -m pip install --upgrade pip
24+
python3 -m pip install -r mkdocs/requirements.txt
25+
26+
- name: Build site
27+
run: mkdocs build --strict
28+
29+
- name: Upload site artifact
30+
uses: actions/upload-pages-artifact@v5
31+
with:
32+
path: ./site
1333

1434
deploy:
1535
needs: build

.pre-commit-config.yaml

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
repos:
2+
- repo: https://github.com/pre-commit/pre-commit-hooks
3+
rev: v6.0.0
4+
hooks:
5+
- id: mixed-line-ending
6+
args: ["--fix=lf"]
7+
- id: end-of-file-fixer
8+
- id: trailing-whitespace
9+
args: ["--markdown-linebreak-ext=md"]
10+
- id: check-merge-conflict
11+
- id: check-case-conflict
12+
- id: check-illegal-windows-names
13+
- id: check-added-large-files
14+
args: ["--maxkb=1024"]
15+
16+
- repo: https://github.com/astral-sh/ruff-pre-commit
17+
rev: v0.15.15
18+
hooks:
19+
- id: ruff-format
20+
21+
- repo: https://github.com/BlankSpruce/gersemi-pre-commit
22+
rev: 0.27.7
23+
hooks:
24+
- id: gersemi
25+
args: ["--indent", "2"]
26+
exclude: '^(subprojects|build|bazel-.*)/'
27+
28+
- repo: https://github.com/keith/pre-commit-buildifier
29+
rev: 8.2.1
30+
hooks:
31+
- id: buildifier
32+
exclude: '^(subprojects|build|bazel-.*)/'
33+
34+
- repo: https://github.com/rhysd/actionlint
35+
rev: v1.7.8
36+
hooks:
37+
- id: actionlint
38+
39+
- repo: local
40+
hooks:
41+
- id: meson-format
42+
name: meson format
43+
language: python
44+
additional_dependencies: ["meson==1.10.1"]
45+
entry: meson
46+
args: ["format", "--check-only", "--recursive", "meson.build"]
47+
pass_filenames: false
48+
files: '(^|/)(meson\.build|meson\.options|meson_options\.txt)$'
49+
50+
- id: clang-format
51+
name: clang-format
52+
language: python
53+
additional_dependencies: ["clang-format==22.1.5"]
54+
entry: python3 tools/format_cpp.py
55+
pass_filenames: false
56+
files: '\.(h|ixx|cpp|md)$'

CONTRIBUTING.md

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
# Contributing
2+
3+
Thanks for contributing! This document covers the local lint setup so your changes match what CI enforces.
4+
5+
## Lint matrix
6+
7+
CI runs the `bvt-lint` job on every PR. The workflow installs `pre-commit`, runs `pre-commit run --all-files`, and then builds the docs with `mkdocs build --strict`. The full check set, file scopes, and pinned tool versions all live in [`.pre-commit-config.yaml`](.pre-commit-config.yaml). That file is the single source of truth.
8+
9+
| Hook | What it checks |
10+
|-----------------------|----------------|
11+
| `pre-commit-hooks` | Basic hygiene. Line endings, trailing whitespace, final newline, merge-conflict markers, case-conflicting paths, Windows-illegal names, files over 1024 KB. |
12+
| `ruff-format` | Python scripts (`*.py`). |
13+
| `clang-format` | Every tracked C++ source file (`*.h`, `*.cpp`, `*.ixx`) and the `## Example` cpp blocks inside any Markdown file. |
14+
| `gersemi` | CMake build files (`CMakeLists.txt` and `*.cmake`) outside `subprojects/`. |
15+
| `meson-format` | Meson build files (`meson.build`, `meson.options`, `meson_options.txt`). |
16+
| `buildifier` | Bazel build files (`BUILD.bazel`, `MODULE.bazel`, `WORKSPACE.bazel`, `*.bzl`). |
17+
| `actionlint` | GitHub Actions workflows (`.github/workflows/*.yml`). |
18+
19+
These hooks run both locally and in CI. On top of them, CI also builds the docs with `mkdocs build --strict` to confirm `docs/` builds cleanly.
20+
21+
The `clang-format` hook is backed by [`tools/format_cpp.py`](tools/format_cpp.py), which you can also run directly.
22+
23+
```sh
24+
python3 tools/format_cpp.py # apply fixes in place
25+
python3 tools/format_cpp.py --check # exits non-zero if any file would change
26+
```
27+
28+
## Running lint locally
29+
30+
Install [`pre-commit`](https://pre-commit.com/), then run `pre-commit install` so the hooks run on every `git commit`. The framework manages each formatter at its pinned version for you. See the [pre-commit docs](https://pre-commit.com/) for everyday usage such as running a single hook or all files.
31+
32+
## Upgrading a formatter
33+
34+
All versions are pinned in [`.pre-commit-config.yaml`](.pre-commit-config.yaml), which is the only file you change to upgrade a formatter:
35+
36+
- For most hooks, bump the `rev:` field.
37+
- For local hooks, bump the version pin in `additional_dependencies:` (for example `meson==X.Y.Z` or `clang-format==X.Y.Z`).
38+
39+
CI reinstalls from the new pin on the next run.

tools/extract_example_code.py

Lines changed: 23 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -11,29 +11,36 @@
1111
from pathlib import Path
1212
from typing import Optional
1313

14+
_EXAMPLE_PATTERN = re.compile(
15+
r"(?P<prefix>## Example\r?\n\r?\n```cpp\r?\n)"
16+
r"(?P<code>.*?)"
17+
r"(?P<suffix>\r?\n```)",
18+
re.DOTALL,
19+
)
1420

15-
def try_extract_example_code(md_path: Path) -> Optional[str]:
16-
"""Return the generated C++ source for *md_path*, or None if it has no example."""
17-
example_pattern = re.compile(
18-
r"## Example\r?\n\r?\n```cpp\r?\n(.*?)\r?\n```", re.DOTALL
19-
)
20-
with open(md_path, "r", encoding="utf-8") as f:
21-
content = f.read()
2221

23-
blocks: list[str] = re.findall(example_pattern, content)
24-
if len(blocks) == 0:
25-
return None
26-
if len(blocks) > 1:
27-
raise ValueError(f"'{md_path}' has more than one '## Example' C++ block.")
22+
def try_extract_example_code(content: str) -> Optional["re.Match[str]"]:
23+
"""Return the sole ## Example cpp block match in *content*, or None.
2824
29-
code = blocks[0]
30-
return f"// This file was auto-generated from:\n// {md_path}\n\n{code}"
25+
Raises ValueError if more than one such block exists.
26+
"""
27+
matches = list(_EXAMPLE_PATTERN.finditer(content))
28+
if len(matches) > 1:
29+
raise ValueError("more than one '## Example' C++ block")
30+
return matches[0] if matches else None
3131

3232

3333
if __name__ == "__main__":
3434
if len(sys.argv) != 3:
3535
print(f"Usage: {sys.argv[0]} INPUT.md OUTPUT.cpp", file=sys.stderr)
3636
sys.exit(1)
37-
code = try_extract_example_code(Path(sys.argv[1]))
38-
if code is not None:
37+
md_path = Path(sys.argv[1])
38+
try:
39+
m = try_extract_example_code(md_path.read_text(encoding="utf-8"))
40+
except ValueError as e:
41+
raise ValueError(f"'{md_path}': {e}") from None
42+
if m is not None:
43+
code = (
44+
f"// This file was auto-generated from:\n// {md_path}\n\n{m.group('code')}"
45+
)
3946
Path(sys.argv[2]).write_text(code, encoding="utf-8")

0 commit comments

Comments
 (0)