From 3daeeaefb115f47bf52ae96fa2b3f01842ec0f0f Mon Sep 17 00:00:00 2001 From: Jade Lovelace Date: Wed, 31 Jul 2024 20:37:27 -0700 Subject: build: implement clang-tidy using our plugin The principle of this is that you can either externally build it with Nix (actual implementation will be in a future commit), or it can be built with meson if the Nix one is not passed in. The idea I have is that dev shells don't receive the one from Nix to avoid having to build it, but CI can use the one from Nix and save some gratuitous rebuilds. The design of this is that you can run `ninja -C build clang-tidy` and it will simply correctly clang-tidy the codebase in spite of PCH bullshit caused by the cc-wrapper. This is a truly horrendous number of hacks in a ball, caused by bugs in several pieces of software, and I am not even getting started. I don't consider this to fix the clang-tidy issue filing, since we still have a fair number of issues to fix even on the existing minimal configuration, and I have not yet implemented it in CI. Realistically we will need to do something like https://github.com/Ericsson/codechecker to be able to silence warnings without physically touching the code, or at least *diff* reports between versions. Also, the run-clang-tidy output design is rather atrocious and must not be inflicted upon anyone I have respect for, since it buries the diagnostics in a pile of invocation logs. We would do really well to integrate with the Gerrit SARIF stuff so we can dump the reports on people in a user-friendly manner. Related: https://git.lix.systems/lix-project/lix/issues/147 Change-Id: Ifefe533f3b56874795de231667046b2da6ff2461 --- meson/clang-tidy/build_required_targets.py | 21 +++++++ meson/clang-tidy/clean_compdb.py | 53 ++++++++++++++++ meson/clang-tidy/fake.cc | 0 meson/clang-tidy/meson.build | 98 ++++++++++++++++++++++++++++++ 4 files changed, 172 insertions(+) create mode 100755 meson/clang-tidy/build_required_targets.py create mode 100755 meson/clang-tidy/clean_compdb.py create mode 100644 meson/clang-tidy/fake.cc create mode 100644 meson/clang-tidy/meson.build (limited to 'meson') diff --git a/meson/clang-tidy/build_required_targets.py b/meson/clang-tidy/build_required_targets.py new file mode 100755 index 000000000..5c0e9641e --- /dev/null +++ b/meson/clang-tidy/build_required_targets.py @@ -0,0 +1,21 @@ +#!/usr/bin/env python3 +import subprocess + +def get_targets_of_rule(build_root: str, rule_name: str) -> list[str]: + return subprocess.check_output(['ninja', '-C', build_root, '-t', 'targets', 'rule', rule_name]).decode().strip().splitlines() + +def ninja_build(build_root: str, targets: list[str]): + subprocess.check_call(['ninja', '-C', build_root, '--', *targets]) + +def main(): + import argparse + ap = argparse.ArgumentParser(description='Builds required targets for clang-tidy') + ap.add_argument('build_root', help='Ninja build root', type=str) + + args = ap.parse_args() + + targets = [t for t in get_targets_of_rule(args.build_root, 'CUSTOM_COMMAND') if t.endswith('gen.hh')] + ninja_build(args.build_root, targets) + +if __name__ == '__main__': + main() diff --git a/meson/clang-tidy/clean_compdb.py b/meson/clang-tidy/clean_compdb.py new file mode 100755 index 000000000..6736fe63a --- /dev/null +++ b/meson/clang-tidy/clean_compdb.py @@ -0,0 +1,53 @@ +#!/usr/bin/env python3 +# Deletes the PCH arguments from a compilation database, to workaround nixpkgs +# stdenv having a cc-wrapper that is impossible to use for anything except cc +# itself, for example, clang-tidy. + +import json +import shlex + + +def process_compdb(compdb: list[dict]) -> list[dict]: + + def munch_command(args: list[str]) -> list[str]: + out = [] + eat_next = False + for i, arg in enumerate(args): + if arg == '-fpch-preprocess': + # as used with gcc + continue + elif arg == '-include-pch' or (arg == '-include' and args[i + 1] == 'precompiled-headers.hh'): + # -include-pch some-pch (clang), or -include some-pch (gcc) + eat_next = True + continue + if not eat_next: + out.append(arg) + eat_next = False + return out + + def chomp(item: dict) -> dict: + item = item.copy() + item['command'] = shlex.join(munch_command(shlex.split(item['command']))) + return item + + return [chomp(x) for x in compdb if not x['file'].endswith('precompiled-headers.hh')] + + +def main(): + import argparse + ap = argparse.ArgumentParser( + description='Delete pch arguments from compilation database') + ap.add_argument('input', + type=argparse.FileType('r'), + help='Input json file') + ap.add_argument('output', + type=argparse.FileType('w'), + help='Output json file') + args = ap.parse_args() + + input_json = json.load(args.input) + json.dump(process_compdb(input_json), args.output, indent=2) + + +if __name__ == '__main__': + main() diff --git a/meson/clang-tidy/fake.cc b/meson/clang-tidy/fake.cc new file mode 100644 index 000000000..e69de29bb diff --git a/meson/clang-tidy/meson.build b/meson/clang-tidy/meson.build new file mode 100644 index 000000000..9aba5fbdc --- /dev/null +++ b/meson/clang-tidy/meson.build @@ -0,0 +1,98 @@ +# The clang-tidy target for Lix + +run_clang_tidy = find_program('run-clang-tidy', required : false) +# Although this looks like it wants to be pkg-config, pkg-config does not +# really work for *plugins*, which are executable-like .so files that also +# cannot be found via find_program. Fun! +if get_option('lix-clang-tidy-checks-path') != '' + lix_clang_tidy_so = get_option('lix-clang-tidy-checks-path') + lix_clang_tidy_so_found = true +else + lix_clang_tidy_subproj = subproject( + 'lix-clang-tidy', + required : false, + default_options : {'build-by-default': false} + ) + if lix_clang_tidy_subproj.found() + lix_clang_tidy_so = lix_clang_tidy_subproj.get_variable('lix_clang_tidy') + lix_clang_tidy_so_found = true + else + lix_clang_tidy_so_found = false + endif +endif + +# Due to numerous problems, such as: +# - Meson does not expose pch targets, but *fine*, I can just ask Ninja for +# them with `ninja -t targets rule cpp_PCH` and build them manually: +# https://github.com/mesonbuild/meson/issues/13499 +# - Nixpkgs stdenv buries the cc-wrapper under a giant pile of assumptions +# about the cc-wrapper actually being used on the cc of a stdenv, rather than +# independently for clang-tidy, and we need to use cc-wrapper to get the +# correct hardening flags so that clang-tidy can actually parse the PCH file +# +# I give up. I am going to delete the damn PCH args and then it will work. +meson.add_postconf_script( + python, + meson.current_source_dir() / 'clean_compdb.py', + meson.global_build_root() / 'compile_commands.json', + meson.current_build_dir() / 'compile_commands.json', +) + +# Horrible hack to get around not being able to depend on another target's +# generated headers in any way in the meson DSL +# https://github.com/mesonbuild/meson/issues/12817 which was incorrectly +# closed, if you *actually* need to generate the files once. +# Also related: https://github.com/mesonbuild/meson/issues/3667 +# +# Or we could ban meson generators because their design is broken. +build_all_generated_headers = custom_target( + command : [ + python, + meson.current_source_dir() / 'build_required_targets.py', + meson.global_build_root(), + ], + output : 'generated_headers.stamp', + build_by_default : false, + build_always_stale : true, +) + +if lix_clang_tidy_so_found + run_clang_tidy_args = [ + '-load', + lix_clang_tidy_so, + '-p', + # We have to workaround a run-clang-tidy bug too, so we must give the + # directory name rather than the actual compdb file. + # https://github.com/llvm/llvm-project/issues/101440 + meson.current_build_dir(), + '-quiet', + ] + run_target( + 'clang-tidy', + command : [ + # XXX: This explicitly invokes it with python because of a nixpkgs bug + # where clang-unwrapped does not patch interpreters in run-clang-tidy. + # However, making clang-unwrapped depend on python is also silly, so idk. + python, + run_clang_tidy, + run_clang_tidy_args, + '-warnings-as-errors', + '*', + ], + depends : [ + build_all_generated_headers, + ], + ) + run_target( + 'clang-tidy-fix', + command : [ + python, + run_clang_tidy, + run_clang_tidy_args, + '-fix', + ], + depends : [ + build_all_generated_headers, + ], + ) +endif -- cgit v1.2.3