diff --git a/docs/codechecker.md b/docs/codechecker.md index 7f4edaa4..5b6ce3f5 100644 --- a/docs/codechecker.md +++ b/docs/codechecker.md @@ -77,15 +77,19 @@ bazel test ... ### Analysis results -You can find the analysis results in the `bazel-bin/` folder, on which you -can run [`CodeChecker store`](https://github.com/Ericsson/codechecker/blob/master/docs/web/user_guide.md#store) -or [`CodeChecker parse`](https://github.com/Ericsson/codechecker/blob/master/docs/analyzer/user_guide.md#parse). -The precise output path to the directory can vary, -but you should look for `your_codechecker_rule_name/codechecker-files/data`. -In simpler cases, something like the following: +Analysis results are written to the bazel-bin/ directory. +The exact output path may vary, but you should find them under: +`bazel-bin/.../your_codechecker_rule_name/codechecker-files/`. +This directory contains: +- result.txt — human-readable results +- result.json — results in structured JSON format +- report/ — results in HTML format +- data/ — the directory containing the raw results + +You can store the results with [`CodeChecker store`](https://github.com/Ericsson/codechecker/blob/master/docs/web/user_guide.md#store). +In simpler cases, with something like the following: ```bash -CodeChecker parse bazel-bin/your_codechecker_rule_name/codechecker-files/data CodeChecker store bazel-bin/your_codechecker_rule_name/codechecker-files/data -n "Run name" ``` diff --git a/src/codechecker.bzl b/src/codechecker.bzl index e4fd8909..3ea88a40 100644 --- a/src/codechecker.bzl +++ b/src/codechecker.bzl @@ -372,6 +372,7 @@ def codechecker_test( options = analyze, skip = skip, config = config, + severities = severities, toolchain = toolchain, tags = codechecker_tags, **kwargs diff --git a/src/codechecker_script.py b/src/codechecker_script.py index 95154335..e37603df 100644 --- a/src/codechecker_script.py +++ b/src/codechecker_script.py @@ -246,7 +246,6 @@ def run(args): analyze(args) parse( input_dir=args.output, - output_dir=args.output, codechecker=args.codechecker, config=args.config, env=args.env, diff --git a/src/common.py b/src/common.py index 25bf028f..76977378 100644 --- a/src/common.py +++ b/src/common.py @@ -136,14 +136,13 @@ def stage(title, method="info"): # pylint: disable=too-many-arguments,too-many-positional-arguments def parse( - input_dir, output_dir, codechecker, config, env, log, clang, clang_tidy + input_dir, codechecker, config, env, log, clang, clang_tidy ): """ Run CodeChecker parse commands Args: input_dir (str): Path to the input directory - output_dir (str): Path to the output directory codechecker (str): Path to the CodeChecker binary config (str): Path to the CodeChecker configuration file env (list): Environment variables @@ -160,18 +159,18 @@ def parse( ) # Save results to JSON file command = ( - f"{codechecker_parse} --export=json > " f"{output_dir}/result.json" + f"{codechecker_parse} --export=json > " f"{input_dir}/result.json" ) execute(log, command, env=env, codes=[0, 2]) # Save results as HTML report logging.info("CodeChecker parse -e html") command = ( - codechecker_parse + " --export=html --output=" + output_dir + "/report" + codechecker_parse + " --export=html --output=" + input_dir + "/report" ) execute(log, command, env=env, codes=[0, 2]) # Save results to text file logging.info("CodeChecker parse to text result") - result_file = output_dir + "/result.txt" + result_file = input_dir + "/result.txt" command = codechecker_parse + " > " + result_file execute(log, command, env=env, codes=[0, 2]) logging.info("Result:\n\n%s\n", read_file(log, result_file)) diff --git a/src/per_file.bzl b/src/per_file.bzl index b1c0db57..36da36d2 100644 --- a/src/per_file.bzl +++ b/src/per_file.bzl @@ -207,6 +207,7 @@ def _per_file_impl(ctx): info = ctx.attr.toolchain[platform_common.ToolchainInfo].codecheckerinfo else: info = ctx.toolchains["//:toolchain_type"].codecheckerinfo + for target in ctx.attr.targets: if not CcInfo in target: continue @@ -235,29 +236,82 @@ def _per_file_impl(ctx): sources_and_headers, ) all_files += outputs + + # Parse action: collect all plists into a directory and run + # CodeChecker parse to produce result.txt, result.json, HTML report + html_parse_dir = ctx.actions.declare_directory( + ctx.label.name + "/report", + ) + json_parse = ctx.actions.declare_file(ctx.label.name + "/result.json") + txt_parse = ctx.actions.declare_file(ctx.label.name + "/result.txt") + codechecker_parse_log = ctx.actions.declare_file( + ctx.label.name + "/codechecker_parse.log", + ) + + ctx.actions.run( + inputs = all_files + [config_file], + outputs = [html_parse_dir, codechecker_parse_log, json_parse, txt_parse], + executable = per_file_script, + tools = [ + info.runfiles, + ctx.attr._per_file_script[DefaultInfo].files_to_run, + ], + arguments = [ + "--mode", + "Parse", + "--codechecker", + info.codechecker.path, + "--data_dir", + html_parse_dir.path + "/..", + "--log", + codechecker_parse_log.path, + "--config", + config_file.path, + "--clang", + info.clangsa.path, + "--clang_tidy", + info.clang_tidy.path, + ], + mnemonic = "CodeCheckerParse", + use_default_shell_env = True, + progress_message = "CodeChecker parse %s" % str(ctx.label), + ) + + all_files += [html_parse_dir, codechecker_parse_log, json_parse, txt_parse] + + launcher = ctx.actions.declare_file(ctx.label.name + "_launcher.sh") ctx.actions.write( - output = ctx.outputs.test_script, + output = launcher, + content = """#!/bin/bash + exec {tool} --mode=Test \ + --data_dir '{codechecker_files}' --severities '{severities}' \ + --clang '{clang}' --clang_tidy '{clang_tidy}' + """.format( + tool = per_file_script.executable.short_path, + codechecker_files = html_parse_dir.short_path + "/..", + severities = " ".join(ctx.attr.severities), + clang = info.clangsa.short_path, + clang_tidy = info.clang_tidy.short_path, + ), is_executable = True, - content = """ - DATA_DIR=$(dirname {dirname}) - # ls -la $DATA_DIR/data - # find $DATA_DIR/data -name *.plist -exec sed -i -e "s|.*execroot/codechecker_bazel/||g" {{}} \\; - # cat $DATA_DIR/data/test-src-lib.cc_clangsa.plist - echo "Running: CodeChecker parse $DATA_DIR/data" - $(realpath {codechecker}) parse $DATA_DIR/data - """.format(dirname = ctx.outputs.test_script.short_path, codechecker = info.codechecker.short_path), - ) - files = depset( - direct = all_files, ) + + # Return test script and all required files run_files = [ - ctx.outputs.test_script, + launcher, ] + info.runfiles.to_list() + all_files + all_runfiles = ctx.runfiles(files = run_files) + + # Add runfiles from the py_binary target (for common.py etc.) + all_runfiles = all_runfiles.merge( + ctx.attr._per_file_script[DefaultInfo].default_runfiles, + ) + return [ DefaultInfo( - files = files, - runfiles = ctx.runfiles(files = run_files), - executable = ctx.outputs.test_script, + files = depset(all_files), + runfiles = all_runfiles, + executable = launcher, ), ] @@ -284,6 +338,10 @@ per_file_test = rule( default = "", #"@platforms//os:linux", doc = "Platform to build for", ), + "severities": attr.string_list( + default = ["HIGH"], + doc = "List of defect severities: HIGH, MEDIUM, LOW, STYLE etc", + ), "skip": attr.string_list( default = [], doc = "List of skip/ignore file rules. " + @@ -310,7 +368,6 @@ per_file_test = rule( } | version_specific_attributes(), outputs = { "compile_commands": "%{name}/compile_commands.json", - "test_script": "%{name}/test_script.sh", }, test = True, toolchains = ["//:toolchain_type"], diff --git a/src/per_file_script.py b/src/per_file_script.py index adcf316d..8bdd98e0 100644 --- a/src/per_file_script.py +++ b/src/per_file_script.py @@ -17,12 +17,14 @@ """ import argparse -from dataclasses import dataclass import os import re import shutil import subprocess -from common import fail, setup_logging, build_env +from dataclasses import dataclass +from common import ( + check_results, fail, parse, setup_logging, build_env +) @dataclass @@ -30,6 +32,7 @@ class Config: # pylint: disable=too-many-instance-attributes """Configuration parsed from command-line arguments.""" execution_mode: str + severities: str codechecker_bin: str compile_commands: str codechecker_args: str @@ -52,30 +55,33 @@ def parse_args(argv=None): ) parser.add_argument("--mode", required=True, help="Execution mode") parser.add_argument( - "--codechecker", required=True, help="Path to CodeChecker binary" + "--codechecker", required=False, help="Path to CodeChecker binary" ) parser.add_argument("--verbosity", default="INFO", help="Log level") parser.add_argument( - "--commands", required=True, help="Path to compile_commands.json" + "--commands", required=False, help="Path to compile_commands.json" ) parser.add_argument( "--analyze", default="", help="CodeChecker analyze arguments" ) - parser.add_argument("--config", required=True, help="Path to config file") + parser.add_argument("--config", required=False, help="Path to config file") parser.add_argument( "--data_dir", required=True, help="Output directory for CodeChecker" ) parser.add_argument( - "--file", required=True, help="Path to the file to be analyzed" + "--file", required=False, help="Path to the file to be analyzed" + ) + parser.add_argument( + "--severities", required=False, help="Severities to check" ) - parser.add_argument("--log", required=True, help="Path to the log file") - parser.add_argument("--skip", required=True, help="Path to the skip file") + parser.add_argument("--log", required=False, help="Path to the log file") + parser.add_argument("--skip", required=False, help="Path to the skip file") parser.add_argument( - "--metadata", required=True, help="Path to the metadata file" + "--metadata", required=False, help="Path to the metadata file" ) parser.add_argument( "--analyzer_plists", - required=True, + required=False, help="Semicolon-separated list of analyzer,plist_path pairs", ) parser.add_argument( @@ -89,13 +95,16 @@ def parse_args(argv=None): args = parser.parse_args(argv) - analyzer_plist_paths = [ - item.split(",") for item in args.analyzer_plists.split(";") - ] + analyzer_plist_paths = [] + if args.analyzer_plists: + analyzer_plist_paths = [ + item.split(",") for item in args.analyzer_plists.split(";") + ] return Config( execution_mode=args.mode, - codechecker_bin=os.path.realpath(args.codechecker), + severities=args.severities, + codechecker_bin=os.path.realpath(args.codechecker or "/"), compile_commands=args.commands, codechecker_args=args.analyze, config_file=args.config, @@ -288,6 +297,20 @@ def main(): _create_compile_commands_json_with_absolute_paths(cfg) _run_codechecker(cfg) _move_output_files(cfg) + elif cfg.execution_mode == "Parse": + with open(cfg.log_file, "a", encoding="utf-8"): + pass + parse( + input_dir=cfg.data_dir, + codechecker=cfg.codechecker_bin, + config=cfg.config_file, + env="", + log=cfg.log_file, + clang=cfg.clang, + clang_tidy=cfg.clang_tidy, + ) + elif cfg.execution_mode == "Test": + check_results(cfg.data_dir, cfg.log_file, cfg.severities) else: fail( cfg.log_file, diff --git a/test/unit/caching/BUILD b/test/unit/caching/BUILD index c173b56b..79ce3a17 100644 --- a/test/unit/caching/BUILD +++ b/test/unit/caching/BUILD @@ -33,14 +33,14 @@ caching_test( caching_test( name = "caching_per_file_test", - expected_action_count = 1, + expected_action_count = 2, file_to_modify = "secondary.cc", target_name = "per_file_caching", ) caching_test( name = "caching_per_file_ctu_test", - expected_action_count = 2, + expected_action_count = 3, file_to_modify = "secondary.cc", target_name = "per_file_caching_ctu", )