Skip to content
Draft
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
12 changes: 11 additions & 1 deletion src/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,21 @@ py_binary(
visibility = ["//visibility:public"],
)

# In bazel 6 we use our own python toolchain,
# therefore we cannot load from rules_python.
# buildifier: disable=native-py
py_binary(
name = "per_file_script",
srcs = ["per_file_script.py"],
main = "per_file_script.py",
# Bazel 6 doesn't see this otherwise
visibility = ["//visibility:public"],
)

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

Expand Down
106 changes: 63 additions & 43 deletions src/per_file.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,9 @@ def _run_code_checker(
env_vars,
compile_commands_json,
compilation_context,
sources_and_headers):
sources_and_headers,
skipfile,
bazel_constants):
# Define Plist and log file names
data_dir = ctx.attr.name + "/data"
file_name_params = (data_dir, src.path.replace("/", "-"))
Expand All @@ -50,38 +52,25 @@ 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),
)

# TODO: Consider using aliases so we don't have to type //src: everywhere.
info = ctx.toolchains["//src:toolchain_type"].codecheckerinfo

mutual_inputs = [
compile_commands_json,
config_file,
skipfile,
info.codechecker,
info.clang_tidy,
info.clangsa,
bazel_constants,
]
if "--ctu" in options:
inputs = [
compile_commands_json,
config_file,
config,
info.codechecker,
info.clangsa,
info.clang_tidy,
] + sources_and_headers
inputs = mutual_inputs + 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,
inputs = depset(mutual_inputs + [
src,
config,
info.codechecker,
info.clangsa,
info.clang_tidy,
], transitive = [headers])

outputs = [clang_tidy_plist, clangsa_plist, codechecker_log]
Expand All @@ -96,13 +85,12 @@ def _run_code_checker(
ctx.actions.run(
inputs = inputs,
outputs = outputs,
executable = ctx.outputs.per_file_script,
executable = ctx.executable._per_file_script,
arguments = [
info.codechecker.path,
bazel_constants.path,
data_dir,
src.path,
codechecker_log.path,
config.path,
analyzer_output_paths,
analyzer_executables,
],
Expand Down Expand Up @@ -153,20 +141,43 @@ def _collect_all_sources_and_headers(ctx):
all_files += headers
return all_files

def _create_wrapper_script(ctx, options, compile_commands_json, config_file):
def _create_constants_file(
ctx,
options,
compile_commands_json,
config_file,
skipfile):
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 = {
"{codechecker_args}": options_str,
"{compile_commands_json}": compile_commands_json.path,
"{config_file}": config_file.path,
},

# Define variables to use in a file
bazel_constants = ctx.actions.declare_file(
"{}/bazel_constants.json".format(ctx.attr.name),
)
info = ctx.toolchains["//src:toolchain_type"].codecheckerinfo
ctx.actions.write(
output = bazel_constants,
content = json.encode_indent({
"codechecker_bin": info.codechecker.path,
"codechecker_args": options_str,
"compile_commands_json": compile_commands_json.path,
"config_file": config_file.path,
"skipfile": skipfile.path,
}),
)
return bazel_constants

def _create_skipfile(ctx):
# Create skipfile
skipfile = ctx.actions.declare_file(
"{}/skipfile".format(ctx.attr.name),
)
ctx.actions.write(
output = skipfile,
content = "\n".join(ctx.attr.skip),
)
return skipfile

def _per_file_impl(ctx):
compile_commands = None
Expand All @@ -181,7 +192,14 @@ def _per_file_impl(ctx):
options = ctx.attr.default_options + ctx.attr.options
all_files = [compile_commands]
config_file, env_vars = get_config_file(ctx)
_create_wrapper_script(ctx, options, compile_commands, config_file)
skipfile = _create_skipfile(ctx)
bazel_constants = _create_constants_file(
ctx,
options,
compile_commands,
config_file,
skipfile,
)
for target in ctx.attr.targets:
if not CcInfo in target:
continue
Expand All @@ -206,6 +224,8 @@ def _per_file_impl(ctx):
compile_commands,
compilation_context,
sources_and_headers,
skipfile,
bazel_constants,
)
all_files += outputs
ctx.actions.write(
Expand Down Expand Up @@ -261,14 +281,14 @@ 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,
"_per_file_script": attr.label(
default = ":per_file_script",
executable = True,
cfg = "exec",
),
},
outputs = {
"compile_commands": "%{name}/compile_commands.json",
"per_file_script": "%{name}/per_file_script.py",
"test_script": "%{name}/test_script.sh",
},
test = True,
Expand Down
28 changes: 13 additions & 15 deletions src/per_file_script.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
#!/usr/bin/env python3

# Copyright 2023 Ericsson AB
#
# Licensed under the Apache License, Version 2.0 (the "License");
Expand All @@ -23,24 +21,23 @@
import shutil
import subprocess
import sys
import json
from pathlib import Path

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[5]
CODECHECKER_BIN = os.path.realpath(sys.argv[1])
CONSTANTS = json.loads(Path(sys.argv[1]).read_text(encoding="utf-8"))
COMPILE_COMMANDS_ABSOLUTE: str = f'{CONSTANTS["compile_commands_json"]}.abs'
CODECHECKER_BIN = os.path.realpath(CONSTANTS["codechecker_bin"])
# The output directory for CodeChecker
DATA_DIR = sys.argv[2]
# The file to be analyzed
FILE_PATH = sys.argv[3]
LOG_FILE = sys.argv[4]
# List of pairs of analyzers and their plist files
ANALYZER_PLIST_PATHS = [item.split(",") for item in sys.argv[6].split(";")]
ANALYZER_PLIST_PATHS = [item.split(",") for item in sys.argv[5].split(";")]
ANALYZER_EXECUTABLES_ENV_VAR = ";".join(
f"{name}:{os.path.realpath(path)}"
for name, path in [
pair.split(":", 1) for pair in sys.argv[7].split(";") if pair
pair.split(":", 1) for pair in sys.argv[6].split(";") if pair
]
)

Expand Down Expand Up @@ -76,7 +73,7 @@ def _create_compile_commands_json_with_absolute_paths():
of the files.
"""
with open(
COMPILE_COMMANDS_JSON, "r", encoding="utf-8"
CONSTANTS["compile_commands_json"], "r", encoding="utf-8"
) as original_file, open(
COMPILE_COMMANDS_ABSOLUTE, "w", encoding="utf-8"
) as new_file:
Expand All @@ -96,6 +93,7 @@ def _get_codechecker_env() -> dict[str, str]:
cc_env = os.environ.copy()
# Overwrite analyzer paths
cc_env["CC_ANALYZER_BIN"] = ANALYZER_EXECUTABLES_ENV_VAR
print(ANALYZER_EXECUTABLES_ENV_VAR)
return cc_env


Expand All @@ -105,11 +103,11 @@ def _run_codechecker() -> None:
"""
codechecker_cmd: list[str] = (
[CODECHECKER_BIN, "analyze"]
+ CODECHECKER_ARGS.split()
+ CONSTANTS["codechecker_args"].split()
+ ["--output=" + DATA_DIR]
+ ["--file=*/" + FILE_PATH]
+ ["--skip", SKIP_FILE]
+ ["--config", CONFIG_FILE]
+ ["--skip", CONSTANTS["skipfile"]]
+ ["--config", CONSTANTS["config_file"]]
+ [COMPILE_COMMANDS_ABSOLUTE]
)
log(f"CodeChecker command: {' '.join(codechecker_cmd)}\n")
Expand Down Expand Up @@ -191,7 +189,7 @@ def main():
"""
Main function of CodeChecker wrapper
"""
if len(sys.argv) != 8:
if len(sys.argv) != 7:
print("Wrong amount of arguments")
sys.exit(1)
_create_compile_commands_json_with_absolute_paths()
Expand Down
Loading