Skip to content

Commit 4b1497a

Browse files
committed
Add target to apply clang-tidy fixes
1 parent 41df6ad commit 4b1497a

File tree

6 files changed

+162
-0
lines changed

6 files changed

+162
-0
lines changed

BUILD

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
load("//:defs.bzl", "clang_tidy_apply_fixes")
2+
13
filegroup(
24
name = "clang_tidy_config_default",
35
srcs = [
@@ -23,6 +25,17 @@ label_flag(
2325
visibility = ["//visibility:public"],
2426
)
2527

28+
filegroup(
29+
name = "clang_apply_replacements_executable_default",
30+
srcs = [], # empty list: system clang-apply-replacements
31+
)
32+
33+
label_flag(
34+
name = "clang_apply_replacements_executable",
35+
build_setting_default = ":clang_apply_replacements_executable_default",
36+
visibility = ["//visibility:public"],
37+
)
38+
2639
filegroup(
2740
name = "clang_tidy_additional_deps_default",
2841
srcs = [],
@@ -33,3 +46,7 @@ label_flag(
3346
build_setting_default = ":clang_tidy_additional_deps_default",
3447
visibility = ["//visibility:public"],
3548
)
49+
50+
clang_tidy_apply_fixes(
51+
name = "apply_fixes",
52+
)

README.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,20 @@ If you have a hermetic build, you can use your own clang-tidy target like this:
8181
build:clang-tidy --@bazel_clang_tidy//:clang_tidy_executable=@local_config_cc//:clangtidy_bin
8282
```
8383

84+
### applying tidy fixes
85+
86+
clang-tidy fixes can be applied with
87+
88+
```sh
89+
bazel run @bazel_clang_tidy//:apply_fixes
90+
```
91+
92+
As with running clang-tidy, the binary target and config file can be specified with
93+
`--@bazel_clang_tidy//:clang_tidy_executable` and
94+
`--@bazel_clang_tidy//:clang_tidy_config`. Similarly, clang-apply-replacements
95+
can be specified with
96+
`--@bazel_clang_tidy//:clang_apply_replacements_executable`.
97+
8498
## Features
8599

86100
- Run clang-tidy on any C++ target

WORKSPACE

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
workspace(name = "bazel_clang_tidy")

clang_tidy/BUILD

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,9 @@ sh_binary(
44
data = ["//:clang_tidy_config"],
55
visibility = ["//visibility:public"],
66
)
7+
8+
filegroup(
9+
name = "apply_fixes_template",
10+
srcs = ["apply_fixes.template.sh"],
11+
visibility = ["//visibility:public"],
12+
)

clang_tidy/apply_fixes.template.sh

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
#!/usr/bin/env bash
2+
set -euo pipefail
3+
4+
bazel=$(readlink -f /proc/${PPID}/exe)
5+
6+
args=$(printf " union %s" "${@}" | sed 's/^ union \(.*\)/\1/')
7+
targets="${args:-//...}"
8+
9+
bazel_tidy_config=(\
10+
"--aspects=@@WORKSPACE@//clang_tidy:clang_tidy.bzl%clang_tidy_aspect" \
11+
"--@@WORKSPACE@//:clang_tidy_executable=@TIDY_BINARY@" \
12+
"--@@WORKSPACE@//:clang_tidy_config=@TIDY_CONFIG@" \
13+
"--output_groups=report")
14+
15+
cd $BUILD_WORKSPACE_DIRECTORY
16+
17+
exported_fixes=$("$bazel" aquery \
18+
"mnemonic(\"ClangTidy\", kind(\"cc_.* rule\", $targets))" \
19+
--noshow_progress \
20+
--ui_event_filters=-info \
21+
"${bazel_tidy_config[@]}" \
22+
| grep 'Outputs:' \
23+
| sed 's:^\s\+Outputs\: \[\(.*\)\]$:\1:')
24+
25+
"$bazel" build \
26+
--noshow_progress \
27+
--ui_event_filters=-info,-error,-stdout,-stderr \
28+
--keep_going \
29+
"${bazel_tidy_config[@]}" \
30+
"${@:-//...}" || true
31+
32+
for file in $exported_fixes; do
33+
# get the build directory which is probably some sandbox
34+
build_dir=$(grep --max-count=1 'BuildDirectory:' "$file" \
35+
| sed "s:\s\+BuildDirectory\:\s\+'\(.*\)':\1:" || true)
36+
37+
# if we didn't find BuildDirectory, it's probably an empty file
38+
if [ -z "$build_dir" ]; then
39+
continue
40+
fi
41+
42+
# relative path of a fix file copied to BUILD_WORKSPACE_DIRECTORY
43+
suggested_fixes=$(basename "$file")
44+
45+
# strip the build_dir prefix
46+
# and set BuildDirectory to empty
47+
# so clang-apply-replacements won't look for it
48+
sed "s:$build_dir/::" "$file" \
49+
| sed "s:$build_dir::" \
50+
> "$suggested_fixes"
51+
52+
# resolve symlinks and relative paths
53+
while path=$(grep --max-count=1 '_virtual_includes\|\./' "$suggested_fixes" \
54+
| sed "s:\s\+FilePath\:\s\+'\(.*\)':\1:" || true); do
55+
if [ -z "$path" ]; then
56+
break
57+
fi
58+
59+
sed -i "s:$path:$(readlink -f $path):" "$suggested_fixes"
60+
done
61+
62+
# remove the original exported fixes, otherwise they are found by
63+
# clang-apply-replacements
64+
rm -f "$file"
65+
done
66+
67+
@APPLY_REPLACEMENTS_BINARY@ -remove-change-desc-files .

defs.bzl

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
"""
2+
clang-tidy fix rule
3+
"""
4+
5+
def _clang_tidy_apply_fixes_impl(ctx):
6+
apply_fixes = ctx.actions.declare_file(
7+
"clang_tidy.{}.sh".format(ctx.attr.name),
8+
)
9+
10+
config = ctx.attr._tidy_config.files.to_list()
11+
if len(config) != 1:
12+
fail(":config ({}) must contain a single file".format(config))
13+
14+
apply_bin = ctx.attr._apply_replacements_binary.files_to_run.executable
15+
apply_path = apply_bin.path if apply_bin else "clang-apply-replacements"
16+
17+
ctx.actions.expand_template(
18+
template = ctx.attr._template.files.to_list()[0],
19+
output = apply_fixes,
20+
substitutions = {
21+
"@APPLY_REPLACEMENTS_BINARY@": apply_path,
22+
"@TIDY_BINARY@": str(ctx.attr._tidy_binary.label),
23+
"@TIDY_CONFIG@": str(ctx.attr._tidy_config.label),
24+
"@WORKSPACE@": ctx.label.workspace_name,
25+
},
26+
)
27+
28+
tidy_bin = ctx.attr._tidy_binary.files_to_run.executable
29+
runfiles = ctx.runfiles(
30+
(
31+
[apply_bin] if apply_bin else [] +
32+
[tidy_bin] if tidy_bin else [] +
33+
config
34+
),
35+
)
36+
37+
return [
38+
DefaultInfo(
39+
executable = apply_fixes,
40+
runfiles = runfiles,
41+
),
42+
]
43+
44+
clang_tidy_apply_fixes = rule(
45+
implementation = _clang_tidy_apply_fixes_impl,
46+
fragments = ["cpp"],
47+
attrs = {
48+
"_template": attr.label(default = Label("//clang_tidy:apply_fixes_template")),
49+
"_tidy_config": attr.label(default = Label("//:clang_tidy_config")),
50+
"_tidy_binary": attr.label(default = Label("//:clang_tidy_executable")),
51+
"_apply_replacements_binary": attr.label(
52+
default = Label("//:clang_apply_replacements_executable"),
53+
),
54+
},
55+
toolchains = ["@bazel_tools//tools/cpp:toolchain_type"],
56+
executable = True,
57+
)

0 commit comments

Comments
 (0)