Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 10 additions & 13 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ env:
jobs:
# Whether anything beyond documentation changed; the builds skip when not.
changes:
uses: Rawframe-Project/.github/.github/workflows/changes.yml@8414d388d2116b581c5c5d8f6641e1da4d863244 # v0.0.42
uses: Rawframe-Project/.github/.github/workflows/changes.yml@972ea41c6abf2652d3b067e57bf0713f1661cf0e # v0.0.51
permissions:
pull-requests: read

Expand All @@ -52,12 +52,12 @@ jobs:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- uses: Rawframe-Project/.github/.github/actions/docs-check@8414d388d2116b581c5c5d8f6641e1da4d863244 # v0.0.42
- uses: Rawframe-Project/.github/.github/actions/docs-check@972ea41c6abf2652d3b067e57bf0713f1661cf0e # v0.0.51
with:
exclude: tools/ucd

zizmor:
uses: Rawframe-Project/.github/.github/workflows/zizmor.yml@8414d388d2116b581c5c5d8f6641e1da4d863244 # v0.0.42
uses: Rawframe-Project/.github/.github/workflows/zizmor.yml@972ea41c6abf2652d3b067e57bf0713f1661cf0e # v0.0.51
permissions:
contents: read

Expand Down Expand Up @@ -134,20 +134,17 @@ jobs:
run: python3 tools/table_budget.py out/table-sizes.txt
# On the Release preset, where the assertions compile out, and with
# them live: each build hides findings the other shows. The header
# filter is given on the command line: clang-tidy-20 takes it from the
# configuration of the directory above the one it runs in, which has
# none, and would drop every finding in our headers as non-user code.
# It is anchored at the source directory the build tree records,
# regex-escaped, so it also leaves out the headers of dependencies
# fetched under out/.
# filter comes from tools/tidy_filter.sh: clang-tidy-20 run from the
# checkout reports no header finding without one, and the filter is
# anchored at the source directory the build tree records, so the
# headers of dependencies under out/ stay out.
- name: clang-tidy
run: |
for build in Release Debug; do
echo "clang-tidy, $build"
cmake --preset linux-clang-20 -B "out/tidy-$build" -DCMAKE_BUILD_TYPE=$build \
-DCMAKE_EXPORT_COMPILE_COMMANDS=ON -D${PREFIX}_BUILD_TESTS=OFF -D${PREFIX}_HARFBUZZ=ON
root=$(sed -n 's/^CMAKE_HOME_DIRECTORY:INTERNAL=//p' "out/tidy-$build/CMakeCache.txt")
filter="^$(printf '%s' "$root" | sed 's/[][\.*^$+?(){}|]/\\&/g')/(src|include)/"
filter=$(tools/tidy_filter.sh "out/tidy-$build")
for f in src/*.c; do
clang-tidy-20 -p "out/tidy-$build" --quiet --header-filter="$filter" \
--warnings-as-errors='*' "$f"
Expand All @@ -174,7 +171,7 @@ jobs:
- { preset: linux-i686-gcc-14 }
- { preset: macos-clang, runner: macos-15 }
- { preset: windows-clang-cl, runner: windows-2025 }
uses: Rawframe-Project/.github/.github/workflows/cpp.yml@8414d388d2116b581c5c5d8f6641e1da4d863244 # v0.0.42
uses: Rawframe-Project/.github/.github/workflows/cpp.yml@972ea41c6abf2652d3b067e57bf0713f1661cf0e # v0.0.51
permissions:
contents: read
with:
Expand Down Expand Up @@ -456,7 +453,7 @@ jobs:
prune-caches:
needs: [build, fuzz]
if: always() && github.event_name != 'pull_request' && github.ref == 'refs/heads/main'
uses: Rawframe-Project/.github/.github/workflows/cache-prune.yml@8414d388d2116b581c5c5d8f6641e1da4d863244 # v0.0.42
uses: Rawframe-Project/.github/.github/workflows/cache-prune.yml@972ea41c6abf2652d3b067e57bf0713f1661cf0e # v0.0.51
permissions:
actions: write

Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/pr-gate.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ permissions: {}

jobs:
gate:
uses: Rawframe-Project/.github/.github/workflows/pr-gate.yml@8414d388d2116b581c5c5d8f6641e1da4d863244 # v0.0.42
uses: Rawframe-Project/.github/.github/workflows/pr-gate.yml@972ea41c6abf2652d3b067e57bf0713f1661cf0e # v0.0.51
permissions:
pull-requests: write
issues: read
2 changes: 1 addition & 1 deletion .github/workflows/pr-title.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ permissions: {}

jobs:
title:
uses: Rawframe-Project/.github/.github/workflows/pr-title.yml@da9f20a2a9bf9d40cecbeaf39569d05321f83a37 # v0.0.45
uses: Rawframe-Project/.github/.github/workflows/pr-title.yml@972ea41c6abf2652d3b067e57bf0713f1661cf0e # v0.0.51
permissions:
pull-requests: read
with:
Expand Down
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ Run the `pr` and `full` tiers through the workspace's heavy-job wrapper on the s
- **Killing by pattern.** Never `pkill -f`, `pgrep | kill` or `kill` a PID found by matching a name or path: other sessions build and fuzz on the same machine with the same binary names. Kill only a PID you started.
- **Endless fuzzing.** A libFuzzer target runs until it finds something. Always pass `-max_total_time`, and stop what you started.
- **Editing generated or external files.** `src/generated/` is written by `tools/munigen`; `tools/ucd/` holds the Unicode files exactly as published, with their SHA-256 in `tools/ucd/README.md`, which `tools/check_ucd.py` checks. Change the generator, or replace the data as [development.md](docs/operations/development.md#a-new-unicode-version) describes.
- **Editing shared family files.** These are synced from the canonical copies in the organization's `.github` repository and fail CI when they differ: `docs/internals/maul-conventions.md`; `.clang-format`, `.clang-tidy`, `.editorconfig`, `.gitattributes`, `.gitignore`; `cmake/CompilerFlags.cmake`, `cmake/Install.cmake`, `cmake/Library.cmake`, `cmake/ReadVersion.cmake`, `cmake/Testing.cmake`, `cmake/Config.cmake.in`, `cmake/pkg-config.pc.in`, `cmake/HeaderCheck/CMakeLists.txt`; `bench/CMakeLists.txt`; `test/test_harness.h`; and `tools/build_docs.py`, `check_docs.py`, `check_exports.py`, `check_guide.py`, `check_lengths.py`, `check_modules.py`, `check_source.py`, `check_test_hygiene.py`, `gen_api.py`, `fuzz_target.sh`, `run_quarantine.sh`, and the fixtures in `tools/test_hygiene/`. Change the canonical copy first.
- **Editing shared family files.** These are synced from the canonical copies in the organization's `.github` repository and fail CI when they differ: `docs/internals/maul-conventions.md`; `.clang-format`, `.clang-tidy`, `.editorconfig`, `.gitattributes`, `.gitignore`; `cmake/CompilerFlags.cmake`, `cmake/Install.cmake`, `cmake/Library.cmake`, `cmake/ReadVersion.cmake`, `cmake/Testing.cmake`, `cmake/Config.cmake.in`, `cmake/pkg-config.pc.in`, `cmake/HeaderCheck/CMakeLists.txt`; `bench/CMakeLists.txt`; `test/test_harness.h`; and `tools/build_docs.py`, `check_docs.py`, `check_exports.py`, `check_guide.py`, `check_lengths.py`, `check_modules.py`, `check_source.py`, `check_test_hygiene.py`, `gen_api.py`, `fuzz_target.sh`, `run_quarantine.sh`, `tidy_filter.sh`, and the fixtures in `tools/test_hygiene/`. Change the canonical copy first.

## Invariants

Expand Down
3 changes: 2 additions & 1 deletion docs/internals/maul-conventions.md
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ The libraries are one product family: a program may link several of them, and a
- Getters that cannot fail take ids and return values. Out-parameters end in `Out`.
- Strings cross the API as UTF-8 with an explicit byte length, never as NUL-terminated text alone, so text from hostile sources is never measured by searching for a terminator.
- A library's root object is a typed opaque pointer. Every object a root owns is named by a typed value id, a 1-based slot index (0 is null) and a generation, one struct per kind. A stale id is refused with a typed result, never dereferenced, which costs one array read and a compare. Platform handles are never identities.
- State a caller holds is checked by how a caller can get it wrong. Opaque state that only the library writes is checked by its cookie, and its ABI version where it records one, and by every index, slot and length in it being in bounds, so that no bit pattern makes a call read outside the state or the data it names; a forged one is outside the contract, and its fields' consistency with each other is not checked again. A value struct with documented fields that a call takes back from the caller, which the caller may copy and assign, carries no raw pointer and size into the library's data: it names the object or data it came from and an index, checked on each call, so that a field copied from another value cannot reach memory outside what it names. A struct the library only fills in for the caller, such as a label it points at, may point into its data. So may one the header also documents as input a caller builds, such as an accessibility update, provided the call that takes it reads through it as through any buffer argument: only within the ranges its own pointers and counts state, checking every count, offset and index it reads there as untrusted, and relying on nothing the library validated earlier. Its pointers and counts are then the caller's to keep valid. Caller memory a call keeps "as checked", such as a bound message, must stay unchanged as documented, and whatever it becomes, a later call reads only within the extents it recorded.
- An operation that finishes later is a request: it returns its status at once and, accepted, a typed request id that exactly one completion record answers, in the owner's notification queue, with a typed outcome. Callbacks that deliver completions fire at unsafe points and leave re-entrancy rules to every caller; a queue does not.

## Floating point and determinism
Expand Down Expand Up @@ -154,7 +155,7 @@ The libraries are one product family: a program may link several of them, and a
| Rule | Tool |
|---|---|
| Formatting, include order, braces, `const` placement | `clang-format` at the version CI pins |
| Naming of locals, parameters and fields; cognitive complexity | `clang-tidy` |
| Naming of locals, parameters and fields; cognitive complexity | `clang-tidy`, over the library's sources and its own headers: every call passes `--header-filter` from `tools/tidy_filter.sh`, anchored at the build tree's `CMAKE_HOME_DIRECTORY`, since clang-tidy run from the checkout reports no header finding without one, and a changed header tidies every source |
| Public API C17, C23 and C++17 clean; MSVC header build | header tests (`cmake/Testing.cmake`, `cmake/HeaderCheck/`) |
| SPDX lines, comment style, history markers, banned calls, mutable file-scope state, `<math.h>` allow list, file names, shell scripts through shellcheck | `tools/check_source.py` |
| Module graph | `tools/check_modules.py` |
Expand Down
22 changes: 8 additions & 14 deletions tools/check.sh
Original file line number Diff line number Diff line change
Expand Up @@ -119,27 +119,21 @@ all_c_files() { git ls-files 'include/*.h' 'src/*.[ch]' 'test/*.[ch]' 'tools/*.[

# clang-tidy on the files given, as CI's checks job runs it: on the
# Release preset, where the assertions compile out, and with them live.
# Each hides findings the other shows. The header filter is given on the
# command line: clang-tidy-20 takes it from the configuration of the
# directory above the one it runs in, which has none, and would drop every
# finding in our headers as non-user code. It is anchored at the source
# directory the build tree records, the path its compile commands name, so
# it also leaves out the headers of dependencies fetched under out/.
header_filter() { # build directory
local root
root=$(sed -n 's/^CMAKE_HOME_DIRECTORY:INTERNAL=//p' "$1/CMakeCache.txt")
printf '^%s/(src|include)/' "$(printf '%s' "$root" | sed 's/[][\.*^$+?(){}|]/\\&/g')"
}

# Each hides findings the other shows. The header filter comes from
# tools/tidy_filter.sh: clang-tidy-20 run from the checkout reports no
# header finding without one, and the filter is anchored at the source
# directory the build tree records, so the headers of dependencies under
# out/ stay out.
tidy() { # files
local build
local build filter
for build in Release Debug; do
step "clang-tidy, $build, on $# files"
cmake --preset linux-clang-20 -B "$out/check-tidy-$build" -DCMAKE_BUILD_TYPE=$build \
-DCMAKE_EXPORT_COMPILE_COMMANDS=ON -DMAUL_UNICODE_BUILD_TESTS=OFF -DMAUL_UNICODE_HARFBUZZ=ON \
>"$out/check-tidy-$build.log"
filter=$(tools/tidy_filter.sh "$out/check-tidy-$build")
printf '%s\n' "$@" | xargs -P "$jobs" -n 1 clang-tidy-20 -p "$out/check-tidy-$build" --quiet \
--header-filter="$(header_filter "$out/check-tidy-$build")" --warnings-as-errors='*' 2>/dev/null
--header-filter="$filter" --warnings-as-errors='*' 2>/dev/null
done
}

Expand Down
51 changes: 51 additions & 0 deletions tools/tidy_filter.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
#!/usr/bin/env bash
# SPDX-License-Identifier: MIT
# Copyright (c) 2026 Sirac Ozmen
#
# Prints clang-tidy's --header-filter for a build tree: this library's
# headers under src/ and include/, anchored at the source directory the
# tree was configured from. CMake writes the include paths through that
# directory as it was given, so in a checkout reached through a symbolic
# link neither pwd nor pwd -P need match them; the tree's
# CMAKE_HOME_DIRECTORY does. The directory is escaped, so that a dot or
# a plus in it matches only itself.
#
# A library whose own sources also sit in subdirectories with their own
# src/ and include/ (such as rhi/ or window/) names them after the tree,
# and their headers are kept too.
#
# Assign the output to a variable of its own before passing it on, so
# that a failure here stops the caller instead of leaving an empty
# filter:
#
# filter=$(tools/tidy_filter.sh out/tidy)
# clang-tidy-20 -p out/tidy --header-filter="$filter" ...
#
# usage: tidy_filter.sh <build tree> [subdirectory ...]
set -euo pipefail

if [ $# -lt 1 ]; then
echo "usage: tidy_filter.sh <build tree> [subdirectory ...]" >&2
exit 2
fi

escape() {
printf '%s' "$1" | sed 's/[][\\.^$*+?(){}|]/\\&/g'
}

home=$(sed -n 's/^CMAKE_HOME_DIRECTORY:INTERNAL=//p' "$1/CMakeCache.txt")
if [ -z "$home" ]; then
echo "no CMAKE_HOME_DIRECTORY in $1/CMakeCache.txt" >&2
exit 1
fi
shift

prefix=""
if [ $# -gt 0 ]; then
names=""
for dir in "$@"; do
names="${names:+$names|}$(escape "$dir")"
done
prefix="(($names)/)?"
fi
printf '^%s/%s(src|include)/\n' "$(escape "$home")" "$prefix"
Loading