|
| 1 | +# GeneralsGameCode Clang-Tidy Custom Checks |
| 2 | + |
| 3 | +Custom clang-tidy checks for the GeneralsGameCode codebase. **This is primarily designed for Windows**, where checks are built directly into clang-tidy. Mac/Linux users can optionally use the plugin version. |
| 4 | + |
| 5 | +## Quick Start |
| 6 | + |
| 7 | +### Windows |
| 8 | + |
| 9 | +**Option 1: Use Pre-built clang-tidy** |
| 10 | +- Precompiled binaries can be found at: https://github.com/TheSuperHackers/GeneralsTools |
| 11 | + |
| 12 | +**Option 2: Build It Yourself** |
| 13 | + |
| 14 | +Follow the manual steps in the [Windows: Building Checks Into clang-tidy](#windows-building-checks-into-clang-tidy) section below. |
| 15 | + |
| 16 | +**Usage** |
| 17 | +```powershell |
| 18 | +llvm-project\build\bin\clang-tidy.exe -p build/clang-tidy ` |
| 19 | + --checks='-*,generals-use-is-empty,generals-use-this-instead-of-singleton' ` |
| 20 | + file.cpp |
| 21 | +``` |
| 22 | + |
| 23 | +### macOS/Linux (Plugin Version) |
| 24 | + |
| 25 | +If you prefer the plugin approach on macOS/Linux: |
| 26 | + |
| 27 | +```bash |
| 28 | +# Build the plugin |
| 29 | +cd scripts/clang-tidy-plugin |
| 30 | +mkdir build && cd build |
| 31 | +cmake .. -DLLVM_DIR=/path/to/llvm/lib/cmake/llvm |
| 32 | +cmake --build . --config Release |
| 33 | + |
| 34 | +# Use with --load flag |
| 35 | +clang-tidy -p build/clang-tidy \ |
| 36 | + --checks='-*,generals-use-is-empty' \ |
| 37 | + -load scripts/clang-tidy-plugin/build/lib/libGeneralsGameCodeClangTidyPlugin.so \ |
| 38 | + file.cpp |
| 39 | +``` |
| 40 | + |
| 41 | +**Note:** On Windows, plugin loading via `--load` is **not supported** due to DLL static initialization limitations. Windows users must use the built-in version. |
| 42 | + |
| 43 | +## Checks |
| 44 | + |
| 45 | +### `generals-use-is-empty` |
| 46 | + |
| 47 | +Finds uses of `getLength() == 0`, `getLength() > 0`, `compare("") == 0`, or `compareNoCase("") == 0` on `AsciiString` and `UnicodeString`, and `Get_Length() == 0` on `StringClass` and `WideStringClass`, and suggests using `isEmpty()`/`Is_Empty()` or `!isEmpty()`/`!Is_Empty()` instead. |
| 48 | + |
| 49 | +**Examples:** |
| 50 | + |
| 51 | +```cpp |
| 52 | +// Before (AsciiString/UnicodeString) |
| 53 | +if (str.getLength() == 0) { ... } |
| 54 | +if (str.getLength() > 0) { ... } |
| 55 | +if (str.compare("") == 0) { ... } |
| 56 | +if (str.compareNoCase("") == 0) { ... } |
| 57 | +if (str.compare(AsciiString::TheEmptyString) == 0) { ... } |
| 58 | + |
| 59 | +// After (AsciiString/UnicodeString) |
| 60 | +if (str.isEmpty()) { ... } |
| 61 | +if (!str.isEmpty()) { ... } |
| 62 | +if (str.isEmpty()) { ... } |
| 63 | +if (str.isEmpty()) { ... } |
| 64 | +if (str.isEmpty()) { ... } |
| 65 | + |
| 66 | +// Before (StringClass/WideStringClass) |
| 67 | +if (str.Get_Length() == 0) { ... } |
| 68 | +if (str.Get_Length() > 0) { ... } |
| 69 | + |
| 70 | +// After (StringClass/WideStringClass) |
| 71 | +if (str.Is_Empty()) { ... } |
| 72 | +if (!str.Is_Empty()) { ... } |
| 73 | +``` |
| 74 | +
|
| 75 | +### `generals-use-this-instead-of-singleton` |
| 76 | +
|
| 77 | +Finds uses of singleton global variables (like `TheGameLogic->method()` or `TheGlobalData->member`) inside member functions of the same class type, and suggests using the member directly (e.g., `method()` or `member`) instead of the singleton reference. |
| 78 | +
|
| 79 | +**Examples:** |
| 80 | +
|
| 81 | +```cpp |
| 82 | +// Before |
| 83 | +void GameLogic::update() { |
| 84 | + UnsignedInt now = TheGameLogic->getFrame(); |
| 85 | + TheGameLogic->setFrame(now + 1); |
| 86 | + TheGameLogic->m_frame = 10; |
| 87 | +} |
| 88 | +
|
| 89 | +// After |
| 90 | +void GameLogic::update() { |
| 91 | + UnsignedInt now = getFrame(); |
| 92 | + setFrame(now + 1); |
| 93 | + m_frame = 10; |
| 94 | +} |
| 95 | +``` |
| 96 | + |
| 97 | +## Prerequisites |
| 98 | + |
| 99 | +Before using clang-tidy, you need to generate a compile commands database: |
| 100 | + |
| 101 | +```bash |
| 102 | +cmake -B build/clang-tidy -DCMAKE_DISABLE_PRECOMPILE_HEADERS=ON -G Ninja |
| 103 | +``` |
| 104 | + |
| 105 | +This creates `build/clang-tidy/compile_commands.json` which tells clang-tidy how to compile each file. |
| 106 | + |
| 107 | +## Windows: Building Checks Into clang-tidy |
| 108 | + |
| 109 | +Since plugin loading via `--load` doesn't work on Windows, the checks are integrated directly into the clang-tidy source tree. This is the recommended approach for Windows. |
| 110 | + |
| 111 | +### Step 1: Clone and Build LLVM |
| 112 | + |
| 113 | +### Step 2: Copy Plugin Files to LLVM Source Tree |
| 114 | + |
| 115 | +```powershell |
| 116 | +# Create the plugin directory in clang-tools-extra |
| 117 | +mkdir llvm-project\clang-tools-extra\clang-tidy\plugins\generalsgamecode |
| 118 | +
|
| 119 | +# Copy the plugin source files |
| 120 | +cp -r scripts\clang-tidy-plugin\*.cpp llvm-project\clang-tools-extra\clang-tidy\plugins\generalsgamecode\ |
| 121 | +cp -r scripts\clang-tidy-plugin\*.h llvm-project\clang-tools-extra\clang-tidy\plugins\generalsgamecode\ |
| 122 | +cp -r scripts\clang-tidy-plugin\readability llvm-project\clang-tools-extra\clang-tidy\plugins\generalsgamecode\ |
| 123 | +``` |
| 124 | + |
| 125 | +**Important:** After copying, you need to update the include paths in the headers: |
| 126 | +- Change `#include "clang-tidy/ClangTidyCheck.h"` to `#include "../../../ClangTidyCheck.h"` |
| 127 | +- Change `#include "clang-tidy/ClangTidyModule.h"` to `#include "../../ClangTidyModule.h"` |
| 128 | + |
| 129 | +### Step 3: Create CMakeLists.txt for the Module |
| 130 | + |
| 131 | +Create `llvm-project/clang-tools-extra/clang-tidy/plugins/generalsgamecode/CMakeLists.txt`: |
| 132 | + |
| 133 | +```cmake |
| 134 | +add_clang_library(clangTidyGeneralsGameCodeModule STATIC |
| 135 | + GeneralsGameCodeTidyModule.cpp |
| 136 | + readability/UseIsEmptyCheck.cpp |
| 137 | + readability/UseThisInsteadOfSingletonCheck.cpp |
| 138 | +
|
| 139 | + LINK_LIBS |
| 140 | + clangTidy |
| 141 | + clangTidyUtils |
| 142 | + ) |
| 143 | +
|
| 144 | +clang_target_link_libraries(clangTidyGeneralsGameCodeModule |
| 145 | + PRIVATE |
| 146 | + clangAST |
| 147 | + clangASTMatchers |
| 148 | + clangBasic |
| 149 | + clangLex |
| 150 | + clangTooling |
| 151 | + ) |
| 152 | +``` |
| 153 | + |
| 154 | +### Step 4: Register the Module |
| 155 | + |
| 156 | +**Modify `llvm-project/clang-tools-extra/clang-tidy/CMakeLists.txt`:** |
| 157 | + |
| 158 | +Add the subdirectory: |
| 159 | +```cmake |
| 160 | +add_subdirectory(plugins/generalsgamecode) |
| 161 | +``` |
| 162 | + |
| 163 | +Add to `ALL_CLANG_TIDY_CHECKS`: |
| 164 | +```cmake |
| 165 | +set(ALL_CLANG_TIDY_CHECKS |
| 166 | + ... |
| 167 | + clangTidyGeneralsGameCodeModule |
| 168 | + ... |
| 169 | +) |
| 170 | +``` |
| 171 | + |
| 172 | +**Modify `llvm-project/clang-tools-extra/clang-tidy/ClangTidyForceLinker.h`:** |
| 173 | + |
| 174 | +Add the anchor to force linker inclusion: |
| 175 | +```cpp |
| 176 | +// This anchor is used to force the linker to link the GeneralsGameCodeModule. |
| 177 | +extern volatile int GeneralsGameCodeModuleAnchorSource; |
| 178 | +static int LLVM_ATTRIBUTE_UNUSED GeneralsGameCodeModuleAnchorDestination = |
| 179 | + GeneralsGameCodeModuleAnchorSource; |
| 180 | +``` |
| 181 | + |
| 182 | +**Modify `llvm-project/clang-tools-extra/clang-tidy/plugins/generalsgamecode/GeneralsGameCodeTidyModule.cpp`:** |
| 183 | + |
| 184 | +Ensure the anchor is defined: |
| 185 | +```cpp |
| 186 | +namespace clang::tidy { |
| 187 | +// ... module registration ... |
| 188 | + |
| 189 | +// Force linker to include this module |
| 190 | +volatile int GeneralsGameCodeModuleAnchorSource = 0; |
| 191 | +} |
| 192 | +``` |
| 193 | +
|
| 194 | +### Step 5: Rebuild clang-tidy |
| 195 | +
|
| 196 | +```powershell |
| 197 | +cd llvm-project\build |
| 198 | +ninja clang-tidy |
| 199 | +``` |
| 200 | + |
| 201 | +### Step 6: Use the Built-in Checks |
| 202 | + |
| 203 | +Once rebuilt, the checks are always available - no `--load` flag needed: |
| 204 | + |
| 205 | +```powershell |
| 206 | +llvm-project\build\bin\clang-tidy.exe -p build/clang-tidy ` |
| 207 | + --checks='-*,generals-use-is-empty,generals-use-this-instead-of-singleton' ` |
| 208 | + file.cpp |
| 209 | +``` |
| 210 | + |
| 211 | + |
| 212 | +## macOS/Linux: Building the Plugin |
| 213 | + |
| 214 | +If you're on macOS or Linux and want to use the plugin version: |
| 215 | + |
| 216 | +### Prerequisites |
| 217 | + |
| 218 | +**macOS:** |
| 219 | +```bash |
| 220 | +brew install llvm@21 |
| 221 | +``` |
| 222 | + |
| 223 | +**Linux (Ubuntu/Debian):** |
| 224 | +```bash |
| 225 | +sudo apt-get install llvm-21-dev clang-21 libclang-21-dev |
| 226 | +``` |
| 227 | + |
| 228 | +### Building the Plugin |
| 229 | + |
| 230 | +```bash |
| 231 | +cd scripts/clang-tidy-plugin |
| 232 | +mkdir build && cd build |
| 233 | +cmake .. -DLLVM_DIR=/path/to/llvm/lib/cmake/llvm -DClang_DIR=/path/to/clang/lib/cmake/clang |
| 234 | +cmake --build . --config Release |
| 235 | +``` |
| 236 | + |
| 237 | +The plugin will be built as a shared library (`.so` on Linux, `.dylib` on macOS) in the `build/lib/` directory. |
| 238 | + |
| 239 | +### Using the Plugin |
| 240 | + |
| 241 | +```bash |
| 242 | +clang-tidy -p build/clang-tidy \ |
| 243 | + --checks='-*,generals-use-is-empty,generals-use-this-instead-of-singleton' \ |
| 244 | + -load scripts/clang-tidy-plugin/build/lib/libGeneralsGameCodeClangTidyPlugin.so \ |
| 245 | + file.cpp |
| 246 | +``` |
| 247 | + |
| 248 | +**Important:** The plugin must be built with the same LLVM version as the `clang-tidy` executable in your PATH. The CMake build will display which LLVM version it found (e.g., `Found LLVM 21.1.7`). Verify this matches your `clang-tidy --version` output. |
| 249 | + |
| 250 | +- Windows plugins not working is a known limitation - see [GitHub issue #159710](https://github.com/llvm/llvm-project/issues/159710) and [LLVM Discourse discussion](https://discourse.llvm.org/t/clang-tidy-is-clang-tidy-out-of-tree-check-plugin-load-mechanism-guaranteed-to-work-with-msvc/84111) |
0 commit comments