From d05ee4e9a9dfd7147f7544606c4849862aface71 Mon Sep 17 00:00:00 2001 From: "F.Tibor" Date: Fri, 27 Feb 2026 13:26:48 +0100 Subject: [PATCH 1/6] Rewrite monolithic rule to use py_binary for execution --- src/BUILD | 7 ++- src/codechecker.bzl | 103 +++++++++++++++++++++----------------- src/codechecker_script.py | 42 +++++++++++----- 3 files changed, 93 insertions(+), 59 deletions(-) diff --git a/src/BUILD b/src/BUILD index 029908cd..31ebd6f5 100644 --- a/src/BUILD +++ b/src/BUILD @@ -19,10 +19,15 @@ py_binary( visibility = ["//visibility:public"], ) +py_binary( + name = "codechecker_script", + srcs = ["codechecker_script.py"], + visibility = ["//visibility:public"], +) + # Build & Test script template exports_files( [ - "codechecker_script.py", "per_file_script.py", ], ) diff --git a/src/codechecker.bzl b/src/codechecker.bzl index 6030b201..59bb918c 100644 --- a/src/codechecker.bzl +++ b/src/codechecker.bzl @@ -97,29 +97,30 @@ def _codechecker_impl(ctx): config_file, codechecker_env = get_config_file(ctx) codechecker_files = ctx.actions.declare_directory(ctx.label.name + "/codechecker-files") - ctx.actions.expand_template( - template = ctx.file._codechecker_script_template, - output = ctx.outputs.codechecker_script, - is_executable = True, - substitutions = { - "{Mode}": "Run", - "{Verbosity}": "DEBUG", - "{PythonPath}": python_path(ctx), # "/usr/bin/env python3", - "{codechecker_bin}": CODECHECKER_BIN_PATH, - "{compile_commands}": ctx.outputs.codechecker_commands.path, - "{codechecker_skipfile}": ctx.outputs.codechecker_skipfile.path, - "{codechecker_config}": config_file.path, - "{codechecker_analyze}": " ".join(ctx.attr.analyze), - "{codechecker_files}": codechecker_files.path, - "{codechecker_log}": ctx.outputs.codechecker_log.path, - "{codechecker_env}": codechecker_env, - }, + + # Use environment variables instead of expand_template + environment_variables = { + "RULES_CODECHECKER_Mode": "Run", + "RULES_CODECHECKER_Verbosity": "DEBUG", + "RULES_CODECHECKER_PythonPath": python_path(ctx), # "/usr/bin/env python3", + "RULES_CODECHECKER_codechecker_bin": CODECHECKER_BIN_PATH, + "RULES_CODECHECKER_compile_commands": ctx.outputs.codechecker_commands.path, + "RULES_CODECHECKER_codechecker_skipfile": ctx.outputs.codechecker_skipfile.path, + "RULES_CODECHECKER_codechecker_config": config_file.path, + "RULES_CODECHECKER_codechecker_analyze": " ".join(ctx.attr.analyze), + "RULES_CODECHECKER_codechecker_files": codechecker_files.path, + "RULES_CODECHECKER_codechecker_log": ctx.outputs.codechecker_log.path, + "RULES_CODECHECKER_codechecker_env": codechecker_env, + } + codechecker_script = ctx.actions.declare_file(ctx.label.name + "/codechecker_script") + ctx.actions.symlink( + output = codechecker_script, + target_file = ctx.executable._codechecker_script, ) - ctx.actions.run( inputs = depset( [ - ctx.outputs.codechecker_script, + codechecker_script, ctx.outputs.codechecker_commands, ctx.outputs.codechecker_skipfile, config_file, @@ -129,10 +130,10 @@ def _codechecker_impl(ctx): codechecker_files, ctx.outputs.codechecker_log, ], - executable = ctx.outputs.codechecker_script, + executable = codechecker_script, + tools = [ctx.attr._codechecker_script[DefaultInfo].files_to_run], arguments = [], - # executable = python_path(ctx), - # arguments = [ctx.outputs.codechecker_script.path], + env = environment_variables, mnemonic = "CodeChecker", progress_message = "CodeChecker %s" % str(ctx.label), # use_default_shell_env = True, @@ -193,16 +194,18 @@ codechecker = rule( cfg = "host", default = ":compile_commands_filter", ), - "_codechecker_script_template": attr.label( - default = ":codechecker_script.py", - allow_single_file = True, + "_codechecker_script": attr.label( + allow_files = True, + executable = True, + cfg = "target", + default = ":codechecker_script", ), }, outputs = { "compile_commands": "%{name}/compile_commands.json", "codechecker_commands": "%{name}/codechecker_commands.json", "codechecker_skipfile": "%{name}/codechecker_skipfile.cfg", - "codechecker_script": "%{name}/codechecker_script.py", + "codechecker_script": "%{name}/codechecker_script", "codechecker_log": "%{name}/codechecker.log", }, toolchains = [python_toolchain_type()], @@ -225,29 +228,37 @@ def _codechecker_test_impl(ctx): if not codechecker_files: fail("Execution results required for codechecker test are not available") - # Create test script from template - ctx.actions.expand_template( - template = ctx.file._codechecker_script_template, - output = ctx.outputs.codechecker_test_script, - is_executable = True, - substitutions = { - "{Mode}": "Test", - "{Verbosity}": "INFO", - "{PythonPath}": python_path(ctx), # "/usr/bin/env python3", - "{codechecker_bin}": CODECHECKER_BIN_PATH, - "{codechecker_files}": codechecker_files.short_path, - "{Severities}": " ".join(ctx.attr.severities), - }, + # Use environment variables instead of expand_template + environment_variables = { + "RULES_CODECHECKER_Mode": "Test", + "RULES_CODECHECKER_Verbosity": "INFO", + "RULES_CODECHECKER_PythonPath": python_path(ctx), # "/usr/bin/env python3", + "RULES_CODECHECKER_codechecker_bin": CODECHECKER_BIN_PATH, + "RULES_CODECHECKER_codechecker_files": codechecker_files.short_path, + "RULES_CODECHECKER_Severities": " ".join(ctx.attr.severities), + } + + # Create test script + codechecker_test_script = ctx.actions.declare_file(ctx.label.name + "/codechecker_test_script") + ctx.actions.symlink( + output = codechecker_test_script, + target_file = ctx.executable._codechecker_script, ) # Return test script and all required files run_files = default_runfiles + [ctx.outputs.codechecker_test_script] + all_runfiles = ctx.runfiles(files = run_files) + # Add runfiles from the py_binary target: + all_runfiles = all_runfiles.merge(ctx.attr._codechecker_script[DefaultInfo].default_runfiles) return [ DefaultInfo( files = depset(all_files), - runfiles = ctx.runfiles(files = run_files), + runfiles = all_runfiles, executable = ctx.outputs.codechecker_test_script, ), + RunEnvironmentInfo( + environment = environment_variables, + ) ] _codechecker_test = rule( @@ -270,9 +281,11 @@ _codechecker_test = rule( cfg = "host", default = ":compile_commands_filter", ), - "_codechecker_script_template": attr.label( - default = ":codechecker_script.py", - allow_single_file = True, + "_codechecker_script": attr.label( + allow_files = True, + executable = True, + cfg = "target", + default = ":codechecker_script", ), "severities": attr.string_list( default = ["HIGH"], @@ -297,9 +310,9 @@ _codechecker_test = rule( "compile_commands": "%{name}/compile_commands.json", "codechecker_commands": "%{name}/codechecker_commands.json", "codechecker_skipfile": "%{name}/codechecker_skipfile.cfg", - "codechecker_script": "%{name}/codechecker_script.py", + "codechecker_script": "%{name}/codechecker_script", "codechecker_log": "%{name}/codechecker.log", - "codechecker_test_script": "%{name}/codechecker_test_script.py", + "codechecker_test_script": "%{name}/codechecker_test_script", }, toolchains = [python_toolchain_type()], test = True, diff --git a/src/codechecker_script.py b/src/codechecker_script.py index dd87a74a..038aca75 100644 --- a/src/codechecker_script.py +++ b/src/codechecker_script.py @@ -1,5 +1,3 @@ -#!{PythonPath} - # Copyright 2023 Ericsson AB # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -28,17 +26,35 @@ import sys -EXECUTION_MODE = "{Mode}" -VERBOSITY = "{Verbosity}" -CODECHECKER_PATH = "{codechecker_bin}" -CODECHECKER_SKIPFILE = "{codechecker_skipfile}" -CODECHECKER_CONFIG = "{codechecker_config}" -CODECHECKER_ANALYZE = "{codechecker_analyze}" -CODECHECKER_FILES = "{codechecker_files}" -CODECHECKER_LOG = "{codechecker_log}" -CODECHECKER_SEVERITIES = "{Severities}" -CODECHECKER_ENV = "{codechecker_env}" -COMPILE_COMMANDS = "{compile_commands}" +EXECUTION_MODE = os.environ.get("RULES_CODECHECKER_Mode", "{Mode}") +VERBOSITY = os.environ.get("RULES_CODECHECKER_Verbosity", "{Verbosity}") +CODECHECKER_PATH = os.environ.get( + "RULES_CODECHECKER_codechecker_bin", "{codechecker_bin}" +) +CODECHECKER_SKIPFILE = os.environ.get( + "RULES_CODECHECKER_codechecker_skipfile", "{codechecker_skipfile}" +) +CODECHECKER_CONFIG = os.environ.get( + "RULES_CODECHECKER_codechecker_config", "{codechecker_config}" +) +CODECHECKER_ANALYZE = os.environ.get( + "RULES_CODECHECKER_codechecker_analyze", "{codechecker_analyze}" +) +CODECHECKER_FILES = os.environ.get( + "RULES_CODECHECKER_codechecker_files", "{codechecker_files}" +) +CODECHECKER_LOG = os.environ.get( + "RULES_CODECHECKER_codechecker_log", "{codechecker_log}" +) +CODECHECKER_SEVERITIES = os.environ.get( + "RULES_CODECHECKER_Severities", "{Severities}" +) +CODECHECKER_ENV = os.environ.get( + "RULES_CODECHECKER_codechecker_env", "{codechecker_env}" +) +COMPILE_COMMANDS = os.environ.get( + "RULES_CODECHECKER_compile_commands", "{compile_commands}" +) START_PATH = r"\/(?:(?!\.\s+)\S)+" BAZEL_PATHS = { From a79d778361addf2baa4ff9ad09e283101bd2889a Mon Sep 17 00:00:00 2001 From: "F.Tibor" Date: Fri, 27 Feb 2026 14:25:55 +0100 Subject: [PATCH 2/6] Add py_binary rule for per_file rule --- src/BUILD | 9 +++---- src/per_file.bzl | 57 ++++++++++++++++++++++++++---------------- src/per_file_script.py | 18 ++++++------- 3 files changed, 48 insertions(+), 36 deletions(-) diff --git a/src/BUILD b/src/BUILD index 31ebd6f5..0d375553 100644 --- a/src/BUILD +++ b/src/BUILD @@ -25,11 +25,10 @@ py_binary( visibility = ["//visibility:public"], ) -# Build & Test script template -exports_files( - [ - "per_file_script.py", - ], +py_binary( + name = "per_file_script", + srcs = ["per_file_script.py"], + visibility = ["//visibility:public"], ) # The following are flags and default values for clang_tidy_aspect diff --git a/src/per_file.bzl b/src/per_file.bzl index 8a9be0fa..cccc69a1 100644 --- a/src/per_file.bzl +++ b/src/per_file.bzl @@ -31,6 +31,7 @@ load( def _run_code_checker( ctx, + per_file_script, src, arguments, target, @@ -66,16 +67,22 @@ def _run_code_checker( ";clang-tidy," + clang_tidy_plist.path # Action to run CodeChecker for a file + # env_vars are unused for now, since + # use_default_shell_env and env are incompatible ctx.actions.run( inputs = inputs, outputs = outputs, - executable = ctx.outputs.per_file_script, + executable = per_file_script, arguments = [ + compile_commands_json.path, + ' '.join(options), + config_file.path, data_dir, src.path, codechecker_log.path, analyzer_output_paths, ], + tools = [ctx.attr._per_file_script[DefaultInfo].files_to_run], mnemonic = "CodeChecker", use_default_shell_env = True, progress_message = "CodeChecker analyze {}".format(src.short_path), @@ -117,21 +124,20 @@ def _collect_all_sources_and_headers(ctx): all_files += headers return all_files -def _create_wrapper_script(ctx, options, compile_commands_json, config_file): - options_str = "" - for item in options: - options_str += item + " " - ctx.actions.expand_template( - template = ctx.file._per_file_script_template, - output = ctx.outputs.per_file_script, - is_executable = True, - substitutions = { - "{PythonPath}": ctx.attr._python_runtime[PyRuntimeInfo].interpreter_path, - "{compile_commands_json}": compile_commands_json.path, - "{codechecker_args}": options_str, - "{config_file}": config_file.path, - }, - ) +def _update_env_vars( + ctx, + options, + compile_commands_json, + config_file, + env_vars): + options_str = " ".join(options) + if not env_vars: + env_vars = {} + return (env_vars | { + "RULES_CODECHECKER_COMPILE_COMMANDS_JSON": compile_commands_json.path, + "RULES_CODECHECKER_CODECHECKER_ARGS": options_str, + "RULES_CODECHECKER_CONFIG_FILE": config_file.path, + }) def _per_file_impl(ctx): compile_commands = None @@ -146,7 +152,13 @@ def _per_file_impl(ctx): options = ctx.attr.default_options + ctx.attr.options all_files = [compile_commands] config_file, env_vars = get_config_file(ctx) - _create_wrapper_script(ctx, options, compile_commands, config_file) + env_vars =_update_env_vars(ctx, options, compile_commands, config_file, env_vars) + # Create per_file_script + per_file_script = ctx.actions.declare_file(ctx.label.name + "/per_file_script") + ctx.actions.symlink( + output = per_file_script, + target_file = ctx.executable._per_file_script, + ) for target in ctx.attr.targets: if not CcInfo in target: continue @@ -161,6 +173,7 @@ def _per_file_impl(ctx): args = target[SourceFilesInfo].compilation_db.to_list() outputs = _run_code_checker( ctx, + per_file_script, src, args, target, @@ -221,9 +234,11 @@ per_file_test = rule( default = None, doc = "CodeChecker configuration", ), - "_per_file_script_template": attr.label( - default = ":per_file_script.py", - allow_single_file = True, + "_per_file_script": attr.label( + allow_files = True, + executable = True, + cfg = "target", + default = ":per_file_script", ), "_python_runtime": attr.label( default = "@default_python_tools//:py3_runtime", @@ -232,7 +247,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", + "per_file_script": "%{name}/per_file_script", }, test = True, ) diff --git a/src/per_file_script.py b/src/per_file_script.py index 1cfad4b9..445a0297 100644 --- a/src/per_file_script.py +++ b/src/per_file_script.py @@ -1,5 +1,3 @@ -#!{PythonPath} - # Copyright 2023 Ericsson AB # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -32,14 +30,14 @@ # List of pairs of analyzers and their plist files ANALYZER_PLIST_PATHS: Optional[list[list[str]]] = None LOG_FILE: Optional[str] = None -COMPILE_COMMANDS_JSON: str = "{compile_commands_json}" +COMPILE_COMMANDS_JSON: str = sys.argv[1] COMPILE_COMMANDS_ABSOLUTE: str = f"{COMPILE_COMMANDS_JSON}.abs" -CODECHECKER_ARGS: str = "{codechecker_args}" -CONFIG_FILE: str = "{config_file}" -DATA_DIR = sys.argv[1] -FILE_PATH = sys.argv[2] -LOG_FILE = sys.argv[3] -ANALYZER_PLIST_PATHS = [item.split(",") for item in sys.argv[4].split(";")] +CODECHECKER_ARGS: str = sys.argv[2] +CONFIG_FILE: str = sys.argv[3] +DATA_DIR = sys.argv[4] +FILE_PATH = sys.argv[5] +LOG_FILE = sys.argv[6] +ANALYZER_PLIST_PATHS = [item.split(",") for item in sys.argv[7].split(";")] def log(msg: str) -> None: @@ -147,7 +145,7 @@ def main(): """ Main function of CodeChecker wrapper """ - if len(sys.argv) != 5: + if len(sys.argv) != 8: print("Wrong amount of arguments") sys.exit(1) _create_compile_commands_json_with_absolute_paths() From f4decaed1340e309d21edb773232f6cd32de6db7 Mon Sep 17 00:00:00 2001 From: "F.Tibor" Date: Fri, 27 Feb 2026 14:27:58 +0100 Subject: [PATCH 3/6] Format environment variables --- src/codechecker.bzl | 33 ++++++++++++++++----------------- src/codechecker_script.py | 22 +++++++++++----------- 2 files changed, 27 insertions(+), 28 deletions(-) diff --git a/src/codechecker.bzl b/src/codechecker.bzl index 59bb918c..2597ad99 100644 --- a/src/codechecker.bzl +++ b/src/codechecker.bzl @@ -100,17 +100,16 @@ def _codechecker_impl(ctx): # Use environment variables instead of expand_template environment_variables = { - "RULES_CODECHECKER_Mode": "Run", - "RULES_CODECHECKER_Verbosity": "DEBUG", - "RULES_CODECHECKER_PythonPath": python_path(ctx), # "/usr/bin/env python3", - "RULES_CODECHECKER_codechecker_bin": CODECHECKER_BIN_PATH, - "RULES_CODECHECKER_compile_commands": ctx.outputs.codechecker_commands.path, - "RULES_CODECHECKER_codechecker_skipfile": ctx.outputs.codechecker_skipfile.path, - "RULES_CODECHECKER_codechecker_config": config_file.path, - "RULES_CODECHECKER_codechecker_analyze": " ".join(ctx.attr.analyze), - "RULES_CODECHECKER_codechecker_files": codechecker_files.path, - "RULES_CODECHECKER_codechecker_log": ctx.outputs.codechecker_log.path, - "RULES_CODECHECKER_codechecker_env": codechecker_env, + "RULES_CODECHECKER_MODE": "Run", + "RULES_CODECHECKER_VERBOSITY": "DEBUG", + "RULES_CODECHECKER_CODECHECKER_BIN": CODECHECKER_BIN_PATH, + "RULES_CODECHECKER_COMPILE_COMMANDS": ctx.outputs.codechecker_commands.path, + "RULES_CODECHECKER_CODECHECKER_SKIPFILE": ctx.outputs.codechecker_skipfile.path, + "RULES_CODECHECKER_CODECHECKER_CONFIG": config_file.path, + "RULES_CODECHECKER_CODECHECKER_ANALYZE": " ".join(ctx.attr.analyze), + "RULES_CODECHECKER_CODECHECKER_FILES": codechecker_files.path, + "RULES_CODECHECKER_CODECHECKER_LOG": ctx.outputs.codechecker_log.path, + "RULES_CODECHECKER_CODECHECKER_ENV": codechecker_env, } codechecker_script = ctx.actions.declare_file(ctx.label.name + "/codechecker_script") ctx.actions.symlink( @@ -230,12 +229,12 @@ def _codechecker_test_impl(ctx): # Use environment variables instead of expand_template environment_variables = { - "RULES_CODECHECKER_Mode": "Test", - "RULES_CODECHECKER_Verbosity": "INFO", - "RULES_CODECHECKER_PythonPath": python_path(ctx), # "/usr/bin/env python3", - "RULES_CODECHECKER_codechecker_bin": CODECHECKER_BIN_PATH, - "RULES_CODECHECKER_codechecker_files": codechecker_files.short_path, - "RULES_CODECHECKER_Severities": " ".join(ctx.attr.severities), + "RULES_CODECHECKER_MODE": "Test", + "RULES_CODECHECKER_VERBOSITY": "INFO", + "RULES_CODECHECKER_PYTHONPATH": python_path(ctx), # "/usr/bin/env python3", + "RULES_CODECHECKER_CODECHECKER_BIN": CODECHECKER_BIN_PATH, + "RULES_CODECHECKER_CODECHECKER_FILES": codechecker_files.short_path, + "RULES_CODECHECKER_SEVERITIES": " ".join(ctx.attr.severities), } # Create test script diff --git a/src/codechecker_script.py b/src/codechecker_script.py index 038aca75..97bb0ffa 100644 --- a/src/codechecker_script.py +++ b/src/codechecker_script.py @@ -26,34 +26,34 @@ import sys -EXECUTION_MODE = os.environ.get("RULES_CODECHECKER_Mode", "{Mode}") -VERBOSITY = os.environ.get("RULES_CODECHECKER_Verbosity", "{Verbosity}") +EXECUTION_MODE = os.environ.get("RULES_CODECHECKER_MODE", "{Mode}") +VERBOSITY = os.environ.get("RULES_CODECHECKER_VERBOSITY", "{Verbosity}") CODECHECKER_PATH = os.environ.get( - "RULES_CODECHECKER_codechecker_bin", "{codechecker_bin}" + "RULES_CODECHECKER_CODECHECKER_BIN", "{codechecker_bin}" ) CODECHECKER_SKIPFILE = os.environ.get( - "RULES_CODECHECKER_codechecker_skipfile", "{codechecker_skipfile}" + "RULES_CODECHECKER_CODECHECKER_SKIPFILE", "{codechecker_skipfile}" ) CODECHECKER_CONFIG = os.environ.get( - "RULES_CODECHECKER_codechecker_config", "{codechecker_config}" + "RULES_CODECHECKER_CODECHECKER_CONFIG", "{codechecker_config}" ) CODECHECKER_ANALYZE = os.environ.get( - "RULES_CODECHECKER_codechecker_analyze", "{codechecker_analyze}" + "RULES_CODECHECKER_CODECHECKER_ANALYZE", "{codechecker_analyze}" ) CODECHECKER_FILES = os.environ.get( - "RULES_CODECHECKER_codechecker_files", "{codechecker_files}" + "RULES_CODECHECKER_CODECHECKER_FILES", "{codechecker_files}" ) CODECHECKER_LOG = os.environ.get( - "RULES_CODECHECKER_codechecker_log", "{codechecker_log}" + "RULES_CODECHECKER_CODECHECKER_LOG", "{codechecker_log}" ) CODECHECKER_SEVERITIES = os.environ.get( - "RULES_CODECHECKER_Severities", "{Severities}" + "RULES_CODECHECKER_SEVERITIES", "{Severities}" ) CODECHECKER_ENV = os.environ.get( - "RULES_CODECHECKER_codechecker_env", "{codechecker_env}" + "RULES_CODECHECKER_CODECHECKER_ENV", "{codechecker_env}" ) COMPILE_COMMANDS = os.environ.get( - "RULES_CODECHECKER_compile_commands", "{compile_commands}" + "RULES_CODECHECKER_COMPILE_COMMANDS", "{compile_commands}" ) START_PATH = r"\/(?:(?!\.\s+)\S)+" From 21bd6493eb92db10898e4efa8c4a5d6a762423de Mon Sep 17 00:00:00 2001 From: "F.Tibor" Date: Mon, 2 Mar 2026 09:26:42 +0100 Subject: [PATCH 4/6] Remove unused function --- src/codechecker.bzl | 2 -- src/common.bzl | 17 ----------------- 2 files changed, 19 deletions(-) diff --git a/src/codechecker.bzl b/src/codechecker.bzl index 2597ad99..33be20ce 100644 --- a/src/codechecker.bzl +++ b/src/codechecker.bzl @@ -27,7 +27,6 @@ load( ) load( "common.bzl", - "python_path", "python_toolchain_type", "version_specific_attributes", ) @@ -231,7 +230,6 @@ def _codechecker_test_impl(ctx): environment_variables = { "RULES_CODECHECKER_MODE": "Test", "RULES_CODECHECKER_VERBOSITY": "INFO", - "RULES_CODECHECKER_PYTHONPATH": python_path(ctx), # "/usr/bin/env python3", "RULES_CODECHECKER_CODECHECKER_BIN": CODECHECKER_BIN_PATH, "RULES_CODECHECKER_CODECHECKER_FILES": codechecker_files.short_path, "RULES_CODECHECKER_SEVERITIES": " ".join(ctx.attr.severities), diff --git a/src/common.bzl b/src/common.bzl index 198b800e..2a7ca856 100644 --- a/src/common.bzl +++ b/src/common.bzl @@ -50,23 +50,6 @@ def python_toolchain_type(): return "@bazel_tools//tools/python:toolchain_type" return "@rules_python//python:toolchain_type" -def python_path(ctx): - """ - Returns version specific Python path - """ - py_toolchain = ctx.toolchains[python_toolchain_type()] - if hasattr(py_toolchain, "py3_runtime_info"): - py_runtime_info = py_toolchain.py3_runtime_info - python_path = py_runtime_info.interpreter - elif hasattr(py_toolchain, "py3_runtime"): - py_runtime = py_toolchain.py3_runtime - python_path = py_runtime.interpreter_path - else: - fail("The resolved Python toolchain does not provide a Python3 runtime.") - if not python_path: - fail("The resolved Python toolchain does not provide a Python3 interpreter.") - return python_path - def warning(ctx, msg): """ Prints message if the debug tag is enabled. From c44eadc7354bdbedb1049a80064d531e4f9054e5 Mon Sep 17 00:00:00 2001 From: "F.Tibor" Date: Thu, 12 Mar 2026 09:51:45 +0100 Subject: [PATCH 5/6] Use arguments instead of environment variables --- src/codechecker.bzl | 50 ++++++++++------ src/codechecker_script.py | 120 ++++++++++++++++++++------------------ 2 files changed, 96 insertions(+), 74 deletions(-) diff --git a/src/codechecker.bzl b/src/codechecker.bzl index 33be20ce..0bbe8e65 100644 --- a/src/codechecker.bzl +++ b/src/codechecker.bzl @@ -96,25 +96,24 @@ def _codechecker_impl(ctx): config_file, codechecker_env = get_config_file(ctx) codechecker_files = ctx.actions.declare_directory(ctx.label.name + "/codechecker-files") - - # Use environment variables instead of expand_template - environment_variables = { - "RULES_CODECHECKER_MODE": "Run", - "RULES_CODECHECKER_VERBOSITY": "DEBUG", - "RULES_CODECHECKER_CODECHECKER_BIN": CODECHECKER_BIN_PATH, - "RULES_CODECHECKER_COMPILE_COMMANDS": ctx.outputs.codechecker_commands.path, - "RULES_CODECHECKER_CODECHECKER_SKIPFILE": ctx.outputs.codechecker_skipfile.path, - "RULES_CODECHECKER_CODECHECKER_CONFIG": config_file.path, - "RULES_CODECHECKER_CODECHECKER_ANALYZE": " ".join(ctx.attr.analyze), - "RULES_CODECHECKER_CODECHECKER_FILES": codechecker_files.path, - "RULES_CODECHECKER_CODECHECKER_LOG": ctx.outputs.codechecker_log.path, - "RULES_CODECHECKER_CODECHECKER_ENV": codechecker_env, - } + codechecker_script = ctx.actions.declare_file(ctx.label.name + "/codechecker_script") ctx.actions.symlink( output = codechecker_script, target_file = ctx.executable._codechecker_script, ) + cmd_args = ctx.actions.args() + cmd_args.add("--mode", "Run") + cmd_args.add("--verbosity", "DEBUG") + cmd_args.add("--codechecker_path", CODECHECKER_BIN_PATH) + cmd_args.add("--commands", ctx.outputs.codechecker_commands.path) + cmd_args.add("--skip", ctx.outputs.codechecker_skipfile.path) + cmd_args.add("--config", config_file.path) + if len(ctx.attr.analyze) != 0: + cmd_args.add("--analyze", "'" + " ".join(ctx.attr.analyze) + "'") + cmd_args.add("--files", codechecker_files.path) + cmd_args.add("--log", ctx.outputs.codechecker_log.path) + cmd_args.add("--env", codechecker_env) ctx.actions.run( inputs = depset( [ @@ -130,8 +129,7 @@ def _codechecker_impl(ctx): ], executable = codechecker_script, tools = [ctx.attr._codechecker_script[DefaultInfo].files_to_run], - arguments = [], - env = environment_variables, + arguments = [cmd_args], mnemonic = "CodeChecker", progress_message = "CodeChecker %s" % str(ctx.label), # use_default_shell_env = True, @@ -242,8 +240,24 @@ def _codechecker_test_impl(ctx): target_file = ctx.executable._codechecker_script, ) + launcher = ctx.actions.declare_file(ctx.label.name + "_launcher.sh") + ctx.actions.write( + output = launcher, + content = """#!/bin/bash + exec {tool} --mode=Test --verbosity=INFO \ + --codechecker_path '{codechecker_path}' \ + --files '{codechecker_files}' --severities '{severities}' + """.format( + tool = ctx.outputs.codechecker_test_script.short_path, + codechecker_path = CODECHECKER_BIN_PATH, + codechecker_files = codechecker_files.short_path, + severities = " ".join(ctx.attr.severities) + ), + is_executable = True, + ) + # 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, launcher] all_runfiles = ctx.runfiles(files = run_files) # Add runfiles from the py_binary target: all_runfiles = all_runfiles.merge(ctx.attr._codechecker_script[DefaultInfo].default_runfiles) @@ -251,7 +265,7 @@ def _codechecker_test_impl(ctx): DefaultInfo( files = depset(all_files), runfiles = all_runfiles, - executable = ctx.outputs.codechecker_test_script, + executable = launcher, ), RunEnvironmentInfo( environment = environment_variables, diff --git a/src/codechecker_script.py b/src/codechecker_script.py index 97bb0ffa..07494b39 100644 --- a/src/codechecker_script.py +++ b/src/codechecker_script.py @@ -24,37 +24,39 @@ import shlex import subprocess import sys +import argparse +parser = argparse.ArgumentParser(description="CodeChecker Bazel Wrapper") -EXECUTION_MODE = os.environ.get("RULES_CODECHECKER_MODE", "{Mode}") -VERBOSITY = os.environ.get("RULES_CODECHECKER_VERBOSITY", "{Verbosity}") -CODECHECKER_PATH = os.environ.get( - "RULES_CODECHECKER_CODECHECKER_BIN", "{codechecker_bin}" +parser.add_argument("--mode", required=True, help="Execution mode") +parser.add_argument("--verbosity", default="INFO", help="Log level") +parser.add_argument( + "--codechecker_path", required=True, help="CodeChecker path" ) -CODECHECKER_SKIPFILE = os.environ.get( - "RULES_CODECHECKER_CODECHECKER_SKIPFILE", "{codechecker_skipfile}" -) -CODECHECKER_CONFIG = os.environ.get( - "RULES_CODECHECKER_CODECHECKER_CONFIG", "{codechecker_config}" -) -CODECHECKER_ANALYZE = os.environ.get( - "RULES_CODECHECKER_CODECHECKER_ANALYZE", "{codechecker_analyze}" -) -CODECHECKER_FILES = os.environ.get( - "RULES_CODECHECKER_CODECHECKER_FILES", "{codechecker_files}" -) -CODECHECKER_LOG = os.environ.get( - "RULES_CODECHECKER_CODECHECKER_LOG", "{codechecker_log}" -) -CODECHECKER_SEVERITIES = os.environ.get( - "RULES_CODECHECKER_SEVERITIES", "{Severities}" -) -CODECHECKER_ENV = os.environ.get( - "RULES_CODECHECKER_CODECHECKER_ENV", "{codechecker_env}" -) -COMPILE_COMMANDS = os.environ.get( - "RULES_CODECHECKER_COMPILE_COMMANDS", "{compile_commands}" +parser.add_argument("--commands", help="Compile commands json") +parser.add_argument("--skip", help="Skipfile path") +parser.add_argument("--config", help="Config file path") +parser.add_argument("--analyze", default="", help="Analysis options") +parser.add_argument( + "--files", help="Folder where CodeChecker will store its results" ) +parser.add_argument("--log", help="Log file path") +parser.add_argument("--env", help="Environment for CodeChecker") +parser.add_argument("--severities", help="List of severities to fail on") + +args = parser.parse_args() + +EXECUTION_MODE = args.mode +VERBOSITY = args.verbosity +CODECHECKER_PATH = args.codechecker_path +COMPILE_COMMANDS = args.commands +CODECHECKER_SKIPFILE = args.skip +CODECHECKER_CONFIG = args.config +CODECHECKER_ANALYZE = args.analyze +CODECHECKER_FILES = args.files +CODECHECKER_LOG = args.log +CODECHECKER_ENV = args.env +CODECHECKER_SEVERITIES = args.severities START_PATH = r"\/(?:(?!\.\s+)\S)+" BAZEL_PATHS = { @@ -65,7 +67,7 @@ def fail(message, exit_code=1): - """ Print error message and return exit code """ + """Print error message and return exit code""" logging.error(message) print() print("*" * 50) @@ -86,7 +88,7 @@ def fail(message, exit_code=1): def read_file(filename): - """ Read text file and return its contents """ + """Read text file and return its contents""" if not os.path.isfile(filename): fail(f"File not found: {filename}") with open(filename, encoding="utf-8") as handle: @@ -94,19 +96,19 @@ def read_file(filename): def separator(method="info"): - """ Print log separator line to logging.info() or other logging methods """ + """Print log separator line to logging.info() or other logging methods""" getattr(logging, method)("#" * 23) def stage(title, method="info"): - """ Print stage title into log """ + """Print stage title into log""" separator(method) getattr(logging, method)("### " + title) separator(method) def valid_parameter(parameter): - """ Check if external parameter is defined and valid """ + """Check if external parameter is defined and valid""" if parameter is None: return False if parameter and parameter[0] == "{": @@ -115,14 +117,14 @@ def valid_parameter(parameter): def log_file_name(): - """ Check and return log file name """ + """Check and return log file name""" if valid_parameter(CODECHECKER_LOG): return CODECHECKER_LOG return None def setup(): - """ Setup logging parameters for execution session """ + """Setup logging parameters for execution session""" if VERBOSITY == "INFO": log_level = logging.INFO elif VERBOSITY == "WARN": @@ -140,7 +142,7 @@ def setup(): def input_data(): - """ Print out input (external) parameters """ + """Print out input (external) parameters""" stage("CodeChecker input data:", "debug") logging.debug("EXECUTION_MODE : %s", str(EXECUTION_MODE)) logging.debug("VERBOSITY : %s", str(VERBOSITY)) @@ -156,7 +158,7 @@ def input_data(): def execute(cmd, env=None, codes=None): - """ Execute CodeChecker commands """ + """Execute CodeChecker commands""" if codes is None: codes = [0] with subprocess.Popen( @@ -178,20 +180,20 @@ def execute(cmd, env=None, codes=None): def create_folder(path): - """ Create folder structure for CodeChecker data files and reports """ + """Create folder structure for CodeChecker data files and reports""" if not os.path.exists(path): os.makedirs(path) def prepare(): - """ Prepare CodeChecker execution environment """ + """Prepare CodeChecker execution environment""" stage("CodeChecker files:") logging.info("Creating folder: %s", CODECHECKER_FILES) create_folder(CODECHECKER_FILES) def analyze(): - """ Run CodeChecker analyze command """ + """Run CodeChecker analyze command""" stage("CodeChecker analyze:") env = os.environ @@ -207,9 +209,11 @@ def analyze(): output = execute(f"{CODECHECKER_PATH} analyzers --details", env=env) logging.debug("Analyzers:\n\n%s", output) - command = f"{CODECHECKER_PATH} analyze --skip={CODECHECKER_SKIPFILE} " \ - f"{COMPILE_COMMANDS} --output={CODECHECKER_FILES}/data " \ - f"--config {CODECHECKER_CONFIG} {CODECHECKER_ANALYZE}" + command = ( + f"{CODECHECKER_PATH} analyze --skip={CODECHECKER_SKIPFILE} " + f"{COMPILE_COMMANDS} --output={CODECHECKER_FILES}/data " + f"--config {CODECHECKER_CONFIG} {CODECHECKER_ANALYZE}" + ) # FIXME: Workaround "CodeChecker simply remove compiler-rt include path". # This can be removed once codechecker 6.16.0 is used. # command += " --keep-gcc-intrin" @@ -222,7 +226,7 @@ def analyze(): def fix_bazel_paths(): - """ Remove Bazel leading paths in all files """ + """Remove Bazel leading paths in all files""" stage("Fix CodeChecker output:") folder = CODECHECKER_FILES logging.info("Fixing Bazel paths in %s", folder) @@ -241,7 +245,7 @@ def fix_bazel_paths(): def realpath(filename): - """ Return real full absolute path for given filename """ + """Return real full absolute path for given filename""" if os.path.exists(filename): real_file_name = os.path.abspath(os.path.realpath(filename)) logging.debug("Updating %s -> %s", filename, real_file_name) @@ -250,7 +254,7 @@ def realpath(filename): def resolve_plist_symlinks(filepath): - """ Resolve the symbolic links in plist files to real file paths """ + """Resolve the symbolic links in plist files to real file paths""" # plistlib replaced readPlist/writePlist with load/dump in Python 3.9. # Since Pylint analyzes every line, # it flags the methods missing in the current environment. @@ -274,7 +278,7 @@ def resolve_plist_symlinks(filepath): def resolve_yaml_symlinks(filepath): - """ Resolve the symbolic links in YAML files to real file paths """ + """Resolve the symbolic links in YAML files to real file paths""" logging.info("Processing YAML file: %s", filepath) fields = [ r"MainSourceFile:\s*", @@ -305,7 +309,7 @@ def resolve_yaml_symlinks(filepath): def resolve_symlinks(): - """ Change ".../execroot/apps" paths to absolute paths in data/* files """ + """Change ".../execroot/apps" paths to absolute paths in data/* files""" stage("Resolve file paths in CodeChecker analyze output:") analyze_outdir = CODECHECKER_FILES + "/data" logging.info( @@ -335,14 +339,18 @@ def update_file_paths(): def parse(): - """ Run CodeChecker parse commands """ + """Run CodeChecker parse commands""" stage("CodeChecker parse:") logging.info("CodeChecker parse -e json") - codechecker_parse = f"{CODECHECKER_PATH} parse --config " \ - f"{CODECHECKER_CONFIG} {CODECHECKER_FILES}/data" + codechecker_parse = ( + f"{CODECHECKER_PATH} parse --config " + f"{CODECHECKER_CONFIG} {CODECHECKER_FILES}/data" + ) # Save results to JSON file - command = f"{codechecker_parse} --export=json > " \ - f"{CODECHECKER_FILES}/result.json" + command = ( + f"{codechecker_parse} --export=json > " + f"{CODECHECKER_FILES}/result.json" + ) execute(command, codes=[0, 2]) # logging.debug( # "JSON:\n\n%s\n", read_file(CODECHECKER_FILES + "/result.json") @@ -366,7 +374,7 @@ def parse(): def run(): - """ Perform all steps for "bazel build" phase """ + """Perform all steps for "bazel build" phase""" prepare() analyze() parse() @@ -374,7 +382,7 @@ def run(): def check_results(): - """ Check/verify CodeChecker results """ + """Check/verify CodeChecker results""" stage("Checking result:") # Get results file and read it result_file = CODECHECKER_FILES + "/result.txt" @@ -421,12 +429,12 @@ def check_results(): def test(): - """ Perform all steps for "bazel test" phase """ + """Perform all steps for "bazel test" phase""" check_results() def main(): - """ Main function """ + """Main function""" setup() input_data() try: From ac2df01cbe8ba9237b510d144fcf015877f557e3 Mon Sep 17 00:00:00 2001 From: "F.Tibor" Date: Thu, 12 Mar 2026 17:23:45 +0100 Subject: [PATCH 6/6] Remove remains of environment variables --- src/codechecker.bzl | 15 ++------------- src/per_file.bzl | 19 ++----------------- 2 files changed, 4 insertions(+), 30 deletions(-) diff --git a/src/codechecker.bzl b/src/codechecker.bzl index 0bbe8e65..cb546828 100644 --- a/src/codechecker.bzl +++ b/src/codechecker.bzl @@ -224,15 +224,6 @@ def _codechecker_test_impl(ctx): if not codechecker_files: fail("Execution results required for codechecker test are not available") - # Use environment variables instead of expand_template - environment_variables = { - "RULES_CODECHECKER_MODE": "Test", - "RULES_CODECHECKER_VERBOSITY": "INFO", - "RULES_CODECHECKER_CODECHECKER_BIN": CODECHECKER_BIN_PATH, - "RULES_CODECHECKER_CODECHECKER_FILES": codechecker_files.short_path, - "RULES_CODECHECKER_SEVERITIES": " ".join(ctx.attr.severities), - } - # Create test script codechecker_test_script = ctx.actions.declare_file(ctx.label.name + "/codechecker_test_script") ctx.actions.symlink( @@ -251,7 +242,7 @@ def _codechecker_test_impl(ctx): tool = ctx.outputs.codechecker_test_script.short_path, codechecker_path = CODECHECKER_BIN_PATH, codechecker_files = codechecker_files.short_path, - severities = " ".join(ctx.attr.severities) + severities = " ".join(ctx.attr.severities), ), is_executable = True, ) @@ -259,6 +250,7 @@ def _codechecker_test_impl(ctx): # Return test script and all required files run_files = default_runfiles + [ctx.outputs.codechecker_test_script, launcher] all_runfiles = ctx.runfiles(files = run_files) + # Add runfiles from the py_binary target: all_runfiles = all_runfiles.merge(ctx.attr._codechecker_script[DefaultInfo].default_runfiles) return [ @@ -267,9 +259,6 @@ def _codechecker_test_impl(ctx): runfiles = all_runfiles, executable = launcher, ), - RunEnvironmentInfo( - environment = environment_variables, - ) ] _codechecker_test = rule( diff --git a/src/per_file.bzl b/src/per_file.bzl index cccc69a1..16334136 100644 --- a/src/per_file.bzl +++ b/src/per_file.bzl @@ -75,7 +75,7 @@ def _run_code_checker( executable = per_file_script, arguments = [ compile_commands_json.path, - ' '.join(options), + " ".join(options), config_file.path, data_dir, src.path, @@ -124,21 +124,6 @@ def _collect_all_sources_and_headers(ctx): all_files += headers return all_files -def _update_env_vars( - ctx, - options, - compile_commands_json, - config_file, - env_vars): - options_str = " ".join(options) - if not env_vars: - env_vars = {} - return (env_vars | { - "RULES_CODECHECKER_COMPILE_COMMANDS_JSON": compile_commands_json.path, - "RULES_CODECHECKER_CODECHECKER_ARGS": options_str, - "RULES_CODECHECKER_CONFIG_FILE": config_file.path, - }) - def _per_file_impl(ctx): compile_commands = None for output in compile_commands_impl(ctx): @@ -152,7 +137,7 @@ def _per_file_impl(ctx): options = ctx.attr.default_options + ctx.attr.options all_files = [compile_commands] config_file, env_vars = get_config_file(ctx) - env_vars =_update_env_vars(ctx, options, compile_commands, config_file, env_vars) + # Create per_file_script per_file_script = ctx.actions.declare_file(ctx.label.name + "/per_file_script") ctx.actions.symlink(