diff --git a/.treefmt.toml b/.treefmt.toml new file mode 100644 index 00000000..1dbcf80d --- /dev/null +++ b/.treefmt.toml @@ -0,0 +1,38 @@ +[global] +excludes = [ + "**/Doxyfile", + "**/Makefile", + "*.*-format", + "*.S", + "*.cmm", + "*.css", + "*.dld", + "*.gdb", + "*.gif", + "*.gitignore", + "*.html", + "*.ini", + "*.josh", + "*.json", + "*.md", + "*.png", + "*.puml", + "*.py", + "*.rb", + "*.rst", + "*.s", + "*.sh", + "*.spec", + "*.toml", + "*.txt", + "*.yaml", + "*.yml", + "docker/**", + "scripts/clang-format-wrapper", + "include/etl/generators/**" +] + +[formatter.cpp] +command = "scripts/clang-format-wrapper" +options = [ "-i", "--style=file" ] +includes = [ "*.c", "*.cc", "*.cpp", "*.h", "*.hh", "*.hpp" ] diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 6231d084..2d7a3028 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -6,6 +6,7 @@ Thanks for considering a contribution! Here’s what you need to know before ope - If you are fixing a bug, add a unit test that *fails* before the bug fix is implemented. - Do not initiate a pull request until all of the units tests pass. See below for information on project files and test scripts. - Branches should be based on the branch `master`. If `development` has pending updates, I’ll rebase the PR against it before pulling.. +- For formatting help, you can use clang-format, or the convenience wrapper treefmt. See also [docs/source-formatting.md](docs/source-formatting.md) There is a project file for VS2022 for C++14, 17, 20, 23, and bash scripts that run the tests for C++11, 14, 17, 20, 23 under Linux with GCC and Clang. There are syntax-only check bash scripts that cover C++03, 11, 14, 17, 20, 23 under Linux with GCC and Clang. diff --git a/docs/source-formatting.md b/docs/source-formatting.md new file mode 100644 index 00000000..4c353c05 --- /dev/null +++ b/docs/source-formatting.md @@ -0,0 +1,99 @@ +# Source Formatting + +This project uses **clang-format** (version 18) to enforce a consistent coding style +for C and C++ source files. For convenience, **treefmt** is also configured as a +single-command wrapper that discovers and formats every file in the tree. + +--- + +## clang-format + +### Configuration file + +The formatting rules live in [`.clang-format`](../.clang-format) at the repository +root. The style is based on **LLVM**. + +See the `.clang-format` file itself for the complete list. + +### Version requirement + +clang-format **18** is required. +The helper script [`scripts/clang-format-wrapper`](../scripts/clang-format-wrapper) +automatically resolves the correct binary: it first looks for `clang-format-18` on +`PATH`, then falls back to `clang-format` and verifies that its major version is 18. +All other tooling in the repo calls this wrapper instead of `clang-format` directly. + +### Running clang-format manually + +Format every tracked source file in the repository: + +```bash +git ls-files -z \ + '*.c' '*.cc' '*.cpp' \ + '*.h' '*.hh' '*.hpp' \ + ':(exclude)include/etl/generators/*' | xargs -0 scripts/clang-format-wrapper -i --verbose --style=file +``` + +You can also format individual files directly: + +```bash +scripts/clang-format-wrapper -i --style=file path/to/file.cpp +``` + +--- + +## treefmt + +[treefmt](https://treefmt.com) is a language-agnostic source-tree formatter. +It reads a single configuration file and dispatches each file to the appropriate +formatter. In this project, it delegates all C/C++ formatting to the same +`clang-format-wrapper` described above. + +In comparison to calling clang-format directly, it brings a significant speedup. + +### Configuration file + +The configuration lives in [`.treefmt.toml`](../.treefmt.toml) at the repository root. + +### Installing treefmt + +treefmt is a standalone Go binary. Install it with any of: + +```bash +# Using the official install script +curl -fsSL https://raw.githubusercontent.com/numtide/treefmt/main/install.sh | bash + +# Or via Homebrew +brew install treefmt + +# Or via Nix +nix profile install nixpkgs#treefmt2 +``` + +See the [treefmt documentation](https://treefmt.com) for more options. + +### Running treefmt + +From the repository root: + +```bash +# Format everything +treefmt + +# Check formatting without modifying files (useful in CI) +treefmt --fail-on-change +``` + +--- + +## Excluded paths + +`.treefmt.toml` excludes generated files under +`include/etl/generators/`. Do **not** format those files manually via clang-format or treefmt. + +## Pre-commit + +Before submitting a PR / contribution, run `treefmt --fail-on-change` to catch +unformatted code before merge. + +Alternatively, a plain `treefmt` automatically fixes any issues. \ No newline at end of file diff --git a/scripts/clang-format-wrapper b/scripts/clang-format-wrapper new file mode 100755 index 00000000..b501b663 --- /dev/null +++ b/scripts/clang-format-wrapper @@ -0,0 +1,48 @@ +#!/usr/bin/env python3 +from shutil import which +import re +import subprocess +import sys + + +def get_correct_clang_format(): + path = which("clang-format-18") + if path: + return path + + if which("clang-format") is None: + raise Exception("no clang-format found") + + result = subprocess.run( + ["clang-format", "--version"], capture_output=True, text=True, check=True + ) + match = re.search(r"\b(\d+\.\d+\.\d+)\b", result.stdout) + if not match: + raise Exception( + f"could not determine clang-format version from: {result.stdout.strip()}" + ) + version = match.group(1) + if version.split(".")[0] != "18": + raise Exception(f"clang-format version 18 required. Found {version}") + + return "clang-format" + + +def main(): + clang_format = get_correct_clang_format() + try: + completed = subprocess.run([clang_format] + sys.argv[1:]) + except FileNotFoundError: + print(f"error: clang-format not found at '{clang_format}'", file=sys.stderr) + sys.exit(1) + except PermissionError: + print(f"error: permission denied when running '{clang_format}'", file=sys.stderr) + sys.exit(1) + except OSError as exc: + print(f"error: failed to run '{clang_format}': {exc}", file=sys.stderr) + sys.exit(1) + sys.exit(completed.returncode) + + +if __name__ == "__main__": + main()