diff --git a/.bazelrc b/.bazelrc index 2d2ed2b..a85b5c1 100644 --- a/.bazelrc +++ b/.bazelrc @@ -1,2 +1,4 @@ build:clang-tidy --aspects @bazel_clang_tidy//clang_tidy:clang_tidy.bzl%clang_tidy_aspect -build:clang-tidy --output_groups=report \ No newline at end of file +build:clang-tidy --output_groups=report +# Print all output from tests +test --test_output=all diff --git a/README.md b/README.md index 0584f75..de444b8 100644 --- a/README.md +++ b/README.md @@ -73,6 +73,62 @@ Now from the command line this is a lot nicer to use; bazel build //... --config clang-tidy ``` +### Use clang_tidy_test rule + +You cane use rule `clang_tidy_test` to test your files with clang-tidy. + +Example code is located here: `example/cc_test_example` + +You can run example with: + +```sh +bazel test //example/cc_test_example:example_tests +``` + +This command is a `test_suite` for build and run your program and run clang-tidy on example files. + +To define clang-tidy test you simply add this rule to your `BUILD` file: + +```text +load("@bazel_clang_tidy//clang_tidy:clang_tidy_test.bzl", "clang_tidy_test") +clang_tidy_test( + name = '', + srcs = [ + "a.hpp" + "a.cpp" + ], +) +``` + +In this rule, you can use the same arguments as in the aspect (they are public here - easier to set): + +```text +'deps' : attr.label_list(), +"cc_toolchain": attr.label(default = Label("@bazel_tools//tools/cpp:current_cc_toolchain")), +"clang_tidy_wrapper": attr.label(default = Label("//clang_tidy:clang_tidy")), +"clang_tidy_executable": attr.label(default = Label("//:clang_tidy_executable")), +"clang_tidy_additional_deps": attr.label(default = Label("//:clang_tidy_additional_deps")), +"clang_tidy_config": attr.label(default = Label("//:clang_tidy_config")), +'srcs' : attr.label_list(allow_files = True), +'hdrs' : attr.label_list(allow_files = True), +'use_flags': attr.bool(default = True), +``` + +They can be set as follows: + +```text +clang_tidy_test( + name = "clang_tidy_test", + clang_tidy_config = "//:clang_tidy_config", + clang_tidy_additional_deps = "//:clang_tidy_additional_deps", + srcs = [ + ":test_sources" + ], + deps = ["some_deps"], + use_flags = False, # You want set this to false if you are using compile_commands.json +) +``` + ### use a non-system clang-tidy by default, bazel_clang_tidy uses the system provided clang-tidy. @@ -104,7 +160,7 @@ To see the tool in action: 1. Run clang-tidy: ```sh - bazel build //example --aspects clang_tidy/clang_tidy.bzl%clang_tidy_aspect --output_groups=report + bazel build //example:lib --aspects clang_tidy/clang_tidy.bzl%clang_tidy_aspect --output_groups=report ``` 1. Check the error: diff --git a/clang_tidy/clang_tidy.bzl b/clang_tidy/clang_tidy.bzl index c52dbbb..925cbe9 100644 --- a/clang_tidy/clang_tidy.bzl +++ b/clang_tidy/clang_tidy.bzl @@ -77,29 +77,30 @@ def _run_tidy( ) return outfile -def _rule_sources(ctx): - def check_valid_file_type(src): - """ - Returns True if the file type matches one of the permitted srcs file types for C and C++ header/source files. - """ - permitted_file_types = [ - ".c", ".cc", ".cpp", ".cxx", ".c++", ".C", ".h", ".hh", ".hpp", ".hxx", ".inc", ".inl", ".H", - ] - for file_type in permitted_file_types: - if src.basename.endswith(file_type): - return True - return False +def check_valid_file_type(src): + """ + Returns True if the file type matches one of the permitted srcs file types for C and C++ header/source files. + """ + permitted_file_types = [ + ".c", ".cc", ".cpp", ".cxx", ".c++", ".C", ".h", ".hh", ".hpp", ".hxx", ".inc", ".inl", ".H", + ] + for file_type in permitted_file_types: + if src.basename.endswith(file_type): + return True + return False +def rule_sources(ctx, attr): srcs = [] - if hasattr(ctx.rule.attr, "srcs"): - for src in ctx.rule.attr.srcs: + + if hasattr(attr, "srcs"): + for src in attr.srcs: srcs += [src for src in src.files.to_list() if src.is_source and check_valid_file_type(src)] - if hasattr(ctx.rule.attr, "hdrs"): - for hdr in ctx.rule.attr.hdrs: + if hasattr(attr, "hdrs"): + for hdr in attr.hdrs: srcs += [hdr for hdr in hdr.files.to_list() if hdr.is_source and check_valid_file_type(hdr)] return srcs -def _toolchain_flags(ctx, action_name = ACTION_NAMES.cpp_compile): +def toolchain_flags(ctx, action_name = ACTION_NAMES.cpp_compile): cc_toolchain = find_cpp_toolchain(ctx) feature_configuration = cc_common.configure_features( ctx = ctx, @@ -117,7 +118,7 @@ def _toolchain_flags(ctx, action_name = ACTION_NAMES.cpp_compile): ) return flags -def _safe_flags(flags): +def safe_flags(flags): # Some flags might be used by GCC, but not understood by Clang. # Remove them here, to allow users to run clang-tidy, without having # a clang toolchain configured (that would produce a good command line with --compiler clang) @@ -154,10 +155,10 @@ def _clang_tidy_aspect_impl(target, ctx): compilation_context = target[CcInfo].compilation_context rule_flags = ctx.rule.attr.copts if hasattr(ctx.rule.attr, "copts") else [] - c_flags = _safe_flags(_toolchain_flags(ctx, ACTION_NAMES.c_compile) + rule_flags) + ["-xc"] - cxx_flags = _safe_flags(_toolchain_flags(ctx, ACTION_NAMES.cpp_compile) + rule_flags) + ["-xc++"] + c_flags = safe_flags(toolchain_flags(ctx, ACTION_NAMES.c_compile) + rule_flags) + ["-xc"] + cxx_flags = safe_flags(toolchain_flags(ctx, ACTION_NAMES.cpp_compile) + rule_flags) + ["-xc++"] - srcs = _rule_sources(ctx) + srcs = rule_sources(ctx, ctx.rule.attr) outputs = [ _run_tidy( diff --git a/clang_tidy/clang_tidy_test.bzl b/clang_tidy/clang_tidy_test.bzl new file mode 100644 index 0000000..cb8daa0 --- /dev/null +++ b/clang_tidy/clang_tidy_test.bzl @@ -0,0 +1,85 @@ +load("@bazel_tools//tools/build_defs/cc:action_names.bzl", "ACTION_NAMES") +load("@bazel_tools//tools/cpp:toolchain_utils.bzl", "find_cpp_toolchain") +load("clang_tidy.bzl", "safe_flags", "check_valid_file_type", "toolchain_flags", "rule_sources") + +def _clang_tidy_rule_impl(ctx): + wrapper = ctx.attr.clang_tidy_wrapper.files.to_list()[0] + exe = ctx.attr.clang_tidy_executable + additional_deps = ctx.attr.clang_tidy_additional_deps + config = ctx.attr.clang_tidy_config.files.to_list()[0] + + c_flags = safe_flags(toolchain_flags(ctx, ACTION_NAMES.c_compile)) + ["-xc"] + cxx_flags = safe_flags(toolchain_flags(ctx, ACTION_NAMES.cpp_compile)) + ["-xc++"] + flags = cxx_flags + + # Declare symlinks + clang_tidy_config = ctx.actions.declare_file(config.basename) + clang_tidy = ctx.actions.declare_file("run_clang_tidy.sh") + + args = [] + srcs = rule_sources(ctx, ctx.attr) + + # specify the output file - twice + outfile = ctx.actions.declare_file( + "bazel_clang_tidy_.clang-tidy.yaml", + ) + ctx.actions.write(outfile, "") + + # this is consumed by the wrapper script + if len(exe.files.to_list()) == 0: + args.append("clang-tidy") + else: + args.append(exe.files_to_run.executable.basename) + + args.append(outfile.basename) # this is consumed by the wrapper script + + # Configure clang-tidy config file + ctx.actions.symlink(output = clang_tidy_config, target_file = config) + args.append(clang_tidy_config.short_path) + + args.append("--export-fixes " + outfile.basename) + + # Configure clang-tidy script + ctx.actions.symlink(output = clang_tidy, target_file = wrapper) + + # Add files to analyze + for src in srcs: + args.append(src.short_path) + + # add args specified by the toolchain, on the command line and rule copts + if ctx.attr.use_flags: + # start args passed to the compiler + args.append("--") + for flag in flags: + args.append(flag) + + ctx.actions.write( + output = ctx.outputs.executable, + content = "./{binary} {args}".format( + binary = clang_tidy.short_path, + args = " ".join(args), + ), + is_executable = True, + ) + # Setup runfiles + runfiles = ctx.runfiles(files = srcs + [clang_tidy, clang_tidy_config]) + runfiles_basefolder = runfiles.files.to_list()[0].dirname + return DefaultInfo(runfiles = runfiles) + +clang_tidy_test = rule( + implementation = _clang_tidy_rule_impl, + test = True, + fragments = ["cpp"], + attrs = { + 'deps' : attr.label_list(), + "cc_toolchain": attr.label(default = Label("@bazel_tools//tools/cpp:current_cc_toolchain")), + "clang_tidy_wrapper": attr.label(default = Label("//clang_tidy:clang_tidy")), + "clang_tidy_executable": attr.label(default = Label("//:clang_tidy_executable")), + "clang_tidy_additional_deps": attr.label(default = Label("//:clang_tidy_additional_deps")), + "clang_tidy_config": attr.label(default = Label("//:clang_tidy_config")), + 'srcs' : attr.label_list(allow_files = True), + 'hdrs' : attr.label_list(allow_files = True), + 'use_flags': attr.bool(default = True), + }, + toolchains = ["@bazel_tools//tools/cpp:toolchain_type"], +) diff --git a/example/cc_test_example/BUILD b/example/cc_test_example/BUILD new file mode 100644 index 0000000..8e715be --- /dev/null +++ b/example/cc_test_example/BUILD @@ -0,0 +1,33 @@ +load("//clang_tidy:clang_tidy_test.bzl", "clang_tidy_test") + +filegroup( + name = "sources", + srcs = glob([ + "*.hpp", + "*.cpp", + ]), +) + +test_suite( + name = "example_tests", + tests = [ + ":check_files_test", + ":cc_test_example", + ], +) + +clang_tidy_test( + name = 'check_files_test', + srcs = [ + ":sources" + ], +) + +cc_test( + name = "cc_test_example", + visibility = ["//visibility:public"], + srcs = [ + ":sources" + ], +) + diff --git a/example/cc_test_example/cc_test_example.cpp b/example/cc_test_example/cc_test_example.cpp new file mode 100644 index 0000000..7de52e7 --- /dev/null +++ b/example/cc_test_example/cc_test_example.cpp @@ -0,0 +1,6 @@ +#include "lib.hpp" + +int main() { + int* ptr; + return func(); +} diff --git a/example/cc_test_example/lib.cpp b/example/cc_test_example/lib.cpp new file mode 100644 index 0000000..f9fb7f6 --- /dev/null +++ b/example/cc_test_example/lib.cpp @@ -0,0 +1,6 @@ +#include "lib.hpp" + +int func() { + int* result = new int{5}; + return result; +} diff --git a/example/cc_test_example/lib.hpp b/example/cc_test_example/lib.hpp new file mode 100644 index 0000000..ac8d178 --- /dev/null +++ b/example/cc_test_example/lib.hpp @@ -0,0 +1,6 @@ +int func(); + +inline bool bad_func() { + int* p = new int{}; + return p; +}