Skip to content

Commit

Permalink
Add target to apply clang-tidy fixes
Browse files Browse the repository at this point in the history
  • Loading branch information
oliverlee committed Jan 6, 2023
1 parent 41df6ad commit 4b1497a
Show file tree
Hide file tree
Showing 6 changed files with 162 additions and 0 deletions.
17 changes: 17 additions & 0 deletions BUILD
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
load("//:defs.bzl", "clang_tidy_apply_fixes")

filegroup(
name = "clang_tidy_config_default",
srcs = [
Expand All @@ -23,6 +25,17 @@ label_flag(
visibility = ["//visibility:public"],
)

filegroup(
name = "clang_apply_replacements_executable_default",
srcs = [], # empty list: system clang-apply-replacements
)

label_flag(
name = "clang_apply_replacements_executable",
build_setting_default = ":clang_apply_replacements_executable_default",
visibility = ["//visibility:public"],
)

filegroup(
name = "clang_tidy_additional_deps_default",
srcs = [],
Expand All @@ -33,3 +46,7 @@ label_flag(
build_setting_default = ":clang_tidy_additional_deps_default",
visibility = ["//visibility:public"],
)

clang_tidy_apply_fixes(
name = "apply_fixes",
)
14 changes: 14 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,20 @@ If you have a hermetic build, you can use your own clang-tidy target like this:
build:clang-tidy --@bazel_clang_tidy//:clang_tidy_executable=@local_config_cc//:clangtidy_bin
```

### applying tidy fixes

clang-tidy fixes can be applied with

```sh
bazel run @bazel_clang_tidy//:apply_fixes
```

As with running clang-tidy, the binary target and config file can be specified with
`--@bazel_clang_tidy//:clang_tidy_executable` and
`--@bazel_clang_tidy//:clang_tidy_config`. Similarly, clang-apply-replacements
can be specified with
`--@bazel_clang_tidy//:clang_apply_replacements_executable`.

## Features

- Run clang-tidy on any C++ target
Expand Down
1 change: 1 addition & 0 deletions WORKSPACE
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
workspace(name = "bazel_clang_tidy")
6 changes: 6 additions & 0 deletions clang_tidy/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,9 @@ sh_binary(
data = ["//:clang_tidy_config"],
visibility = ["//visibility:public"],
)

filegroup(
name = "apply_fixes_template",
srcs = ["apply_fixes.template.sh"],
visibility = ["//visibility:public"],
)
67 changes: 67 additions & 0 deletions clang_tidy/apply_fixes.template.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
#!/usr/bin/env bash
set -euo pipefail

bazel=$(readlink -f /proc/${PPID}/exe)

args=$(printf " union %s" "${@}" | sed 's/^ union \(.*\)/\1/')
targets="${args:-//...}"

bazel_tidy_config=(\
"--aspects=@@WORKSPACE@//clang_tidy:clang_tidy.bzl%clang_tidy_aspect" \
"--@@WORKSPACE@//:clang_tidy_executable=@TIDY_BINARY@" \
"--@@WORKSPACE@//:clang_tidy_config=@TIDY_CONFIG@" \
"--output_groups=report")

cd $BUILD_WORKSPACE_DIRECTORY

exported_fixes=$("$bazel" aquery \
"mnemonic(\"ClangTidy\", kind(\"cc_.* rule\", $targets))" \
--noshow_progress \
--ui_event_filters=-info \
"${bazel_tidy_config[@]}" \
| grep 'Outputs:' \
| sed 's:^\s\+Outputs\: \[\(.*\)\]$:\1:')

"$bazel" build \
--noshow_progress \
--ui_event_filters=-info,-error,-stdout,-stderr \
--keep_going \
"${bazel_tidy_config[@]}" \
"${@:-//...}" || true

for file in $exported_fixes; do
# get the build directory which is probably some sandbox
build_dir=$(grep --max-count=1 'BuildDirectory:' "$file" \
| sed "s:\s\+BuildDirectory\:\s\+'\(.*\)':\1:" || true)

# if we didn't find BuildDirectory, it's probably an empty file
if [ -z "$build_dir" ]; then
continue
fi

# relative path of a fix file copied to BUILD_WORKSPACE_DIRECTORY
suggested_fixes=$(basename "$file")

# strip the build_dir prefix
# and set BuildDirectory to empty
# so clang-apply-replacements won't look for it
sed "s:$build_dir/::" "$file" \
| sed "s:$build_dir::" \
> "$suggested_fixes"

# resolve symlinks and relative paths
while path=$(grep --max-count=1 '_virtual_includes\|\./' "$suggested_fixes" \
| sed "s:\s\+FilePath\:\s\+'\(.*\)':\1:" || true); do
if [ -z "$path" ]; then
break
fi

sed -i "s:$path:$(readlink -f $path):" "$suggested_fixes"
done

# remove the original exported fixes, otherwise they are found by
# clang-apply-replacements
rm -f "$file"
done

@APPLY_REPLACEMENTS_BINARY@ -remove-change-desc-files .
57 changes: 57 additions & 0 deletions defs.bzl
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
"""
clang-tidy fix rule
"""

def _clang_tidy_apply_fixes_impl(ctx):
apply_fixes = ctx.actions.declare_file(
"clang_tidy.{}.sh".format(ctx.attr.name),
)

config = ctx.attr._tidy_config.files.to_list()
if len(config) != 1:
fail(":config ({}) must contain a single file".format(config))

apply_bin = ctx.attr._apply_replacements_binary.files_to_run.executable
apply_path = apply_bin.path if apply_bin else "clang-apply-replacements"

ctx.actions.expand_template(
template = ctx.attr._template.files.to_list()[0],
output = apply_fixes,
substitutions = {
"@APPLY_REPLACEMENTS_BINARY@": apply_path,
"@TIDY_BINARY@": str(ctx.attr._tidy_binary.label),
"@TIDY_CONFIG@": str(ctx.attr._tidy_config.label),
"@WORKSPACE@": ctx.label.workspace_name,
},
)

tidy_bin = ctx.attr._tidy_binary.files_to_run.executable
runfiles = ctx.runfiles(
(
[apply_bin] if apply_bin else [] +
[tidy_bin] if tidy_bin else [] +
config
),
)

return [
DefaultInfo(
executable = apply_fixes,
runfiles = runfiles,
),
]

clang_tidy_apply_fixes = rule(
implementation = _clang_tidy_apply_fixes_impl,
fragments = ["cpp"],
attrs = {
"_template": attr.label(default = Label("//clang_tidy:apply_fixes_template")),
"_tidy_config": attr.label(default = Label("//:clang_tidy_config")),
"_tidy_binary": attr.label(default = Label("//:clang_tidy_executable")),
"_apply_replacements_binary": attr.label(
default = Label("//:clang_apply_replacements_executable"),
),
},
toolchains = ["@bazel_tools//tools/cpp:toolchain_type"],
executable = True,
)

0 comments on commit 4b1497a

Please sign in to comment.