Coverage for lobster/tools/cpp/cpp.py: 0%
87 statements
« prev ^ index » next coverage.py v7.10.6, created at 2025-09-18 11:07 +0000
« prev ^ index » next coverage.py v7.10.6, created at 2025-09-18 11:07 +0000
1#!/usr/bin/env python3
2#
3# lobster_cpp - Extract C/C++ tracing tags for LOBSTER
4# Copyright (C) 2023-2025 Bayerische Motoren Werke Aktiengesellschaft (BMW AG)
5#
6# This program is free software: you can redistribute it and/or modify
7# it under the terms of the GNU Affero General Public License as
8# published by the Free Software Foundation, either version 3 of the
9# License, or (at your option) any later version.
10#
11# This program is distributed in the hope that it will be useful, but
12# WITHOUT ANY WARRANTY; without even the implied warranty of
13# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14# Affero General Public License for more details.
15#
16# You should have received a copy of the GNU Affero General Public
17# License along with this program. If not, see
18# <https://www.gnu.org/licenses/>.
20from argparse import Namespace
21import os.path
22import subprocess
23import re
24from typing import Optional, Sequence
26from lobster.common.items import Tracing_Tag, Implementation
27from lobster.common.multi_file_input_config import Config
28from lobster.tools.cpp.implementation_builder import ImplementationBuilder
29from lobster.common.multi_file_input_tool import create_worklist, MultiFileInputTool
32FILE_LINE_PATTERN = r"(.*):(\d+):\d+:"
33KIND_PATTERN = r"(function|main function|method)"
34NAME_PATTERN = r"([a-zA-Z0-9_:~^()&\s<>,=*!+-.|[\]/\"]+)"
35PREFIX = "^%s warning:" % FILE_LINE_PATTERN
36SUFFIX = r"\[lobster-tracing\]$"
38RE_NOTAGS = (PREFIX + " " +
39 r"%s %s has no tracing tags" % (KIND_PATTERN,
40 NAME_PATTERN) +
41 " " + SUFFIX)
42RE_TAGS = (PREFIX + " " +
43 r"%s %s traces to +([^,\n]+(?:\s*,\s*[^,\n]+)*) +" % (KIND_PATTERN,
44 NAME_PATTERN) +
45 SUFFIX)
46RE_JUST = (PREFIX + " " +
47 r"%s %s exempt from tracing: +(.+) +" % (KIND_PATTERN,
48 NAME_PATTERN) +
49 SUFFIX)
52def extract_clang_finding_name(line: str) -> Optional[str]:
53 """extracts the name of the clang finding from the end of the line"""
54 if line.endswith("]") and ("[" in line):
55 return line.split("[")[-1].rstrip("]")
56 return None
59class CppTool(MultiFileInputTool):
60 def __init__(self):
61 super().__init__(
62 name = "cpp",
63 description = "Extract tracing tags from C++ using clang-tidy",
64 extensions=("cpp", "cc", "c", "h"),
65 official = True,
66 )
67 self._argument_parser.add_argument(
68 "--clang-tidy",
69 default="clang-tidy",
70 metavar="FILE",
71 help=("use the specified clang-tidy; by default we"
72 " pick the one on PATH"),
73 )
74 self._argument_parser.add_argument(
75 "--compile-commands",
76 metavar="FILE",
77 default=None,
78 help=("Path to the compile command database for all targets for "
79 "'clang tidy', or none to use the default behavior of "
80 "'clang tidy'. This is equal to calling 'clang tidy' "
81 "directly with its '-p' option. Refer to its official "
82 "documentation for more details."),
83 )
84 self._argument_parser.add_argument(
85 "--skip-clang-errors",
86 default=[],
87 nargs="*",
88 metavar="FINDINGS",
89 help="List of all clang-tidy errors to ignore.",
90 )
92 def _add_config_argument(self):
93 # This tool does not use a config file
94 pass
96 def _run_impl(self, options: Namespace) -> int:
97 config = Config(
98 inputs=None,
99 inputs_from_file=None,
100 extensions=self._extensions,
101 exclude_patterns=None,
102 schema=Implementation,
103 )
104 file_list = create_worklist(config, options.dir_or_files)
105 clang_tidy_path = os.path.expanduser(options.clang_tidy)
107 # Test if the clang-tidy can be used
108 rv = subprocess.run(
109 [
110 clang_tidy_path,
111 "-checks=-*,lobster-tracing",
112 "--list-checks",
113 ],
114 stdout=subprocess.PIPE,
115 stderr=subprocess.PIPE,
116 encoding="UTF-8",
117 check=False,
118 )
120 if "No checks enabled." in rv.stderr:
121 print("The provided clang-tidy does include the lobster-tracing check")
122 print("> Please build from "
123 "https://github.com/bmw-software-engineering/llvm-project")
124 print("> Or make sure to provide the "
125 "correct binary using the --clang-tidy flag")
126 return 1
128 subprocess_args = [
129 clang_tidy_path,
130 "-checks=-*,lobster-tracing",
131 ]
132 if options.compile_commands:
133 subprocess_args.append("-p")
134 subprocess_args.append(options.compile_commands)
136 subprocess_args += file_list
138 rv = subprocess.run(
139 subprocess_args,
140 stdout=subprocess.PIPE,
141 stderr=subprocess.PIPE,
142 encoding="UTF-8",
143 check=False,
144 )
146 if rv.returncode != 0:
147 print(rv.stdout)
148 print()
149 print(rv.stderr)
151 for line in rv.stdout.splitlines():
152 if "error: " in line:
153 clang_error = extract_clang_finding_name(line)
154 if clang_error and (clang_error in options.skip_clang_errors):
155 print(f"Ignoring clang-tidy error {clang_error}")
156 else:
157 print("Found clang-tidy error: ", clang_error)
158 return 1
160 db = {}
161 implementation_builder = ImplementationBuilder()
163 for line in rv.stdout.splitlines():
164 if not line.endswith("[lobster-tracing]"):
165 continue
167 match = re.match(RE_NOTAGS, line)
168 if match:
169 impl = implementation_builder.from_match(match)
170 assert impl.tag.key() not in db
171 db[impl.tag.key()] = impl
172 continue
174 match = re.match(RE_JUST, line)
175 if match:
176 impl = implementation_builder.from_match_if_new(db, match)
177 impl.just_up.append(
178 match.group(implementation_builder.REASON_GROUP_NUM),
179 )
180 continue
182 match = re.match(RE_TAGS, line)
183 if match:
184 impl = implementation_builder.from_match_if_new(db, match)
185 all_tags = match.group(implementation_builder.REFERENCE_GROUP_NUM)
186 for tag in re.split(r"[, ]+", all_tags.strip()):
187 if tag:
188 impl.add_tracing_target(
189 Tracing_Tag(
190 "req",
191 tag,
192 ),
193 )
194 continue
196 print("could not parse line")
197 print(">", line)
198 return 1
200 self._write_output(
201 schema=config.schema,
202 out_file=options.out,
203 items=db.values(),
204 )
206 return 0
209def main(args: Optional[Sequence[str]] = None) -> int:
210 return CppTool().run(args)