From 644aa47815c121330899a7f2234cdf7826509fd5 Mon Sep 17 00:00:00 2001 From: Jared Rodgers Date: Thu, 24 Jul 2025 19:39:35 -0700 Subject: [PATCH 1/6] feat: clang-tidy by file instead of by target --- lint/clang_tidy.bzl | 76 +++++++++++++++++++++++------------- lint/private/lint_aspect.bzl | 18 +++++---- 2 files changed, 59 insertions(+), 35 deletions(-) diff --git a/lint/clang_tidy.bzl b/lint/clang_tidy.bzl index 5538f1d7..b00b2310 100644 --- a/lint/clang_tidy.bzl +++ b/lint/clang_tidy.bzl @@ -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 @@ -308,7 +308,7 @@ def clang_tidy_action(ctx, compilation_context, executable, srcs, stdout, exit_c """ 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,23 +317,23 @@ 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) ctx.actions.run_shell( - inputs = _gather_inputs(ctx, compilation_context, srcs), + inputs = _gather_inputs(ctx, compilation_context, [src]), outputs = outputs, tools = [executable._clang_tidy_wrapper, executable._clang_tidy, find_cpp_toolchain(ctx).all_files], 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(src.short_path), ) -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: @@ -345,23 +345,23 @@ def clang_tidy_fix(ctx, compilation_context, executable, srcs, patch, stdout, ex 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 +373,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 @@ -381,6 +381,15 @@ def _clang_tidy_aspect_impl(target, ctx): if not CcInfo in target: return [] + ignore_tags = [ + "noclangtidy", + "no-clang-tidy", + ] + + for tag in ignore_tags: + if tag in ctx.rule.attr.tags: + return [] + files_to_lint = _filter_srcs(ctx.rule) compilation_context = target[CcInfo].compilation_context if hasattr(ctx.rule.attr, "implementation_deps"): @@ -389,25 +398,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, is_file=True) + else: + outputs, info = output_files(_MNEMONIC, file, ctx, is_file=True) + + 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..cfc64537 100644 --- a/lint/private/lint_aspect.bzl +++ b/lint/private/lint_aspect.bzl @@ -49,21 +49,23 @@ def should_visit(rule, allow_kinds, allow_filegroup_tags = []): OUTFILE_FORMAT = "{label}.{mnemonic}.{suffix}" -def output_files(mnemonic, target, ctx): +def output_files(mnemonic, target, ctx, is_file = False): """Declare linter output files. Args: mnemonic: used as part of the filename target: the target being visited by a linter aspect ctx: the aspect context + is_file: if True, will assume the target is of type File and of type Target 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")) + identifier = target.label.name if not is_file else target.short_path + 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 +75,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] @@ -97,8 +99,10 @@ def output_files(mnemonic, target, ctx): _validation = depset([human_out]), ) -def patch_file(mnemonic, target, ctx): - patch = ctx.actions.declare_file(OUTFILE_FORMAT.format(label = target.label.name, mnemonic = mnemonic, suffix = "patch")) +def patch_file(mnemonic, target, ctx, is_file = False): + identifier = target.label.name if not is_file else target.short_path + + 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. From 8e6a13db10103a612518a059e8a06fe67e94512c Mon Sep 17 00:00:00 2001 From: Jared Rodgers Date: Thu, 24 Jul 2025 20:03:54 -0700 Subject: [PATCH 2/6] Remove extra arg and check type instead --- lint/clang_tidy.bzl | 4 ++-- lint/private/lint_aspect.bzl | 18 ++++++++++++------ 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/lint/clang_tidy.bzl b/lint/clang_tidy.bzl index b00b2310..127ebdf5 100644 --- a/lint/clang_tidy.bzl +++ b/lint/clang_tidy.bzl @@ -410,9 +410,9 @@ def _clang_tidy_aspect_impl(target, ctx): infos = [] for file in files_to_lint: if ctx.attr._options[LintOptionsInfo].fix: - outputs, info = patch_and_output_files(_MNEMONIC, file, ctx, is_file=True) + outputs, info = patch_and_output_files(_MNEMONIC, file, ctx) else: - outputs, info = output_files(_MNEMONIC, file, ctx, is_file=True) + outputs, info = output_files(_MNEMONIC, file, ctx) infos.append(info) diff --git a/lint/private/lint_aspect.bzl b/lint/private/lint_aspect.bzl index cfc64537..18b14217 100644 --- a/lint/private/lint_aspect.bzl +++ b/lint/private/lint_aspect.bzl @@ -49,19 +49,22 @@ def should_visit(rule, allow_kinds, allow_filegroup_tags = []): OUTFILE_FORMAT = "{label}.{mnemonic}.{suffix}" -def output_files(mnemonic, target, ctx, is_file = False): +def output_files(mnemonic, target, ctx): """Declare linter output files. 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 - is_file: if True, will assume the target is of type File and of type Target Returns: tuple of struct() of output files, and the OutputGroupInfo provider that the rule should return """ - identifier = target.label.name if not is_file else target.short_path + if is_instance(target, File): + identifier = target.short_path + 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 @@ -99,8 +102,11 @@ def output_files(mnemonic, target, ctx, is_file = False): _validation = depset([human_out]), ) -def patch_file(mnemonic, target, ctx, is_file = False): - identifier = target.label.name if not is_file else target.short_path +def patch_file(mnemonic, target, ctx): + if is_instance(target, File): + identifier = target.short_path + 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])) From 473e956eef7718bbd9bb18db0e192ebbe36cd89b Mon Sep 17 00:00:00 2001 From: Jared Rodgers Date: Thu, 24 Jul 2025 20:05:04 -0700 Subject: [PATCH 3/6] Remove Local Patch for noclangtidy --- lint/clang_tidy.bzl | 9 --------- 1 file changed, 9 deletions(-) diff --git a/lint/clang_tidy.bzl b/lint/clang_tidy.bzl index 127ebdf5..0c709965 100644 --- a/lint/clang_tidy.bzl +++ b/lint/clang_tidy.bzl @@ -381,15 +381,6 @@ def _clang_tidy_aspect_impl(target, ctx): if not CcInfo in target: return [] - ignore_tags = [ - "noclangtidy", - "no-clang-tidy", - ] - - for tag in ignore_tags: - if tag in ctx.rule.attr.tags: - return [] - files_to_lint = _filter_srcs(ctx.rule) compilation_context = target[CcInfo].compilation_context if hasattr(ctx.rule.attr, "implementation_deps"): From af114db1b6c6262786823a7c2def41f3a2818b65 Mon Sep 17 00:00:00 2001 From: Jared Rodgers Date: Thu, 24 Jul 2025 20:29:08 -0700 Subject: [PATCH 4/6] Fix Type Checking --- lint/private/lint_aspect.bzl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lint/private/lint_aspect.bzl b/lint/private/lint_aspect.bzl index 18b14217..73b5d2f0 100644 --- a/lint/private/lint_aspect.bzl +++ b/lint/private/lint_aspect.bzl @@ -60,7 +60,7 @@ def output_files(mnemonic, target, ctx): Returns: tuple of struct() of output files, and the OutputGroupInfo provider that the rule should return """ - if is_instance(target, File): + if type(target) == "File": identifier = target.short_path else: identifier = target.label.name @@ -103,7 +103,7 @@ def output_files(mnemonic, target, ctx): ) def patch_file(mnemonic, target, ctx): - if is_instance(target, File): + if type(target) == "File": identifier = target.short_path else: identifier = target.label.name From 58f77bf6013e4f719e6ad6f1ab26ccb4c1bc1b4e Mon Sep 17 00:00:00 2001 From: Jared Rodgers Date: Fri, 25 Jul 2025 09:20:35 -0700 Subject: [PATCH 5/6] Fix: rename outputs so different targets with the same files don't overlap output names --- lint/private/lint_aspect.bzl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lint/private/lint_aspect.bzl b/lint/private/lint_aspect.bzl index 73b5d2f0..878476e3 100644 --- a/lint/private/lint_aspect.bzl +++ b/lint/private/lint_aspect.bzl @@ -61,7 +61,7 @@ def output_files(mnemonic, target, ctx): tuple of struct() of output files, and the OutputGroupInfo provider that the rule should return """ if type(target) == "File": - identifier = target.short_path + identifier = "{}.{}".format(target.short_path, ctx.label.name) else: identifier = target.label.name @@ -104,7 +104,7 @@ def output_files(mnemonic, target, ctx): def patch_file(mnemonic, target, ctx): if type(target) == "File": - identifier = target.short_path + identifier = "{}.{}".format(target.short_path, ctx.label.name) else: identifier = target.label.name From e763e5d13fd8173914f04451fb09e71ec5a394df Mon Sep 17 00:00:00 2001 From: Jared Rodgers Date: Mon, 28 Jul 2025 16:37:01 -0700 Subject: [PATCH 6/6] src changes and doc --- docs/clang-tidy.md | 8 ++++---- lint/clang_tidy.bzl | 36 +++++++++++++++++++----------------- 2 files changed, 23 insertions(+), 21 deletions(-) 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 0c709965..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: @@ -301,14 +301,14 @@ def clang_tidy_action(ctx, compilation_context, executable, src, stdout, exit_co 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, [src]) + env = _get_env(ctx, src) env["CLANG_TIDY__STDOUT_STDERR_OUTPUT_FILE"] = stdout.path if exit_code: @@ -317,20 +317,22 @@ def clang_tidy_action(ctx, compilation_context, executable, src, stdout, exit_co # 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, [src]) + clang_tidy_args = _get_args(ctx, compilation_context, src) compiler_args = ctx.actions.args() - compiler_args.add_all(_get_compiler_args(ctx, compilation_context, [src])) + 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, [src]), + 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 {} with clang-tidy".format(src.short_path), + progress_message = "Linting {} with clang-tidy".format(progress_message_identifier), ) def clang_tidy_fix(ctx, compilation_context, executable, src, patch, stdout, exit_code): @@ -340,7 +342,7 @@ def clang_tidy_fix(ctx, compilation_context, executable, src, patch, stdout, exi 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