Skip to content

Commit 65fc0a8

Browse files
committed
Add cppcheck lint aspect
xml output tests Cleanup precommit
1 parent 4ba6e3f commit 65fc0a8

File tree

6 files changed

+278
-0
lines changed

6 files changed

+278
-0
lines changed

example/tools/lint/BUILD.bazel

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,3 +104,8 @@ native_binary(
104104
),
105105
out = "clang_tidy",
106106
)
107+
108+
sh_binary(
109+
name = "cppcheck",
110+
srcs = ["cppcheck_wrapper.sh"],
111+
)
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
~/.local/bin/cppcheckpremium/cppcheck \
2+
--check-level=exhaustive \
3+
--enable=warning,style,performance,portability,information \
4+
"$@"

example/tools/lint/linters.bzl

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
load("@aspect_rules_lint//lint:buf.bzl", "lint_buf_aspect")
44
load("@aspect_rules_lint//lint:checkstyle.bzl", "lint_checkstyle_aspect")
55
load("@aspect_rules_lint//lint:clang_tidy.bzl", "lint_clang_tidy_aspect")
6+
load("@aspect_rules_lint//lint:cppcheck.bzl", "lint_cppcheck_aspect")
67
load("@aspect_rules_lint//lint:eslint.bzl", "lint_eslint_aspect")
78
load("@aspect_rules_lint//lint:flake8.bzl", "lint_flake8_aspect")
89
load("@aspect_rules_lint//lint:keep_sorted.bzl", "lint_keep_sorted_aspect")
@@ -102,6 +103,12 @@ clang_tidy = lint_clang_tidy_aspect(
102103

103104
clang_tidy_test = lint_test(aspect = clang_tidy)
104105

106+
cppcheck = lint_cppcheck_aspect(
107+
binary = Label("//tools/lint:cppcheck"),
108+
verbose = True,
109+
)
110+
cppcheck_test = lint_test(aspect = cppcheck)
111+
105112
# an example of setting up a different clang-tidy aspect with different
106113
# options. This one uses a single global clang-tidy file
107114
clang_tidy_global_config = lint_clang_tidy_aspect(

lint/BUILD.bazel

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -269,3 +269,8 @@ sh_binary(
269269
name = "clang_tidy_wrapper",
270270
srcs = ["clang_tidy_wrapper.bash"],
271271
)
272+
273+
sh_binary(
274+
name = "cppcheck_wrapper",
275+
srcs = ["cppcheck_wrapper.bash"],
276+
)

lint/cppcheck.bzl

Lines changed: 190 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
1+
"""API for calling declaring a cppcheck lint aspect.
2+
"""
3+
4+
load("@bazel_tools//tools/cpp:toolchain_utils.bzl", "find_cpp_toolchain")
5+
load("//lint/private:lint_aspect.bzl", "LintOptionsInfo", "OPTIONAL_SARIF_PARSER_TOOLCHAIN", "OUTFILE_FORMAT", "noop_lint_action", "output_files", "parse_to_sarif_action")
6+
7+
_MNEMONIC = "AspectRulesLintCppCheck"
8+
9+
def _gather_inputs(compilation_context, srcs):
10+
inputs = srcs
11+
return depset(inputs, transitive = [compilation_context.headers])
12+
13+
# taken over from clang_tidy.bzl
14+
def _is_source(file):
15+
permitted_source_types = [
16+
"c",
17+
"cc",
18+
"cpp",
19+
"cxx",
20+
"c++",
21+
"C",
22+
]
23+
return (file.is_source and file.extension in permitted_source_types)
24+
25+
# taken over from clang_tidy.bzl
26+
# modification of filter_srcs in lint_aspect.bzl that filters out header files
27+
def _filter_srcs(rule):
28+
# some rules can return a CcInfo without having a srcs attribute
29+
if not hasattr(rule.attr, "srcs"):
30+
return []
31+
if "lint-genfiles" in rule.attr.tags:
32+
return rule.files.srcs
33+
else:
34+
return [s for s in rule.files.srcs if _is_source(s)]
35+
36+
def _prefixed(list, prefix):
37+
array = []
38+
for arg in list:
39+
array.append("{} {}".format(prefix, arg))
40+
return array
41+
42+
def _get_compiler_args(compilation_context):
43+
# add includes
44+
args = []
45+
args.extend(_prefixed(compilation_context.framework_includes.to_list(), "-I"))
46+
args.extend(_prefixed(compilation_context.includes.to_list(), "-I"))
47+
args.extend(_prefixed(compilation_context.quote_includes.to_list(), "-I"))
48+
args.extend(_prefixed(compilation_context.system_includes.to_list(), "-I"))
49+
args.extend(_prefixed(compilation_context.external_includes.to_list(), "-I"))
50+
return args
51+
52+
def cppcheck_action(ctx, compilation_context, executable, srcs, stdout, exit_code, do_xml = False):
53+
"""Create a Bazel Action that spawns a cppcheck process.
54+
55+
Args:
56+
ctx: an action context OR aspect context
57+
compilation_context: from target
58+
executable: struct with a cppcheck field
59+
srcs: file objects to lint
60+
stdout: output file containing the stdout or --output-file of cppcheck
61+
exit_code: output file containing the exit code of cppcheck.
62+
If None, then fail the build when cppcheck exits non-zero.
63+
do_xml: If true, xml output is generated
64+
"""
65+
66+
outputs = [stdout]
67+
env = {}
68+
env["CPPCHECK__STDOUT_STDERR_OUTPUT_FILE"] = stdout.path
69+
70+
if exit_code:
71+
env["CPPCHECK__EXIT_CODE_OUTPUT_FILE"] = exit_code.path
72+
outputs.append(exit_code)
73+
74+
env["CPPCHECK__VERBOSE"] = "1" if ctx.attr._verbose else ""
75+
76+
cppcheck_args = []
77+
78+
# cppcheck shall fail with exit code != 0 if issues found
79+
cppcheck_args.append("--error-exitcode=31")
80+
81+
# add include paths
82+
cppcheck_args.extend(_get_compiler_args(compilation_context))
83+
84+
if do_xml:
85+
cppcheck_args.append("--xml-version=3")
86+
87+
for f in srcs:
88+
cppcheck_args.append(f.short_path)
89+
90+
ctx.actions.run_shell(
91+
inputs = _gather_inputs(compilation_context, srcs),
92+
outputs = outputs,
93+
tools = [executable._cppcheck_wrapper, executable._cppcheck, find_cpp_toolchain(ctx).all_files],
94+
command = executable._cppcheck_wrapper.path + " $@",
95+
arguments = [executable._cppcheck.path] + cppcheck_args,
96+
env = env,
97+
mnemonic = _MNEMONIC,
98+
progress_message = "Linting %{label} with cppcheck",
99+
)
100+
101+
def _cppcheck_aspect_impl(target, ctx):
102+
if not CcInfo in target:
103+
return []
104+
105+
files_to_lint = _filter_srcs(ctx.rule)
106+
compilation_context = target[CcInfo].compilation_context
107+
if hasattr(ctx.rule.attr, "implementation_deps"):
108+
compilation_context = cc_common.merge_compilation_contexts(
109+
compilation_contexts = [compilation_context] +
110+
[implementation_dep[CcInfo].compilation_context for implementation_dep in ctx.rule.attr.implementation_deps],
111+
)
112+
113+
outputs, info = output_files(_MNEMONIC, target, ctx)
114+
115+
if len(files_to_lint) == 0:
116+
noop_lint_action(ctx, outputs)
117+
return [info]
118+
119+
cppcheck_action(ctx, compilation_context, ctx.executable, files_to_lint, outputs.human.out, outputs.human.exit_code)
120+
121+
# report:
122+
raw_machine_report = ctx.actions.declare_file(OUTFILE_FORMAT.format(label = target.label.name, mnemonic = _MNEMONIC, suffix = "raw_machine_report"))
123+
cppcheck_action(ctx, compilation_context, ctx.executable, files_to_lint, raw_machine_report, outputs.machine.exit_code, do_xml = True)
124+
parse_to_sarif_action(ctx, _MNEMONIC, raw_machine_report, outputs.machine.out)
125+
126+
# xml_output = ctx.actions.declare_file(OUTFILE_FORMAT.format(label = target.label.name, mnemonic = _MNEMONIC, suffix = "xml"))
127+
# xml_exit_code = ctx.actions.declare_file(OUTFILE_FORMAT.format(label = target.label.name, mnemonic = _MNEMONIC, suffix = "xml_exit_code"))
128+
# cppcheck_action(ctx, compilation_context, ctx.executable, files_to_lint, xml_output, xml_exit_code, do_xml = True)
129+
130+
# # Create new OutputGroupInfo with xml_output added to machine outputs
131+
# info = OutputGroupInfo(
132+
# rules_lint_human = info.rules_lint_human,
133+
# rules_lint_machine = depset([xml_output], transitive = [info.rules_lint_machine]),
134+
# rules_lint_report = depset([xml_output], transitive = [info.rules_lint_report]),
135+
# _validation = info._validation,
136+
# )
137+
return [info]
138+
139+
def lint_cppcheck_aspect(binary, verbose = False):
140+
"""A factory function to create a linter aspect.
141+
142+
Args:
143+
binary: the cppcheck binary, typically a rule like
144+
145+
```starlark
146+
native_binary(
147+
name = "cppcheck",
148+
src = "cppcheck_wrapper.sh"
149+
)
150+
```
151+
As cppcheck does not support any configuration files so far, all arguments
152+
shall be directly implemented in the wrapper script. This file can also directly
153+
pass the license file to cppcheck, if needed.
154+
155+
verbose: print debug messages including clang-tidy command lines being invoked.
156+
"""
157+
158+
return aspect(
159+
implementation = _cppcheck_aspect_impl,
160+
attrs = {
161+
"_options": attr.label(
162+
default = "//lint:options",
163+
providers = [LintOptionsInfo],
164+
),
165+
"_verbose": attr.bool(
166+
default = verbose,
167+
),
168+
"_cppcheck": attr.label(
169+
default = binary,
170+
executable = True,
171+
cfg = "exec",
172+
),
173+
"_cppcheck_wrapper": attr.label(
174+
default = Label("@aspect_rules_lint//lint:cppcheck_wrapper"),
175+
executable = True,
176+
cfg = "exec",
177+
),
178+
"_patcher": attr.label(
179+
default = "@aspect_rules_lint//lint/private:patcher",
180+
executable = True,
181+
cfg = "exec",
182+
),
183+
"_cc_toolchain": attr.label(default = Label("@bazel_tools//tools/cpp:current_cc_toolchain")),
184+
},
185+
toolchains = [
186+
OPTIONAL_SARIF_PARSER_TOOLCHAIN,
187+
"@bazel_tools//tools/cpp:toolchain_type",
188+
],
189+
fragments = ["cpp"],
190+
)

lint/cppcheck_wrapper.bash

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
#!/usr/bin/env bash
2+
# This is a wrapper for cppcheck which gives us control over error handling
3+
# Usage: cppcheck_wrapper.bash <cppcheck-path> <args>
4+
#
5+
# Controls:
6+
# - CPPCHECK__VERBOSE: If set, be verbose
7+
# - CPPCHECK__STDOUT_STDERR_OUTPUT_FILE: If set, write stdout and stderr to this file
8+
# - CPPCHECK__EXIT_CODE_OUTPUT_FILE: If set, write the highest exit code
9+
# to this file and return success
10+
11+
# First arg is cppcheck path
12+
cppcheck=$1
13+
shift
14+
15+
if [[ -n $CPPCHECK__STDOUT_STDERR_OUTPUT_FILE ]]; then
16+
# Create the file if it doesn't exist
17+
touch $CPPCHECK__STDOUT_STDERR_OUTPUT_FILE
18+
# Clear the file if it does exist
19+
> $CPPCHECK__STDOUT_STDERR_OUTPUT_FILE
20+
if [[ -n $CPPCHECK__VERBOSE ]]; then
21+
echo "Output > ${CPPCHECK__STDOUT_STDERR_OUTPUT_FILE}"
22+
fi
23+
fi
24+
if [[ -n $CPPCHECK__EXIT_CODE_OUTPUT_FILE ]]; then
25+
if [[ -n $CPPCHECK__VERBOSE ]]; then
26+
echo "Exit Code -> ${CPPCHECK__EXIT_CODE_OUTPUT_FILE}"
27+
fi
28+
fi
29+
30+
if [[ -n $CPPCHECK__STDOUT_STDERR_OUTPUT_FILE ]]; then
31+
out_file=$CPPCHECK__STDOUT_STDERR_OUTPUT_FILE
32+
else
33+
out_file=$(mktemp)
34+
fi
35+
# include stderr in output file; it contains some of the diagnostics
36+
command="$cppcheck $@ $file > $out_file 2>&1"
37+
if [[ -n $CPPCHECK__VERBOSE ]]; then
38+
echo "$@"
39+
echo "cwd: " `pwd`
40+
echo $command
41+
fi
42+
eval $command
43+
exit_code=$?
44+
if [ $exit_code -eq 1 ] && [ -s $out_file ]; then
45+
echo "Error: " $exit_code
46+
echo "Something went wrong when running cppcheck. Maybe license file missing?"
47+
fi
48+
cat $out_file
49+
50+
if [[ -z $CPPCHECK__STDOUT_STDERR_OUTPUT_FILE ]]; then
51+
rm $out_file
52+
fi
53+
# if CPPCHECK__EXIT_CODE_FILE is set, write the max exit code to that file and return success
54+
if [[ -n $CPPCHECK__EXIT_CODE_OUTPUT_FILE ]]; then
55+
if [[ -n $CPPCHECK__VERBOSE ]]; then
56+
echo "echo $exit_code > $CPPCHECK__EXIT_CODE_OUTPUT_FILE"
57+
echo "exit 0"
58+
fi
59+
echo $exit_code > $CPPCHECK__EXIT_CODE_OUTPUT_FILE
60+
exit 0
61+
fi
62+
63+
if [[ -n $CPPCHECK__VERBOSE ]]; then
64+
echo exit $exit_code
65+
fi
66+
67+
exit $exit_code

0 commit comments

Comments
 (0)