From 1b6d6435f3b0fda3fea3ad05856eee6b26ca1dcd Mon Sep 17 00:00:00 2001 From: "F.Tibor" Date: Tue, 5 May 2026 09:29:08 +0200 Subject: [PATCH 1/8] Initial idea for a toolchain implementation --- MODULE.bazel | 4 ++++ src/BUILD | 16 ++++++++++++++++ src/codechecker_toolchain.bzl | 25 +++++++++++++++++++++++++ src/per_file.bzl | 6 ++++++ src/per_file_script.py | 15 ++++++++------- 5 files changed, 59 insertions(+), 7 deletions(-) create mode 100644 src/codechecker_toolchain.bzl diff --git a/MODULE.bazel b/MODULE.bazel index fde5f260..0ddbaece 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -38,3 +38,7 @@ codechecker_extension = use_extension( "module_register_default_codechecker_tools", ) use_repo(codechecker_extension, "default_codechecker_tools") + +register_toolchains( + "//src:codechecker_local_toolchain", +) diff --git a/src/BUILD b/src/BUILD index 79c101ee..5d449d18 100644 --- a/src/BUILD +++ b/src/BUILD @@ -11,6 +11,7 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. +load(":codechecker_toolchain.bzl", "codechecker_toolchain") # Tool filter compile_commands.json file # In bazel 6 we use our own python toolchain, @@ -63,3 +64,18 @@ label_flag( build_setting_default = ":clang_tidy_additional_deps_default", visibility = ["//visibility:public"], ) + +# named this by convention +# https://bazel.build/extending/toolchains#:~:text=%23%20By%20convention%2C%20toolchain%5Ftype%20targets%20are%20named%20%22toolchain%5Ftype%22%20and +toolchain_type(name = "toolchain_type") + +codechecker_toolchain( + name = "codechecker_local", + analyzer_binary = "CodeChecker", # Find it in PATH ? +) + +toolchain( + name = "codechecker_local_toolchain", + toolchain = "codechecker_local", + toolchain_type = ":toolchain_type", +) diff --git a/src/codechecker_toolchain.bzl b/src/codechecker_toolchain.bzl new file mode 100644 index 00000000..a454a5e4 --- /dev/null +++ b/src/codechecker_toolchain.bzl @@ -0,0 +1,25 @@ +""" +This file provides the toolchain rule for CodeChecker +""" + +CodeCheckerInfo = provider( + doc = "This provider provides the executable path for CodeChecker", + fields = [ + "executable", + ], +) + +def _codechecker_toolchain_impl(ctx): + toolchain_info = platform_common.ToolchainInfo( + codecheckerinfo = CodeCheckerInfo( + executable = ctx.attr.analyzer_binary, + ), + ) + return [toolchain_info] + +codechecker_toolchain = rule( + implementation = _codechecker_toolchain_impl, + attrs = { + "analyzer_binary": attr.string(), + }, +) diff --git a/src/per_file.bzl b/src/per_file.bzl index 57d55e56..ace77b15 100644 --- a/src/per_file.bzl +++ b/src/per_file.bzl @@ -59,6 +59,8 @@ def _run_code_checker( content = "\n".join(ctx.attr.skip), ) + info = ctx.toolchains["//src:toolchain_type"].codecheckerinfo + if "--ctu" in options: inputs = [ compile_commands_json, @@ -86,6 +88,7 @@ def _run_code_checker( outputs = outputs, executable = ctx.outputs.per_file_script, arguments = [ + info.executable, data_dir, src.path, codechecker_log.path, @@ -155,6 +158,8 @@ def _create_wrapper_script(ctx, options, compile_commands_json, config_file): ) def _per_file_impl(ctx): + info = ctx.toolchains["//src:toolchain_type"].codecheckerinfo + print("CodeChecker path resolved:", info.executable) compile_commands = None for output in compile_commands_impl(ctx): if type(output) == "DefaultInfo": @@ -258,4 +263,5 @@ per_file_test = rule( "test_script": "%{name}/test_script.sh", }, test = True, + toolchains = ["//src:toolchain_type"], ) diff --git a/src/per_file_script.py b/src/per_file_script.py index ab9901dc..f510c0b8 100644 --- a/src/per_file_script.py +++ b/src/per_file_script.py @@ -28,14 +28,15 @@ COMPILE_COMMANDS_ABSOLUTE: str = f"{COMPILE_COMMANDS_JSON}.abs" CODECHECKER_ARGS: str = "{codechecker_args}" CONFIG_FILE: str = "{config_file}" -SKIP_FILE: str = sys.argv[4] +SKIP_FILE: str = sys.argv[5] +CODECHECKER_BIN = sys.argv[1] # The output directory for CodeChecker -DATA_DIR = sys.argv[1] +DATA_DIR = sys.argv[2] # The file to be analyzed -FILE_PATH = sys.argv[2] -LOG_FILE = sys.argv[3] +FILE_PATH = sys.argv[3] +LOG_FILE = sys.argv[4] # List of pairs of analyzers and their plist files -ANALYZER_PLIST_PATHS = [item.split(",") for item in sys.argv[5].split(";")] +ANALYZER_PLIST_PATHS = [item.split(",") for item in sys.argv[6].split(";")] EMPTY_PLIST = """ @@ -86,7 +87,7 @@ def _run_codechecker() -> None: Runs CodeChecker analyze """ codechecker_cmd: list[str] = ( - ["CodeChecker", "analyze"] + [CODECHECKER_BIN, "analyze"] + CODECHECKER_ARGS.split() + ["--output=" + DATA_DIR] + ["--file=*/" + FILE_PATH] @@ -173,7 +174,7 @@ def main(): """ Main function of CodeChecker wrapper """ - if len(sys.argv) != 6: + if len(sys.argv) != 7: print("Wrong amount of arguments") sys.exit(1) _create_compile_commands_json_with_absolute_paths() From 78acf880201ef7c1babe28b152ea7cd84fa9b90d Mon Sep 17 00:00:00 2001 From: "F.Tibor" Date: Tue, 19 May 2026 11:53:06 +0200 Subject: [PATCH 2/8] Add toolchain to WORKSPACE file --- WORKSPACE | 5 ++++- test/foss/templates/WORKSPACE.template | 5 ++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/WORKSPACE b/WORKSPACE index 6be16fee..d03a850f 100644 --- a/WORKSPACE +++ b/WORKSPACE @@ -22,7 +22,10 @@ load( register_default_python_toolchain() -register_toolchains("@default_python_tools//:python_toolchain") +register_toolchains( + "@default_python_tools//:python_toolchain", + "//src:codechecker_local_toolchain", +) register_default_codechecker_tools() diff --git a/test/foss/templates/WORKSPACE.template b/test/foss/templates/WORKSPACE.template index 82d71ce8..e6ffe6a0 100644 --- a/test/foss/templates/WORKSPACE.template +++ b/test/foss/templates/WORKSPACE.template @@ -14,7 +14,10 @@ load( ) register_default_python_toolchain() -register_toolchains("@default_python_tools//:python_toolchain") +register_toolchains( + "@default_python_tools//:python_toolchain", + "@rules_codechecker//src:codechecker_local_toolchain", +) register_default_codechecker_tools() From 3b6ec427da1c572612ac117bfe7c7ee8f96d9a87 Mon Sep 17 00:00:00 2001 From: "F.Tibor" Date: Wed, 3 Jun 2026 14:26:26 +0200 Subject: [PATCH 3/8] Support configuration of analyzer binaries too --- src/BUILD | 4 +++- src/codechecker_toolchain.bzl | 18 ++++++++++++------ src/per_file.bzl | 8 +++++--- src/per_file_script.py | 16 +++++++++++++--- 4 files changed, 33 insertions(+), 13 deletions(-) diff --git a/src/BUILD b/src/BUILD index 5d449d18..dee97ee7 100644 --- a/src/BUILD +++ b/src/BUILD @@ -71,7 +71,9 @@ toolchain_type(name = "toolchain_type") codechecker_toolchain( name = "codechecker_local", - analyzer_binary = "CodeChecker", # Find it in PATH ? + clang_tidy_binary = "clang-tidy", + clangsa_binary = "clang", + codechecker_binary = "CodeChecker", # Find it in PATH ? ) toolchain( diff --git a/src/codechecker_toolchain.bzl b/src/codechecker_toolchain.bzl index a454a5e4..1d5977cc 100644 --- a/src/codechecker_toolchain.bzl +++ b/src/codechecker_toolchain.bzl @@ -3,16 +3,20 @@ This file provides the toolchain rule for CodeChecker """ CodeCheckerInfo = provider( - doc = "This provider provides the executable path for CodeChecker", - fields = [ - "executable", - ], + doc = "This provider provides the executable path for CodeChecker and its related tools", + fields = { + "clang_tidy_bin": "clang-tidy executable", + "clangsa_bin": "Clang executable", + "codechecker_bin": "CodeChecker executable", + }, ) def _codechecker_toolchain_impl(ctx): toolchain_info = platform_common.ToolchainInfo( codecheckerinfo = CodeCheckerInfo( - executable = ctx.attr.analyzer_binary, + codechecker_bin = ctx.attr.codechecker_binary, + clang_tidy_bin = ctx.attr.clang_tidy_binary, + clangsa_bin = ctx.attr.clangsa_binary, ), ) return [toolchain_info] @@ -20,6 +24,8 @@ def _codechecker_toolchain_impl(ctx): codechecker_toolchain = rule( implementation = _codechecker_toolchain_impl, attrs = { - "analyzer_binary": attr.string(), + "clang_tidy_binary": attr.string(), + "clangsa_binary": attr.string(), + "codechecker_binary": attr.string(), }, ) diff --git a/src/per_file.bzl b/src/per_file.bzl index ace77b15..bbc20a7b 100644 --- a/src/per_file.bzl +++ b/src/per_file.bzl @@ -82,18 +82,22 @@ def _run_code_checker( analyzer_output_paths = "clangsa," + clangsa_plist.path + \ ";clang-tidy," + clang_tidy_plist.path + analyzer_executables = "clangsa:" + info.clangsa_bin + \ + ";clang-tidy:" + info.clang_tidy_bin + # Action to run CodeChecker for a file ctx.actions.run( inputs = inputs, outputs = outputs, executable = ctx.outputs.per_file_script, arguments = [ - info.executable, + info.codechecker_bin, data_dir, src.path, codechecker_log.path, config.path, analyzer_output_paths, + analyzer_executables, ], mnemonic = "CodeChecker", use_default_shell_env = True, @@ -158,8 +162,6 @@ def _create_wrapper_script(ctx, options, compile_commands_json, config_file): ) def _per_file_impl(ctx): - info = ctx.toolchains["//src:toolchain_type"].codecheckerinfo - print("CodeChecker path resolved:", info.executable) compile_commands = None for output in compile_commands_impl(ctx): if type(output) == "DefaultInfo": diff --git a/src/per_file_script.py b/src/per_file_script.py index f510c0b8..21c3fbf8 100644 --- a/src/per_file_script.py +++ b/src/per_file_script.py @@ -37,6 +37,7 @@ LOG_FILE = sys.argv[4] # List of pairs of analyzers and their plist files ANALYZER_PLIST_PATHS = [item.split(",") for item in sys.argv[6].split(";")] +ANALYZER_EXECUTABLES_ENV_VAR = sys.argv[7] EMPTY_PLIST = """ @@ -82,6 +83,15 @@ def _create_compile_commands_json_with_absolute_paths(): new_file.write(new_content) +def _get_codechecker_env() -> dict[str, str]: + """ + Returns the environment for running CodeChecker + """ + cc_env = os.environ.copy() + # Overwrite analyzer paths + cc_env["CC_ANALYZER_BIN"] = ANALYZER_EXECUTABLES_ENV_VAR + return cc_env + def _run_codechecker() -> None: """ Runs CodeChecker analyze @@ -103,7 +113,7 @@ def _run_codechecker() -> None: result = subprocess.run( ["echo", "$PATH"], shell=True, - env=os.environ, + env=_get_codechecker_env(), capture_output=True, text=True, check=False, @@ -114,7 +124,7 @@ def _run_codechecker() -> None: with open(LOG_FILE, "a", encoding="utf-8") as log_file: subprocess.run( codechecker_cmd, - env=os.environ, + env=_get_codechecker_env(), stdout=log_file, stderr=log_file, check=True, @@ -174,7 +184,7 @@ def main(): """ Main function of CodeChecker wrapper """ - if len(sys.argv) != 7: + if len(sys.argv) != 8: print("Wrong amount of arguments") sys.exit(1) _create_compile_commands_json_with_absolute_paths() From 3c1331a8b0f4a5d82b6c954c411c6abbc9b4b11f Mon Sep 17 00:00:00 2001 From: "F.Tibor" Date: Thu, 4 Jun 2026 10:46:37 +0200 Subject: [PATCH 4/8] Prototype using labels for toolchain --- src/BUILD | 2 +- src/codechecker_toolchain.bzl | 9 +++++++-- src/per_file.bzl | 4 +++- src/per_file_script.py | 2 +- src/tools.bzl | 20 ++++++++++++++------ 5 files changed, 26 insertions(+), 11 deletions(-) diff --git a/src/BUILD b/src/BUILD index dee97ee7..c1278df8 100644 --- a/src/BUILD +++ b/src/BUILD @@ -73,7 +73,7 @@ codechecker_toolchain( name = "codechecker_local", clang_tidy_binary = "clang-tidy", clangsa_binary = "clang", - codechecker_binary = "CodeChecker", # Find it in PATH ? + codechecker_binary = "@default_codechecker_tools//:CodeChecker", ) toolchain( diff --git a/src/codechecker_toolchain.bzl b/src/codechecker_toolchain.bzl index 1d5977cc..c9fd331b 100644 --- a/src/codechecker_toolchain.bzl +++ b/src/codechecker_toolchain.bzl @@ -14,7 +14,7 @@ CodeCheckerInfo = provider( def _codechecker_toolchain_impl(ctx): toolchain_info = platform_common.ToolchainInfo( codecheckerinfo = CodeCheckerInfo( - codechecker_bin = ctx.attr.codechecker_binary, + codechecker_bin = ctx.executable.codechecker_binary, clang_tidy_bin = ctx.attr.clang_tidy_binary, clangsa_bin = ctx.attr.clangsa_binary, ), @@ -26,6 +26,11 @@ codechecker_toolchain = rule( attrs = { "clang_tidy_binary": attr.string(), "clangsa_binary": attr.string(), - "codechecker_binary": attr.string(), + "codechecker_binary": attr.label( + doc = "CodeChecker executable label", + allow_single_file = True, + executable = True, + cfg = "exec", + ), }, ) diff --git a/src/per_file.bzl b/src/per_file.bzl index bbc20a7b..cf98f6f5 100644 --- a/src/per_file.bzl +++ b/src/per_file.bzl @@ -66,6 +66,7 @@ def _run_code_checker( compile_commands_json, config_file, config, + info.codechecker_bin, ] + sources_and_headers else: # NOTE: we collect only headers, so CTU may not work! @@ -75,6 +76,7 @@ def _run_code_checker( config_file, src, config, + info.codechecker_bin, ], transitive = [headers]) outputs = [clang_tidy_plist, clangsa_plist, codechecker_log] @@ -91,7 +93,7 @@ def _run_code_checker( outputs = outputs, executable = ctx.outputs.per_file_script, arguments = [ - info.codechecker_bin, + info.codechecker_bin.path, data_dir, src.path, codechecker_log.path, diff --git a/src/per_file_script.py b/src/per_file_script.py index 21c3fbf8..6d6ba125 100644 --- a/src/per_file_script.py +++ b/src/per_file_script.py @@ -29,7 +29,7 @@ CODECHECKER_ARGS: str = "{codechecker_args}" CONFIG_FILE: str = "{config_file}" SKIP_FILE: str = sys.argv[5] -CODECHECKER_BIN = sys.argv[1] +CODECHECKER_BIN = os.path.realpath(sys.argv[1]) # The output directory for CodeChecker DATA_DIR = sys.argv[2] # The file to be analyzed diff --git a/src/tools.bzl b/src/tools.bzl index f15d4fd1..caa2eeb8 100644 --- a/src/tools.bzl +++ b/src/tools.bzl @@ -97,12 +97,6 @@ module_register_default_python_toolchain = module_extension( ) def _codechecker_local_repository_impl(repository_ctx): - repository_ctx.file( - repository_ctx.path("BUILD"), - content = "", - executable = False, - ) - codechecker_bin_path = repository_ctx.which("CodeChecker") if not codechecker_bin_path: fail("ERROR! CodeChecker is not detected") @@ -123,6 +117,20 @@ def _codechecker_local_repository_impl(repository_ctx): executable = False, ) + repository_ctx.symlink(codechecker_bin_path, "codechecker_bin") + + repository_ctx.file( + repository_ctx.path("BUILD"), + content = """ +filegroup( + name = "CodeChecker", + srcs = ["codechecker_bin"], + visibility = ["//visibility:public"], +) + """, + executable = False, + ) + default_codechecker_tools = repository_rule( attrs = {}, local = True, From 93dd1e4e0f86aab7665494d4b5e63efff037bd2b Mon Sep 17 00:00:00 2001 From: "F.Tibor" Date: Thu, 4 Jun 2026 14:13:57 +0200 Subject: [PATCH 5/8] Clang and clang-tidy label conversion --- src/BUILD | 4 ++-- src/codechecker_toolchain.bzl | 20 +++++++++++++++----- src/per_file.bzl | 8 ++++++-- src/per_file_script.py | 9 ++++++++- src/tools.bzl | 15 +++++++++++++++ 5 files changed, 46 insertions(+), 10 deletions(-) diff --git a/src/BUILD b/src/BUILD index c1278df8..e79c6332 100644 --- a/src/BUILD +++ b/src/BUILD @@ -71,8 +71,8 @@ toolchain_type(name = "toolchain_type") codechecker_toolchain( name = "codechecker_local", - clang_tidy_binary = "clang-tidy", - clangsa_binary = "clang", + clang_tidy_binary = "@default_codechecker_tools//:clang_tidy", + clangsa_binary = "@default_codechecker_tools//:clang", codechecker_binary = "@default_codechecker_tools//:CodeChecker", ) diff --git a/src/codechecker_toolchain.bzl b/src/codechecker_toolchain.bzl index c9fd331b..2d382350 100644 --- a/src/codechecker_toolchain.bzl +++ b/src/codechecker_toolchain.bzl @@ -15,8 +15,8 @@ def _codechecker_toolchain_impl(ctx): toolchain_info = platform_common.ToolchainInfo( codecheckerinfo = CodeCheckerInfo( codechecker_bin = ctx.executable.codechecker_binary, - clang_tidy_bin = ctx.attr.clang_tidy_binary, - clangsa_bin = ctx.attr.clangsa_binary, + clang_tidy_bin = ctx.executable.clang_tidy_binary, + clangsa_bin = ctx.executable.clangsa_binary, ), ) return [toolchain_info] @@ -24,10 +24,20 @@ def _codechecker_toolchain_impl(ctx): codechecker_toolchain = rule( implementation = _codechecker_toolchain_impl, attrs = { - "clang_tidy_binary": attr.string(), - "clangsa_binary": attr.string(), + "clang_tidy_binary": attr.label( + doc = "clang-tidy executable", + allow_single_file = True, + executable = True, + cfg = "exec", + ), + "clangsa_binary": attr.label( + doc = "clang executable", + allow_single_file = True, + executable = True, + cfg = "exec", + ), "codechecker_binary": attr.label( - doc = "CodeChecker executable label", + doc = "CodeChecker executable", allow_single_file = True, executable = True, cfg = "exec", diff --git a/src/per_file.bzl b/src/per_file.bzl index cf98f6f5..d69d9645 100644 --- a/src/per_file.bzl +++ b/src/per_file.bzl @@ -67,6 +67,8 @@ def _run_code_checker( config_file, config, info.codechecker_bin, + info.clangsa_bin, + info.clang_tidy_bin, ] + sources_and_headers else: # NOTE: we collect only headers, so CTU may not work! @@ -77,6 +79,8 @@ def _run_code_checker( src, config, info.codechecker_bin, + info.clangsa_bin, + info.clang_tidy_bin, ], transitive = [headers]) outputs = [clang_tidy_plist, clangsa_plist, codechecker_log] @@ -84,8 +88,8 @@ def _run_code_checker( analyzer_output_paths = "clangsa," + clangsa_plist.path + \ ";clang-tidy," + clang_tidy_plist.path - analyzer_executables = "clangsa:" + info.clangsa_bin + \ - ";clang-tidy:" + info.clang_tidy_bin + analyzer_executables = "clangsa:" + info.clangsa_bin.path + \ + ";clang-tidy:" + info.clang_tidy_bin.path # Action to run CodeChecker for a file ctx.actions.run( diff --git a/src/per_file_script.py b/src/per_file_script.py index 6d6ba125..dc5a1476 100644 --- a/src/per_file_script.py +++ b/src/per_file_script.py @@ -37,7 +37,13 @@ LOG_FILE = sys.argv[4] # List of pairs of analyzers and their plist files ANALYZER_PLIST_PATHS = [item.split(",") for item in sys.argv[6].split(";")] -ANALYZER_EXECUTABLES_ENV_VAR = sys.argv[7] +ANALYZER_EXECUTABLES_ENV_VAR = ";".join( + f"{name}:{os.path.realpath(path)}" + for name, path in [ + pair.split(":", 1) for pair in sys.argv[7].split(";") if pair + ] +) + EMPTY_PLIST = """ @@ -92,6 +98,7 @@ def _get_codechecker_env() -> dict[str, str]: cc_env["CC_ANALYZER_BIN"] = ANALYZER_EXECUTABLES_ENV_VAR return cc_env + def _run_codechecker() -> None: """ Runs CodeChecker analyze diff --git a/src/tools.bzl b/src/tools.bzl index caa2eeb8..e9da2ec3 100644 --- a/src/tools.bzl +++ b/src/tools.bzl @@ -117,7 +117,12 @@ def _codechecker_local_repository_impl(repository_ctx): executable = False, ) + clang_bin_path = repository_ctx.which("clang") + clang_tidy_bin_path = repository_ctx.which("clang-tidy") + repository_ctx.symlink(codechecker_bin_path, "codechecker_bin") + repository_ctx.symlink(clang_bin_path, "clang_bin") + repository_ctx.symlink(clang_tidy_bin_path, "clang_tidy_bin") repository_ctx.file( repository_ctx.path("BUILD"), @@ -126,6 +131,16 @@ filegroup( name = "CodeChecker", srcs = ["codechecker_bin"], visibility = ["//visibility:public"], +) +filegroup( + name = "clang", + srcs = ["clang_bin"], + visibility = ["//visibility:public"], +) +filegroup( + name = "clang_tidy", + srcs = ["clang_tidy_bin"], + visibility = ["//visibility:public"], ) """, executable = False, From bb67ef793dd6b3360c140642a676740572a2ff71 Mon Sep 17 00:00:00 2001 From: "F.Tibor" Date: Wed, 17 Jun 2026 16:39:56 +0200 Subject: [PATCH 6/8] Remove duplicate which --- src/tools.bzl | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/tools.bzl b/src/tools.bzl index e9da2ec3..4ef0f90b 100644 --- a/src/tools.bzl +++ b/src/tools.bzl @@ -117,9 +117,6 @@ def _codechecker_local_repository_impl(repository_ctx): executable = False, ) - clang_bin_path = repository_ctx.which("clang") - clang_tidy_bin_path = repository_ctx.which("clang-tidy") - repository_ctx.symlink(codechecker_bin_path, "codechecker_bin") repository_ctx.symlink(clang_bin_path, "clang_bin") repository_ctx.symlink(clang_tidy_bin_path, "clang_tidy_bin") From b1a7210fd0a441ef13fcba00ca6d8eec409b8018 Mon Sep 17 00:00:00 2001 From: "F.Tibor" Date: Wed, 17 Jun 2026 17:48:10 +0200 Subject: [PATCH 7/8] Apply toolchain to monolithic rule --- src/codechecker.bzl | 42 +++++++++++++++++++++++++-------------- src/codechecker_script.py | 2 +- 2 files changed, 28 insertions(+), 16 deletions(-) diff --git a/src/codechecker.bzl b/src/codechecker.bzl index 899ad917..8a1f5323 100644 --- a/src/codechecker.bzl +++ b/src/codechecker.bzl @@ -16,12 +16,6 @@ Rulesets for running codechecker in a single Bazel job. """ -load( - "@default_codechecker_tools//:defs.bzl", - "CLANG_BIN_PATH", - "CLANG_TIDY_BIN_PATH", - "CODECHECKER_BIN_PATH", -) load( "codechecker_config.bzl", "codechecker_config_internal", @@ -99,6 +93,8 @@ def _codechecker_impl(ctx): config_file, codechecker_env = get_config_file(ctx) + info = ctx.toolchains["//src:toolchain_type"].codecheckerinfo + codechecker_files = ctx.actions.declare_directory(ctx.label.name + "/codechecker-files") ctx.actions.expand_template( template = ctx.file._codechecker_script_template, @@ -107,10 +103,10 @@ def _codechecker_impl(ctx): substitutions = { "{Mode}": "Run", "{Verbosity}": "DEBUG", - "{clang_bin}": CLANG_BIN_PATH, - "{clang_tidy_bin}": CLANG_TIDY_BIN_PATH, + "{clang_bin}": info.clangsa_bin.path, + "{clang_tidy_bin}": info.clang_tidy_bin.path, "{codechecker_analyze}": " ".join(ctx.attr.analyze), - "{codechecker_bin}": CODECHECKER_BIN_PATH, + "{codechecker_bin}": info.codechecker_bin.path, "{codechecker_config}": config_file.path, "{codechecker_env}": codechecker_env, "{codechecker_files}": codechecker_files.path, @@ -123,6 +119,9 @@ def _codechecker_impl(ctx): ctx.actions.run( inputs = depset( [ + info.codechecker_bin, + info.clangsa_bin, + info.clang_tidy_bin, ctx.outputs.codechecker_script, ctx.outputs.codechecker_commands, ctx.outputs.codechecker_skipfile, @@ -209,7 +208,10 @@ codechecker = rule( "codechecker_skipfile": "%{name}/codechecker_skipfile.cfg", "compile_commands": "%{name}/compile_commands.json", }, - toolchains = [python_toolchain_type()], + toolchains = [ + python_toolchain_type(), + "//src:toolchain_type", + ], ) def _codechecker_test_impl(ctx): @@ -229,6 +231,8 @@ def _codechecker_test_impl(ctx): if not codechecker_files: fail("Execution results required for codechecker test are not available") + info = ctx.toolchains["//src:toolchain_type"].codecheckerinfo + # Create test script from template ctx.actions.expand_template( template = ctx.file._codechecker_script_template, @@ -238,15 +242,20 @@ def _codechecker_test_impl(ctx): "{Mode}": "Test", "{Severities}": " ".join(ctx.attr.severities), "{Verbosity}": "INFO", - "{clang_bin}": CLANG_BIN_PATH, - "{clang_tidy_bin}": CLANG_TIDY_BIN_PATH, - "{codechecker_bin}": CODECHECKER_BIN_PATH, + "{clang_bin}": info.clangsa_bin.short_path, + "{clang_tidy_bin}": info.clang_tidy_bin.short_path, + "{codechecker_bin}": info.codechecker_bin.short_path, "{codechecker_files}": codechecker_files.short_path, }, ) # Return test script and all required files - run_files = default_runfiles + [ctx.outputs.codechecker_test_script] + run_files = default_runfiles + [ + ctx.outputs.codechecker_test_script, + info.codechecker_bin, + info.clang_tidy_bin, + info.clangsa_bin, + ] return [ DefaultInfo( files = depset(all_files), @@ -306,7 +315,10 @@ _codechecker_test = rule( "codechecker_test_script": "%{name}/codechecker_test_script.py", "compile_commands": "%{name}/compile_commands.json", }, - toolchains = [python_toolchain_type()], + toolchains = [ + python_toolchain_type(), + "//src:toolchain_type", + ], test = True, ) diff --git a/src/codechecker_script.py b/src/codechecker_script.py index 5854ec2d..51f22eaa 100644 --- a/src/codechecker_script.py +++ b/src/codechecker_script.py @@ -29,7 +29,7 @@ EXECUTION_MODE = "{Mode}" VERBOSITY = "{Verbosity}" -CODECHECKER_PATH = "{codechecker_bin}" +CODECHECKER_PATH = os.path.realpath("{codechecker_bin}") CLANG_PATH = "{clang_bin}" CLANG_TIDY_PATH = "{clang_tidy_bin}" CODECHECKER_SKIPFILE = "{codechecker_skipfile}" From fb6fc1c47b4b952969d7e4453c384bc640a5178d Mon Sep 17 00:00:00 2001 From: "F.Tibor" Date: Wed, 17 Jun 2026 17:49:06 +0200 Subject: [PATCH 8/8] Add documentation fro toolchains --- README.md | 80 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 80 insertions(+) diff --git a/README.md b/README.md index b8d5e702..883596bd 100644 --- a/README.md +++ b/README.md @@ -427,3 +427,83 @@ After that you can find all artifacts in `bazel-bin` directory: # compile_commands.json for compile_commands_pass cat bazel-bin/test/compile_commands_pass/compile_commands.json + +Toolchains +---------- + +The previous rules locate the CodeChecker, Clang and clang-tidy executables through a +Bazel [toolchain](https://bazel.build/extending/toolchains). Modeling the tools +as a toolchain means the analysis rule (`codechecker_test`) never hardcode binary paths: +Bazel resolves the correct tools at build time, and you can override them +per-project or per-platform without touching the rules themselves. + +### The default toolchain + +For most users no setup is required. `rules_codechecker` ships with a module +extension that provisions a default set of tools and a pre-registered +toolchain. + +> [!NOTE] +> The default tools resolve to the `CodeChecker`, `clang` and `clang-tidy` +> binaries available from PATH on your system (see the [Prerequisites](#prerequisites)). + +To enable it, add the following to your `MODULE.bazel`: + +```python +codechecker_extension = use_extension( + "@rules_codechecker//src:tools.bzl", + "module_register_default_codechecker_tools", +) +use_repo(codechecker_extension, "default_codechecker_tools") +register_toolchains("@rules_codechecker//src:codechecker_local_toolchain") +``` + +Or the following to your `WORKSPACE`: + +```python +load( + "@rules_codechecker//src:tools.bzl", + "register_default_codechecker_tools", +) + +register_default_codechecker_tools() +register_toolchains( + "//src:codechecker_local_toolchain", +) +``` + +### Providing your own tools + +If you don't want the default system tools -- for example to pin a specific +CodeChecker version or point at a custom build —- you can define and register your own toolchain. + +First, define an implementation in a BUILD file with `codechecker_toolchain()`: + +```python +load( + "@rules_codechecker//src:codechecker_toolchain.bzl", + "codechecker_toolchain", +) + +codechecker_toolchain( + name = "codechecker_custom", + clang_tidy_binary = "@my_tools//:clang-tidy", + clangsa_binary = "@my_tools//:clang", + codechecker_binary = "@my_tools//:CodeChecker", +) + +toolchain( + name = "codechecker_custom_toolchain", + toolchain = ":codechecker_custom", + toolchain_type = "@rules_codechecker//src:toolchain_type", + # Optionally constrain which execution platform this applies to: + # exec_compatible_with = ["@platforms//os:linux"], +) +``` +Finally register it in your `MODULE.bazel` (or `WORKSPACE`): +```python +register_toolchains("//path/to:codechecker_custom_toolchain") +``` + +[!NOTE] +> Toolchains you register yourself take precedence over the default one if registered earlier.