James Gallagher, ChatGPT 5.0
To reformat only the C++ source files in git, use git with the ls-files command and the file globbing patterns for our code. Take that and pipe it into clang-format. Note the use of xargs to limit the number of things (-n) passed to the formater and set the number of parallel invocations (-P).
Tip
Avoid running this at the top of a repo since it will recurse and edit thousands of files. Unless that's what you want.
For all of this stuff, try it first on one file. Look at the results, then go forward with caution. Test and commit after every change. Here's how to run the formatter on just one file. Note that we have a .clang-format file in the root of both libdap4 and bes.
clang-format -i --style=file BESVersionResponseHandler.hHere's a more general version that will reformat many files. Be cautious since mistakes and get out of hand quickly.
git ls-files '*.[ch]pp' '*.cc' '*.h' '*.hh' \
| xargs -n 50 -P "$(sysctl -n hw.ncpu)" clang-format -i --style=fileHere's a basic one-file fix that will change all the declarations in a header to use the C++14 override keyword were applicable. There is a script that will run this in bes/dispatch/cpp-modernize-all.sh.
ROOT=/Users/jimg/src/opendap/hyrax/bes # <-- fix this path
SDK=$(xcrun --show-sdk-path)
clang-tidy -p . -checks='-*,modernize-use-override' -fix \
-extra-arg=-std=c++14 \
-extra-arg=-stdlib=libc++ \
-extra-arg=-isysroot -extra-arg="$SDK" \
-extra-arg=-I"$SDK/usr/include/c++/v1" \
-extra-arg=-I"$ROOT/dap" \
-extra-arg=-I"$ROOT/xmlcommand" \
-extra-arg=-isystem -extra-arg=/usr/local/include \
BESVersionResponseHandler.hIt's tempting to make up a 'one script to rule them all,' but don't. Plod along slowly working on groups of files. Test and commit. Also, the above will need more -I options if the code in question references headers that clang-tidy cannot find in the CWD.
Here is an example of the error from a missing header directory:
/Users/jimg/src/opendap/hyrax/bes/dispatch/unit-tests/BESCatalogListTest.cc:25:10: error: 'cppunit/TextTestRunner.h' file not found [clang-diagnostic-error]
25 | #include <cppunit/TextTestRunner.h>
| ^~~~~~~~~~~~~~~~~~~~~~~~~~Caution
Avoid the temptation of running clang-tidy in parallel using xargs. Doing that will result in corrupt files.
When clang-tidy finds errors, it will not fix anything in that source file. There is a workaround; use the -fix-errors option. But this may not be what is best. The output of clang-tidy should not contain errors. Warnings are OK, but errors mean the command is likely missing some header files. Here's what the output should look like. The warnings are OK, but if you see errors, then there are problems clang-tidy cannot handle, and I would not trust it to edit the code.
[1/10] Processing file /Users/jimg/src/opendap/hyrax/bes/dispatch/unit-tests/BESCatalogListTest.cc.
[2/10] Processing file /Users/jimg/src/opendap/hyrax/bes/dispatch/unit-tests/BESFileLockingCacheTest.cc.
[3/10] Processing file /Users/jimg/src/opendap/hyrax/bes/dispatch/unit-tests/CatalogItemTest.cc.
[4/10] Processing file /Users/jimg/src/opendap/hyrax/bes/dispatch/unit-tests/CatalogNodeTest.cc.
[5/10] Processing file /Users/jimg/src/opendap/hyrax/bes/dispatch/unit-tests/FileCacheTest.cc.
4 warnings generated.
[6/10] Processing file /Users/jimg/src/opendap/hyrax/bes/dispatch/unit-tests/RequestTimerTest.cc.
4 warnings generated.
[7/10] Processing file /Users/jimg/src/opendap/hyrax/bes/dispatch/unit-tests/ServerAdministratorTest.cc.
4 warnings generated.
[8/10] Processing file /Users/jimg/src/opendap/hyrax/bes/dispatch/unit-tests/TestReporter.cc.
4 warnings generated.
[9/10] Processing file /Users/jimg/src/opendap/hyrax/bes/dispatch/unit-tests/TestRequestHandler.cc.
4 warnings generated.
[10/10] Processing file /Users/jimg/src/opendap/hyrax/bes/dispatch/unit-tests/TestResponseHandler.cc.
4 warnings generated.
/Users/jimg/src/opendap/hyrax/bes/dispatch/unit-tests/FileCacheTest.cc:60:5: warning: 'SHA256_Init' is deprecated [clang-diagnostic-deprecated-declarations]
60 | SHA256_Init(&sha256);
| ^
/usr/local/include/openssl/sha.h:73:1: note: 'SHA256_Init' has been explicitly marked deprecated here
73 | OSSL_DEPRECATEDIN_3_0 int SHA256_Init(SHA256_CTX *c);
| ^
/usr/local/include/openssl/macros.h:229:49: note: expanded from macro 'OSSL_DEPRECATEDIN_3_0'
229 | # define OSSL_DEPRECATEDIN_3_0 OSSL_DEPRECATED(3.0)
| ^
/usr/local/include/openssl/macros.h:62:52: note: expanded from macro 'OSSL_DEPRECATED'
62 | # define OSSL_DEPRECATED(since) __attribute__((deprecated))
| ^
/Users/jimg/src/opendap/hyrax/bes/dispatch/unit-tests/FileCacheTest.cc:69:5: warning: 'SHA256_Update' is deprecated [clang-diagnostic-deprecated-declarations]
69 | SHA256_Update(&sha256, buffer.data(), length);
| ^
...There is a way to make a compilation database using the command bear, but I found that was harder to use and limit the cope of changes. There is also a second command run-clang-tidy, but I found there were issues with that.
Here’s a focused, C++14-friendly bundle of clang-tidy checks you can run individually or in small groups. These are grouped them by “theme” and show the check names; you can plug them into the above in place of modernize-use-override above.
This assumes:
- You’re compiling as C++14
- You’re using the macOS flags you already found work (
--extra-arg=-std=c++14,--extra-arg=-isysroot, etc.) - You want safe, low-surprise modernizations (no big semantic leaps like auto everywhere).
These are very safe, very C++11/14 core style improvements:
modernize-use-overridemodernize-use-equals-defaultmodernize-use-equals-deletemodernize-redundant-void-arg
All C++14 friendly:
modernize-use-nullptr– replace0/NULLwithnullptr.modernize-make-unique– turnnew T(...)intostd::make_unique<T>(...)where safe.
If you don’t use std::unique_ptr heavily or can’t rely on <memory> availability,
skip modernize-make-unique. Otherwise, it’s a great upgrade.
Some modernize checks can be a bit aggressive; here are ones that are usually tame:
modernize-loop-convertConvertfor (i = 0; i < vec.size(); ++i)to range-for where safe.modernize-use-emplaceReplacecontainer.push_back(T(args...))withemplace_back(args...).
Review these changes carefully.
These are usually low drama:
modernize-use-bool-literals– replace0/1used as bool withfalse/true.modernize-use-bare-nullptr(if available in your version) – similar spirit touse-nullptr.modernize-deprecated-headers– replace<stdio.h>with<cstdio>, etc.
These are powerful but more likely to cause friction in an older, widely used codebase, even though they’re C++14-compatible:
modernize-use-auto– can be stylistically controversial and occasionally hurt readability in generic-heavy code.modernize-pass-by-value– changes function signatures; can cause ABI or overload selection changes.modernize-use-using– rewritestypedeftousing; safe, but touches a ton of code and can be noisy in diffs.modernize-use-noexcept– can change exception specifications, sometimes interacting badly with older exception-heavy code.
I’d either:
- run these very late, once everything else is clean,
- or hand-pick them for specific directories / modules via
HeaderFilterRegexor limiting the file list.
If you want a practical sequence, that’s unlikely to blow anything up:
-
Overrides & special members &
voidargs:modernize-use-override modernize-use-equals-default modernize-use-equals-delete modernize-redundant-void-arg -
Null & optional smart pointer upgrade:
modernize-use-nullptr [maybe] modernize-make-unique -
Literal + header cleanup:
modernize-use-bool-literals modernize-deprecated-headers -
Loop + emplace (one at a time if you prefer):
modernize-loop-convert modernize-use-emplace
Here’s a conservative, low-drama order I’d use for a legacy C++14 codebase, with short “why” notes. Each bullet is a pass (one clang-tidy run), and you can always split a pass into multiple runs if you want even smaller diffs.
I’ll assume you run clang-format at the very end, once.
1. Overrides & special members & (void)
modernize-use-overridemodernize-use-equals-defaultmodernize-use-equals-deletemodernize-redundant-void-arg
Why first: Touches class APIs and function declarations, but is very low risk and clarifies intent for later checks. Also reduces noise when you inspect later diffs.
2. Null pointers (and optionally smart pointers)
- Always:
modernize-use-nullptr - Optional:
modernize-make-unique
If you’re at all nervous about ownership/ABI, split this into two passes and run modernize-use-nullptr alone first.
Why now:
Null semantics get cleaned up early; later checks may be easier to read with nullptr. modernize-make-unique
it is still “semantic” (ownership), so keep it relatively early so you can test it in isolation.
3. Member functions to static
readability-convert-member-functions-to-static
Why here:
This changes member function signatures (removes the implicit this), but only for functions that don’t use this, so behavior is preserved. Doing it after override / equals/delete means member APIs are already in good shape; doing it before stylistic loops/emplace avoids their noise when reviewing these more structural changes.
4. Internal linkage / static for free functions
misc-use-internal-linkage
Why here: This can change linkage and may have minor ABI or symbol visibility implications. Keeping it after the obvious “interface cleanup” but before cosmetic stuff makes it easier to reason about symbol changes.
5. Single-character string → char in string::find
performance-faster-string-find
Why here:
Purely local changes ("x" → 'x' in specific call contexts). Running it before raw string literals and loop/emplace changes keeps its diff small and easy to inspect.
6. Heavily escaped strings → raw strings
modernize-raw-string-literal
Why here:
This can produce big, visually noisy changes (long R"(...) blocks, changed line breaks), but doesn’t affect control flow. Doing it before loop/emplace conversions avoids mixing “big string blobs” with control-structure changes in the same diff chunks.
If you have some particularly critical files, you might run this with a narrow HeaderFilterRegex first, then broaden it.
7. Loop conversions
modernize-loop-convert
Why this is applied later: Changes the shape of control flow (index-based loops → range-for). Semantics should stay the same, but it can interact with subtle off-by-one code, and the visual diff is larger. Doing it after your APIs, linkage, and string literals are stable, makes regressions easier to isolate.
8. Container emplace
modernize-use-emplace
Why after loops:
It mostly affects call sites (push_back(T(...)) → emplace_back(...)) and can change which constructor gets picked (usually for the better, but still). Put it after all structural cleanups, so if something misbehaves, it’s obvious this pass was the cause.
These are pretty safe and can be slotted after Phase 2 or 3 if you like; I’d still keep them before loops/emplace in case they interact with templates/macros.
modernize-use-bool-literalsmodernize-deprecated-headers
You can bundle them or run separately:
modernize-use-bool-literals
modernize-deprecated-headers
Once all clang-tidy passes are done and committed (ideally one commit per phase so you can bisect easily):
- Run
clang-format -i --style=fileover your whole tree.
This will normalize all the whitespace/indentation churn caused by the edits above.
modernize-use-override, modernize-use-equals-default, modernize-use-equals-delete, modernize-redundant-void-argmodernize-use-nullptr(then maybemodernize-make-unique)readability-convert-member-functions-to-staticmisc-use-internal-linkageperformance-faster-string-findmodernize-raw-string-literalmodernize-loop-convertmodernize-use-emplace- (Optional)
modernize-use-bool-literals,modernize-deprecated-headers(anywhere after 2, before 7) clang-formatpass
[!NOTE] clang‑tidy is not installed by default on macOS (even if you have Xcode / the command line tools). (Stack Overflow)
Here’s how to install it and get it working:
-
Install LLVM via Homebrew
brew install llvm
This will install newer LLVM tools (including clang-tidy), but they will be keg-only (not automatically in your PATH). (Gist)
-
Add the LLVM tool-bin directory to your PATH (or create symlinks) For example, on Apple Silicon (homebrew default
/opt/homebrew) you might do:export PATH="/opt/homebrew/opt/llvm/bin:$PATH"
Or symlink:
ln -s "$(brew --prefix llvm)/bin/clang-tidy" /usr/local/bin/clang-tidy -
Verify installation:
clang-tidy --version
It should print something like
LLVM (clang-tidy) version X.Y.Z. If it doesn’t exist, your PATH/symlink isn’t set up. (Markaicode) -
(Optional) Create a
compile_commands.jsonfor your project so clang-tidy can use the correct include paths, macros, etc.
- Because this installs a newer LLVM than the system’s default clang, you may end up using a different compiler version than Xcode’s default — that’s usually fine but something to keep aware of.
- On macOS with M1/ARM or newer SDKs, sometimes you’ll hit compatibility issues if the tooling was built for a different architecture or SDK. (There are issues reported for arm64 builds of clang-tidy on macOS) (GitHub)
- If you only want a lightweight installation (just clang-tidy), you may look at alternative binaries (there is a “clang-tools” package on PyPI, etc.), but the Homebrew llvm route is the most reliable. (PyPI)