aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJade Lovelace <lix@jade.fyi>2024-07-31 20:37:27 -0700
committerJade Lovelace <lix@jade.fyi>2024-08-04 20:41:19 -0700
commit3daeeaefb115f47bf52ae96fa2b3f01842ec0f0f (patch)
tree9e1f7be5c068055e9d1a815e6f47090252bb61f5
parent32ca194ebfa3610c8099e5713ee7d5cbb8fb125c (diff)
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
-rw-r--r--clang-tidy/meson.build13
-rw-r--r--doc/manual/rl-next/clang-tidy-sorta.md10
-rw-r--r--flake.nix4
-rw-r--r--justfile10
-rw-r--r--meson.build2
-rw-r--r--meson.options8
-rwxr-xr-xmeson/clang-tidy/build_required_targets.py21
-rwxr-xr-xmeson/clang-tidy/clean_compdb.py53
-rw-r--r--meson/clang-tidy/fake.cc0
-rw-r--r--meson/clang-tidy/meson.build98
-rw-r--r--subprojects/lix-clang-tidy/.clang-format (renamed from clang-tidy/.clang-format)0
-rw-r--r--subprojects/lix-clang-tidy/.editorconfig (renamed from clang-tidy/.editorconfig)0
-rw-r--r--subprojects/lix-clang-tidy/FixIncludes.cc (renamed from clang-tidy/FixIncludes.cc)0
-rw-r--r--subprojects/lix-clang-tidy/FixIncludes.hh (renamed from clang-tidy/FixIncludes.hh)0
-rw-r--r--subprojects/lix-clang-tidy/HasPrefixSuffix.cc (renamed from clang-tidy/HasPrefixSuffix.cc)0
-rw-r--r--subprojects/lix-clang-tidy/HasPrefixSuffix.hh (renamed from clang-tidy/HasPrefixSuffix.hh)0
-rw-r--r--subprojects/lix-clang-tidy/LixClangTidyChecks.cc (renamed from clang-tidy/LixClangTidyChecks.cc)0
-rw-r--r--subprojects/lix-clang-tidy/README.md (renamed from clang-tidy/README.md)0
-rw-r--r--subprojects/lix-clang-tidy/default.nix44
-rw-r--r--subprojects/lix-clang-tidy/meson.build18
-rw-r--r--subprojects/lix-clang-tidy/meson.options3
21 files changed, 269 insertions, 15 deletions
diff --git a/clang-tidy/meson.build b/clang-tidy/meson.build
deleted file mode 100644
index c59164a72..000000000
--- a/clang-tidy/meson.build
+++ /dev/null
@@ -1,13 +0,0 @@
-project('lix-clang-tidy', ['cpp', 'c'],
- version : '0.1',
- default_options : ['warning_level=3', 'cpp_std=c++20'])
-
-llvm = dependency('Clang', version: '>= 14', modules: ['libclang'])
-sources = files(
- 'HasPrefixSuffix.cc',
- 'LixClangTidyChecks.cc',
- 'FixIncludes.cc',
-)
-
-shared_module('lix-clang-tidy', sources,
- dependencies: llvm)
diff --git a/doc/manual/rl-next/clang-tidy-sorta.md b/doc/manual/rl-next/clang-tidy-sorta.md
new file mode 100644
index 000000000..5f3f84348
--- /dev/null
+++ b/doc/manual/rl-next/clang-tidy-sorta.md
@@ -0,0 +1,10 @@
+---
+synopsis: "clang-tidy support"
+cls: 1697
+issues: fj#147
+credits: jade
+category: Development
+---
+
+`clang-tidy` can be used to lint Lix with a limited set of lints using `ninja -C build clang-tidy` and `ninja -C build clang-tidy-fix`.
+In practice, this fixes the built-in meson rule that was used the same as above being broken ever since precompiled headers were introduced.
diff --git a/flake.nix b/flake.nix
index a1fc947b7..14fdf4147 100644
--- a/flake.nix
+++ b/flake.nix
@@ -197,6 +197,8 @@
busybox-sandbox-shell = final.busybox-sandbox-shell or final.default-busybox-sandbox-shell;
};
+ lix-clang-tidy = final.callPackage ./subprojects/lix-clang-tidy { };
+
# Export the patched version of boehmgc that Lix uses into the overlay
# for consumers of this flake.
boehmgc-nix = final.nix.passthru.boehmgc-nix;
@@ -384,6 +386,8 @@
rec {
inherit (nixpkgsFor.${system}.native) nix;
default = nix;
+
+ inherit (nixpkgsFor.${system}.native) lix-clang-tidy;
}
// (
lib.optionalAttrs (builtins.elem system linux64BitSystems) {
diff --git a/justfile b/justfile
index 6a93fa63f..1576c759d 100644
--- a/justfile
+++ b/justfile
@@ -25,3 +25,13 @@ install *OPTIONS: (build OPTIONS)
# Run tests
test *OPTIONS:
meson test -C build --print-errorlogs {{ OPTIONS }}
+
+alias clang-tidy := lint
+
+lint:
+ ninja -C build clang-tidy
+
+alias clang-tidy-fix := lint-fix
+
+lint-fix:
+ ninja -C build clang-tidy-fix
diff --git a/meson.build b/meson.build
index ed50dff78..8577657d9 100644
--- a/meson.build
+++ b/meson.build
@@ -548,3 +548,5 @@ if enable_tests
subdir('tests/unit')
subdir('tests/functional')
endif
+
+subdir('meson/clang-tidy')
diff --git a/meson.options b/meson.options
index fc2050809..679d88347 100644
--- a/meson.options
+++ b/meson.options
@@ -1,7 +1,7 @@
# vim: filetype=meson
option('enable-build', type : 'boolean', value : true,
- description : 'Set to false to not actually build. Only really makes sense with -Dinternal-api-docs=true',
+ description : 'set to false to not actually build. Only really makes sense with -Dinternal-api-docs=true',
)
option('gc', type : 'feature',
@@ -37,7 +37,7 @@ option('tests-brief', type : 'boolean', value : false,
)
option('profile-build', type : 'feature', value: 'disabled',
- description : 'whether to enable -ftime-trace in clang builds, allowing for speeding up the build.'
+ description : 'whether to enable -ftime-trace in clang builds, allowing for diagnosing the cause of build time.'
)
option('store-dir', type : 'string', value : '/nix/store',
@@ -68,3 +68,7 @@ option('profile-dir', type : 'string', value : 'etc/profile.d',
option('enable-pch-std', type : 'boolean', value : true,
description : 'whether to use precompiled headers for C++\'s standard library (breaks clangd if you\'re using GCC)',
)
+
+option('lix-clang-tidy-checks-path', type : 'string', value : '',
+ description: 'path to lix-clang-tidy-checks library file, if providing it externally. Uses an internal one if this is not set',
+)
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
--- /dev/null
+++ b/meson/clang-tidy/fake.cc
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
diff --git a/clang-tidy/.clang-format b/subprojects/lix-clang-tidy/.clang-format
index cd8995543..cd8995543 100644
--- a/clang-tidy/.clang-format
+++ b/subprojects/lix-clang-tidy/.clang-format
diff --git a/clang-tidy/.editorconfig b/subprojects/lix-clang-tidy/.editorconfig
index 19ee09eec..19ee09eec 100644
--- a/clang-tidy/.editorconfig
+++ b/subprojects/lix-clang-tidy/.editorconfig
diff --git a/clang-tidy/FixIncludes.cc b/subprojects/lix-clang-tidy/FixIncludes.cc
index 602d3d355..602d3d355 100644
--- a/clang-tidy/FixIncludes.cc
+++ b/subprojects/lix-clang-tidy/FixIncludes.cc
diff --git a/clang-tidy/FixIncludes.hh b/subprojects/lix-clang-tidy/FixIncludes.hh
index ea890cd39..ea890cd39 100644
--- a/clang-tidy/FixIncludes.hh
+++ b/subprojects/lix-clang-tidy/FixIncludes.hh
diff --git a/clang-tidy/HasPrefixSuffix.cc b/subprojects/lix-clang-tidy/HasPrefixSuffix.cc
index e29b67e7c..e29b67e7c 100644
--- a/clang-tidy/HasPrefixSuffix.cc
+++ b/subprojects/lix-clang-tidy/HasPrefixSuffix.cc
diff --git a/clang-tidy/HasPrefixSuffix.hh b/subprojects/lix-clang-tidy/HasPrefixSuffix.hh
index 8693b6767..8693b6767 100644
--- a/clang-tidy/HasPrefixSuffix.hh
+++ b/subprojects/lix-clang-tidy/HasPrefixSuffix.hh
diff --git a/clang-tidy/LixClangTidyChecks.cc b/subprojects/lix-clang-tidy/LixClangTidyChecks.cc
index b3503dd3a..b3503dd3a 100644
--- a/clang-tidy/LixClangTidyChecks.cc
+++ b/subprojects/lix-clang-tidy/LixClangTidyChecks.cc
diff --git a/clang-tidy/README.md b/subprojects/lix-clang-tidy/README.md
index c2d1cb258..c2d1cb258 100644
--- a/clang-tidy/README.md
+++ b/subprojects/lix-clang-tidy/README.md
diff --git a/subprojects/lix-clang-tidy/default.nix b/subprojects/lix-clang-tidy/default.nix
new file mode 100644
index 000000000..1bfc2d9a4
--- /dev/null
+++ b/subprojects/lix-clang-tidy/default.nix
@@ -0,0 +1,44 @@
+{
+ lib,
+ stdenv,
+ cmake,
+ meson,
+ ninja,
+ llvmPackages,
+}:
+let
+ inherit (lib) fileset;
+in
+stdenv.mkDerivation {
+ pname = "lix-clang-tidy-checks";
+ # Setting the version to the Lix version is just going to cause pointless
+ # rebuilds due to versionSuffix and similar, and I cannot conceive of a usage
+ # where we actually care about its version since this is internal-only.
+ version = "0.1";
+
+ src = fileset.toSource {
+ root = ./.;
+ fileset = fileset.unions [
+ ./meson.build
+ ./meson.options
+ (fileset.fileFilter (
+ { hasExt, ... }:
+ builtins.any hasExt [
+ "cc"
+ "hh"
+ ]
+ ) ./.)
+ ];
+ };
+
+ nativeBuildInputs = [
+ meson
+ cmake
+ ninja
+ ];
+
+ buildInputs = [
+ llvmPackages.llvm
+ llvmPackages.clang-unwrapped.dev
+ ];
+}
diff --git a/subprojects/lix-clang-tidy/meson.build b/subprojects/lix-clang-tidy/meson.build
new file mode 100644
index 000000000..ef0226420
--- /dev/null
+++ b/subprojects/lix-clang-tidy/meson.build
@@ -0,0 +1,18 @@
+project('lix-clang-tidy', ['cpp', 'c'],
+ version : '0.1',
+ default_options : ['warning_level=3', 'cpp_std=c++20']
+)
+
+llvm = dependency('Clang', version: '>= 17', modules: ['libclang'])
+sources = files(
+ 'HasPrefixSuffix.cc',
+ 'LixClangTidyChecks.cc',
+ 'FixIncludes.cc',
+)
+
+lix_clang_tidy = shared_module('lix-clang-tidy', sources,
+ dependencies: llvm,
+ # overrides build_by_default, see https://github.com/mesonbuild/meson/issues/13498
+ install : get_option('build-by-default'),
+ build_by_default : get_option('build-by-default')
+)
diff --git a/subprojects/lix-clang-tidy/meson.options b/subprojects/lix-clang-tidy/meson.options
new file mode 100644
index 000000000..57f0f713b
--- /dev/null
+++ b/subprojects/lix-clang-tidy/meson.options
@@ -0,0 +1,3 @@
+option('build-by-default', type : 'boolean', value : true,
+ description : 'set to false to not actually build targets by default. This is a hack to deal with meson lacking a build_by_default default option and building subprojects by default'
+)