Skip to content

Add target to apply clang-tidy fixes #39

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

Open
wants to merge 1 commit 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
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 .
64 changes: 64 additions & 0 deletions defs.bzl
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
"""
clang-tidy apply fixes 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,
),
# support use of a .bazelrc config containing `--output_groups=report`
# for example, bazel run @bazel_clang_tidy//:apply-fixes --config=clang-tidy ...
# with
# build:clang-tidy --aspects @bazel_clang_tidy...
# build:clang-tidy --@bazel_clang_tidy//:clang_tidy_config=...
# build:clang-tidy --output_groups=report
OutputGroupInfo(report = depset(direct = [apply_fixes])),
]

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,
)