Linting Strategy and Caching¶
This document outlines the static analysis strategy for the suckless-ogl project and the implementation of its high-performance caching mechanism.
Strategy: Clang-Tidy¶
We use clang-tidy for static analysis. The configuration is defined in .clang-tidy, focusing on:
- Security: Avoiding insecure buffer handling and deprecated APIs.
- Reliability: Detecting narrowing conversions and uninitialized variables.
- Readability: Enforcing consistent coding styles and removing "magic numbers".
- Portability: Ensuring compliance with C standards (CERT, HICPP).
Style Preferences¶
We prioritize "Suckless" philosophy:
- Minimize external dependencies.
- Avoid
NOLINTcomments unless absolutely necessary (e.g., global variables for test context). - Use
static constorenuminstead of magic numbers.
Incremental Caching (Sentinel Files)¶
Originally, we explored cltcache. However, due to its overhead and specific requirement for explicit compiler flags (--), we transitioned to a native Sentinel-based caching system implemented directly in the Makefile.
How it Works¶
Instead of linting every file on every run, we use "Sentinel files" (.linted) to track the status of each source file.
- Dependency Tracking: Each
.lintedfile in.lint_cache/depends on:- The corresponding
.csource file. - The project's
.clang-tidyconfiguration. - The
compile_commands.jsondatabase.
- The corresponding
- Date Comparison:
makenatively compares the timestamp of the source vs. the sentinel. If the source is older than the sentinel, the file is skipped. - Updating: If a file needs linting,
clang-tidyis executed. On success, the sentinel file is updated usingtouch. - Dependencies: Before linting, the system ensures that generated headers (like
glad/glad.h) are ready by building the necessary targets. - Parallelization: The process is parallelized using
make -j$(NPROCS), allowing simultaneous analysis of multiple files.
Why this approach?¶
- Speed: Subsequent runs are near-instantaneous (O(1) file stat check).
- Robustness: If an analysis is interrupted, the sentinel isn't updated, ensuring it runs again on the next try.
- Simplicity: No external Python dependencies or complex cache databases; it leverages the operating system's file system and standard build tools.
- Visibility: The
Makefileoutput clearly shows which file is being processed, providing immediate feedback.
Maintenance¶
To clear the cache and force a full re-lint:
Training a new rule in .clang-tidy will also automatically invalidate the entire cache, ensuring project-wide compliance.
Include Hygiene (misc-include-cleaner)¶
The misc-include-cleaner check is enabled in .clang-tidy to detect unused #include directives at lint time.
Configuration¶
# .clang-tidy (excerpt)
Checks: '...,misc-*,...'
CheckOptions:
- key: misc-include-cleaner.MissingIncludes
value: 'false'
- UnusedIncludes: Enabled — flags headers that are included but never directly used.
- MissingIncludes: Disabled — avoids false positives on symbols available through transitive includes (common with cglm, stb, GLFW).
This ensures just lint catches stale includes automatically, without requiring IDE-specific tooling.
GLSL Shader Validation¶
Shaders are validated at lint time using glslangValidator via scripts/lint_shaders.sh.
Standard Mode (integrated in just lint)¶
Validates all .vert, .frag, and .comp shaders in shaders/. The script resolves custom @header include directives before passing the resolved source to glslangValidator.
Strict Mode (optional, SPIR-V target)¶
Runs validation with --target-env opengl (SPIR-V rules). This surfaces issues like missing layout(location=N) qualifiers that cause RenderDoc's shader debugger to fail silently.
As of March 2026, all 33 shader files pass strict SPIR-V validation. The project enforces explicit layout(location=N) on all varyings and non-opaque uniforms, and layout(binding=N) on all samplers/images. See renderdoc_guide.md for the full rationale.