Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add clang_tidy bazel rule with example #65

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .bazelrc
Original file line number Diff line number Diff line change
@@ -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
build:clang-tidy --output_groups=report
# Print all output from tests
test --test_output=all
58 changes: 57 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 = '<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.
Expand Down Expand Up @@ -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:
Expand Down
43 changes: 22 additions & 21 deletions clang_tidy/clang_tidy.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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)
Expand Down Expand Up @@ -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(
Expand Down
85 changes: 85 additions & 0 deletions clang_tidy/clang_tidy_test.bzl
Original file line number Diff line number Diff line change
@@ -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"],
)
33 changes: 33 additions & 0 deletions example/cc_test_example/BUILD
Original file line number Diff line number Diff line change
@@ -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"
],
)

6 changes: 6 additions & 0 deletions example/cc_test_example/cc_test_example.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
#include "lib.hpp"

int main() {
int* ptr;
return func();
}
6 changes: 6 additions & 0 deletions example/cc_test_example/lib.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
#include "lib.hpp"

int func() {
int* result = new int{5};
return result;
}
6 changes: 6 additions & 0 deletions example/cc_test_example/lib.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
int func();

inline bool bad_func() {
int* p = new int{};
return p;
}
Loading