Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 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
5 changes: 4 additions & 1 deletion src/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,10 @@ py_binary(

# Build & Test script template
exports_files(
["codechecker_script.py"],
[
"codechecker_script.py",
"per_file_script.py",
],
)

# The following are flags and default values for clang_tidy_aspect
Expand Down
95 changes: 33 additions & 62 deletions src/per_file.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -4,43 +4,6 @@
load("@bazel_tools//tools/build_defs/cc:action_names.bzl", "ACTION_NAMES")
load("@bazel_tools//tools/cpp:toolchain_utils.bzl", "find_cpp_toolchain")

CODE_CHECKER_WRAPPER_SCRIPT = """#!/usr/bin/env bash
#set -x
DATA_DIR=$1
shift
CLANG_TIDY_PLIST=$1
shift
CLANGSA_PLIST=$1
shift
LOG_FILE=$1
shift
COMPILE_COMMANDS_JSON=$1
shift
COMPILE_COMMANDS_ABS=$COMPILE_COMMANDS_JSON.abs
sed 's|"directory":"."|"directory":"'$(pwd)'"|g' $COMPILE_COMMANDS_JSON > $COMPILE_COMMANDS_ABS
echo "CodeChecker command: $@" $COMPILE_COMMANDS_ABS > $LOG_FILE
echo "===-----------------------------------------------------===" >> $LOG_FILE
echo " CodeChecker error log " >> $LOG_FILE
echo "===-----------------------------------------------------===" >> $LOG_FILE
eval "$@" $COMPILE_COMMANDS_ABS >> $LOG_FILE 2>&1
# ls -la $DATA_DIR
# NOTE: the following we do to get rid of md5 hash in plist file names
ret_code=$?
echo "===-----------------------------------------------------===" >> $LOG_FILE
if [ $ret_code -eq 1 ] || [ $ret_code -ge 128 ]; then
echo "===-----------------------------------------------------==="
echo "[ERROR]: CodeChecker returned with $ret_code!"
cat $LOG_FILE
exit 1
fi
cp $DATA_DIR/*_clang-tidy_*.plist $CLANG_TIDY_PLIST
cp $DATA_DIR/*_clangsa_*.plist $CLANGSA_PLIST

# sed -i -e "s|<string>.*execroot/bazel_codechecker/|<string>|g" $CLANG_TIDY_PLIST
# sed -i -e "s|<string>.*execroot/bazel_codechecker/|<string>|g" $CLANGSA_PLIST

"""

def _run_code_checker(
ctx,
src,
Expand Down Expand Up @@ -71,35 +34,20 @@ def _run_code_checker(

outputs = [clang_tidy_plist, clangsa_plist, codechecker_log]

# Create CodeChecker wrapper script
wrapper = ctx.actions.declare_file(ctx.attr.name + "/code_checker.sh")
ctx.actions.write(
output = wrapper,
is_executable = True,
content = CODE_CHECKER_WRAPPER_SCRIPT,
)

# Prepare arguments
args = ctx.actions.args()

# NOTE: we pass: data dir, PList and log file names as first 4 arguments
args.add(data_dir)
args.add(clang_tidy_plist.path)
args.add(clangsa_plist.path)
args.add(codechecker_log.path)
args.add(compile_commands_json.path)
args.add("CodeChecker")
args.add("analyze")
args.add_all(options)
args.add("--output=" + data_dir)
args.add("--file=*/" + src.path)
analyzer_output_paths = "clangsa," + clangsa_plist.path + \
";clang-tidy," + clang_tidy_plist.path

# Action to run CodeChecker for a file
ctx.actions.run(
inputs = inputs,
outputs = outputs,
executable = wrapper,
arguments = [args],
executable = ctx.outputs.per_file_script,
arguments = [
data_dir,
src.path,
codechecker_log.path,
analyzer_output_paths
],
mnemonic = "CodeChecker",
use_default_shell_env = True,
progress_message = "CodeChecker analyze {}".format(src.short_path),
Expand All @@ -125,7 +73,6 @@ def check_valid_file_type(src):
return False

def _rule_sources(ctx):

srcs = []
if hasattr(ctx.rule.attr, "srcs"):
for src in ctx.rule.attr.srcs:
Expand Down Expand Up @@ -314,11 +261,27 @@ 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):
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 = {
"{PythonPath}": ctx.attr._python_runtime[PyRuntimeInfo].interpreter_path,
"{compile_commands_json}": compile_commands_json.path,
"{codechecker_args}": options_str,
},
)

def _per_file_impl(ctx):
compile_commands_json = _compile_commands_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)
for target in ctx.attr.targets:
if not CcInfo in target:
continue
Expand Down Expand Up @@ -384,9 +347,17 @@ 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,
),
"_python_runtime": attr.label(
default = "@default_python_tools//:py3_runtime",
),
},
outputs = {
"test_script": "%{name}/test_script.sh",
"per_file_script": "%{name}/per_file_script.py",
},
test = True,
)
Expand Down
143 changes: 143 additions & 0 deletions src/per_file_script.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
#!{PythonPath}

# 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.


import os
import re
import shutil
import subprocess
import sys

DATA_DIR: str = sys.argv[1]
FILE_PATH: str = sys.argv[2]
ANALYZER_PLIST_PATHS: list[list[str]] = [
item.split(",") for item in sys.argv[4].split(";")
]
LOG_FILE: str = sys.argv[3]
Comment thread
furtib marked this conversation as resolved.
Outdated
COMPILE_COMMANDS_JSON: str = "{compile_commands_json}"
COMPILE_COMMANDS_ABSOLUTE: str = f"{COMPILE_COMMANDS_JSON}.abs"
CODECHECKER_ARGS: str = "{codechecker_args}"


def log(msg: str) -> None:
"""
Append message to the log file
"""
with open(LOG_FILE, "a") as log_file:
log_file.write(msg)


def _create_compile_commands_json_with_absolute_paths():
"""
Modifies the paths in compile_commands.json to contain the absolute path
of the files.
"""
with open(COMPILE_COMMANDS_JSON, "r") as original_file, open(
COMPILE_COMMANDS_ABSOLUTE, "w"
) as new_file:
content = original_file.read()
# Replace '"directory":"."' with the absolute path
Comment thread
furtib marked this conversation as resolved.
Outdated
# of the current working directory
new_content = content.replace(
'"directory":".', f'"directory":"{os.getcwd()}'
)
new_file.write(new_content)


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"
)
log("===-----------------------------------------------------===\n")
log(" CodeChecker error log \n")
log("===-----------------------------------------------------===\n")

result = subprocess.run(
["echo", "$PATH"],
shell=True,
env=os.environ,
capture_output=True,
text=True,
)
log(result.stdout)

codechecker_cmd: list[str] = (
["CodeChecker", "analyze"]
+ CODECHECKER_ARGS.split()
+ ["--output=" + DATA_DIR]
+ ["--file=*/" + FILE_PATH]
+ [COMPILE_COMMANDS_ABSOLUTE]
)

try:
with open(LOG_FILE, "a") as log_file:
subprocess.run(
codechecker_cmd,
env=os.environ,
stdout=log_file,
stderr=log_file,
check=True,
)
except subprocess.CalledProcessError as e:
log(e.output.decode() if e.output else "")
if e.returncode == 1 or e.returncode >= 128:
_display_error(e.returncode)


def _display_error(ret_code: int) -> None:
"""
Display the log file, and exit with 1
"""
# Log and exit on error
print("===-----------------------------------------------------===")
print(f"[ERROR]: CodeChecker returned with {ret_code}!")
with open(LOG_FILE, "r") 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
"""
# 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:
if re.search(
rf"_{analyzer_info[0]}_.*\.plist$", file
) and os.path.isfile(os.path.join(DATA_DIR, file)):
shutil.move(os.path.join(DATA_DIR, file), analyzer_info[1])


def main():
_create_compile_commands_json_with_absolute_paths()
_run_codechecker()
_move_plist_files()


if __name__ == "__main__":
main()


# I have conserved this comment from the original bash script
# The sed commands are commented out, so we won't implement them
# # sed -i -e "s|<string>.*execroot/bazel_codechecker/|<string>|g" $CLANG_TIDY_PLIST
# # sed -i -e "s|<string>.*execroot/bazel_codechecker/|<string>|g" $CLANGSA_PLIST
Comment thread
furtib marked this conversation as resolved.