diff --git a/.clang-tidy b/.clang-tidy new file mode 100644 index 0000000000000000000000000000000000000000..a14b2319185216425e40d41da6983c616c70f12c --- /dev/null +++ b/.clang-tidy @@ -0,0 +1,18 @@ +HeaderFilterRegex: ".*" +Checks: "-*,boost-*,bugprone-argument-comment,bugprone-assert-side-effect,bugprone-bad-signal-to-kill-thread,bugprone-bool-pointer-implicit-conversion,bugprone-branch-clone,bugprone-copy-constructor-init,bugprone-dangling-handle,bugprone-dynamic-static-initializers,bugprone-exception-escape,bugprone-fold-init-type,bugprone-forward-declaration-namespace,bugprone-forwarding-reference-overload,bugprone-implicit-widening-of-multiplication-result,bugprone-inaccurate-erase,bugprone-incorrect-roundings,bugprone-infinite-loop,bugprone-integer-division,bugprone-lambda-function-name,bugprone-macro-*,bugprone-misplaced-*,bugprone-move-forwarding-reference,bugprone-multiple-statement-macro,bugprone-narrowing-conversions,bugprone-no-escape,bugprone-not-null-terminated-result,bugprone-parent-virtual-call,bugprone-posix-return,bugprone-redundant-branch-condition,bugprone-reserved-identifier,bugprone-signal-handler,bugprone-signed-char-misuse,bugprone-sizeof-*,bugprone-spuriously-wake-up-functions,bugprone-string-*,bugprone-suspicious-*,bugprone-swapped-arguments,bugprone-terminating-continue,bugprone-throw-keyword-missing,bugprone-too-small-loop-variable,bugprone-undefined-memory-manipulation,bugprone-undelegated-constructor,bugprone-unhandled-*,bugprone-unused-*,bugprone-use-after-move,bugprone-virtual-near-miss,clang-*,cppcoreguidelines-interfaces-global-init,cppcoreguidelines-narrowing-conversions,cppcoreguidelines-no-malloc,cppcoreguidelines-owning-memory,cppcoreguidelines-pro-type-const-cast,cppcoreguidelines-pro-type-cstyle-cast,cppcoreguidelines-pro-type-member-init,cppcoreguidelines-pro-type-reinterpret-cast,cppcoreguidelines-pro-type-static-cast-downcast,cppcoreguidelines-pro-type-union-access,cppcoreguidelines-slicing,darwin-*,google-build-explicit-make-pair,google-build-namespaces,google-default-arguments,google-explicit-constructor,google-global-names-in-headers,google-objc-*,google-readability-avoid-underscore-in-googletest-name,readability-braces-around-statements,google-runtime-*,google-upgrade-googletest-case,hicpp-exception-baseclass,hicpp-multiway-paths-covered,hicpp-no-assembler,hicpp-signed-bitwise,linuxkernel-*,llvm-include-order,llvm-namespace-comment,llvm-prefer-*,llvm-twine-local,misc-definitions-in-headers,misc-misplaced-const,misc-new-delete-overloads,misc-non-copyable-objects,misc-redundant-expression,misc-static-assert,misc-throw-by-value-catch-by-reference,misc-unconventional-assign-operator,misc-uniqueptr-reset-release,misc-unused-*,modernize-avoid-bind,modernize-avoid-c-arrays,modernize-concat-nested-namespaces,modernize-deprecated-*,modernize-loop-convert,modernize-make-*,modernize-pass-by-value,modernize-raw-string-literal,modernize-redundant-void-arg,modernize-replace-*,modernize-return-braced-init-list,modernize-shrink-to-fit,modernize-unary-static-assert,modernize-use-auto,modernize-use-bool-literals,modernize-use-default-member-init,modernize-use-emplace,modernize-use-equals-*,modernize-use-nodiscard,modernize-use-noexcept,modernize-use-nullptr,modernize-use-override,modernize-use-transparent-functors,modernize-use-uncaught-exceptions,modernize-use-using,mpi-*,objc-*,openmp-*,performance-faster-string-find,performance-for-range-copy,performance-implicit-conversion-in-loop,performance-inefficient-algorithm,performance-inefficient-vector-operation,performance-move-*,performance-no-automatic-move,performance-noexcept-move-constructor,performance-trivially-destructible,performance-type-promotion-in-math-fn,performance-unnecessary-*,portability-*,readability-avoid-const-params-in-decls,readability-const-return-type,readability-container-size-empty,readability-convert-member-functions-to-static,readability-delete-null-pointer,readability-else-after-return,readability-function-size,readability-identifier-naming,readability-inconsistent-declaration-parameter-name,readability-make-member-function-const,readability-misleading-indentation,readability-misplaced-array-index,readability-non-const-parameter,readability-qualified-auto,readability-redundant-*,readability-simplify-*,readability-static-*,readability-string-compare,readability-uniqueptr-delete-release,readability-uppercase-literal-suffix,readability-use-anyofallof,zircon-*,missing-noreturn" + +CheckOptions: + - key: readability-identifier-naming.PrivateMemberPrefix + value: "_" + - key: readability-identifier-naming.ProtectedMemberPrefix + value: "_" + - key: readability-identifier-naming.MemberCase + value: "camelBack" + - key: readability-identifier-naming.FunctionCase + value: "camelBack" + - key: readability-identifier-naming.ClassCase + value: "CamelCase" + - key: readability-identifier-naming.PublicMemberPrefix + value: "" + - key: readability-identifier-naming.MacroDefinitionCase + value: "UPPER_CASE" diff --git a/cmake/add_linter_target.cmake b/cmake/add_linter_target.cmake new file mode 100644 index 0000000000000000000000000000000000000000..80a504c2a57cc0ddd6849e05d3b1a39b362fbbd8 --- /dev/null +++ b/cmake/add_linter_target.cmake @@ -0,0 +1,13 @@ +set(CTK_CLANG_TIDY_VERSION "14" CACHE STRING "Version of the clang-tidy binary to use") +set(CTK_ENABLE_TIDY_WHILE_BUILDING OFF CACHE BOOL "Whether to run clang-tidy on every compilation unit") +if (CTK_ENABLE_TIDY_WHILE_BUILDING) + set(CMAKE_CXX_CLANG_TIDY /usr/bin/clang-tidy-${CTK_CLANG_TIDY_VERSION};-config-file=${CMAKE_SOURCE_DIR}/.clang-tidy) + set(CMAKE_C_CLANG_TIDY /usr/bin/clang-tidy-${CTK_CLANG_TIDY_VERSION};-config-file=${CMAKE_SOURCE_DIR}/.clang-tidy) +endif() +set(CMAKE_EXPORT_COMPILE_COMMANDS ON) +add_custom_target(run-linter COMMAND run-clang-tidy-${CTK_CLANG_TIDY_VERSION} -p ${CMAKE_BINARY_DIR} WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}) +add_custom_target(fix-linter-stepwise COMMAND ${CMAKE_SOURCE_DIR}/cmake/fix-linter-for-all.py --tidy=clang-tidy-${CTK_CLANG_TIDY_VERSION} --apply-tool=clang-apply-replacements-${CTK_CLANG_TIDY_VERSION} --exclude="${CMAKE_BINARY_DIR}" ${CMAKE_BINARY_DIR}/compile_commands.json + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}) +add_custom_target(fix-linter COMMAND run-clang-tidy-${CTK_CLANG_TIDY_VERSION} -fix -format -p ${CMAKE_BINARY_DIR} WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}) + + diff --git a/cmake/fix-linter-for-all.py b/cmake/fix-linter-for-all.py new file mode 100755 index 0000000000000000000000000000000000000000..e3746be9ec058bcf36f40aa7af8c799722f37224 --- /dev/null +++ b/cmake/fix-linter-for-all.py @@ -0,0 +1,90 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: LGPL-3.0-or-later +# SPDX-FileCopyRightText: Deutsches Elektronen-Synchrotron DESY, MSK, ChimeraTK Project <chimeratk-support@desy.de> + +import argparse +import json +import os +import re +import shutil +import subprocess +import sys +import tempfile + + +def make_absolute(file: str, path: str): + if os.path.isabs(file): + return file + return os.path.normpath(os.path.join(path, file)) + + +def main(): + parser = argparse.ArgumentParser(description="Runs clang-tidy over a compilation database and applies the fixes") + parser.add_argument("compile_db", metavar='PATH') + parser.add_argument("--tidy", help="Path to clang-tidy", default="clang-tidy-14") + parser.add_argument("--apply-tool", help='Path to clang-apply-replacements', default="clang-apply-replacements-14") + parser.add_argument("--git", help="Path to git", default="git") + parser.add_argument("--source", help="Path to top-level source folder", default=".") + parser.add_argument("--exclude", help="Regex of files to exclude", default=None) + + args = parser.parse_args() + + apply_version = subprocess.run([args.apply_tool, "--version"], capture_output=True, + encoding="latin1").stdout.strip() + + version_regex = re.compile(r"ersion\s*(\d+)\.(\d+)\.(\d+)") + match = version_regex.search(apply_version) + has_ignore_insert_conflict = int(match.group(1)) > 14 + + files = None + + try: + database = json.load(open(args.compile_db)) + files = set([make_absolute(entry['file'], entry['directory']) for entry in database]) + except FileNotFoundError: + print(f"Failed to open {args.compile_db}: Not found") + except json.decoder.JSONDecodeError: + print(f"Failed to open {args.compile_db}: Not valid json") + + + if not files: + sys.exit(1) + + tmpdir = tempfile.mkdtemp() + source_folder = os.path.abspath(args.source) + + exclude_re = re.compile(args.exclude) if args.exclude else None + + for file in files: + if exclude_re and exclude_re.search(file): + continue + tidy = [args.tidy, "-header-filter=.*", "-export-fixes"] + (handle, name) = tempfile.mkstemp(suffix='.yaml', dir=tmpdir) + os.close(handle) + tidy.append(name) + tidy.append("-p=" + os.path.dirname(os.path.normpath(args.compile_db))) + tidy.append(file) + try: + print(f"Linting {file}...") + subprocess.run(tidy, check=True) + except subprocess.CalledProcessError: + print("Linting failed. Usually that means the previously applied fix has introduced a compiler error. Aborting.") + print("Most likely it converted a conversion operator to explicit") + sys.exit(1) + + fixer = [args.apply_tool, "--format", "--style=file", "--remove-change-desc-files"] + if has_ignore_insert_conflict: + fixer.append('--ignore-insert-conflict') + fixer.append(tmpdir) + if subprocess.call(fixer) == 0: + git_update = [args.git, 'add', '-u'] + subprocess.call(git_update) + git_commit = [args.git, 'commit', '-m', f'clang-tidy: {os.path.relpath(file, start=source_folder)}'] + subprocess.call(git_commit) + else: + print("Error applying fixes, not committing") + shutil.rmtree(tmpdir) + + +if __name__ == "__main__": + main()