From 007fbca4b0a185960ade065ffced3a9592dbbe25 Mon Sep 17 00:00:00 2001 From: "F.Tibor" Date: Tue, 21 Oct 2025 13:30:31 +0200 Subject: [PATCH 01/13] Use the compile_commands implementation from the monolithic rule --- src/per_file.bzl | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/src/per_file.bzl b/src/per_file.bzl index ae19582a..2eea9cf5 100644 --- a/src/per_file.bzl +++ b/src/per_file.bzl @@ -17,6 +17,12 @@ Rulesets for running codechecker in a different Bazel action for each translation unit. """ +load( + "compile_commands.bzl", + "compile_commands_aspect", + "compile_commands_impl", + "platforms_transition", +) load("@bazel_tools//tools/build_defs/cc:action_names.bzl", "ACTION_NAMES") load("@bazel_tools//tools/cpp:toolchain_utils.bzl", "find_cpp_toolchain") load("codechecker_config.bzl", "get_config_file") @@ -294,7 +300,20 @@ def _create_wrapper_script(ctx, options, compile_commands_json, config_file): ) def _per_file_impl(ctx): - compile_commands_json = _compile_commands_impl(ctx) + #compile_commands_json = _compile_commands_impl(ctx) + compile_commands = None + source_files = None + for output in compile_commands_impl(ctx): + if type(output) == "DefaultInfo": + compile_commands = output.files.to_list()[0] + source_files = output.default_runfiles.files.to_list() + if not compile_commands: + fail("Failed to generate compile_commands.json file!") + if not source_files: + fail("Failed to collect source files!") + if compile_commands != ctx.outputs.compile_commands: + fail("Seems compile_commands.json file is incorrect!") + compile_commands_json = compile_commands sources_and_headers = _collect_all_sources_and_headers(ctx) options = ctx.attr.default_options + ctx.attr.options all_files = [compile_commands_json] @@ -364,6 +383,7 @@ per_file_test = rule( "targets": attr.label_list( aspects = [ compile_info_aspect, + compile_commands_aspect, ], doc = "List of compilable targets which should be checked.", ), @@ -381,6 +401,7 @@ per_file_test = rule( }, outputs = { + "compile_commands": "%{name}/compile_commands.json", "test_script": "%{name}/test_script.sh", "per_file_script": "%{name}/per_file_script.py", }, From 1f229423fd880c1ec050f992e3f2c61ca516fc59 Mon Sep 17 00:00:00 2001 From: "F.Tibor" Date: Tue, 21 Oct 2025 13:33:10 +0200 Subject: [PATCH 02/13] Relocated, compile_commands.json, fix the test for it --- test/unit/compile_flags/test_compile_flags.py | 1 - 1 file changed, 1 deletion(-) diff --git a/test/unit/compile_flags/test_compile_flags.py b/test/unit/compile_flags/test_compile_flags.py index 84d89c07..e72778d7 100644 --- a/test/unit/compile_flags/test_compile_flags.py +++ b/test/unit/compile_flags/test_compile_flags.py @@ -85,7 +85,6 @@ def test_bazel_test_per_file_filter(self): compile_commands = os.path.join( self.BAZEL_BIN_DIR, "per_file_filter", - "data", "compile_commands.json", ) self.assertTrue(os.path.exists(compile_commands)) From 501cd7230cb0335475ba8b8eb7d0a64700e5311c Mon Sep 17 00:00:00 2001 From: "F.Tibor" Date: Sat, 10 Jan 2026 23:12:06 +0100 Subject: [PATCH 03/13] Remove unused code --- src/per_file.bzl | 211 ++++++----------------------------------------- 1 file changed, 27 insertions(+), 184 deletions(-) diff --git a/src/per_file.bzl b/src/per_file.bzl index 2eea9cf5..062b7ba1 100644 --- a/src/per_file.bzl +++ b/src/per_file.bzl @@ -22,6 +22,7 @@ load( "compile_commands_aspect", "compile_commands_impl", "platforms_transition", + "SourceFilesInfo", ) load("@bazel_tools//tools/build_defs/cc:action_names.bzl", "ACTION_NAMES") load("@bazel_tools//tools/cpp:toolchain_utils.bzl", "find_cpp_toolchain") @@ -32,6 +33,7 @@ def _run_code_checker( ctx, src, arguments, + target, label, options, config_file, @@ -55,7 +57,11 @@ def _run_code_checker( inputs = [compile_commands_json, config_file] + sources_and_headers else: # NOTE: we collect only headers, so CTU may not work! - headers = depset([src], transitive = [compilation_context.headers]) + trans_depsets = [compilation_context.headers] + for dset in target[SourceFilesInfo].headers.to_list(): + trans_depsets.append(dset) + headers = depset(direct = [src], + transitive = trans_depsets) inputs = depset([compile_commands_json, config_file, src], transitive = [headers]) outputs = [clang_tidy_plist, clangsa_plist, codechecker_log] @@ -98,190 +104,26 @@ def check_valid_file_type(src): return True return False -def _rule_sources(ctx): - srcs = [] - if hasattr(ctx.rule.attr, "srcs"): - for src in ctx.rule.attr.srcs: - srcs += [src for src in src.files.to_list() if check_valid_file_type(src)] - return srcs - -def _toolchain_flags(ctx, action_name = ACTION_NAMES.cpp_compile): - cc_toolchain = find_cpp_toolchain(ctx) - feature_configuration = cc_common.configure_features( - ctx = ctx, - cc_toolchain = cc_toolchain, - ) - user_comp_flag_builder = ctx.fragments.cpp.copts - if action_name == ACTION_NAMES.cpp_compile: - user_comp_flag_builder += ctx.fragments.cpp.cxxopts - elif action_name == ACTION_NAMES.c_compile: - user_comp_flag_builder += ctx.fragments.cpp.conlyopts - else: - fail("Unhandled action name!") - compile_variables = cc_common.create_compile_variables( - feature_configuration = feature_configuration, - cc_toolchain = cc_toolchain, - user_compile_flags = user_comp_flag_builder, - ) - flags = cc_common.get_memory_inefficient_command_line( - feature_configuration = feature_configuration, - action_name = action_name, - variables = compile_variables, - ) - compiler = cc_common.get_tool_for_action( - feature_configuration = feature_configuration, - action_name = action_name, - ) - return [compiler] + flags - -def _compile_args(compilation_context): - compile_args = [] - for define in compilation_context.defines.to_list(): - compile_args.append("-D" + define) - for define in compilation_context.local_defines.to_list(): - compile_args.append("-D" + define) - for include in compilation_context.framework_includes.to_list(): - compile_args.append("-F" + include) - for include in compilation_context.includes.to_list(): - compile_args.append("-I" + include) - for include in compilation_context.quote_includes.to_list(): - compile_args.append("-iquote " + include) - for include in compilation_context.system_includes.to_list(): - compile_args.append("-isystem " + include) - return compile_args - -def _safe_flags(flags): - # Some flags might be used by GCC, but not understood by Clang. - # Remove them here, to allow users to run clang-tidy, without having - # a clang toolchain configured (that would produce a good command line with --compiler clang) - unsupported_flags = [ - "-fno-canonical-system-headers", - "-fstack-usage", - ] - - return [flag for flag in flags if flag not in unsupported_flags] - -CompileInfo = provider( - doc = "Source files and corresponding compilation arguments", - fields = { - "arguments": "dict: file -> list of arguments", - }, -) - -def _compile_info_sources(deps): - sources = [] - if type(deps) == "list": - for dep in deps: - if CompileInfo in dep: - if hasattr(dep[CompileInfo], "arguments"): - srcs = dep[CompileInfo].arguments.keys() - sources += srcs - return sources - -def _collect_all_sources(ctx): - sources = _rule_sources(ctx) - for attr in SOURCE_ATTR: - if hasattr(ctx.rule.attr, attr): - deps = getattr(ctx.rule.attr, attr) - sources += _compile_info_sources(deps) - - # Remove duplicates - sources = depset(sources).to_list() - return sources - -def _compile_info_aspect_impl(target, ctx): - if not CcInfo in target: - return [] - compilation_context = target[CcInfo].compilation_context - - rule_flags = ctx.rule.attr.copts if hasattr(ctx.rule.attr, "copts") else [] - c_flags = _safe_flags(_toolchain_flags(ctx, ACTION_NAMES.c_compile) + rule_flags) # + ["-xc"] - cxx_flags = _safe_flags(_toolchain_flags(ctx, ACTION_NAMES.cpp_compile) + rule_flags) # + ["-xc++"] - - srcs = _collect_all_sources(ctx) - - compile_args = _compile_args(compilation_context) - arguments = {} - for src in srcs: - if src.extension.lower() in ["c"]: - flags = c_flags - elif src.extension.lower() in ["cc", "cpp", "cxx", "c++"]: - flags = cxx_flags - else: - print("Unknown file extension for", src.short_path, "defaulting to C++ compile flags") - flags = cxx_flags - arguments[src] = flags + compile_args + [src.path] - return [ - CompileInfo( - arguments = arguments, - ), - ] - -compile_info_aspect = aspect( - implementation = _compile_info_aspect_impl, - fragments = ["cpp"], - attrs = { - "_cc_toolchain": attr.label(default = Label("@bazel_tools//tools/cpp:current_cc_toolchain")), - }, - attr_aspects = SOURCE_ATTR, - toolchains = ["@bazel_tools//tools/cpp:toolchain_type"], -) - -def _compile_commands_json(compile_commands): - json_file = "[\n" - entries = [json.encode(entry) for entry in compile_commands] - json_file += ",\n".join(entries) - json_file += "]\n" - return json_file - -def _compile_commands_data(ctx): - compile_commands = [] - for target in ctx.attr.targets: - if not CcInfo in target: - continue - if CompileInfo in target: - if hasattr(target[CompileInfo], "arguments"): - srcs = target[CompileInfo].arguments.keys() - for src in srcs: - args = target[CompileInfo].arguments[src] - - # print("args =", str(args)) - record = struct( - file = src.path, - command = " ".join(args), - directory = ".", - ) - compile_commands.append(record) - return compile_commands - -def _compile_commands_impl(ctx): - compile_commands = _compile_commands_data(ctx) - content = _compile_commands_json(compile_commands) - file_name = ctx.attr.name + "/data/compile_commands.json" - compile_commands_json = ctx.actions.declare_file(file_name) - ctx.actions.write( - output = compile_commands_json, - content = content, - ) - return compile_commands_json - def _collect_all_sources_and_headers(ctx): # NOTE: we are only using this function for CTU all_files = [] - headers = depset() for target in ctx.attr.targets: if not CcInfo in target: continue - if CompileInfo in target: - if hasattr(target[CompileInfo], "arguments"): - srcs = target[CompileInfo].arguments.keys() + if SourceFilesInfo in target: + if (hasattr(target[SourceFilesInfo], "transitive_source_files") + and hasattr(target[SourceFilesInfo], "headers")): + srcs = target[SourceFilesInfo].transitive_source_files.to_list() + headers_mid = target[SourceFilesInfo].headers.to_list() + headers = [] + for elem in headers_mid: + if type(elem) == "depset": + headers.extend(elem.to_list()) + else: + headers.append(elem) all_files += srcs - compilation_context = target[CcInfo].compilation_context - headers = depset( - transitive = [headers, compilation_context.headers], - ) - sources_and_headers = all_files + headers.to_list() - return sources_and_headers + all_files += headers + return all_files def _create_wrapper_script(ctx, options, compile_commands_json, config_file): options_str = "" @@ -300,7 +142,6 @@ def _create_wrapper_script(ctx, options, compile_commands_json, config_file): ) def _per_file_impl(ctx): - #compile_commands_json = _compile_commands_impl(ctx) compile_commands = None source_files = None for output in compile_commands_impl(ctx): @@ -322,17 +163,20 @@ def _per_file_impl(ctx): for target in ctx.attr.targets: if not CcInfo in target: continue - if CompileInfo in target: - if hasattr(target[CompileInfo], "arguments"): - srcs = target[CompileInfo].arguments.keys() + if SourceFilesInfo in target: + if hasattr(target[SourceFilesInfo], "transitive_source_files"): + srcs = target[SourceFilesInfo].transitive_source_files.to_list() all_files += srcs compilation_context = target[CcInfo].compilation_context for src in srcs: - args = target[CompileInfo].arguments[src] + if not check_valid_file_type(src): + continue + args = target[SourceFilesInfo].compilation_db.to_list() outputs = _run_code_checker( ctx, src, args, + target, ctx.attr.name, options, config_file, @@ -382,7 +226,6 @@ per_file_test = rule( ), "targets": attr.label_list( aspects = [ - compile_info_aspect, compile_commands_aspect, ], doc = "List of compilable targets which should be checked.", From ba33a2d73d1357287e70eefee3209821a48bafee Mon Sep 17 00:00:00 2001 From: "F.Tibor" Date: Mon, 12 Jan 2026 08:26:18 +0100 Subject: [PATCH 04/13] Enable test for per_file implementation_deps virtual headers, using the compile_commands solution transitively fixes the per_file rule --- test/unit/virtual_include/test_virtual_include.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/test/unit/virtual_include/test_virtual_include.py b/test/unit/virtual_include/test_virtual_include.py index a0782b11..0dc53462 100644 --- a/test/unit/virtual_include/test_virtual_include.py +++ b/test/unit/virtual_include/test_virtual_include.py @@ -120,8 +120,7 @@ def test_bazel_per_file_implementation_deps_virtual_include(self): ret, _, _ = self.run_command( "bazel build --experimental_cc_implementation_deps //test/unit/virtual_include:per_file_impl_deps_include" ) - # TODO: change to 0, CodeChecker should finish analysis successfully - self.assertEqual(ret, 1) + self.assertEqual(ret, 0) if __name__ == "__main__": From 8204fcda4e8eb9177587e8dde0086ebdcefdf7ae Mon Sep 17 00:00:00 2001 From: "F.Tibor" Date: Mon, 12 Jan 2026 08:32:04 +0100 Subject: [PATCH 05/13] Enable test for external repositories for per_file rule, using compile_commands.bzl fixes it transitively --- test/unit/external_repository/test_external_repo.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/test/unit/external_repository/test_external_repo.py b/test/unit/external_repository/test_external_repo.py index 0028bd3c..d9d0791e 100644 --- a/test/unit/external_repository/test_external_repo.py +++ b/test/unit/external_repository/test_external_repo.py @@ -110,8 +110,7 @@ def test_per_file_external_lib(self): """Test: bazel build :per_file_external_deps""" ret, _, _ = self.run_command( "bazel build :per_file_external_deps") - # TODO: set to 1, the nothing header should be found - self.assertEqual(ret, 1) + self.assertEqual(ret, 0) if __name__ == "__main__": From 698b29d71e086f7aa8397bd3382ac221f30a8ea7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krist=C3=B3f=20Umann?= Date: Mon, 12 Jan 2026 12:33:03 +0100 Subject: [PATCH 06/13] Remove code duplication for monolithic and per-file compile commands assembly --- src/codechecker.bzl | 16 ++-------------- src/compile_commands.bzl | 23 +++++++++++++++++++++++ src/per_file.bzl | 18 ++++-------------- 3 files changed, 29 insertions(+), 28 deletions(-) diff --git a/src/codechecker.bzl b/src/codechecker.bzl index a569f8ac..48c82e46 100644 --- a/src/codechecker.bzl +++ b/src/codechecker.bzl @@ -19,7 +19,7 @@ Rulesets for running codechecker in a single Bazel job. load( "compile_commands.bzl", "compile_commands_aspect", - "compile_commands_impl", + "get_compile_commands_json_and_srcs", "platforms_transition", ) load( @@ -58,19 +58,7 @@ def _codechecker_impl(ctx): py_runtime_info = ctx.attr._python_runtime[PyRuntimeInfo] python_path = py_runtime_info.interpreter_path - # Get compile_commands.json file and source files - compile_commands = None - source_files = None - for output in compile_commands_impl(ctx): - if type(output) == "DefaultInfo": - compile_commands = output.files.to_list()[0] - source_files = output.default_runfiles.files.to_list() - if not compile_commands: - fail("Failed to generate compile_commands.json file!") - if not source_files: - fail("Failed to collect source files!") - if compile_commands != ctx.outputs.compile_commands: - fail("Seems compile_commands.json file is incorrect!") + compile_commands, source_files = get_compile_commands_json_and_srcs(ctx) # Convert flacc calls to clang in compile_commands.json # and save to codechecker_commands.json diff --git a/src/compile_commands.bzl b/src/compile_commands.bzl index 24aa2652..579ebfa5 100644 --- a/src/compile_commands.bzl +++ b/src/compile_commands.bzl @@ -418,6 +418,29 @@ def compile_commands_impl(ctx): ), ] +def get_compile_commands_json_and_srcs(ctx): + """ Creates the compilation database for internal use. + + Returns: + compile_commands - location of the compilation database file + source_files - files to analyze + """ + # Get compile_commands.json file and source files + compile_commands = None + source_files = None + for output in compile_commands_impl(ctx): + if type(output) == "DefaultInfo": + compile_commands = output.files.to_list()[0] + source_files = output.default_runfiles.files.to_list() + if not compile_commands: + fail("Failed to generate compile_commands.json file!") + if not source_files: + fail("Failed to collect source files!") + if compile_commands != ctx.outputs.compile_commands: + fail("Seems compile_commands.json file is incorrect!") + + return compile_commands, source_files + _compile_commands = rule( implementation = compile_commands_impl, attrs = { diff --git a/src/per_file.bzl b/src/per_file.bzl index 062b7ba1..64d185e0 100644 --- a/src/per_file.bzl +++ b/src/per_file.bzl @@ -20,7 +20,7 @@ for each translation unit. load( "compile_commands.bzl", "compile_commands_aspect", - "compile_commands_impl", + "get_compile_commands_json_and_srcs", "platforms_transition", "SourceFilesInfo", ) @@ -142,19 +142,9 @@ def _create_wrapper_script(ctx, options, compile_commands_json, config_file): ) def _per_file_impl(ctx): - compile_commands = None - source_files = None - for output in compile_commands_impl(ctx): - if type(output) == "DefaultInfo": - compile_commands = output.files.to_list()[0] - source_files = output.default_runfiles.files.to_list() - if not compile_commands: - fail("Failed to generate compile_commands.json file!") - if not source_files: - fail("Failed to collect source files!") - if compile_commands != ctx.outputs.compile_commands: - fail("Seems compile_commands.json file is incorrect!") - compile_commands_json = compile_commands + compile_commands_json, source_files = \ + get_compile_commands_json_and_srcs(ctx) + sources_and_headers = _collect_all_sources_and_headers(ctx) options = ctx.attr.default_options + ctx.attr.options all_files = [compile_commands_json] From a91eaa1a5a5f6715c8ddeed50ce18bf8aa2cac80 Mon Sep 17 00:00:00 2001 From: "F.Tibor" Date: Wed, 21 Jan 2026 15:15:09 +0100 Subject: [PATCH 07/13] Refactor depset flattening --- src/per_file.bzl | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/src/per_file.bzl b/src/per_file.bzl index 64d185e0..417715a5 100644 --- a/src/per_file.bzl +++ b/src/per_file.bzl @@ -29,6 +29,12 @@ load("@bazel_tools//tools/cpp:toolchain_utils.bzl", "find_cpp_toolchain") load("codechecker_config.bzl", "get_config_file") load("common.bzl", "SOURCE_ATTR") +def _flatten_depset(in_depset): + depset_list = [] + for d_set in in_depset.to_list(): + depset_list.append(d_set) + return depset(direct=[], transitive=depset_list) + def _run_code_checker( ctx, src, @@ -57,10 +63,11 @@ def _run_code_checker( inputs = [compile_commands_json, config_file] + sources_and_headers else: # NOTE: we collect only headers, so CTU may not work! - trans_depsets = [compilation_context.headers] - for dset in target[SourceFilesInfo].headers.to_list(): - trans_depsets.append(dset) - headers = depset(direct = [src], + trans_depsets = [ + compilation_context.headers, + _flatten_depset(target[SourceFilesInfo].headers) + ] + headers = depset(direct = [], transitive = trans_depsets) inputs = depset([compile_commands_json, config_file, src], transitive = [headers]) @@ -114,13 +121,9 @@ def _collect_all_sources_and_headers(ctx): if (hasattr(target[SourceFilesInfo], "transitive_source_files") and hasattr(target[SourceFilesInfo], "headers")): srcs = target[SourceFilesInfo].transitive_source_files.to_list() - headers_mid = target[SourceFilesInfo].headers.to_list() - headers = [] - for elem in headers_mid: - if type(elem) == "depset": - headers.extend(elem.to_list()) - else: - headers.append(elem) + headers = _flatten_depset( + target[SourceFilesInfo].headers + ).to_list() all_files += srcs all_files += headers return all_files From 65a0aeb5c7d78c5564bb86294ae0fa4f477ec888 Mon Sep 17 00:00:00 2001 From: "F.Tibor" Date: Wed, 21 Jan 2026 15:23:08 +0100 Subject: [PATCH 08/13] Restore codechecker.bzl --- src/codechecker.bzl | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/codechecker.bzl b/src/codechecker.bzl index 48c82e46..a569f8ac 100644 --- a/src/codechecker.bzl +++ b/src/codechecker.bzl @@ -19,7 +19,7 @@ Rulesets for running codechecker in a single Bazel job. load( "compile_commands.bzl", "compile_commands_aspect", - "get_compile_commands_json_and_srcs", + "compile_commands_impl", "platforms_transition", ) load( @@ -58,7 +58,19 @@ def _codechecker_impl(ctx): py_runtime_info = ctx.attr._python_runtime[PyRuntimeInfo] python_path = py_runtime_info.interpreter_path - compile_commands, source_files = get_compile_commands_json_and_srcs(ctx) + # Get compile_commands.json file and source files + compile_commands = None + source_files = None + for output in compile_commands_impl(ctx): + if type(output) == "DefaultInfo": + compile_commands = output.files.to_list()[0] + source_files = output.default_runfiles.files.to_list() + if not compile_commands: + fail("Failed to generate compile_commands.json file!") + if not source_files: + fail("Failed to collect source files!") + if compile_commands != ctx.outputs.compile_commands: + fail("Seems compile_commands.json file is incorrect!") # Convert flacc calls to clang in compile_commands.json # and save to codechecker_commands.json From 105c659e0ec1f342d951fc702ad140f9dbe76dac Mon Sep 17 00:00:00 2001 From: "F.Tibor" Date: Wed, 21 Jan 2026 15:26:19 +0100 Subject: [PATCH 09/13] Remove compile_commands.json getter function --- src/compile_commands.bzl | 23 ----------------------- src/per_file.bzl | 13 +++++++++---- 2 files changed, 9 insertions(+), 27 deletions(-) diff --git a/src/compile_commands.bzl b/src/compile_commands.bzl index 579ebfa5..24aa2652 100644 --- a/src/compile_commands.bzl +++ b/src/compile_commands.bzl @@ -418,29 +418,6 @@ def compile_commands_impl(ctx): ), ] -def get_compile_commands_json_and_srcs(ctx): - """ Creates the compilation database for internal use. - - Returns: - compile_commands - location of the compilation database file - source_files - files to analyze - """ - # Get compile_commands.json file and source files - compile_commands = None - source_files = None - for output in compile_commands_impl(ctx): - if type(output) == "DefaultInfo": - compile_commands = output.files.to_list()[0] - source_files = output.default_runfiles.files.to_list() - if not compile_commands: - fail("Failed to generate compile_commands.json file!") - if not source_files: - fail("Failed to collect source files!") - if compile_commands != ctx.outputs.compile_commands: - fail("Seems compile_commands.json file is incorrect!") - - return compile_commands, source_files - _compile_commands = rule( implementation = compile_commands_impl, attrs = { diff --git a/src/per_file.bzl b/src/per_file.bzl index 417715a5..a893bc42 100644 --- a/src/per_file.bzl +++ b/src/per_file.bzl @@ -20,7 +20,7 @@ for each translation unit. load( "compile_commands.bzl", "compile_commands_aspect", - "get_compile_commands_json_and_srcs", + "compile_commands_impl", "platforms_transition", "SourceFilesInfo", ) @@ -145,9 +145,14 @@ def _create_wrapper_script(ctx, options, compile_commands_json, config_file): ) def _per_file_impl(ctx): - compile_commands_json, source_files = \ - get_compile_commands_json_and_srcs(ctx) - + compile_commands = None + for output in compile_commands_impl(ctx): + if type(output) == "DefaultInfo": + compile_commands = output.files.to_list()[0] + if not compile_commands: + fail("Failed to generate compile_commands.json file!") + if compile_commands != ctx.outputs.compile_commands: + fail("Seems compile_commands.json file is incorrect!") sources_and_headers = _collect_all_sources_and_headers(ctx) options = ctx.attr.default_options + ctx.attr.options all_files = [compile_commands_json] From dff2e7b784607c8cf6906c9caadbfdcad205f70c Mon Sep 17 00:00:00 2001 From: "F.Tibor" Date: Thu, 22 Jan 2026 15:22:16 +0100 Subject: [PATCH 10/13] Make flatten function more understandable --- src/per_file.bzl | 28 ++++++++++++++++++++-------- 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/src/per_file.bzl b/src/per_file.bzl index a893bc42..273231c0 100644 --- a/src/per_file.bzl +++ b/src/per_file.bzl @@ -30,6 +30,13 @@ load("codechecker_config.bzl", "get_config_file") load("common.bzl", "SOURCE_ATTR") def _flatten_depset(in_depset): + """ + in_depset - Multi level depset + + Flattens the input depset by 1, like so: + [[A,B],[C],[D,E]] -> [A,B,C,D,E] + [[[A],[B]],[[C]]] -> [[A,B],[C]] + """ depset_list = [] for d_set in in_depset.to_list(): depset_list.append(d_set) @@ -63,12 +70,17 @@ def _run_code_checker( inputs = [compile_commands_json, config_file] + sources_and_headers else: # NOTE: we collect only headers, so CTU may not work! - trans_depsets = [ - compilation_context.headers, - _flatten_depset(target[SourceFilesInfo].headers) - ] headers = depset(direct = [], - transitive = trans_depsets) + transitive = [ + compilation_context.headers, + # This is necessary because + # target[SourceFilesInfo].headers contains + # depsets of header files, and we can only + # append depsets of files not depset of depsets + # to the headers depset. + _flatten_depset(target[SourceFilesInfo].headers) + ] + ) inputs = depset([compile_commands_json, config_file, src], transitive = [headers]) outputs = [clang_tidy_plist, clangsa_plist, codechecker_log] @@ -155,9 +167,9 @@ def _per_file_impl(ctx): fail("Seems compile_commands.json file is incorrect!") sources_and_headers = _collect_all_sources_and_headers(ctx) options = ctx.attr.default_options + ctx.attr.options - all_files = [compile_commands_json] + all_files = [compile_commands] config_file, env_vars = get_config_file(ctx) - _create_wrapper_script(ctx, options, compile_commands_json, config_file) + _create_wrapper_script(ctx, options, compile_commands, config_file) for target in ctx.attr.targets: if not CcInfo in target: continue @@ -179,7 +191,7 @@ def _per_file_impl(ctx): options, config_file, env_vars, - compile_commands_json, + compile_commands, compilation_context, sources_and_headers, ) From ee394b6f2d8f37487e0221fc8e87dd436cf9ffe7 Mon Sep 17 00:00:00 2001 From: "F.Tibor" Date: Mon, 26 Jan 2026 15:20:51 +0100 Subject: [PATCH 11/13] Remove flattening --- src/per_file.bzl | 31 ++++++------------------------- 1 file changed, 6 insertions(+), 25 deletions(-) diff --git a/src/per_file.bzl b/src/per_file.bzl index 273231c0..afb9f452 100644 --- a/src/per_file.bzl +++ b/src/per_file.bzl @@ -29,19 +29,6 @@ load("@bazel_tools//tools/cpp:toolchain_utils.bzl", "find_cpp_toolchain") load("codechecker_config.bzl", "get_config_file") load("common.bzl", "SOURCE_ATTR") -def _flatten_depset(in_depset): - """ - in_depset - Multi level depset - - Flattens the input depset by 1, like so: - [[A,B],[C],[D,E]] -> [A,B,C,D,E] - [[[A],[B]],[[C]]] -> [[A,B],[C]] - """ - depset_list = [] - for d_set in in_depset.to_list(): - depset_list.append(d_set) - return depset(direct=[], transitive=depset_list) - def _run_code_checker( ctx, src, @@ -71,15 +58,10 @@ def _run_code_checker( else: # NOTE: we collect only headers, so CTU may not work! headers = depset(direct = [], - transitive = [ - compilation_context.headers, - # This is necessary because - # target[SourceFilesInfo].headers contains - # depsets of header files, and we can only - # append depsets of files not depset of depsets - # to the headers depset. - _flatten_depset(target[SourceFilesInfo].headers) - ] + transitive = ( + [compilation_context.headers] + + target[SourceFilesInfo].headers.to_list() + ) ) inputs = depset([compile_commands_json, config_file, src], transitive = [headers]) @@ -87,7 +69,6 @@ def _run_code_checker( analyzer_output_paths = "clangsa," + clangsa_plist.path + \ ";clang-tidy," + clang_tidy_plist.path - # Action to run CodeChecker for a file ctx.actions.run( inputs = inputs, @@ -133,8 +114,8 @@ def _collect_all_sources_and_headers(ctx): if (hasattr(target[SourceFilesInfo], "transitive_source_files") and hasattr(target[SourceFilesInfo], "headers")): srcs = target[SourceFilesInfo].transitive_source_files.to_list() - headers = _flatten_depset( - target[SourceFilesInfo].headers + headers = depset( + transitive = target[SourceFilesInfo].headers.to_list() ).to_list() all_files += srcs all_files += headers From ea8ccd423fc460d328c1b2a3443428c808c588e8 Mon Sep 17 00:00:00 2001 From: "F.Tibor" Date: Mon, 26 Jan 2026 15:49:27 +0100 Subject: [PATCH 12/13] Remove unnecessary header inclusion --- src/per_file.bzl | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/per_file.bzl b/src/per_file.bzl index afb9f452..de347fde 100644 --- a/src/per_file.bzl +++ b/src/per_file.bzl @@ -58,10 +58,7 @@ def _run_code_checker( else: # NOTE: we collect only headers, so CTU may not work! headers = depset(direct = [], - transitive = ( - [compilation_context.headers] + - target[SourceFilesInfo].headers.to_list() - ) + transitive = target[SourceFilesInfo].headers.to_list() ) inputs = depset([compile_commands_json, config_file, src], transitive = [headers]) From 9be50385a83608b83aacdc875927297cbd9cb507 Mon Sep 17 00:00:00 2001 From: "F.Tibor" Date: Tue, 27 Jan 2026 07:27:09 +0100 Subject: [PATCH 13/13] Remove unnecessary parameter --- src/per_file.bzl | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/per_file.bzl b/src/per_file.bzl index de347fde..99ddfa75 100644 --- a/src/per_file.bzl +++ b/src/per_file.bzl @@ -57,9 +57,7 @@ def _run_code_checker( inputs = [compile_commands_json, config_file] + sources_and_headers else: # NOTE: we collect only headers, so CTU may not work! - headers = depset(direct = [], - transitive = target[SourceFilesInfo].headers.to_list() - ) + headers = depset(transitive = target[SourceFilesInfo].headers.to_list()) inputs = depset([compile_commands_json, config_file, src], transitive = [headers]) outputs = [clang_tidy_plist, clangsa_plist, codechecker_log]