diff --git a/src/codechecker.bzl b/src/codechecker.bzl index a3ebd009..af569c76 100644 --- a/src/codechecker.bzl +++ b/src/codechecker.bzl @@ -18,6 +18,10 @@ load( "tools.bzl", "warning" ) +load( + "@bazel_codechecker//src:codechecker_config.bzl", + "get_config_file" +) def get_platform_alias(platform): """ @@ -33,61 +37,6 @@ def get_platform_alias(platform): platform = shortname return platform -CodeCheckerConfigInfo = provider( - doc = "Defines CodeChecker configuration", - fields = { - "analyze": "List of arguments for CodeChecker analyze command", - "parse": "List of arguments for CodeChecker parse command", - "config_file": "CodeChecker configuration file in JSON format", - "env": "Environment variables for CodeChecker", - }, -) - -def _codechecker_config_impl(ctx): - return [ - CodeCheckerConfigInfo( - analyze = ctx.attr.analyze, - parse = ctx.attr.parse, - config_file = ctx.attr.config_file, - env = ctx.attr.env, - ), - ] - -codechecker_config = rule( - implementation = _codechecker_config_impl, - attrs = { - "analyze": attr.string_list( - default = [], - doc = "List of arguments for CodeChecker analyze command", - ), - "parse": attr.string_list( - default = [], - doc = "List of arguments for CodeChecker parse command", - ), - "config_file": attr.label( - default = None, - allow_single_file = True, - ), - "env": attr.string_list( - default = [], - doc = "List of environment variables for CodeChecker", - ), - }, -) - -def _copy_config_to_default(config_file, ctx): - ctx.actions.run( - inputs = [config_file], - outputs = [ctx.outputs.codechecker_config], - mnemonic = "CopyFile", - progress_message = "Copying CodeChecker config file", - executable = "cp", - arguments = [ - config_file.path, - ctx.outputs.codechecker_config.path, - ], - ) - def _codechecker_impl(ctx): py_runtime_info = ctx.attr._python_runtime[PyRuntimeInfo] python_path = py_runtime_info.interpreter_path @@ -129,43 +78,7 @@ def _codechecker_impl(ctx): is_executable = False, ) - # Create CodeChecker JSON config file and env vars - if ctx.attr.config: - if type(ctx.attr.config) == "list": - config_info = ctx.attr.config[0][CodeCheckerConfigInfo] - else: - config_info = ctx.attr.config[CodeCheckerConfigInfo] - if config_info.config_file: - # Create a copy of CodeChecker configuration file - # provided via codechecker_config(config_file) - config_file = config_info.config_file.files.to_list()[0] - _copy_config_to_default(config_file, ctx) - else: - # Create CodeChecker configuration file in JSON format - # from Bazel codechecker_config(analyze, parse) - config_json = {} - if config_info.analyze: - config_json["analyze"] = config_info.analyze - if config_info.parse: - config_json["parse"] = config_info.parse - config_content = json.encode_indent(config_json) - ctx.actions.write( - output = ctx.outputs.codechecker_config, - content = config_content, - is_executable = False, - ) - - # Pack env vars for CodeChecker - codechecker_env = "; ".join(config_info.env) - else: - # Empty CodeChecker JSON config file - ctx.actions.write( - output = ctx.outputs.codechecker_config, - content = "{}", - is_executable = False, - ) - codechecker_env = "" - + 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, @@ -178,7 +91,7 @@ def _codechecker_impl(ctx): "{codechecker_bin}": CODECHECKER_BIN_PATH, "{compile_commands}": ctx.outputs.codechecker_commands.path, "{codechecker_skipfile}": ctx.outputs.codechecker_skipfile.path, - "{codechecker_config}": ctx.outputs.codechecker_config.path, + "{codechecker_config}": ctx_config_file.path, "{codechecker_analyze}": " ".join(ctx.attr.analyze), "{codechecker_files}": codechecker_files.path, "{codechecker_log}": ctx.outputs.codechecker_log.path, @@ -192,7 +105,7 @@ def _codechecker_impl(ctx): ctx.outputs.codechecker_script, ctx.outputs.codechecker_commands, ctx.outputs.codechecker_skipfile, - ctx.outputs.codechecker_config, + ctx_config_file, ] + source_files, ), outputs = [ @@ -211,7 +124,7 @@ def _codechecker_impl(ctx): ctx.outputs.compile_commands, ctx.outputs.codechecker_commands, ctx.outputs.codechecker_skipfile, - ctx.outputs.codechecker_config, + ctx_config_file, codechecker_files, ctx.outputs.codechecker_script, ctx.outputs.codechecker_log, @@ -273,7 +186,6 @@ codechecker = rule( "compile_commands": "%{name}/compile_commands.json", "codechecker_commands": "%{name}/codechecker_commands.json", "codechecker_skipfile": "%{name}/codechecker_skipfile.cfg", - "codechecker_config": "%{name}/codechecker_config.json", "codechecker_script": "%{name}/codechecker_script.py", "codechecker_log": "%{name}/codechecker.log", }, @@ -378,7 +290,6 @@ _codechecker_test = rule( "compile_commands": "%{name}/compile_commands.json", "codechecker_commands": "%{name}/codechecker_commands.json", "codechecker_skipfile": "%{name}/codechecker_skipfile.cfg", - "codechecker_config": "%{name}/codechecker_config.json", "codechecker_script": "%{name}/codechecker_script.py", "codechecker_log": "%{name}/codechecker.log", "codechecker_test_script": "%{name}/codechecker_test_script.py", @@ -406,6 +317,7 @@ def codechecker_test( name = name, targets = targets, options = analyze, + config = config, tags = tags, **kwargs ) diff --git a/src/codechecker_config.bzl b/src/codechecker_config.bzl new file mode 100644 index 00000000..d9c876de --- /dev/null +++ b/src/codechecker_config.bzl @@ -0,0 +1,124 @@ +# Copyright 2023 Ericsson AB +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# 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. + +def get_config_file(ctx): + """ + Returns (config_file, environment_variables) + config_file is a file object that is readable during Codechecker execution + """ + + # Decide whether to use json or yaml for configuration + config_file_name = ctx.attr.name + "/config.json" + if ctx.attr.config: + if type(ctx.attr.config) == "list": + config_info = ctx.attr.config[0][CodeCheckerConfigInfo] + else: + config_info = ctx.attr.config[CodeCheckerConfigInfo] + if config_info.config_file: + # Create a copy of CodeChecker configuration file + # provided via codechecker_config(config_file) + config_file = config_info.config_file.files.to_list()[0] + config_file_name = ctx.attr.name + \ + "/config." + config_file.extension + ctx_config_file = ctx.actions.declare_file(config_file_name) + + # Create CodeChecker JSON config file and env vars + if ctx.attr.config: + if type(ctx.attr.config) == "list": + config_info = ctx.attr.config[0][CodeCheckerConfigInfo] + else: + config_info = ctx.attr.config[CodeCheckerConfigInfo] + if config_info.config_file: + # Create a copy of CodeChecker configuration file + # provided via codechecker_config(config_file) + config_file = config_info.config_file.files.to_list()[0] + ctx.actions.run( + inputs = [config_file], + outputs = [ctx_config_file], + mnemonic = "CopyFile", + progress_message = "Copying CodeChecker config file", + executable = "cp", + arguments = [ + config_file.path, + ctx_config_file.path, + ], + ) + else: + # Create CodeChecker configuration file in JSON format + # from Bazel codechecker_config(analyze, parse) + config_json = {} + if config_info.analyze: + config_json["analyze"] = config_info.analyze + if config_info.parse: + config_json["parse"] = config_info.parse + config_content = json.encode_indent(config_json) + ctx.actions.write( + output = ctx_config_file, + content = config_content, + is_executable = False, + ) + + # Pack env vars for CodeChecker + codechecker_env = "; ".join(config_info.env) + else: + # Empty CodeChecker JSON config file + ctx.actions.write( + output = ctx_config_file, + content = "{}", + is_executable = False, + ) + codechecker_env = "" + return (ctx_config_file, codechecker_env) + +CodeCheckerConfigInfo = provider( + doc = "Defines CodeChecker configuration", + fields = { + "analyze": "List of arguments for CodeChecker analyze command", + "parse": "List of arguments for CodeChecker parse command", + "config_file": "CodeChecker configuration file in JSON format", + "env": "Environment variables for CodeChecker", + }, +) + +def _codechecker_config_impl(ctx): + return [ + CodeCheckerConfigInfo( + analyze = ctx.attr.analyze, + parse = ctx.attr.parse, + config_file = ctx.attr.config_file, + env = ctx.attr.env, + ), + ] + +codechecker_config = rule( + implementation = _codechecker_config_impl, + attrs = { + "analyze": attr.string_list( + default = [], + doc = "List of arguments for CodeChecker analyze command", + ), + "parse": attr.string_list( + default = [], + doc = "List of arguments for CodeChecker parse command", + ), + "config_file": attr.label( + default = None, + allow_single_file = True, + ), + "env": attr.string_list( + default = [], + doc = "List of environment variables for CodeChecker", + ), + }, +) diff --git a/src/per_file.bzl b/src/per_file.bzl index 22d84884..170cd709 100644 --- a/src/per_file.bzl +++ b/src/per_file.bzl @@ -4,6 +4,14 @@ load("@bazel_tools//tools/build_defs/cc:action_names.bzl", "ACTION_NAMES") load("@bazel_tools//tools/cpp:toolchain_utils.bzl", "find_cpp_toolchain") load("@bazel_codechecker//src:tools.bzl", "warning") +load( + "compile_commands.bzl", + "platforms_transition", +) +load( + "@bazel_codechecker//src:codechecker_config.bzl", + "get_config_file" +) def _run_code_checker( ctx, @@ -11,6 +19,8 @@ def _run_code_checker( arguments, label, options, + config_file, + env_vars, compile_commands_json, compilation_context, sources_and_headers): @@ -27,11 +37,12 @@ def _run_code_checker( codechecker_log = ctx.actions.declare_file(codechecker_log_file_name) if "--ctu" in options: - inputs = [compile_commands_json] + sources_and_headers + 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]) - inputs = depset([compile_commands_json, src], transitive = [headers]) + inputs = depset([compile_commands_json, config_file, src], + transitive = [headers]) outputs = [clang_tidy_plist, clangsa_plist, codechecker_log] @@ -261,7 +272,7 @@ def _collect_all_sources_and_headers(ctx): sources_and_headers = all_files + headers.to_list() return sources_and_headers -def _create_wrapper_script(ctx, options, compile_commands_json): +def _create_wrapper_script(ctx, options, compile_commands_json, config_file): options_str = "" for item in options: options_str += item + " " @@ -273,6 +284,7 @@ def _create_wrapper_script(ctx, options, compile_commands_json): "{PythonPath}": ctx.attr._python_runtime[PyRuntimeInfo].interpreter_path, "{compile_commands_json}": compile_commands_json.path, "{codechecker_args}": options_str, + "{config_file}": config_file.path, }, ) @@ -281,7 +293,8 @@ def _per_file_impl(ctx): sources_and_headers = _collect_all_sources_and_headers(ctx) options = ctx.attr.default_options + ctx.attr.options all_files = [compile_commands_json] - _create_wrapper_script(ctx, options, compile_commands_json) + config_file, env_vars = get_config_file(ctx) + _create_wrapper_script(ctx, options, compile_commands_json, config_file) for target in ctx.attr.targets: if not CcInfo in target: continue @@ -298,6 +311,8 @@ def _per_file_impl(ctx): args, ctx.attr.name, options, + config_file, + env_vars, compile_commands_json, compilation_context, sources_and_headers, @@ -347,6 +362,19 @@ per_file_test = rule( ], doc = "List of compilable targets which should be checked.", ), + "_whitelist_function_transition": attr.label( + default = "@bazel_tools//tools/whitelists/function_transition_whitelist", + doc = "needed for transitions", + ), + "config": attr.label( + default = None, + cfg = platforms_transition, + doc = "CodeChecker configuration", + ), + "platform": attr.string( + default = "", #"@platforms//os:linux", + doc = "Platform to build for", + ), "_per_file_script_template": attr.label( default = ":per_file_script.py", allow_single_file = True, @@ -366,12 +394,14 @@ per_file_test = rule( def code_checker_test( name, targets, + config = None, options = [], tags = [], ): per_file_test( name = name, options = options, + config = config, targets = targets, tags = tags, ) diff --git a/src/per_file_script.py b/src/per_file_script.py index 46bd13b4..75f7995f 100644 --- a/src/per_file_script.py +++ b/src/per_file_script.py @@ -32,6 +32,7 @@ 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}" def parse_args(): @@ -82,10 +83,15 @@ def _run_codechecker() -> None: """ Runs CodeChecker analyze """ - log( - f"CodeChecker command: CodeChecker analyze {CODECHECKER_ARGS} \ -{COMPILE_COMMANDS_ABSOLUTE} --output={DATA_DIR} --file=*/{FILE_PATH}\n" + codechecker_cmd: list[str] = ( + ["CodeChecker", "analyze"] + + CODECHECKER_ARGS.split() + + ["--output=" + DATA_DIR] # type: ignore + + ["--file=*/" + FILE_PATH] # type: ignore + + ["--config=" + CONFIG_FILE] + + [COMPILE_COMMANDS_ABSOLUTE] ) + log(f"CodeChecker command: {' '.join(codechecker_cmd)}\n") log("===-----------------------------------------------------===\n") log(" CodeChecker error log \n") log("===-----------------------------------------------------===\n") @@ -99,14 +105,6 @@ def _run_codechecker() -> None: ) log(result.stdout) - codechecker_cmd: list[str] = ( - ["CodeChecker", "analyze"] - + CODECHECKER_ARGS.split() - + ["--output=" + DATA_DIR] # type: ignore - + ["--file=*/" + FILE_PATH] # type: ignore - + [COMPILE_COMMANDS_ABSOLUTE] - ) - try: with open(LOG_FILE, "a") as log_file: # type: ignore subprocess.run( diff --git a/test/unit/legacy/BUILD b/test/unit/legacy/BUILD index 9488855d..924db3c3 100644 --- a/test/unit/legacy/BUILD +++ b/test/unit/legacy/BUILD @@ -15,11 +15,15 @@ load( load( "@bazel_codechecker//src:codechecker.bzl", "codechecker", - "codechecker_config", "codechecker_suite", "codechecker_test", ) +load( + "@bazel_codechecker//src:codechecker_config.bzl", + "codechecker_config", +) + # clang-tidy and clang -analyze rules load( "@bazel_codechecker//src:clang.bzl",