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
1 change: 1 addition & 0 deletions src/codechecker.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -341,6 +341,7 @@ def codechecker_test(
name = name,
targets = targets,
options = analyze,
skip = skip,
config = config,
tags = tags,
**kwargs
Expand Down
28 changes: 26 additions & 2 deletions src/per_file.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -50,12 +50,30 @@ 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),
)

if "--ctu" in options:
inputs = [compile_commands_json, config_file] + sources_and_headers
inputs = [
compile_commands_json,
config_file,
config,
] + 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, src], transitive = [headers])
inputs = depset([
compile_commands_json,
config_file,
src,
config,
], transitive = [headers])

outputs = [clang_tidy_plist, clangsa_plist, codechecker_log]

Expand All @@ -71,6 +89,7 @@ def _run_code_checker(
data_dir,
src.path,
codechecker_log.path,
config.path,
analyzer_output_paths,
],
mnemonic = "CodeChecker",
Expand Down Expand Up @@ -217,6 +236,11 @@ per_file_test = rule(
default = [],
doc = "List of CodeChecker options, e.g.: --ctu",
),
"skip": attr.string_list(
default = [],
doc = "List of skip/ignore file rules. " +
"See https://codechecker.readthedocs.io/en/latest/analyzer/user_guide/#skip-file",
),
"targets": attr.label_list(
aspects = [
compile_commands_aspect,
Expand Down
67 changes: 40 additions & 27 deletions src/per_file_script.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,30 +23,38 @@
import shutil
import subprocess
import sys
from typing import Optional

# The output directory for CodeChecker
DATA_DIR: Optional[str] = None
# The file to be analyzed
FILE_PATH: Optional[str] = None
# List of pairs of analyzers and their plist files
ANALYZER_PLIST_PATHS: Optional[list[list[str]]] = None
LOG_FILE: Optional[str] = None
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[4]
# The output directory for CodeChecker
DATA_DIR = sys.argv[1]
# The file to be analyzed
FILE_PATH = sys.argv[2]
LOG_FILE = sys.argv[3]
ANALYZER_PLIST_PATHS = [item.split(",") for item in sys.argv[4].split(";")]
# List of pairs of analyzers and their plist files
ANALYZER_PLIST_PATHS = [item.split(",") for item in sys.argv[5].split(";")]

EMPTY_PLIST = """<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>diagnostics</key>
<array/>
<key>files</key>
<array/>
</dict>
</plist>
"""


def log(msg: str) -> None:
"""
Append message to the log file
"""
with open(LOG_FILE, "a", encoding="utf-8") as log_file: # type: ignore
with open(LOG_FILE, "a", encoding="utf-8") as log_file:
log_file.write(msg)


Expand Down Expand Up @@ -76,8 +84,9 @@ def _run_codechecker() -> None:
codechecker_cmd: list[str] = (
["CodeChecker", "analyze"]
+ CODECHECKER_ARGS.split()
+ ["--output=" + DATA_DIR] # type: ignore
+ ["--file=*/" + FILE_PATH] # type: ignore
+ ["--output=" + DATA_DIR]
+ ["--file=*/" + FILE_PATH]
+ ["--skip", SKIP_FILE]
+ ["--config", CONFIG_FILE]
+ [COMPILE_COMMANDS_ABSOLUTE]
)
Expand All @@ -97,7 +106,7 @@ def _run_codechecker() -> None:
log(result.stdout)

try:
with open(LOG_FILE, "a", encoding="utf-8") as log_file: # type: ignore
with open(LOG_FILE, "a", encoding="utf-8") as log_file:
subprocess.run(
codechecker_cmd,
env=os.environ,
Expand All @@ -118,36 +127,40 @@ def _display_error(ret_code: int) -> None:
# Log and exit on error
print("===-----------------------------------------------------===")
print(f"[ERROR]: CodeChecker returned with {ret_code}!")
with open(LOG_FILE, "r", encoding="utf-8") as log_file: # type: ignore
with open(LOG_FILE, "r", encoding="utf-8") as log_file:
print(log_file.read())
sys.exit(1)


def _move_plist_files():
"""
Move the plist files from the temporary directory to their final destination
If the files doesn't exists, write an empty plist file to the target.
"""
# NOTE: the following we do to get rid of md5 hash in plist file names
# Copy the plist files to the specified destinations
for file in os.listdir(DATA_DIR):
for analyzer_info in ANALYZER_PLIST_PATHS: # type: ignore
if re.search(
rf"_{analyzer_info[0]}_.*\.plist$", file
) and os.path.isfile(
os.path.join(DATA_DIR, file) # type: ignore

):
shutil.move(
os.path.join(DATA_DIR, file), # type: ignore
analyzer_info[1],
)
compiled_analyzers = [
(re.compile(rf"_{analyzer[0]}_.*\.plist$"), analyzer[1])
for analyzer in ANALYZER_PLIST_PATHS
]

for regex, target_file in compiled_analyzers:
for file_path in os.listdir(DATA_DIR):
if not os.path.isfile(os.path.join(DATA_DIR, file_path)):
continue
if regex.search(file_path):
shutil.move(os.path.join(DATA_DIR, file_path), target_file)
break
else:
with open(target_file, "w", encoding="utf-8") as file:
file.write(EMPTY_PLIST)


def main():
"""
Main function of CodeChecker wrapper
"""
if len(sys.argv) != 5:
if len(sys.argv) != 6:
print("Wrong amount of arguments")
sys.exit(1)
_create_compile_commands_json_with_absolute_paths()
Expand Down
12 changes: 12 additions & 0 deletions test/unit/skip/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,18 @@ codechecker_test(
skip = [
"-*test/unit/skip/skip*",
],
targets = [
"simple_target",
],
)

codechecker_test(
name = "per_file_skipfile_both_files_except_one",
per_file = True,
skip = [
"+*test/unit/skip/skip.cc",
"-*test/unit/skip/skip*",
],
tags = ["manual"],
targets = [
"simple_target",
Expand Down
36 changes: 26 additions & 10 deletions test/unit/skip/test_skip.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ def test_codechecker_skipfile(self):
)
self.assertEqual(ret, 0, stderr)

def test_per_file_skipfile_full_path(self):
def test_per_file_skipfile_exact_file_path(self):
"""
Test: bazel test //test/unit/skip:per_file_skipfile_exact_file_path
"""
Expand All @@ -53,8 +53,7 @@ def test_per_file_skipfile_full_path(self):
f"{self.BAZEL_TESTLOGS_DIR}/"
"per_file_skipfile_exact_file_path/test.log"
)
# FIXME: change to assertFalse, this file should be skipped
self.assertTrue(
self.assertFalse(
self.contains_regex_in_file(log_file, r"defect\(s\) in skip.cc")
)
self.assertTrue(
Expand All @@ -73,8 +72,7 @@ def test_per_file_skipfile_folder_skip_path(self):
f"{self.BAZEL_TESTLOGS_DIR}/"
"per_file_skipfile_folder_skip_path/test.log"
)
# FIXME: change to assertFalse, this file should be skipped
self.assertTrue(
self.assertFalse(
self.contains_regex_in_file(log_file, r"defect\(s\) in skip.cc")
)
# This is correct.
Expand All @@ -89,17 +87,35 @@ def test_per_file_skipfile_both_files(self):
ret, _, stderr = self.run_command(
"bazel test //test/unit/skip:per_file_skipfile_both_files"
)
# FIXME: The return code here should be 0, both files should be skipped
self.assertEqual(ret, 3, stderr)
self.assertEqual(ret, 0, stderr)
log_file = (
f"{self.BAZEL_TESTLOGS_DIR}/per_file_skipfile_both_files/test.log"
)
# FIXME: Change to assertFalse after fix, should have been skipped.
self.assertTrue(
self.assertFalse(
self.contains_regex_in_file(log_file, r"defect\(s\) in skip.cc")
)
# FIXME: Change to assertFalse after fix, should have been skipped.
self.assertFalse(
self.contains_regex_in_file(log_file, r"defect\(s\) in skip2.cc")
)

def test_per_file_skipfile_both_files_except_one(self):
"""
Test: bazel test
//test/unit/skip:per_file_skipfile_both_files_except_one
"""
ret, _, stderr = self.run_command(
"bazel test //test/unit/skip:"
"per_file_skipfile_both_files_except_one"
)
self.assertEqual(ret, 3, stderr)
log_file = (
f"{self.BAZEL_TESTLOGS_DIR}/"
"per_file_skipfile_both_files_except_one/test.log"
)
self.assertTrue(
self.contains_regex_in_file(log_file, r"defect\(s\) in skip.cc")
)
self.assertFalse(
self.contains_regex_in_file(log_file, r"defect\(s\) in skip2.cc")
)

Expand Down
Loading