Skip to content
Draft
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
8 changes: 4 additions & 4 deletions docs/clang-tidy.md

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

93 changes: 53 additions & 40 deletions lint/clang_tidy.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,8 @@ _DISABLED_FEATURES = [
"layering_check",
]

def _gather_inputs(ctx, compilation_context, srcs):
inputs = srcs + ctx.files._configs
def _gather_inputs(ctx, compilation_context, src):
inputs = [src] + ctx.files._configs
if (any(ctx.files._global_config)):
inputs.append(ctx.files._global_config[0])
return depset(inputs, transitive = [compilation_context.headers])
Expand Down Expand Up @@ -229,8 +229,8 @@ def _aggregate_regex(ctx, compilation_context):
def _quoted_arg(arg):
return "\"" + arg + "\""

def _get_env(ctx, srcs):
sources_are_cxx = _is_cxx(srcs[0])
def _get_env(ctx, src):
sources_are_cxx = _is_cxx(src)
if (sources_are_cxx):
user_flags = ctx.fragments.cpp.cxxopts + ctx.fragments.cpp.copts
env = _toolchain_env(ctx, user_flags, ACTION_NAMES.cpp_compile)
Expand All @@ -245,7 +245,7 @@ def _get_env(ctx, srcs):
env["MSYS_ARG_CONV_EXCL"] = "*"
return env

def _get_args(ctx, compilation_context, srcs):
def _get_args(ctx, compilation_context, src):
args = []
if (any(ctx.files._global_config)):
args.append("--config-file=" + ctx.files._global_config[0].short_path)
Expand All @@ -256,15 +256,15 @@ def _get_args(ctx, compilation_context, srcs):
elif (ctx.attr._header_filter):
regex = ctx.attr._header_filter
args.append(_quoted_arg("-header-filter=" + regex))
args.extend([src.short_path for src in srcs])
args.append(src.short_path)
return args

def _get_compiler_args(ctx, compilation_context, srcs):
def _get_compiler_args(ctx, compilation_context, src):
# add args specified by the toolchain, on the command line and rule copts
args = []
rule_flags = ctx.rule.attr.copts if hasattr(ctx.rule.attr, "copts") else []
sources_are_cxx = _is_cxx(srcs[0])
if (sources_are_cxx):
source_is_cxx = _is_cxx(src)
if (source_is_cxx):
user_flags = ctx.fragments.cpp.cxxopts + ctx.fragments.cpp.copts
args.extend(_safe_flags(ctx, _toolchain_flags(ctx, user_flags, ACTION_NAMES.cpp_compile) + rule_flags) + ["-xc++"])
else:
Expand All @@ -291,7 +291,7 @@ def _get_compiler_args(ctx, compilation_context, srcs):
args.extend(_prefixed(compilation_context.external_includes.to_list(), "-isystem"))
return args

def clang_tidy_action(ctx, compilation_context, executable, srcs, stdout, exit_code):
def clang_tidy_action(ctx, compilation_context, executable, src, stdout, exit_code):
"""Create a Bazel Action that spawns a clang-tidy process.

Adapter for wrapping Bazel around
Expand All @@ -301,14 +301,14 @@ def clang_tidy_action(ctx, compilation_context, executable, srcs, stdout, exit_c
ctx: an action context OR aspect context
compilation_context: from target
executable: struct with a clang-tidy field
srcs: file objects to lint
src: file object to lint
stdout: output file containing the stdout or --output-file of clang-tidy
exit_code: output file containing the exit code of clang-tidy.
If None, then fail the build when clang-tidy exits non-zero.
"""

outputs = [stdout]
env = _get_env(ctx, srcs)
env = _get_env(ctx, src)
env["CLANG_TIDY__STDOUT_STDERR_OUTPUT_FILE"] = stdout.path

if exit_code:
Expand All @@ -317,51 +317,53 @@ def clang_tidy_action(ctx, compilation_context, executable, srcs, stdout, exit_c

# pass compiler args via a params file. The command line may already be long due to
# sources, which can't go the params file, so materialize it always.
clang_tidy_args = _get_args(ctx, compilation_context, srcs)
clang_tidy_args = _get_args(ctx, compilation_context, src)
compiler_args = ctx.actions.args()
compiler_args.add_all(_get_compiler_args(ctx, compilation_context, srcs))
compiler_args.add_all(_get_compiler_args(ctx, compilation_context, src))
compiler_args.use_param_file("--config %s", use_always = True)

progress_message_identifier = src.short_path

ctx.actions.run_shell(
inputs = _gather_inputs(ctx, compilation_context, srcs),
inputs = depset([src], transitive = [_gather_inputs(ctx, compilation_context, src), find_cpp_toolchain(ctx).all_files]),
outputs = outputs,
tools = [executable._clang_tidy_wrapper, executable._clang_tidy, find_cpp_toolchain(ctx).all_files],
tools = [executable._clang_tidy_wrapper, executable._clang_tidy],
command = executable._clang_tidy_wrapper.path + " $@",
arguments = [executable._clang_tidy.path] + clang_tidy_args + ["--", compiler_args],
env = env,
mnemonic = _MNEMONIC,
progress_message = "Linting %{label} with clang-tidy",
progress_message = "Linting {} with clang-tidy".format(progress_message_identifier),
)

def clang_tidy_fix(ctx, compilation_context, executable, srcs, patch, stdout, exit_code):
def clang_tidy_fix(ctx, compilation_context, executable, src, patch, stdout, exit_code):
"""Create a Bazel Action that spawns clang-tidy with --fix.

Args:
ctx: an action context OR aspect context
compilation_context: from target
executable: struct with a clang_tidy field
srcs: list of file objects to lint
src: file object to lint
patch: output file containing the applied fixes that can be applied with the patch(1) command.
stdout: output file containing the stdout or --output-file of clang-tidy
exit_code: output file containing the exit code of clang-tidy
"""
patch_cfg = ctx.actions.declare_file("_{}.patch_cfg".format(ctx.label.name))
clang_tidy_args = _get_args(ctx, compilation_context, srcs)
compiler_args = _get_compiler_args(ctx, compilation_context, srcs)
patch_cfg = ctx.actions.declare_file("_{}.patch_cfg".format(src.short_path))
clang_tidy_args = _get_args(ctx, compilation_context, [src])
compiler_args = _get_compiler_args(ctx, compilation_context, [src])

ctx.actions.write(
output = patch_cfg,
content = json.encode({
"linter": executable._clang_tidy_wrapper.path,
"args": [executable._clang_tidy.path, "--fix"] + clang_tidy_args + ["--"] + compiler_args,
"env": _get_env(ctx, srcs),
"files_to_diff": [src.path for src in srcs],
"env": _get_env(ctx, [src]),
"files_to_diff": [src.path],
"output": patch.path,
}),
)

ctx.actions.run(
inputs = depset([patch_cfg], transitive = [_gather_inputs(ctx, compilation_context, srcs)]),
inputs = depset([patch_cfg], transitive = [_gather_inputs(ctx, compilation_context, [src])]),
outputs = [patch, stdout, exit_code],
executable = executable._patcher,
arguments = [patch_cfg.path],
Expand All @@ -373,7 +375,7 @@ def clang_tidy_fix(ctx, compilation_context, executable, srcs, patch, stdout, ex
},
tools = [executable._clang_tidy_wrapper, executable._clang_tidy, find_cpp_toolchain(ctx).all_files],
mnemonic = _MNEMONIC,
progress_message = "Linting %{label} with clang-tidy",
progress_message = "Linting {} with clang-tidy".format(src.short_path),
)

# buildifier: disable=function-docstring
Expand All @@ -389,25 +391,36 @@ def _clang_tidy_aspect_impl(target, ctx):
[implementation_dep[CcInfo].compilation_context for implementation_dep in ctx.rule.attr.implementation_deps],
)

if ctx.attr._options[LintOptionsInfo].fix:
outputs, info = patch_and_output_files(_MNEMONIC, target, ctx)
else:
outputs, info = output_files(_MNEMONIC, target, ctx)

if len(files_to_lint) == 0:
if ctx.attr._options[LintOptionsInfo].fix:
outputs, info = patch_and_output_files(_MNEMONIC, target, ctx)
else:
outputs, info = output_files(_MNEMONIC, target, ctx)

noop_lint_action(ctx, outputs)
return [info]

if hasattr(outputs, "patch"):
clang_tidy_fix(ctx, compilation_context, ctx.executable, files_to_lint, outputs.patch, outputs.human.out, outputs.human.exit_code)
else:
clang_tidy_action(ctx, compilation_context, ctx.executable, files_to_lint, outputs.human.out, outputs.human.exit_code)
infos = []
for file in files_to_lint:
if ctx.attr._options[LintOptionsInfo].fix:
outputs, info = patch_and_output_files(_MNEMONIC, file, ctx)
else:
outputs, info = output_files(_MNEMONIC, file, ctx)

infos.append(info)

if hasattr(outputs, "patch"):
clang_tidy_fix(ctx, compilation_context, ctx.executable, file, outputs.patch, outputs.human.out, outputs.human.exit_code)
else:
clang_tidy_action(ctx, compilation_context, ctx.executable, file, outputs.human.out, outputs.human.exit_code)

# TODO(alex): if we run with --fix, this will report the issues that were fixed. Does a machine reader want to know about them?
raw_machine_report = ctx.actions.declare_file(OUTFILE_FORMAT.format(label = file.short_path, mnemonic = _MNEMONIC, suffix = "raw_machine_report"))
clang_tidy_action(ctx, compilation_context, ctx.executable, file, raw_machine_report, outputs.machine.exit_code)
parse_to_sarif_action(ctx, _MNEMONIC, raw_machine_report, outputs.machine.out)

return infos

# TODO(alex): if we run with --fix, this will report the issues that were fixed. Does a machine reader want to know about them?
raw_machine_report = ctx.actions.declare_file(OUTFILE_FORMAT.format(label = target.label.name, mnemonic = _MNEMONIC, suffix = "raw_machine_report"))
clang_tidy_action(ctx, compilation_context, ctx.executable, files_to_lint, raw_machine_report, outputs.machine.exit_code)
parse_to_sarif_action(ctx, _MNEMONIC, raw_machine_report, outputs.machine.out)
return [info]

def lint_clang_tidy_aspect(binary, configs = [], global_config = [], header_filter = "", lint_target_headers = False, angle_includes_are_system = True, verbose = False):
"""A factory function to create a linter aspect.
Expand Down
22 changes: 16 additions & 6 deletions lint/private/lint_aspect.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -54,16 +54,21 @@ def output_files(mnemonic, target, ctx):

Args:
mnemonic: used as part of the filename
target: the target being visited by a linter aspect
target: the target or file being visited by a linter aspect
ctx: the aspect context

Returns:
tuple of struct() of output files, and the OutputGroupInfo provider that the rule should return
"""
human_out = ctx.actions.declare_file(OUTFILE_FORMAT.format(label = target.label.name, mnemonic = mnemonic, suffix = "out"))
if type(target) == "File":
identifier = "{}.{}".format(target.short_path, ctx.label.name)
else:
identifier = target.label.name

human_out = ctx.actions.declare_file(OUTFILE_FORMAT.format(label = identifier, mnemonic = mnemonic, suffix = "out"))

# NB: named ".report" as there are existing callers depending on that
machine_out = ctx.actions.declare_file(OUTFILE_FORMAT.format(label = target.label.name, mnemonic = mnemonic, suffix = "report"))
machine_out = ctx.actions.declare_file(OUTFILE_FORMAT.format(label = identifier, mnemonic = mnemonic, suffix = "report"))

if ctx.attr._options[LintOptionsInfo].fail_on_violation:
# Fail on violation means the exit code is reported to Bazel as the action result
Expand All @@ -73,8 +78,8 @@ def output_files(mnemonic, target, ctx):
# The exit codes should instead be provided as action outputs so the build succeeds.
# Downstream tooling like `aspect lint` will be responsible for reading the exit codes
# and interpreting them.
human_exit_code = ctx.actions.declare_file(OUTFILE_FORMAT.format(label = target.label.name, mnemonic = mnemonic, suffix = "out.exit_code"))
machine_exit_code = ctx.actions.declare_file(OUTFILE_FORMAT.format(label = target.label.name, mnemonic = mnemonic, suffix = "report.exit_code"))
human_exit_code = ctx.actions.declare_file(OUTFILE_FORMAT.format(label = identifier, mnemonic = mnemonic, suffix = "out.exit_code"))
machine_exit_code = ctx.actions.declare_file(OUTFILE_FORMAT.format(label = identifier, mnemonic = mnemonic, suffix = "report.exit_code"))

human_outputs = [f for f in [human_out, human_exit_code] if f]
machine_outputs = [f for f in [machine_out, machine_exit_code] if f]
Expand All @@ -98,7 +103,12 @@ def output_files(mnemonic, target, ctx):
)

def patch_file(mnemonic, target, ctx):
patch = ctx.actions.declare_file(OUTFILE_FORMAT.format(label = target.label.name, mnemonic = mnemonic, suffix = "patch"))
if type(target) == "File":
identifier = "{}.{}".format(target.short_path, ctx.label.name)
else:
identifier = target.label.name

patch = ctx.actions.declare_file(OUTFILE_FORMAT.format(label = identifier, mnemonic = mnemonic, suffix = "patch"))
return patch, OutputGroupInfo(rules_lint_patch = depset([patch]))

# If we return multiple OutputGroupInfo from a rule implementation, only one will get used.
Expand Down