diff --git a/MODULE.bazel b/MODULE.bazel index 13e4a60a..fde5f260 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -35,6 +35,6 @@ single_version_override( codechecker_extension = use_extension( "//src:tools.bzl", - "module_register_default_codechecker", + "module_register_default_codechecker_tools", ) use_repo(codechecker_extension, "default_codechecker_tools") diff --git a/WORKSPACE b/WORKSPACE index 53f2e0a3..6be16fee 100644 --- a/WORKSPACE +++ b/WORKSPACE @@ -16,7 +16,7 @@ workspace(name = "rules_codechecker") load( "@rules_codechecker//src:tools.bzl", - "register_default_codechecker", + "register_default_codechecker_tools", "register_default_python_toolchain", ) @@ -24,7 +24,7 @@ register_default_python_toolchain() register_toolchains("@default_python_tools//:python_toolchain") -register_default_codechecker() +register_default_codechecker_tools() # Dev dependencies diff --git a/src/codechecker.bzl b/src/codechecker.bzl index bb428ff3..2c5d0943 100644 --- a/src/codechecker.bzl +++ b/src/codechecker.bzl @@ -18,6 +18,8 @@ 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( @@ -105,6 +107,8 @@ def _codechecker_impl(ctx): substitutions = { "{Mode}": "Run", "{Verbosity}": "DEBUG", + "{clang_bin}": CLANG_BIN_PATH, + "{clang_tidy_bin}": CLANG_TIDY_BIN_PATH, "{codechecker_analyze}": " ".join(ctx.attr.analyze), "{codechecker_bin}": CODECHECKER_BIN_PATH, "{codechecker_config}": config_file.path, @@ -234,6 +238,8 @@ 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, "{codechecker_files}": codechecker_files.short_path, }, diff --git a/src/codechecker_script.py b/src/codechecker_script.py index ce63f26b..5854ec2d 100644 --- a/src/codechecker_script.py +++ b/src/codechecker_script.py @@ -27,10 +27,11 @@ import subprocess import sys - EXECUTION_MODE = "{Mode}" VERBOSITY = "{Verbosity}" CODECHECKER_PATH = "{codechecker_bin}" +CLANG_PATH = "{clang_bin}" +CLANG_TIDY_PATH = "{clang_tidy_bin}" CODECHECKER_SKIPFILE = "{codechecker_skipfile}" CODECHECKER_CONFIG = "{codechecker_config}" CODECHECKER_ANALYZE = "{codechecker_analyze}" @@ -49,7 +50,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) @@ -70,7 +71,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: @@ -78,19 +79,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] == "{": @@ -99,14 +100,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": @@ -124,7 +125,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)) @@ -140,7 +141,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( @@ -162,20 +163,28 @@ 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 generate_analyzer_executables(): + """ + Generates the value for the CC_ANALYZER_BIN environment variable + """ + analyzer_executables = f"clangsa:{CLANG_PATH};clang-tidy:{CLANG_TIDY_PATH}" + return analyzer_executables + + def analyze(): - """ Run CodeChecker analyze command """ + """Run CodeChecker analyze command""" stage("CodeChecker analyze:") env = os.environ @@ -186,14 +195,17 @@ def analyze(): env.update(codechecker_env) if "PATH" not in env: env["PATH"] = "/bin" # NOTE: this is workaround for CodeChecker 6.24.4 + env["CC_ANALYZER_BIN"] = generate_analyzer_executables() logging.debug("env: %s", str(env)) 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" @@ -205,7 +217,7 @@ def analyze(): fail("Make sure that the target can be built first") -def fix_path_with_regex(data:str) -> str: +def fix_path_with_regex(data: str) -> str: """ The absolute paths of the analyzed source files found in the plist files do not point to their original location, but rather wherever bazel copied @@ -218,8 +230,9 @@ def fix_path_with_regex(data:str) -> str: data = re.sub(pattern, replace, data) return data + 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) @@ -236,7 +249,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) @@ -245,7 +258,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. @@ -269,7 +282,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*", @@ -300,7 +313,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( @@ -330,14 +343,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") @@ -361,7 +378,7 @@ def parse(): def run(): - """ Perform all steps for "bazel build" phase """ + """Perform all steps for "bazel build" phase""" prepare() analyze() parse() @@ -369,7 +386,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" @@ -416,12 +433,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: diff --git a/src/tools.bzl b/src/tools.bzl index 769cf43a..f15d4fd1 100644 --- a/src/tools.bzl +++ b/src/tools.bzl @@ -106,8 +106,16 @@ def _codechecker_local_repository_impl(repository_ctx): codechecker_bin_path = repository_ctx.which("CodeChecker") if not codechecker_bin_path: fail("ERROR! CodeChecker is not detected") + clang_bin_path = repository_ctx.which("clang") + if not clang_bin_path: + fail("ERROR! Clang is not detected") + clang_tidy_bin_path = repository_ctx.which("clang-tidy") + if not clang_tidy_bin_path: + fail("ERROR! Clang-tidy is not detected") defs = "CODECHECKER_BIN_PATH = '{}'\n".format(codechecker_bin_path) + defs += "CLANG_BIN_PATH = '{}'\n".format(clang_bin_path) + defs += "CLANG_TIDY_BIN_PATH = '{}'\n".format(clang_tidy_bin_path) defs += "BAZEL_VERSION = '{}'\n".format(native.bazel_version) repository_ctx.file( repository_ctx.path("defs.bzl"), @@ -124,10 +132,10 @@ default_codechecker_tools = repository_rule( # buildifier: disable=unused-variable # This parameter is provided, regardless if we use it or not -def register_default_codechecker(ctx = None): +def register_default_codechecker_tools(ctx = None): default_codechecker_tools(name = "default_codechecker_tools") # Define the extension here -module_register_default_codechecker = module_extension( - implementation = register_default_codechecker, +module_register_default_codechecker_tools = module_extension( + implementation = register_default_codechecker_tools, ) diff --git a/test/foss/templates/WORKSPACE.template b/test/foss/templates/WORKSPACE.template index f2527511..82d71ce8 100644 --- a/test/foss/templates/WORKSPACE.template +++ b/test/foss/templates/WORKSPACE.template @@ -9,13 +9,13 @@ local_repository( load( "@rules_codechecker//src:tools.bzl", - "register_default_codechecker", + "register_default_codechecker_tools", "register_default_python_toolchain", ) register_default_python_toolchain() register_toolchains("@default_python_tools//:python_toolchain") -register_default_codechecker() +register_default_codechecker_tools() #----------------------------------------------------