diff --git a/src/BUILD b/src/BUILD index 5670ffd8..6b392f71 100644 --- a/src/BUILD +++ b/src/BUILD @@ -23,11 +23,21 @@ py_binary( visibility = ["//visibility:public"], ) +# In bazel 6 we use our own python toolchain, +# therefore we cannot load from rules_python. +# buildifier: disable=native-py +py_binary( + name = "per_file_script", + srcs = ["per_file_script.py"], + main = "per_file_script.py", + # Bazel 6 doesn't see this otherwise + visibility = ["//visibility:public"], +) + # Build & Test script template exports_files( [ "codechecker_script.py", - "per_file_script.py", ], ) diff --git a/src/per_file.bzl b/src/per_file.bzl index 7185d9fc..2367a8ff 100644 --- a/src/per_file.bzl +++ b/src/per_file.bzl @@ -37,7 +37,9 @@ def _run_code_checker( env_vars, compile_commands_json, compilation_context, - sources_and_headers): + sources_and_headers, + skipfile, + bazel_constants): # Define Plist and log file names data_dir = ctx.attr.name + "/data" file_name_params = (data_dir, src.path.replace("/", "-")) @@ -50,38 +52,25 @@ def _run_code_checker( clangsa_plist = ctx.actions.declare_file(clangsa_plist_file_name) codechecker_log = ctx.actions.declare_file(codechecker_log_file_name) - # Create skipfile - config = ctx.actions.declare_file( - "{}/{}_skipfile".format(*file_name_params), - ) - ctx.actions.write( - output = config, - content = "\n".join(ctx.attr.skip), - ) - # TODO: Consider using aliases so we don't have to type //src: everywhere. info = ctx.toolchains["//src:toolchain_type"].codecheckerinfo + mutual_inputs = [ + compile_commands_json, + config_file, + skipfile, + info.codechecker, + info.clang_tidy, + info.clangsa, + bazel_constants, + ] if "--ctu" in options: - inputs = [ - compile_commands_json, - config_file, - config, - info.codechecker, - info.clangsa, - info.clang_tidy, - ] + sources_and_headers + inputs = mutual_inputs + sources_and_headers else: # NOTE: we collect only headers, so CTU may not work! headers = depset(transitive = target[SourceFilesInfo].headers.to_list()) - inputs = depset([ - compile_commands_json, - config_file, + inputs = depset(mutual_inputs + [ src, - config, - info.codechecker, - info.clangsa, - info.clang_tidy, ], transitive = [headers]) outputs = [clang_tidy_plist, clangsa_plist, codechecker_log] @@ -96,13 +85,12 @@ def _run_code_checker( ctx.actions.run( inputs = inputs, outputs = outputs, - executable = ctx.outputs.per_file_script, + executable = ctx.executable._per_file_script, arguments = [ - info.codechecker.path, + bazel_constants.path, data_dir, src.path, codechecker_log.path, - config.path, analyzer_output_paths, analyzer_executables, ], @@ -153,20 +141,43 @@ def _collect_all_sources_and_headers(ctx): all_files += headers return all_files -def _create_wrapper_script(ctx, options, compile_commands_json, config_file): +def _create_constants_file( + ctx, + options, + compile_commands_json, + config_file, + skipfile): 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 = { - "{codechecker_args}": options_str, - "{compile_commands_json}": compile_commands_json.path, - "{config_file}": config_file.path, - }, + + # Define variables to use in a file + bazel_constants = ctx.actions.declare_file( + "{}/bazel_constants.json".format(ctx.attr.name), + ) + info = ctx.toolchains["//src:toolchain_type"].codecheckerinfo + ctx.actions.write( + output = bazel_constants, + content = json.encode_indent({ + "codechecker_bin": info.codechecker.path, + "codechecker_args": options_str, + "compile_commands_json": compile_commands_json.path, + "config_file": config_file.path, + "skipfile": skipfile.path, + }), + ) + return bazel_constants + +def _create_skipfile(ctx): + # Create skipfile + skipfile = ctx.actions.declare_file( + "{}/skipfile".format(ctx.attr.name), ) + ctx.actions.write( + output = skipfile, + content = "\n".join(ctx.attr.skip), + ) + return skipfile def _per_file_impl(ctx): compile_commands = None @@ -181,7 +192,14 @@ 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) + skipfile = _create_skipfile(ctx) + bazel_constants = _create_constants_file( + ctx, + options, + compile_commands, + config_file, + skipfile, + ) for target in ctx.attr.targets: if not CcInfo in target: continue @@ -206,6 +224,8 @@ def _per_file_impl(ctx): compile_commands, compilation_context, sources_and_headers, + skipfile, + bazel_constants, ) all_files += outputs ctx.actions.write( @@ -261,14 +281,14 @@ per_file_test = rule( ], doc = "List of compilable targets which should be checked.", ), - "_per_file_script_template": attr.label( - default = ":per_file_script.py", - allow_single_file = True, + "_per_file_script": attr.label( + default = ":per_file_script", + executable = True, + cfg = "exec", ), }, outputs = { "compile_commands": "%{name}/compile_commands.json", - "per_file_script": "%{name}/per_file_script.py", "test_script": "%{name}/test_script.sh", }, test = True, diff --git a/src/per_file_script.py b/src/per_file_script.py index dc5a1476..f0fe333e 100644 --- a/src/per_file_script.py +++ b/src/per_file_script.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python3 - # Copyright 2023 Ericsson AB # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -23,24 +21,23 @@ import shutil import subprocess import sys +import json +from pathlib import Path -COMPILE_COMMANDS_JSON: str = "{compile_commands_json}" -COMPILE_COMMANDS_ABSOLUTE: str = f"{COMPILE_COMMANDS_JSON}.abs" -CODECHECKER_ARGS: str = "{codechecker_args}" -CONFIG_FILE: str = "{config_file}" -SKIP_FILE: str = sys.argv[5] -CODECHECKER_BIN = os.path.realpath(sys.argv[1]) +CONSTANTS = json.loads(Path(sys.argv[1]).read_text(encoding="utf-8")) +COMPILE_COMMANDS_ABSOLUTE: str = f'{CONSTANTS["compile_commands_json"]}.abs' +CODECHECKER_BIN = os.path.realpath(CONSTANTS["codechecker_bin"]) # The output directory for CodeChecker DATA_DIR = sys.argv[2] # The file to be analyzed 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[6].split(";")] +ANALYZER_PLIST_PATHS = [item.split(",") for item in sys.argv[5].split(";")] 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 + pair.split(":", 1) for pair in sys.argv[6].split(";") if pair ] ) @@ -76,7 +73,7 @@ def _create_compile_commands_json_with_absolute_paths(): of the files. """ with open( - COMPILE_COMMANDS_JSON, "r", encoding="utf-8" + CONSTANTS["compile_commands_json"], "r", encoding="utf-8" ) as original_file, open( COMPILE_COMMANDS_ABSOLUTE, "w", encoding="utf-8" ) as new_file: @@ -96,6 +93,7 @@ def _get_codechecker_env() -> dict[str, str]: cc_env = os.environ.copy() # Overwrite analyzer paths cc_env["CC_ANALYZER_BIN"] = ANALYZER_EXECUTABLES_ENV_VAR + print(ANALYZER_EXECUTABLES_ENV_VAR) return cc_env @@ -105,11 +103,11 @@ def _run_codechecker() -> None: """ codechecker_cmd: list[str] = ( [CODECHECKER_BIN, "analyze"] - + CODECHECKER_ARGS.split() + + CONSTANTS["codechecker_args"].split() + ["--output=" + DATA_DIR] + ["--file=*/" + FILE_PATH] - + ["--skip", SKIP_FILE] - + ["--config", CONFIG_FILE] + + ["--skip", CONSTANTS["skipfile"]] + + ["--config", CONSTANTS["config_file"]] + [COMPILE_COMMANDS_ABSOLUTE] ) log(f"CodeChecker command: {' '.join(codechecker_cmd)}\n") @@ -191,7 +189,7 @@ def main(): """ Main function of CodeChecker wrapper """ - if len(sys.argv) != 8: + if len(sys.argv) != 7: print("Wrong amount of arguments") sys.exit(1) _create_compile_commands_json_with_absolute_paths()