Skip to content

build: make uncrustify enforce the project's actual code style - #121

Merged
dogo merged 5 commits into
masterfrom
chore/uncrustify-config
Sep 24, 2026
Merged

dogo merged 5 commits into
masterfrom
chore/uncrustify-config

Conversation

@dogo

@dogo dogo commented Sep 24, 2026 •

Copy link
Copy Markdown
Owner

Problem

uncrustify.cfg was a verbatim uncrustify --show-config dump from version 0.78.1, with zero customizations. Comparing it option by option against the defaults, the only difference was a duplicated sp_paren_paren line. Since nearly every rule ships as ignore, running the formatter changed nothing:

0 of 71 files would be reformatted

So: there was a make uncrustify target, there was a .uncrustifyignore, there was a script — and none of it enforced any style. Security theater.

Other problems found:

  • .uncrustifyignore was dead code. It listed only lib/ paths, but the script scanned src/ only. No pattern ever matched.
  • Inconsistent line endings. 57 CRLF files against 14 LF ones in src/, no .gitattributes, and the config's newlines = auto kept the mix alive.
  • samples/ was never formatted.
  • Script bugs: unquoted for file in $files, ignore matching by loose substring, no check mode.
  • No CI enforcement. Nothing stopped an unformatted PR.

Changes

uncrustify.cfg — 152 KB / 850 options → 166 lines. Only the rules that describe the style, derived by counting occurrences in the existing code:

Rule Evidence
K&R ({ on the same line) 232×62 functions, 605×63 blocks
if ( with a space 754 × 64
foo( without one 1908 × 88
char *p 321 × 42
(int)x without a space 133 × 1
} else on one line 88 × 19
case at the switch level 105 × 8
#include in column 1 957 × 104

code_width = 0 — the p99 line is 116 columns; automatic wrapping would only cause damage. There is a comment at the top of the file warning never to paste a --show-config dump over it, which is exactly what flattened the previous config.

.gitattributes (new) — * text=auto eol=lf, CRLF kept for .bat, binaries marked.

run-uncrustify.sh — rewritten:

  • file discovery through git ls-files, which drops build output and submodules on its own (the old find saw 88 files, the real count is 85 — samples/cbuild/CMakeFiles/.../CMakeCCompilerId.c and friends were in scope)
  • covers samples/ as well as src/
  • .uncrustifyignore now matches as a prefix of the repo-relative path
  • --check mode (writes nothing, exits 1), --help, accepts individual files
  • set -euo pipefail, NUL-delimited arrays

CMakeLists.txt — new uncrustify-check target.

.github/workflows/Build.yml — a format job with Uncrustify pinned to 0.83.0, built from the tag and cached. apt install would bring a moving version and the check would start failing on its own after an upgrade.

.git-blame-ignore-revs — the two whitespace commits, so git blame stays useful.

Verification

The reformatting is semantics-preserving. diff -w flagged 24 files, but it does not forgive line joins (} else). I compared the C token streams before and after, using a tokenizer that handles strings, chars, comments and line continuations:

69 files formatted, 0 with a token difference

Idempotent — a second pass changes nothing.

Builds — make exits 0 with no new warnings under -Wall, make install OK, and the audio, shapes and adhoc samples build.

Blame preserved — lines remain attributed to their original authors.

How to review

The commits are split by nature; worth reviewing one at a time:

Commit What it is
34d7fea the config and the tooling — the only one with real content to review
20816b6 CRLF → LF, pure whitespace
d49cd33 one line in the ignore file
5bf4400 the formatting, pure whitespace
52394b1 blame-ignore

For the formatting diff without the noise: git diff -w 20816b6 5bf4400.

uncrustify.cfg was a verbatim `--show-config` dump of Uncrustify 0.78.1 with
zero customizations, so every rule sat at its default (mostly "ignore") and a
full run reformatted 0 of 71 files. Replace it with a 166-line config that
encodes the style already dominant in src/ and samples/: K&R braces, hard tabs
at width 8, "if (" with a space, "foo()" without, pointer star bound to the
name, casts without a trailing space, "case" at the "switch" level and
preprocessor directives in column 1.

Also:
- add .gitattributes so line endings are LF in the repository (src/ held 57
  CRLF files against 14 LF ones), matching the config's `newlines = lf`
- rewrite run-uncrustify.sh: discover files through `git ls-files` so build
  output and submodules drop out on their own, cover samples/ as well as src/,
  match .uncrustifyignore as a path prefix instead of a loose substring (the
  file listed only lib/ paths while the script scanned src/ only, so it never
  matched anything), and add a --check mode
- add an `uncrustify-check` CMake target and a `format` CI job pinned to
  Uncrustify 0.83.0
Applies the .gitattributes rules added in the previous commit. Whitespace
only: no file's content changes when compared with CR stripped.
Whitespace only. Verified semantics-preserving: the C token stream of each of
the 69 changed files is byte-identical to its previous revision.
@dogo
dogo merged commit 0c7ae79 into master Sep 24, 2026
10 checks passed
@dogo
dogo deleted the chore/uncrustify-config branch September 24, 2026 02:07

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 52394b117d

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread run-uncrustify.sh
elif git rev-parse --is-inside-work-tree >/dev/null 2>&1; then
# Tracked files only: skips build output and submodule contents.
local roots=() r
for r in "${ROOTS[@]}"; do roots+=("$r/*.c" "$r/*.h"); done

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Include C++ sources in formatter discovery

When a PR changes a C++ source, the new format job still succeeds without checking it because the job invokes ./run-uncrustify.sh --check without filenames and this pathspec collects only .c and .h files. In particular, the built samples/shapes/main.cpp and its tracked shapes.hpp header are omitted even though they currently contain constructs that the new configuration would reformat, so include .cpp and .hpp in default discovery.

Useful? React with 👍 / 👎.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant