Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 11 additions & 7 deletions docs/codechecker.md
Original file line number Diff line number Diff line change
Expand Up @@ -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"
```

Expand Down
1 change: 1 addition & 0 deletions src/codechecker.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -372,6 +372,7 @@ def codechecker_test(
options = analyze,
skip = skip,
config = config,
severities = severities,
toolchain = toolchain,
tags = codechecker_tags,
**kwargs
Expand Down
1 change: 0 additions & 1 deletion src/codechecker_script.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
9 changes: 4 additions & 5 deletions src/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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))
Expand Down
91 changes: 74 additions & 17 deletions src/per_file.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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|<string>.*execroot/codechecker_bazel/|<string>|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,
),
]

Expand All @@ -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. " +
Expand All @@ -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"],
Expand Down
51 changes: 37 additions & 14 deletions src/per_file_script.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,19 +17,22 @@
"""

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
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
Expand All @@ -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(
Expand All @@ -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,
Expand Down Expand Up @@ -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,
Expand Down
4 changes: 2 additions & 2 deletions test/unit/caching/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Comment on lines +36 to +43

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What are we seeing here?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The parse action is a separate action, thus, the action count is increased, as expected.

file_to_modify = "secondary.cc",
target_name = "per_file_caching_ctu",
)
Loading