diff --git a/docs/clang-tidy.md b/docs/clang-tidy.md index 7dec2f58..e922154b 100644 --- a/docs/clang-tidy.md +++ b/docs/clang-tidy.md @@ -45,7 +45,7 @@ clang_tidy = lint_clang_tidy_aspect(
 load("@aspect_rules_lint//lint:clang_tidy.bzl", "clang_tidy_action")
 
-clang_tidy_action(ctx, compilation_context, executable, srcs, stdout, exit_code)
+clang_tidy_action(ctx, compilation_context, executable, src, stdout, exit_code)
 
Create a Bazel Action that spawns a clang-tidy process. @@ -62,7 +62,7 @@ https://clang.llvm.org/extra/clang-tidy/ | ctx | an action context OR aspect context | none | | compilation_context | from target | none | | executable | struct with a clang-tidy field | none | -| srcs | file objects to lint | none | +| src | file object to lint | none | | stdout | output file containing the stdout or --output-file of clang-tidy | none | | exit_code | output file containing the exit code of clang-tidy. If None, then fail the build when clang-tidy exits non-zero. | none | @@ -74,7 +74,7 @@ https://clang.llvm.org/extra/clang-tidy/
 load("@aspect_rules_lint//lint:clang_tidy.bzl", "clang_tidy_fix")
 
-clang_tidy_fix(ctx, compilation_context, executable, srcs, patch, stdout, exit_code)
+clang_tidy_fix(ctx, compilation_context, executable, src, patch, stdout, exit_code)
 
Create a Bazel Action that spawns clang-tidy with --fix. @@ -87,7 +87,7 @@ Create a Bazel Action that spawns clang-tidy with --fix. | ctx | an action context OR aspect context | none | | compilation_context | from target | none | | executable | struct with a clang_tidy field | none | -| srcs | list of file objects to lint | none | +| src | file object to lint | none | | patch | output file containing the applied fixes that can be applied with the patch(1) command. | none | | stdout | output file containing the stdout or --output-file of clang-tidy | none | | exit_code | output file containing the exit code of clang-tidy | none | diff --git a/lint/clang_tidy.bzl b/lint/clang_tidy.bzl index 5538f1d7..8976f0e6 100644 --- a/lint/clang_tidy.bzl +++ b/lint/clang_tidy.bzl @@ -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]) @@ -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) @@ -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) @@ -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: @@ -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 @@ -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: @@ -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], @@ -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 @@ -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. diff --git a/lint/private/lint_aspect.bzl b/lint/private/lint_aspect.bzl index cce11060..878476e3 100644 --- a/lint/private/lint_aspect.bzl +++ b/lint/private/lint_aspect.bzl @@ -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 @@ -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] @@ -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.